From 2fe9dd208a3f2f3f1b308102809c4f2cbdd826f8 Mon Sep 17 00:00:00 2001 From: V1K1NGbg Date: Fri, 2 Aug 2024 14:48:01 +0300 Subject: fun, type_check and api update --- src/luarocks/fun.tl | 6 +- src/luarocks/type_check.tl | 36 ++++--- src/luarocks/upload/api.tl | 232 +++++++++++++++++++++++---------------------- 3 files changed, 138 insertions(+), 136 deletions(-) diff --git a/src/luarocks/fun.tl b/src/luarocks/fun.tl index 9d9551ed..16873dc2 100644 --- a/src/luarocks/fun.tl +++ b/src/luarocks/fun.tl @@ -3,7 +3,7 @@ local record fun end -function fun.concat(xs: {any}, ys: {any}): {any} --? not generic becuse lua suports differnt types in one array +function fun.concat(xs: {K}, ys: {K}): {K} local rs = {} local n = #xs for i = 1, n do @@ -16,7 +16,7 @@ function fun.concat(xs: {any}, ys: {any}): {any} --? not generic becuse lua supo return rs end -function fun.contains(xs: {any}, v: any): boolean --? same logic as above fine? +function fun.contains(xs: {K}, v: K): boolean --? same logic as above fine? for _, x in ipairs(xs) do if v == x then return true @@ -55,7 +55,7 @@ function fun.traverse(t: {K}, f: function(K): V): {V} | V --? right or {an end) end -function fun.reverse_in(t: {any}): {any} +function fun.reverse_in(t: {K}): {K} for i = 1, math.floor(#t/2) do local m, n = i, #t - i + 1 local a, b = t[m], t[n] diff --git a/src/luarocks/type_check.tl b/src/luarocks/type_check.tl index 09dfa96d..1252133d 100644 --- a/src/luarocks/type_check.tl +++ b/src/luarocks/type_check.tl @@ -14,7 +14,7 @@ local vers = require("luarocks.core.vers") type_check.MAGIC_PLATFORMS = 0xEBABEFAC do - local function fill_in_version(tbl, version?) + local function fill_in_version(tbl: {any: any}, version?: string) --! tighter bound? --? What is filled if no version is provided for _, v in pairs(tbl) do if v is table then if v._version == nil then @@ -25,9 +25,9 @@ do end end - local function expand_magic_platforms(tbl) + local function expand_magic_platforms(tbl: {any: any}) --! tbl type for k,v in pairs(tbl) do - if v == type_check.MAGIC_PLATFORMS then + if v == type_check.MAGIC_PLATFORMS then --! v is int, next line it is not tbl[k] = { _any = util.deep_copy(tbl) } @@ -43,9 +43,9 @@ do -- and the value is a schema specification. Schema versions are considered -- incremental: version "2.0" only needs to specify what's new/changed from -- version "1.0". - function type_check.declare_schemas(inputs: {string: any}): {any: any}, {any} --? - local schemas = {} - local parent_version + function type_check.declare_schemas(inputs: {string: {any: any}}): {string : {any : any}}, {string} --! schema type: {string: {any: any}} + local schemas: {string: {any: any}} = {} + local parent_version: string local versions = fun.reverse_in(fun.sort_in(util.keys(inputs), vers.compare_versions)) @@ -68,7 +68,7 @@ end -------------------------------------------------------------------------------- -local function check_version(version: string, typetbl, context: string) +local function check_version(version: string, typetbl: {string: string}, context: string): boolean, string --! typetbl local typetbl_version = typetbl._version or "1.0" if vers.compare_versions(typetbl_version, version) then if context == "" then @@ -80,6 +80,8 @@ local function check_version(version: string, typetbl, context: string) return true end + + --- Type check an object. -- The object is compared against an archetypical value -- matching the expected type -- the actual values don't matter, @@ -92,8 +94,7 @@ end -- @return boolean or (nil, string): true if type checking -- succeeded, or nil and an error message if it failed. -- @see type_check_table -local function type_check_item(version, item, typetbl, context) - assert(type(version) == "string") +local function type_check_item(version: string, item: any, typetbl: {string: string}, context: string): boolean, string --! typetbl if typetbl._version and typetbl._version ~= "1.0" then local ok, err = check_version(version, typetbl, context) @@ -115,9 +116,9 @@ local function type_check_item(version, item, typetbl, context) end local pattern = typetbl._pattern if pattern then - if not item:match("^"..pattern.."$") then + if not item:match("^"..pattern.."$") then --! cast or tostring local what = typetbl._name or ("'"..pattern.."'") - return nil, "Type mismatch on field "..context..": invalid value '"..item.."' does not match " .. what + return nil, "Type mismatch on field "..context..": invalid value '"..item.."' does not match " .. what --! cast or tostring end end elseif expected_type == "table" then @@ -132,10 +133,10 @@ local function type_check_item(version, item, typetbl, context) return true end -local function mkfield(context, field) +local function mkfield(context: string, field: any): string if context == "" then return tostring(field) - elseif type(field) == "string" then + elseif field is string then return context.."."..field else return context.."["..tostring(field).."]" @@ -164,10 +165,7 @@ end -- to be used by error messages. -- @return boolean or (nil, string): true if type checking -- succeeded, or nil and an error message if it failed. -function type_check.type_check_table(version, tbl, typetbl, context) - assert(type(version) == "string") - assert(type(tbl) == "table") - assert(type(typetbl) == "table") +function type_check.type_check_table(version: string, tbl: {any: any}, typetbl: {string: string}, context: string): boolean, string --! tbl and typetbl types local ok, err = check_version(version, typetbl, context) if not ok then @@ -188,7 +186,7 @@ function type_check.type_check_table(version, tbl, typetbl, context) end end for k, v in pairs(typetbl) do - if k:sub(1,1) ~= "_" and v._mandatory then + if k:sub(1,1) ~= "_" and v._mandatory then --! if not tbl[k] then return nil, "Mandatory field "..mkfield(context, k).." is missing." end @@ -197,7 +195,7 @@ function type_check.type_check_table(version, tbl, typetbl, context) return true end -function type_check.check_undeclared_globals(globals, typetbl) +function type_check.check_undeclared_globals(globals: {string: any}, typetbl: {string: string}): boolean, string --! tbl and typetbl types local undeclared = {} for glob, _ in pairs(globals) do if not (typetbl[glob] or typetbl["MUST_"..glob]) then diff --git a/src/luarocks/upload/api.tl b/src/luarocks/upload/api.tl index fd837fd8..0c94a6ee 100644 --- a/src/luarocks/upload/api.tl +++ b/src/luarocks/upload/api.tl @@ -15,9 +15,13 @@ local record Api raw_method: function(Api, string) record config server: any --! why to string + key: string end + debug: boolean end +local type Parameters = multipart.Parameters + local function upload_config_file(): string if not cfg.config_files.user.file then return nil @@ -32,7 +36,7 @@ function Api:load_config(): {any: any} --? tighter bound? return config end -function Api:save_config() +function Api:save_config(): nil, string --! nil? -- Test configuration before saving it. local res, err = self:raw_method("status") if not res then @@ -90,25 +94,25 @@ function Api:method(...) return res end -function Api.raw_method(self: Api, path: string, ...) --! path, ... type +function Api:raw_method(path: string, ...) --! path, ... type self:check_version() local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path return self:request(url, ...) end -local function encode_query_string(t, sep) +local function encode_query_string(t: Parameters, sep?: string): string if sep == nil then sep = "&" end local i = 0 - local buf = { } - for k, v in pairs(t) do - if type(k) == "number" and type(v) == "table" then + local buf: {string} = { } + for k, v in pairs(t.map) do --! pairs problem for Parameters + if k is number and v is {string} then k, v = v[1], v[2] end - buf[i + 1] = multipart.url_escape(k) + buf[i + 1] = multipart.url_escape(k as string) --! cast buf[i + 2] = "=" - buf[i + 3] = multipart.url_escape(v) + buf[i + 3] = multipart.url_escape(v as string) --! cast buf[i + 4] = sep i = i + 4 end @@ -116,140 +120,140 @@ local function encode_query_string(t, sep) return table.concat(buf) end -local function redact_api_url(url) +local function redact_api_url(url: any): string url = tostring(url) - return (url:gsub(".*/api/[^/]+/[^/]+", "")) or "" + return ((url as string):gsub(".*/api/[^/]+/[^/]+", "")) or "" --! cast end local ltn12_ok, ltn12 = pcall(require, "ltn12") if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... -function Api.request(self: Api, url: string, params?, post_params?): {string : any}, string - local vars = cfg.variables - - if fs.which_tool("downloader") == "wget" then - local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") - if not curl_ok then - return nil, err - end - end + function Api:request(url: string, params?: Parameters, post_params?: Parameters): {string : any}, string + local vars = cfg.variables - if not self.config.key then - return nil, "Must have API key before performing any actions." - end - if params and next(params) then - url = url .. ("?" .. encode_query_string(params)) - end - local method = "GET" - local out - local tmpfile = fs.tmpname() - if post_params then - method = "POST" - local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" " - for k,v in pairs(post_params) do - local var = v - if type(v) == "table" then - var = "@"..v.fname + if fs.which_tool("downloader") == "wget" then + local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") + if not curl_ok then + return nil, err end - curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" " end - if cfg.connection_timeout and cfg.connection_timeout > 0 then - curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " + + if not self.config.key then + return nil, "Must have API key before performing any actions." end - local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile)) - if not ok then - return nil, "API failure: " .. redact_api_url(url) + if params and next(params) then --! + url = url .. ("?" .. encode_query_string(params)) end - else - local ok, err = fs.download(url, tmpfile) - if not ok then - return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url) + local method = "GET" + local out: string + local tmpfile = fs.tmpname() + if post_params then + method = "POST" + local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" " + for k,v in pairs(post_params) do + local var = v + if v is {string: string} then + var = "@"..v.fname + end + curl_cmd = curl_cmd .. "--form \""..k.."="..var as string.."\" " --! cast + end + if cfg.connection_timeout and cfg.connection_timeout > 0 then + curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " + end + local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile)) + if not ok then + return nil, "API failure: " .. redact_api_url(url) + end + else + local ok, err = fs.download(url, tmpfile) + if not ok then + return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url) + end end - end - local tmpfd = io.open(tmpfile) - if not tmpfd then + local tmpfd = io.open(tmpfile) + if not tmpfd then + os.remove(tmpfile) + return nil, "API failure reading temporary file - " .. redact_api_url(url) + end + out = tmpfd:read("*a") + tmpfd:close() os.remove(tmpfile) - return nil, "API failure reading temporary file - " .. redact_api_url(url) - end - out = tmpfd:read("*a") - tmpfd:close() - os.remove(tmpfile) - if self.debug then - util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") - end + if self.debug then + util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") + end - return json.decode(out) -end + return json.decode(out) + end else -- use LuaSocket and LuaSec -local warned_luasec = false + local warned_luasec = false -function Api:request(url, params, post_params) - local server = tostring(self.config.server) - local http_ok, http - local via = "luasocket" - if server:match("^https://") then - http_ok, http = pcall(require, "ssl.https") - if http_ok then - via = "luasec" - else - if not warned_luasec then - util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.") - warned_luasec = true + function Api:request(url: string, params?: Parameters, post_params?: Parameters): {string : any}, string + local server = tostring(self.config.server) + local http_ok, http: boolean, any + local via = "luasocket" + if server:match("^https://") then + http_ok, http = pcall(require, "ssl.https") --! a lot of new files in the src dir + if http_ok then + via = "luasec" + else + if not warned_luasec then + util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.") + warned_luasec = true + end + http_ok, http = pcall(require, "socket.http") + url = url:gsub("^https", "http") + via = "luasocket" end + else http_ok, http = pcall(require, "socket.http") - url = url:gsub("^https", "http") - via = "luasocket" end - else - http_ok, http = pcall(require, "socket.http") - end - if not http_ok then - return nil, "Failed loading socket library!" - end + if not http_ok then + return nil, "Failed loading socket library!" + end - if not self.config.key then - return nil, "Must have API key before performing any actions." - end - local body - local headers = {} - if params and next(params) then - url = url .. ("?" .. encode_query_string(params)) - end - if post_params then - local boundary - body, boundary = multipart.encode(post_params) - headers["Content-length"] = #body - headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) - end - local method = post_params and "POST" or "GET" - if self.debug then - util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ") - end - local out = {} - local _, status = http.request({ - url = url, - headers = headers, - method = method, - sink = ltn12.sink.table(out), - source = body and ltn12.source.string(body) - }) - if self.debug then - util.printout(tostring(status)) - end - local pok, ret, err = pcall(json.decode, table.concat(out)) - if pok and ret then - return ret + if not self.config.key then + return nil, "Must have API key before performing any actions." + end + local body: string + local headers: {string: string | integer} = {} + if params and next(params) then --! + url = url .. ("?" .. encode_query_string(params)) + end + if post_params then + local boundary: string + body, boundary = multipart.encode(post_params) + headers["Content-length"] = #body + headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) + end + local method = post_params and "POST" or "GET" + if self.debug then + util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ") + end + local out = {} + local _, status = http.request({ + url = url, + headers = headers, + method = method, + sink = ltn12.sink.table(out), + source = body and ltn12.source.string(body) + }) + if self.debug then + util.printout(tostring(status)) + end + local pok, ret, err = pcall(json.decode, table.concat(out)) + if pok and ret then + return ret + end + return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url) end - return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url) -end end -function api.new(args) +function api.new(args): Api local self = {} setmetatable(self, { __index = Api }) self.config = self:load_config() or {} -- cgit v1.2.3-55-g6feb