diff options
author | Karl Berry <karl@freefriends.org> | 2018-12-18 23:33:00 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2018-12-18 23:33:00 +0000 |
commit | 25f0a34ef212818e30ee07866877d0ea7985c691 (patch) | |
tree | 662b89b9ef15c4f96048f02fb21320600954cd3a /Master/texmf-dist/scripts/l3build/l3build-upload.lua | |
parent | 39e028ec9200b17474c8bc3c38aee237705bc2d7 (diff) |
l3build (18dec18)
git-svn-id: svn://tug.org/texlive/trunk@49450 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/l3build/l3build-upload.lua')
-rw-r--r-- | Master/texmf-dist/scripts/l3build/l3build-upload.lua | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/l3build/l3build-upload.lua b/Master/texmf-dist/scripts/l3build/l3build-upload.lua new file mode 100644 index 00000000000..68692b0af4c --- /dev/null +++ b/Master/texmf-dist/scripts/l3build/l3build-upload.lua @@ -0,0 +1,237 @@ +--[[ + +File l3build-upload.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + + +-- UPLOAD() +-- +-- takes a package configuration table and an optional boolean +-- +-- if the upload parameter is not supplied or is not true, only package validation +-- is used, if upload is true then package upload will be attempted if validation +-- succeeds. + +-- fields are given as a string, or optionally for fields allowing multiple +-- values, as a table of strings. + +-- Mandatory fields are checked in Lua +-- Maximum string lengths are checked. + +-- Currently string values are not checked, eg licence names, or URL syntax. + +-- The input form could be used to construct a post body but +-- luasec is not included in texlua. Instead an external program is used to post. +-- As Windows (since April 2018) includes curl now use curl. +-- A version using ctan-o-mat is available in the ctan-post github repo. + +-- the main interface is +-- upload() +-- with a configuration table `uploadconfig` + + +local curl_debug = curl_debug or false -- to disable posting +-- For now, this is undocumented. + +local ctanupload = ctanupload or "ask" +-- if ctanupload is nil or false, only validation is attempted +-- if ctanupload is true the ctan upload URL will be used after validation +-- if upload is anything else, the user will be prompted whether to upload. +-- For now, this is undocumented. I think I would prefer to keep it always set to ask for the time being. + +function upload() + + local uploadfile = ctanzip..".zip" + + -- try a sensible default for the package name: + uploadconfig.pkg = uploadconfig.pkg or ctanpkg or nil + + -- start building the curl command: + ctan_post = curlexe .. " " + + -- build up the curl command field-by-field: + + -- field max desc mandatory multi + -- ---------------------------------------------------------------------------------------------------- + ctan_field("announcement", uploadconfig.announcement, 8192, "Announcement", false, false ) + ctan_field("author", uploadconfig.author, 128, "Author name", true, false ) + ctan_field("bugtracker", uploadconfig.bugtracker, 255, "URL(s) of bug tracker", false, true ) + ctan_field("ctanPath", uploadconfig.ctanPath, 255, "CTAN path", true, false ) + ctan_field("description", uploadconfig.description, 4096, "Short description of package", false, false ) + ctan_field("development", uploadconfig.development, 255, "URL(s) of development channels", false, true ) + ctan_field("email", uploadconfig.email, 255, "Email of uploader", true, false ) + ctan_field("home", uploadconfig.home, 255, "URL(s) of home page", false, true ) + ctan_field("license", uploadconfig.license, 2048, "Package license(s)", true, true ) + ctan_field("note", uploadconfig.note, 4096, "Internal note to ctan", false, false ) + ctan_field("pkg", uploadconfig.pkg, 32, "Package name", true, false ) + ctan_field("repository", uploadconfig.repository, 255, "URL(s) of source repositories", false, true ) + ctan_field("summary", uploadconfig.summary, 128, "One-line summary of package", true, false ) + ctan_field("support", uploadconfig.support, 255, "URL(s) of support channels", false, true ) + ctan_field("topic", uploadconfig.topic, 1024, "Topic(s)", false, true ) + ctan_field("update", uploadconfig.update, 8, "Boolean: true=update, false=new pkg", false, false ) + ctan_field("uploader", uploadconfig.uploader, 255, "Name of uploader", true, false ) + ctan_field("version", uploadconfig.version, 32, "Package version", true, false ) + + -- finish constructing the curl command: + ctan_post = ctan_post .. " --form 'file=@" .. tostring(uploadfile) .. ";filename=" .. tostring(uploadfile) .. "'" + ctan_post = ctan_post .. " https://ctan.org/submit/" + + -- avoid lower level error from post command if zip file missing + local zip=io.open(trim_space(tostring(uploadfile)),"r") + if zip~=nil then + io.close(zip) + else + error("Missing zip file '" .. tostring(uploadfile) .. "'") + end + + -- call post command to validate the upload at CTAN's validate URL + local exit_status=0 + local fp_return="" + + -- use popen not execute so get the return body local exit_status=os.execute(ctan_post .. "validate") + if (curl_debug==false) then + local fp = assert(io.popen(ctan_post .. "validate", 'r')) + fp_return = assert(fp:read('*a')) + fp:close() + else + fp_return="WARNING: curl_debug==true: posting disabled" + print(ctan_post) + end + if string.match(fp_return,"WARNING") or string.match(fp_return,"ERROR") then + exit_status=1 + end + + -- if upload requested and validation succeeded repost to the upload URL + if (exit_status==0 or exit_status==nil) then + if (ctanupload ~=nil and ctanupload ~=false and ctanupload ~= true) then + print("Validation successful, do you want to upload to CTAN? [y/n]" ) + local answer="" + io.write("> ") + io.flush() + answer=io.read() + if(string.lower(answer,1,1)=="y") then + ctanupload=true + end + end + if (ctanupload==true) then + local fp = assert(io.popen(ctan_post .. "upload", 'r')) + fp_return = assert(fp:read('*a')) + fp:close() +-- this is just html, could save to a file +-- or echo a cleaned up version + print('Response from CTAN:') + print(fp_return) + if string.match(fp_return,"WARNING") or string.match(fp_return,"ERROR") then + exit_status=1 + end + else + print("CTAN validation successful") + end + else + error("Warnings from CTAN package validation:\n" .. fp_return) + end + return exit_status +end + + +function trim_space(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + + +function ctan_field(fname,fvalue,max,desc,mandatory,multi) + if (type(fvalue)=="table" and multi==true) then + for i, v in pairs(fvalue) do + ctan_single_field(fname,v,max,desc,mandatory and i==1) + end + else + ctan_single_field(fname,fvalue,max,desc,mandatory) + end +end + + +function ctan_single_field(fname,fvalue,max,desc,mandatory) + print('ctan-post: ' .. fname .. ' ' ..tostring(fvalue or '??')) + if ((fvalue==nil and mandatory) or (fvalue == 'ask')) then + if (max < 256) then + fvalue=input_single_line_field(fname) + else + fvalue=input_multi_line_field(fname) + end + end + if (fvalue==nil or type(fvalue)~="table") then + local vs=trim_space(tostring(fvalue)) + if (mandatory==true and (fvalue == nil or vs=="")) then + error("The field " .. fname .. " must contain " .. desc) + end + if (fvalue ~=nil and string.len(vs) > 0) then + if (max > 0 and string.len(vs) > max) then + error("The field " .. fname .. " is longer than " .. max) + end + vs = vs:gsub('"','\\"') + vs = vs:gsub('`','\\`') + ctan_post=ctan_post .." --form " .. fname .. '="' .. vs .. '"' + end + else + error("The value of the field '" .. fname .."' must be a scalar not a table") + end +end + + +-- function for interactive multiline fields +function input_multi_line_field (name) + print("Enter " .. name .. " three <return> or ctrl-D to stop") + + local field="" + + local answer_line + local return_count=0 + repeat + io.write("> ") + io.flush() + answer_line=io.read() + if answer_line=="" then + return_count=return_count+1 + else + for i=1,return_count,1 do + field = field .. "\n" + end + return_count=0 + if answer_line~=nil then + field = field .. "\n" .. answer_line + end + end + until (return_count==3 or answer_line==nil) + return field +end + +function input_single_line_field(name) + print("Enter " .. name ) + + local field="" + + io.write("> ") + io.flush() + field=io.read() + return field +end + |