From 0e6068b309a4bb084322ed2934b8191d33f50626 Mon Sep 17 00:00:00 2001 From: V1K1NGbg Date: Mon, 22 Jul 2024 21:07:47 +0300 Subject: test core with the next version of teal --- src/luarocks/core/manif.tl | 2 +- src/luarocks/core/persist.tl | 4 +- src/luarocks/core/util.tl | 78 ++--- src/luarocks/persist-original.lua | 259 ---------------- src/luarocks/persist.lua | 259 ++++++++++++++++ src/luarocks/persist.tl | 31 +- src/luarocks/type/rockspec.lua | 28 +- src/luarocks/util-original.lua | 634 -------------------------------------- src/luarocks/util.lua | 634 ++++++++++++++++++++++++++++++++++++++ src/luarocks/util.tl | 13 +- 10 files changed, 981 insertions(+), 961 deletions(-) delete mode 100644 src/luarocks/persist-original.lua create mode 100644 src/luarocks/persist.lua delete mode 100644 src/luarocks/util-original.lua create mode 100644 src/luarocks/util.lua diff --git a/src/luarocks/core/manif.tl b/src/luarocks/core/manif.tl index a089b5d8..0713d293 100644 --- a/src/luarocks/core/manif.tl +++ b/src/luarocks/core/manif.tl @@ -57,7 +57,7 @@ end -- @param repo_url string: The repository identifier. -- @param lua_version string: Lua version in "5.x" format, defaults to installed version. -- @return table or nil: loaded manifest or nil if cache is empty. -function manif.get_cached_manifest(repo_url: string, lua_version: string): Manifest +function manif.get_cached_manifest(repo_url: string, lua_version?: string): Manifest lua_version = lua_version or cfg.lua_version return manifest_cache[repo_url] and manifest_cache[repo_url][lua_version] end diff --git a/src/luarocks/core/persist.tl b/src/luarocks/core/persist.tl index 31dfe289..89cac97e 100644 --- a/src/luarocks/core/persist.tl +++ b/src/luarocks/core/persist.tl @@ -21,7 +21,7 @@ function persist.run_file(filename: string, env: {string:any}): boolean, any | s return nil, read_err, "open" end str = str:gsub("^#![^\n]*\n", "") - local chunk, ran, err: function(any):(any), boolean, any + local chunk, ran, err: function(...: any):(any), boolean, any chunk, err = load(str, filename, "t", env) if chunk then ran, err = pcall(chunk) @@ -45,7 +45,7 @@ end -- or nil, an error message and an error code ("open"; couldn't open the file, -- "load"; compile-time error, or "run"; run-time error) -- in case of errors. -function persist.load_into_table(filename: string, tbl: {string:any}) : {any: any}, {any: any} | string, string +function persist.load_into_table(filename: string, tbl?: {string:any}) : {any: any}, {any: any} | string, string local result: {string:any} = tbl or {} local globals = {} diff --git a/src/luarocks/core/util.tl b/src/luarocks/core/util.tl index e9c21dac..39862dcb 100644 --- a/src/luarocks/core/util.tl +++ b/src/luarocks/core/util.tl @@ -1,13 +1,18 @@ local record util - type CompFn = function(any, any): boolean + record Ordering + {K} + + sub_orders: {K: Ordering} + end end -------------------------------------------------------------------------------- local dir_sep = package.config:sub(1, 1) -local type CompFn = util.CompFn +local type SortBy = table.SortFunction | util.Ordering + --- Run a process and read a its output. -- Equivalent to io.popen(cmd):read("*l"), except that it @@ -57,7 +62,7 @@ function util.show_table(t: {any:any}, tname: string, top_indent: string): strin local function basic_serialize(o: any): string local so = tostring(o) - if type(o) == "function" then + if o is function then local info = debug and debug.getinfo(o, "S") if not info then return ("%q"):format(so) @@ -76,7 +81,7 @@ function util.show_table(t: {any:any}, tname: string, top_indent: string): strin end end - local function add_to_cart (value: any | {any:any}, name: string, indent: string, saved: {any: string}, field: string) + local function add_to_cart (value: any | {any:any}, name: string, indent: string, saved?: {any: string}, field?: string) indent = indent or "" saved = saved or {} field = field or name @@ -172,7 +177,7 @@ end -- from http://lua-users.org/wiki/SplitJoin -- by Philippe Lhoste -function util.split_string(str: string, delim: string, maxNb: number): {string} +function util.split_string(str: string, delim: string, maxNb?: number): {string} -- Eliminate bad cases... if string.find(str, delim) == nil then return { str } @@ -183,11 +188,11 @@ function util.split_string(str: string, delim: string, maxNb: number): {string} local result = {} local pat = "(.-)" .. delim .. "()" local nb = 0 - local lastPos: number + local lastPos: integer for part, pos in string.gmatch(str, pat) do nb = nb + 1 result[nb] = part - lastPos = tonumber(pos) + lastPos = math.tointeger(pos) if nb == maxNb then break end end -- Handle the last field @@ -288,51 +293,46 @@ end -- for that key, which is returned by the iterator as the third value after the key -- and the value. -- @return function: the iterator function. -function util.sortedpairs(tbl: {K: V}, sort_function: CompFn | {K | {K, S}}): function(): K, V, S - if not sort_function then - sort_function = default_sort - end +function util.sortedpairs(tbl: {K: V}, sort_by?: SortBy): function(): K, V, util.Ordering local keys = util.keys(tbl) - local sub_orders: {K: S} = {} + local sub_orders: {K: util.Ordering} = nil - if sort_function is CompFn then - table.sort(keys, sort_function) + if sort_by == nil then + table.sort(keys, default_sort) + elseif sort_by is table.SortFunction then + table.sort(keys, sort_by) else - local order = sort_function - local ordered_keys = {} - local all_keys = keys - keys = {} - - for _, order_entry in ipairs(order) do - local key, sub_order: K, S - - if not order_entry is {K, S} then --TEAL BUG - key = order_entry as K - else - key = order_entry[1] - sub_order = order_entry[2] - end - + -- sort_by is Ordering + + sub_orders = sort_by.sub_orders + + local seen_ordered_key: {K: boolean} = {} + + local my_ordered_keys: {K} = {} + + for _, key in ipairs(sort_by) do if tbl[key] then - ordered_keys[key] = true - sub_orders[key] = sub_order - table.insert(keys, key) + seen_ordered_key[key] = true + table.insert(my_ordered_keys, key) end end - table.sort(all_keys, default_sort) - for _, key in ipairs(all_keys) do - if not ordered_keys[key] then - table.insert(keys, key) + table.sort(keys, default_sort) + + for _, key in ipairs(keys) do + if not seen_ordered_key[key] then + table.insert(my_ordered_keys, key) end end + + keys = my_ordered_keys end - + local i = 1 - return function(): K, V, S + return function(): K, V, util.Ordering local key = keys[i] i = i + 1 - return key, tbl[key], sub_orders[key] + return key, tbl[key], sub_orders and sub_orders[key] end end diff --git a/src/luarocks/persist-original.lua b/src/luarocks/persist-original.lua deleted file mode 100644 index 4dcd930a..00000000 --- a/src/luarocks/persist-original.lua +++ /dev/null @@ -1,259 +0,0 @@ - ---- Utility module for loading files into tables and --- saving tables into files. -local persist = {} - -local core = require("luarocks.core.persist") -local util = require("luarocks.util") -local dir = require("luarocks.dir") -local fs = require("luarocks.fs") - -persist.run_file = core.run_file -persist.load_into_table = core.load_into_table - -local write_table - ---- Write a value as Lua code. --- This function handles only numbers and strings, invoking write_table --- to write tables. --- @param out table or userdata: a writer object supporting :write() method. --- @param v: the value to be written. --- @param level number: the indentation level --- @param sub_order table: optional prioritization table --- @see write_table -function persist.write_value(out, v, level, sub_order) - if type(v) == "table" then - level = level or 0 - write_table(out, v, level + 1, sub_order) - elseif type(v) == "string" then - if v:match("[\r\n]") then - local open, close = "[[", "]]" - local equals = 0 - local v_with_bracket = v.."]" - while v_with_bracket:find(close, 1, true) do - equals = equals + 1 - local eqs = ("="):rep(equals) - open, close = "["..eqs.."[", "]"..eqs.."]" - end - out:write(open.."\n"..v..close) - else - out:write(("%q"):format(v)) - end - else - out:write(tostring(v)) - end -end - -local is_valid_plain_key -do - local keywords = { - ["and"] = true, - ["break"] = true, - ["do"] = true, - ["else"] = true, - ["elseif"] = true, - ["end"] = true, - ["false"] = true, - ["for"] = true, - ["function"] = true, - ["goto"] = true, - ["if"] = true, - ["in"] = true, - ["local"] = true, - ["nil"] = true, - ["not"] = true, - ["or"] = true, - ["repeat"] = true, - ["return"] = true, - ["then"] = true, - ["true"] = true, - ["until"] = true, - ["while"] = true, - } - function is_valid_plain_key(k) - return type(k) == "string" - and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") - and not keywords[k] - end -end - -local function write_table_key_assignment(out, k, level) - if is_valid_plain_key(k) then - out:write(k) - else - out:write("[") - persist.write_value(out, k, level) - out:write("]") - end - - out:write(" = ") -end - ---- Write a table as Lua code in curly brackets notation to a writer object. --- Only numbers, strings and tables (containing numbers, strings --- or other recursively processed tables) are supported. --- @param out table or userdata: a writer object supporting :write() method. --- @param tbl table: the table to be written. --- @param level number: the indentation level --- @param field_order table: optional prioritization table -write_table = function(out, tbl, level, field_order) - out:write("{") - local sep = "\n" - local indentation = " " - local indent = true - local i = 1 - for k, v, sub_order in util.sortedpairs(tbl, field_order) do - out:write(sep) - if indent then - for _ = 1, level do out:write(indentation) end - end - - if k == i then - i = i + 1 - else - write_table_key_assignment(out, k, level) - end - - persist.write_value(out, v, level, sub_order) - if type(v) == "number" then - sep = ", " - indent = false - else - sep = ",\n" - indent = true - end - end - if sep ~= "\n" then - out:write("\n") - for _ = 1, level - 1 do out:write(indentation) end - end - out:write("}") -end - ---- Write a table as series of assignments to a writer object. --- @param out table or userdata: a writer object supporting :write() method. --- @param tbl table: the table to be written. --- @param field_order table: optional prioritization table --- @return true if successful; nil and error message if failed. -local function write_table_as_assignments(out, tbl, field_order) - for k, v, sub_order in util.sortedpairs(tbl, field_order) do - if not is_valid_plain_key(k) then - return nil, "cannot store '"..tostring(k).."' as a plain key." - end - out:write(k.." = ") - persist.write_value(out, v, 0, sub_order) - out:write("\n") - end - return true -end - ---- Write a table using Lua table syntax to a writer object. --- @param out table or userdata: a writer object supporting :write() method. --- @param tbl table: the table to be written. -local function write_table_as_table(out, tbl) - out:write("return {\n") - for k, v, sub_order in util.sortedpairs(tbl) do - out:write(" ") - write_table_key_assignment(out, k, 1) - persist.write_value(out, v, 1, sub_order) - out:write(",\n") - end - out:write("}\n") -end - ---- Save the contents of a table to a string. --- Each element of the table is saved as a global assignment. --- Only numbers, strings and tables (containing numbers, strings --- or other recursively processed tables) are supported. --- @param tbl table: the table containing the data to be written --- @param field_order table: an optional array indicating the order of top-level fields. --- @return persisted data as string; or nil and an error message -function persist.save_from_table_to_string(tbl, field_order) - local out = {buffer = {}} - function out:write(data) table.insert(self.buffer, data) end - local ok, err = write_table_as_assignments(out, tbl, field_order) - if not ok then - return nil, err - end - return table.concat(out.buffer) -end - ---- Save the contents of a table in a file. --- Each element of the table is saved as a global assignment. --- Only numbers, strings and tables (containing numbers, strings --- or other recursively processed tables) are supported. --- @param filename string: the output filename --- @param tbl table: the table containing the data to be written --- @param field_order table: an optional array indicating the order of top-level fields. --- @return boolean or (nil, string): true if successful, or nil and a --- message in case of errors. -function persist.save_from_table(filename, tbl, field_order) - local prefix = dir.dir_name(filename) - fs.make_dir(prefix) - local out = io.open(filename, "w") - if not out then - return nil, "Cannot create file at "..filename - end - local ok, err = write_table_as_assignments(out, tbl, field_order) - out:close() - if not ok then - return nil, err - end - return true -end - ---- Save the contents of a table as a module. --- The module contains a 'return' statement that returns the table. --- Only numbers, strings and tables (containing numbers, strings --- or other recursively processed tables) are supported. --- @param filename string: the output filename --- @param tbl table: the table containing the data to be written --- @return boolean or (nil, string): true if successful, or nil and a --- message in case of errors. -function persist.save_as_module(filename, tbl) - local out = io.open(filename, "w") - if not out then - return nil, "Cannot create file at "..filename - end - write_table_as_table(out, tbl) - out:close() - return true -end - -function persist.load_config_file_if_basic(filename, cfg) - local env = { - home = cfg.home - } - local result, err, errcode = persist.load_into_table(filename, env) - if errcode == "load" or errcode == "run" then - -- bad config file or depends on env, so error out - return nil, "Could not read existing config file " .. filename - end - - local tbl - if errcode == "open" then - -- could not open, maybe file does not exist - tbl = {} - else - tbl = result - tbl.home = nil - end - - return tbl -end - -function persist.save_default_lua_version(prefix, lua_version) - local ok, err = fs.make_dir(prefix) - if not ok then - return nil, err - end - local fd, err = io.open(dir.path(prefix, "default-lua-version.lua"), "w") - if not fd then - return nil, err - end - fd:write('return "' .. lua_version .. '"\n') - fd:close() - return true -end - -return persist diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua new file mode 100644 index 00000000..4dcd930a --- /dev/null +++ b/src/luarocks/persist.lua @@ -0,0 +1,259 @@ + +--- Utility module for loading files into tables and +-- saving tables into files. +local persist = {} + +local core = require("luarocks.core.persist") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local fs = require("luarocks.fs") + +persist.run_file = core.run_file +persist.load_into_table = core.load_into_table + +local write_table + +--- Write a value as Lua code. +-- This function handles only numbers and strings, invoking write_table +-- to write tables. +-- @param out table or userdata: a writer object supporting :write() method. +-- @param v: the value to be written. +-- @param level number: the indentation level +-- @param sub_order table: optional prioritization table +-- @see write_table +function persist.write_value(out, v, level, sub_order) + if type(v) == "table" then + level = level or 0 + write_table(out, v, level + 1, sub_order) + elseif type(v) == "string" then + if v:match("[\r\n]") then + local open, close = "[[", "]]" + local equals = 0 + local v_with_bracket = v.."]" + while v_with_bracket:find(close, 1, true) do + equals = equals + 1 + local eqs = ("="):rep(equals) + open, close = "["..eqs.."[", "]"..eqs.."]" + end + out:write(open.."\n"..v..close) + else + out:write(("%q"):format(v)) + end + else + out:write(tostring(v)) + end +end + +local is_valid_plain_key +do + local keywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, + } + function is_valid_plain_key(k) + return type(k) == "string" + and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") + and not keywords[k] + end +end + +local function write_table_key_assignment(out, k, level) + if is_valid_plain_key(k) then + out:write(k) + else + out:write("[") + persist.write_value(out, k, level) + out:write("]") + end + + out:write(" = ") +end + +--- Write a table as Lua code in curly brackets notation to a writer object. +-- Only numbers, strings and tables (containing numbers, strings +-- or other recursively processed tables) are supported. +-- @param out table or userdata: a writer object supporting :write() method. +-- @param tbl table: the table to be written. +-- @param level number: the indentation level +-- @param field_order table: optional prioritization table +write_table = function(out, tbl, level, field_order) + out:write("{") + local sep = "\n" + local indentation = " " + local indent = true + local i = 1 + for k, v, sub_order in util.sortedpairs(tbl, field_order) do + out:write(sep) + if indent then + for _ = 1, level do out:write(indentation) end + end + + if k == i then + i = i + 1 + else + write_table_key_assignment(out, k, level) + end + + persist.write_value(out, v, level, sub_order) + if type(v) == "number" then + sep = ", " + indent = false + else + sep = ",\n" + indent = true + end + end + if sep ~= "\n" then + out:write("\n") + for _ = 1, level - 1 do out:write(indentation) end + end + out:write("}") +end + +--- Write a table as series of assignments to a writer object. +-- @param out table or userdata: a writer object supporting :write() method. +-- @param tbl table: the table to be written. +-- @param field_order table: optional prioritization table +-- @return true if successful; nil and error message if failed. +local function write_table_as_assignments(out, tbl, field_order) + for k, v, sub_order in util.sortedpairs(tbl, field_order) do + if not is_valid_plain_key(k) then + return nil, "cannot store '"..tostring(k).."' as a plain key." + end + out:write(k.." = ") + persist.write_value(out, v, 0, sub_order) + out:write("\n") + end + return true +end + +--- Write a table using Lua table syntax to a writer object. +-- @param out table or userdata: a writer object supporting :write() method. +-- @param tbl table: the table to be written. +local function write_table_as_table(out, tbl) + out:write("return {\n") + for k, v, sub_order in util.sortedpairs(tbl) do + out:write(" ") + write_table_key_assignment(out, k, 1) + persist.write_value(out, v, 1, sub_order) + out:write(",\n") + end + out:write("}\n") +end + +--- Save the contents of a table to a string. +-- Each element of the table is saved as a global assignment. +-- Only numbers, strings and tables (containing numbers, strings +-- or other recursively processed tables) are supported. +-- @param tbl table: the table containing the data to be written +-- @param field_order table: an optional array indicating the order of top-level fields. +-- @return persisted data as string; or nil and an error message +function persist.save_from_table_to_string(tbl, field_order) + local out = {buffer = {}} + function out:write(data) table.insert(self.buffer, data) end + local ok, err = write_table_as_assignments(out, tbl, field_order) + if not ok then + return nil, err + end + return table.concat(out.buffer) +end + +--- Save the contents of a table in a file. +-- Each element of the table is saved as a global assignment. +-- Only numbers, strings and tables (containing numbers, strings +-- or other recursively processed tables) are supported. +-- @param filename string: the output filename +-- @param tbl table: the table containing the data to be written +-- @param field_order table: an optional array indicating the order of top-level fields. +-- @return boolean or (nil, string): true if successful, or nil and a +-- message in case of errors. +function persist.save_from_table(filename, tbl, field_order) + local prefix = dir.dir_name(filename) + fs.make_dir(prefix) + local out = io.open(filename, "w") + if not out then + return nil, "Cannot create file at "..filename + end + local ok, err = write_table_as_assignments(out, tbl, field_order) + out:close() + if not ok then + return nil, err + end + return true +end + +--- Save the contents of a table as a module. +-- The module contains a 'return' statement that returns the table. +-- Only numbers, strings and tables (containing numbers, strings +-- or other recursively processed tables) are supported. +-- @param filename string: the output filename +-- @param tbl table: the table containing the data to be written +-- @return boolean or (nil, string): true if successful, or nil and a +-- message in case of errors. +function persist.save_as_module(filename, tbl) + local out = io.open(filename, "w") + if not out then + return nil, "Cannot create file at "..filename + end + write_table_as_table(out, tbl) + out:close() + return true +end + +function persist.load_config_file_if_basic(filename, cfg) + local env = { + home = cfg.home + } + local result, err, errcode = persist.load_into_table(filename, env) + if errcode == "load" or errcode == "run" then + -- bad config file or depends on env, so error out + return nil, "Could not read existing config file " .. filename + end + + local tbl + if errcode == "open" then + -- could not open, maybe file does not exist + tbl = {} + else + tbl = result + tbl.home = nil + end + + return tbl +end + +function persist.save_default_lua_version(prefix, lua_version) + local ok, err = fs.make_dir(prefix) + if not ok then + return nil, err + end + local fd, err = io.open(dir.path(prefix, "default-lua-version.lua"), "w") + if not fd then + return nil, err + end + fd:write('return "' .. lua_version .. '"\n') + fd:close() + return true +end + +return persist diff --git a/src/luarocks/persist.tl b/src/luarocks/persist.tl index 5611b684..f02fcfee 100644 --- a/src/luarocks/persist.tl +++ b/src/luarocks/persist.tl @@ -3,10 +3,11 @@ -- saving tables into files. local record persist run_file: function(string, {string:any}): boolean, any | string, string - load_into_table: function(string, {string:any}) : {any: any}, {any: any} | string, string + load_into_table: function(string, ?{string:any}) : {any: any}, {any: any} | string, string end local core = require("luarocks.core.persist") +local coreutil = require("luarocks.core.util") local util = require("luarocks.util") local dir = require("luarocks.dir") local fs = require("luarocks.fs") @@ -14,7 +15,13 @@ local fs = require("luarocks.fs") persist.run_file = core.run_file persist.load_into_table = core.load_into_table -local write_table: function(out, tbl: {number | string: number| string}, level: integer, field_order: {(number | string): any}) +local type SortBy = table.SortFunction | coreutil.Ordering + +local interface Writer + write: function(self: Writer, data: string) +end + +local write_table: function(out: Writer, tbl: {number | string: number| string}, level: integer, field_order: SortBy) --- Write a value as Lua code. -- This function handles only numbers and strings, invoking write_table @@ -24,8 +31,8 @@ local write_table: function(out, tbl: {number | string: number| string}, level: -- @param level number: the indentation level -- @param sub_order table: optional prioritization table -- @see write_table -function persist.write_value(out, v: any, level: integer, sub_order) --! out type - if v is {any: any} then +function persist.write_value(out: Writer, v: any, level: integer, sub_order?: SortBy) + if v is {number | string: number | string} then level = level or 0 write_table(out, v, level + 1, sub_order) elseif v is string then @@ -79,7 +86,7 @@ do end end -local function write_table_key_assignment(out, k: string | number, level: integer) +local function write_table_key_assignment(out: Writer, k: string | number, level: integer) if k is string and is_valid_plain_key(k) then out:write(k) else @@ -98,7 +105,7 @@ end -- @param tbl table: the table to be written. -- @param level number: the indentation level -- @param field_order table: optional prioritization table -write_table = function(out, tbl: {number | string: number| string}, level: integer, field_order: {(number | string): any}) --? suborrder type? +write_table = function(out: Writer, tbl: {number | string: number| string}, level: integer, field_order: SortBy) out:write("{") local sep = "\n" local indentation = " " @@ -137,9 +144,9 @@ end -- @param tbl table: the table to be written. -- @param field_order table: optional prioritization table -- @return true if successful; nil and error message if failed. -local function write_table_as_assignments(out, tbl, field_order) +local function write_table_as_assignments(out: Writer, tbl: {number | string: number| string}, field_order: SortBy): boolean, string for k, v, sub_order in util.sortedpairs(tbl, field_order) do - if not is_valid_plain_key(k) then + if not is_valid_plain_key(k) then --? tostring return nil, "cannot store '"..tostring(k).."' as a plain key." end out:write(k.." = ") @@ -152,9 +159,9 @@ end --- Write a table using Lua table syntax to a writer object. -- @param out table or userdata: a writer object supporting :write() method. -- @param tbl table: the table to be written. -local function write_table_as_table(out, tbl) +local function write_table_as_table(out: Writer, tbl: {number | string: number| string}) out:write("return {\n") - for k, v, sub_order in util.sortedpairs(tbl) do + for k, v, sub_order in util.sortedpairs(tbl) do --! that has the right inputs and outputs yet it throws an error out:write(" ") write_table_key_assignment(out, k, 1) persist.write_value(out, v, 1, sub_order) @@ -170,8 +177,8 @@ end -- @param tbl table: the table containing the data to be written -- @param field_order table: an optional array indicating the order of top-level fields. -- @return persisted data as string; or nil and an error message -function persist.save_from_table_to_string(tbl, field_order) - local out = {buffer = {}} +function persist.save_from_table_to_string(tbl: {number | string: number| string}, field_order: SortBy): string + local out: Writer = {buffer = {}} --! function out:write(data) table.insert(self.buffer, data) end local ok, err = write_table_as_assignments(out, tbl, field_order) if not ok then diff --git a/src/luarocks/type/rockspec.lua b/src/luarocks/type/rockspec.lua index 0b4b5dcf..5ee944a4 100644 --- a/src/luarocks/type/rockspec.lua +++ b/src/luarocks/type/rockspec.lua @@ -135,13 +135,27 @@ local rockspec_formats, versions = type_check.declare_schemas({ } }) -type_rockspec.order = {"rockspec_format", "package", "version", - { "source", { "url", "tag", "branch", "md5" } }, - { "description", {"summary", "detailed", "homepage", "license" } }, - "supported_platforms", "dependencies", "build_dependencies", "external_dependencies", - { "build", {"type", "modules", "copy_directories", "platforms"} }, - "test_dependencies", { "test", {"type"} }, - "hooks"} +type_rockspec.order = { + "rockspec_format", + "package", + "version", + "source", + "description", + "supported_platforms", + "dependencies", + "build_dependencies", + "external_dependencies", + "build", + "test_dependencies", + "test", + "hooks", + sub_orders = { + ["source"] = { "url", "tag", "branch", "md5" }, + ["description"] = {"summary", "detailed", "homepage", "license" }, + ["build"] = { "type", "modules", "copy_directories", "platforms" }, + ["test"] = { "type" } + } +} local function check_rockspec_using_version(rockspec, globals, version) local schema = rockspec_formats[version] diff --git a/src/luarocks/util-original.lua b/src/luarocks/util-original.lua deleted file mode 100644 index de9157fc..00000000 --- a/src/luarocks/util-original.lua +++ /dev/null @@ -1,634 +0,0 @@ - ---- Assorted utilities for managing tables, plus a scheduler for rollback functions. --- Does not requires modules directly (only as locals --- inside specific functions) to avoid interdependencies, --- as this is used in the bootstrapping stage of luarocks.core.cfg. - -local util = {} - -local core = require("luarocks.core.util") - -util.cleanup_path = core.cleanup_path -util.split_string = core.split_string -util.sortedpairs = core.sortedpairs -util.deep_merge = core.deep_merge -util.deep_merge_under = core.deep_merge_under -util.popen_read = core.popen_read -util.show_table = core.show_table -util.printerr = core.printerr -util.warning = core.warning -util.keys = core.keys - -local unpack = unpack or table.unpack -local pack = table.pack or function(...) return { n = select("#", ...), ... } end - -local scheduled_functions = {} - ---- Schedule a function to be executed upon program termination. --- This is useful for actions such as deleting temporary directories --- or failure rollbacks. --- @param f function: Function to be executed. --- @param ... arguments to be passed to function. --- @return table: A token representing the scheduled execution, --- which can be used to remove the item later from the list. -function util.schedule_function(f, ...) - assert(type(f) == "function") - - local item = { fn = f, args = pack(...) } - table.insert(scheduled_functions, item) - return item -end - ---- Unschedule a function. --- This is useful for cancelling a rollback of a completed operation. --- @param item table: The token representing the scheduled function that was --- returned from the schedule_function call. -function util.remove_scheduled_function(item) - for k, v in pairs(scheduled_functions) do - if v == item then - table.remove(scheduled_functions, k) - return - end - end -end - ---- Execute scheduled functions. --- Some calls create temporary files and/or directories and register --- corresponding cleanup functions. Calling this function will run --- these function, erasing temporaries. --- Functions are executed in the inverse order they were scheduled. -function util.run_scheduled_functions() - local fs = require("luarocks.fs") - if fs.change_dir_to_root then - fs.change_dir_to_root() - end - for i = #scheduled_functions, 1, -1 do - local item = scheduled_functions[i] - item.fn(unpack(item.args, 1, item.args.n)) - end -end - ---- Produce a Lua pattern that matches precisely the given string --- (this is suitable to be concatenating to other patterns, --- so it does not include beginning- and end-of-string markers (^$) --- @param s string: The input string --- @return string: The equivalent pattern -function util.matchquote(s) - return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) -end - -local var_format_pattern = "%$%((%a[%a%d_]+)%)" - --- Check if a set of needed variables are referenced --- somewhere in a list of definitions, warning the user --- about any unused ones. Each key in needed_set should --- appear as a $(XYZ) variable at least once as a --- substring of some value of var_defs. --- @param var_defs: a table with string keys and string --- values, containing variable definitions. --- @param needed_set: a set where keys are the names of --- needed variables. --- @param msg string: the warning message to display. -function util.warn_if_not_used(var_defs, needed_set, msg) - local seen = {} - for _, val in pairs(var_defs) do - for used in val:gmatch(var_format_pattern) do - seen[used] = true - end - end - for var, _ in pairs(needed_set) do - if not seen[var] then - util.warning(msg:format(var)) - end - end -end - --- Output any entries that might remain in $(XYZ) format, --- warning the user that substitutions have failed. --- @param line string: the input string -local function warn_failed_matches(line) - local any_failed = false - if line:match(var_format_pattern) then - for unmatched in line:gmatch(var_format_pattern) do - util.warning("unmatched variable " .. unmatched) - any_failed = true - end - end - return any_failed -end - ---- Perform make-style variable substitutions on string values of a table. --- For every string value tbl.x which contains a substring of the format --- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field --- exists in vars. Only string values are processed; this function --- does not scan subtables recursively. --- @param tbl table: Table to have its string values modified. --- @param vars table: Table containing string-string key-value pairs --- representing variables to replace in the strings values of tbl. -function util.variable_substitutions(tbl, vars) - assert(type(tbl) == "table") - assert(type(vars) == "table") - - local updated = {} - for k, v in pairs(tbl) do - if type(v) == "string" then - updated[k] = v:gsub(var_format_pattern, vars) - if warn_failed_matches(updated[k]) then - updated[k] = updated[k]:gsub(var_format_pattern, "") - end - end - end - for k, v in pairs(updated) do - tbl[k] = v - end -end - -function util.lua_versions(sort) - local versions = { "5.1", "5.2", "5.3", "5.4" } - local i = 0 - if sort == "descending" then - i = #versions + 1 - return function() - i = i - 1 - return versions[i] - end - else - return function() - i = i + 1 - return versions[i] - end - end -end - -function util.lua_path_variables() - local cfg = require("luarocks.core.cfg") - local lpath_var = "LUA_PATH" - local lcpath_var = "LUA_CPATH" - - local lv = cfg.lua_version:gsub("%.", "_") - if lv ~= "5_1" then - if os.getenv("LUA_PATH_" .. lv) then - lpath_var = "LUA_PATH_" .. lv - end - if os.getenv("LUA_CPATH_" .. lv) then - lcpath_var = "LUA_CPATH_" .. lv - end - end - return lpath_var, lcpath_var -end - -function util.starts_with(s, prefix) - return s:sub(1,#prefix) == prefix -end - ---- Print a line to standard output -function util.printout(...) - io.stdout:write(table.concat({...},"\t")) - io.stdout:write("\n") -end - -function util.title(msg, porcelain, underline) - if porcelain then return end - util.printout() - util.printout(msg) - util.printout((underline or "-"):rep(#msg)) - util.printout() -end - -function util.this_program(default) - local i = 1 - local last, cur = default, default - while i do - local dbg = debug and debug.getinfo(i,"S") - if not dbg then break end - last = cur - cur = dbg.source - i=i+1 - end - local prog = last:sub(1,1) == "@" and last:sub(2) or last - - -- Check if we found the true path of a script that has a wrapper - local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") - if lrdir then - -- Return the wrapper instead - return lrdir .. binpath - end - - return prog -end - -function util.format_rock_name(name, namespace, version) - return (namespace and namespace.."/" or "")..name..(version and " "..version or "") -end - -function util.deps_mode_option(parser, program) - local cfg = require("luarocks.core.cfg") - - parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n".. - "* all - use all trees from the rocks_trees list for finding dependencies\n".. - "* one - use only the current tree (possibly set with --tree)\n".. - "* order - use trees based on order (use the current tree and all ".. - "trees below it on the rocks_trees list)\n".. - "* none - ignore dependencies altogether.\n".. - "The default mode may be set with the deps_mode entry in the configuration file.\n".. - 'The current default is "'..cfg.deps_mode..'".\n'.. - "Type '"..util.this_program(program or "luarocks").."' with no ".. - "arguments to see your list of rocks trees.") - :argname("") - :choices({"all", "one", "order", "none"}) - parser:flag("--nodeps"):hidden(true) -end - -function util.see_help(command, program) - return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." -end - -function util.see_also(text) - local see_also = "See also:\n" - if text then - see_also = see_also..text.."\n" - end - return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration." -end - -function util.announce_install(rockspec) - local cfg = require("luarocks.core.cfg") - local path = require("luarocks.path") - - local suffix = "" - if rockspec.description and rockspec.description.license then - suffix = " (license: "..rockspec.description.license..")" - end - - util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix) - util.printout() -end - ---- Collect rockspecs located in a subdirectory. --- @param versions table: A table mapping rock names to newest rockspec versions. --- @param paths table: A table mapping rock names to newest rockspec paths. --- @param unnamed_paths table: An array of rockspec paths that don't contain rock --- name and version in regular format. --- @param subdir string: path to subdirectory. -local function collect_rockspecs(versions, paths, unnamed_paths, subdir) - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - local path = require("luarocks.path") - local vers = require("luarocks.core.vers") - - if fs.is_dir(subdir) then - for file in fs.dir(subdir) do - file = dir.path(subdir, file) - - if file:match("rockspec$") and fs.is_file(file) then - local rock, version = path.parse_name(file) - - if rock then - if not versions[rock] or vers.compare_versions(version, versions[rock]) then - versions[rock] = version - paths[rock] = file - end - else - table.insert(unnamed_paths, file) - end - end - end - end -end - ---- Get default rockspec name for commands that take optional rockspec name. --- @return string or (nil, string): path to the rockspec or nil and error message. -function util.get_default_rockspec() - local versions, paths, unnamed_paths = {}, {}, {} - -- Look for rockspecs in some common locations. - collect_rockspecs(versions, paths, unnamed_paths, ".") - collect_rockspecs(versions, paths, unnamed_paths, "rockspec") - collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") - - if #unnamed_paths > 0 then - -- There are rockspecs not following "name-version.rockspec" format. - -- More than one are ambiguous. - if #unnamed_paths > 1 then - return nil, "Please specify which rockspec file to use." - else - return unnamed_paths[1] - end - else - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - local basename = dir.base_name(fs.current_dir()) - - if paths[basename] then - return paths[basename] - end - - local rock = next(versions) - - if rock then - -- If there are rockspecs for multiple rocks it's ambiguous. - if next(versions, rock) then - return nil, "Please specify which rockspec file to use." - else - return paths[rock] - end - else - return nil, "Argument missing: please specify a rockspec to use on current directory." - end - end -end - --- Quote Lua string, analogous to fs.Q. --- @param s A string, such as "hello" --- @return string: A quoted string, such as '"hello"' -function util.LQ(s) - return ("%q"):format(s) -end - --- Split name and namespace of a package name. --- @param ns_name a name that may be in "namespace/name" format --- @return string, string? - name and optionally a namespace -function util.split_namespace(ns_name) - local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") - if p1 then - return p2, p1 - end - return ns_name -end - ---- Argparse action callback for namespaced rock arguments. -function util.namespaced_name_action(args, target, ns_name) - assert(type(args) == "table") - assert(type(target) == "string") - assert(type(ns_name) == "string" or not ns_name) - - if not ns_name then - return - end - - if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then - args[target] = ns_name - else - local name, namespace = util.split_namespace(ns_name) - args[target] = name:lower() - if namespace then - args.namespace = namespace:lower() - end - end -end - -function util.deep_copy(tbl) - local copy = {} - for k, v in pairs(tbl) do - if type(v) == "table" then - copy[k] = util.deep_copy(v) - else - copy[k] = v - end - end - return copy -end - --- A portable version of fs.exists that can be used at early startup, --- before the platform has been determined and luarocks.fs has been --- initialized. -function util.exists(file) - local fd, _, code = io.open(file, "r") - if code == 13 then - -- code 13 means "Permission denied" on both Unix and Windows - -- io.open on folders always fails with code 13 on Windows - return true - end - if fd then - fd:close() - return true - end - return false -end - -do - local function Q(pathname) - if pathname:match("^.:") then - return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' - end - return '"' .. pathname .. '"' - end - - function util.check_lua_version(lua, luaver) - if not util.exists(lua) then - return nil - end - local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') - if lv == "" then - return nil - end - if luaver and luaver ~= lv then - return nil - end - return lv - end - - function util.get_luajit_version() - local cfg = require("luarocks.core.cfg") - if cfg.cache.luajit_version_checked then - return cfg.cache.luajit_version - end - cfg.cache.luajit_version_checked = true - - if not cfg.variables.LUA then - return nil - end - - local ljv - if cfg.lua_version == "5.1" then - -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info" - ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') - if ljv == "nil" then - ljv = nil - end - end - cfg.cache.luajit_version = ljv - return ljv - end - - local find_lua_bindir - do - local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") - - local function insert_lua_variants(names, luaver) - local variants = { - "lua" .. luaver .. exe_suffix, - "lua" .. luaver:gsub("%.", "") .. exe_suffix, - "lua-" .. luaver .. exe_suffix, - "lua-" .. luaver:gsub("%.", "") .. exe_suffix, - } - for _, name in ipairs(variants) do - names[name] = luaver - table.insert(names, name) - end - end - - find_lua_bindir = function(prefix, luaver, verbose) - local names = {} - if luaver then - insert_lua_variants(names, luaver) - else - for v in util.lua_versions("descending") do - insert_lua_variants(names, v) - end - end - if luaver == "5.1" or not luaver then - table.insert(names, "luajit" .. exe_suffix) - end - table.insert(names, "lua" .. exe_suffix) - - local tried = {} - local dir_sep = package.config:sub(1, 1) - for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do - for _, name in ipairs(names) do - local lua = d .. dir_sep .. name - local is_wrapper, err = util.lua_is_wrapper(lua) - if is_wrapper == false then - local lv = util.check_lua_version(lua, luaver) - if lv then - return lua, d, lv - end - elseif is_wrapper == true or err == nil then - table.insert(tried, lua) - else - table.insert(tried, string.format("%-13s (%s)", lua, err)) - end - end - end - local interp = luaver - and ("Lua " .. luaver .. " interpreter") - or "Lua interpreter" - return nil, interp .. " not found at " .. prefix .. "\n" .. - (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") - end - end - - function util.find_lua(prefix, luaver, verbose) - local lua, bindir - lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) - if not lua then - return nil, bindir - end - - return { - lua_version = luaver, - lua = lua, - lua_dir = prefix, - lua_bindir = bindir, - } - end -end - -function util.lua_is_wrapper(interp) - local fd, err = io.open(interp, "r") - if not fd then - return nil, err - end - local data, err = fd:read(1000) - fd:close() - if not data then - return nil, err - end - return not not data:match("LUAROCKS_SYSCONFDIR") -end - -function util.opts_table(type_name, valid_opts) - local opts_mt = {} - - opts_mt.__index = opts_mt - - function opts_mt.type() - return type_name - end - - return function(opts) - for k, v in pairs(opts) do - local tv = type(v) - if not valid_opts[k] then - error("invalid option: "..k) - end - local vo, optional = valid_opts[k]:match("^(.-)(%??)$") - if not (tv == vo or (optional == "?" and tv == nil)) then - error("invalid type option: "..k.." - got "..tv..", expected "..vo) - end - end - for k, v in pairs(valid_opts) do - if (not v:find("?", 1, true)) and opts[k] == nil then - error("missing option: "..k) - end - end - return setmetatable(opts, opts_mt) - end -end - ---- Return a table of modules that are already provided by the VM, which --- can be specified as dependencies without having to install an actual rock. --- @param rockspec (optional) a rockspec table, so that rockspec format --- version compatibility can be checked. If not given, maximum compatibility --- is assumed. --- @return a table with rock names as keys and versions and values, --- specifying modules that are already provided by the VM (including --- "lua" for the Lua version and, for format 3.0+, "luajit" if detected). -function util.get_rocks_provided(rockspec) - local cfg = require("luarocks.core.cfg") - - if not rockspec and cfg.cache.rocks_provided then - return cfg.cache.rocks_provided - end - - local rocks_provided = {} - - local lv = cfg.lua_version - - rocks_provided["lua"] = lv.."-1" - - if lv == "5.2" then - rocks_provided["bit32"] = lv.."-1" - end - - if lv == "5.3" or lv == "5.4" then - rocks_provided["utf8"] = lv.."-1" - end - - if lv == "5.1" then - local ljv = util.get_luajit_version() - if ljv then - rocks_provided["luabitop"] = ljv.."-1" - if (not rockspec) or rockspec:format_is_at_least("3.0") then - rocks_provided["luajit"] = ljv.."-1" - end - end - end - - if cfg.rocks_provided then - util.deep_merge_under(rocks_provided, cfg.rocks_provided) - end - - if not rockspec then - cfg.cache.rocks_provided = rocks_provided - end - - return rocks_provided -end - -function util.remove_doc_dir(name, version) - local path = require("luarocks.path") - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - - local install_dir = path.install_dir(name, version) - for _, f in ipairs(fs.list_dir(install_dir)) do - local doc_dirs = { "doc", "docs" } - for _, d in ipairs(doc_dirs) do - if f == d then - fs.delete(dir.path(install_dir, f)) - end - end - end -end - -return util diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua new file mode 100644 index 00000000..de9157fc --- /dev/null +++ b/src/luarocks/util.lua @@ -0,0 +1,634 @@ + +--- Assorted utilities for managing tables, plus a scheduler for rollback functions. +-- Does not requires modules directly (only as locals +-- inside specific functions) to avoid interdependencies, +-- as this is used in the bootstrapping stage of luarocks.core.cfg. + +local util = {} + +local core = require("luarocks.core.util") + +util.cleanup_path = core.cleanup_path +util.split_string = core.split_string +util.sortedpairs = core.sortedpairs +util.deep_merge = core.deep_merge +util.deep_merge_under = core.deep_merge_under +util.popen_read = core.popen_read +util.show_table = core.show_table +util.printerr = core.printerr +util.warning = core.warning +util.keys = core.keys + +local unpack = unpack or table.unpack +local pack = table.pack or function(...) return { n = select("#", ...), ... } end + +local scheduled_functions = {} + +--- Schedule a function to be executed upon program termination. +-- This is useful for actions such as deleting temporary directories +-- or failure rollbacks. +-- @param f function: Function to be executed. +-- @param ... arguments to be passed to function. +-- @return table: A token representing the scheduled execution, +-- which can be used to remove the item later from the list. +function util.schedule_function(f, ...) + assert(type(f) == "function") + + local item = { fn = f, args = pack(...) } + table.insert(scheduled_functions, item) + return item +end + +--- Unschedule a function. +-- This is useful for cancelling a rollback of a completed operation. +-- @param item table: The token representing the scheduled function that was +-- returned from the schedule_function call. +function util.remove_scheduled_function(item) + for k, v in pairs(scheduled_functions) do + if v == item then + table.remove(scheduled_functions, k) + return + end + end +end + +--- Execute scheduled functions. +-- Some calls create temporary files and/or directories and register +-- corresponding cleanup functions. Calling this function will run +-- these function, erasing temporaries. +-- Functions are executed in the inverse order they were scheduled. +function util.run_scheduled_functions() + local fs = require("luarocks.fs") + if fs.change_dir_to_root then + fs.change_dir_to_root() + end + for i = #scheduled_functions, 1, -1 do + local item = scheduled_functions[i] + item.fn(unpack(item.args, 1, item.args.n)) + end +end + +--- Produce a Lua pattern that matches precisely the given string +-- (this is suitable to be concatenating to other patterns, +-- so it does not include beginning- and end-of-string markers (^$) +-- @param s string: The input string +-- @return string: The equivalent pattern +function util.matchquote(s) + return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) +end + +local var_format_pattern = "%$%((%a[%a%d_]+)%)" + +-- Check if a set of needed variables are referenced +-- somewhere in a list of definitions, warning the user +-- about any unused ones. Each key in needed_set should +-- appear as a $(XYZ) variable at least once as a +-- substring of some value of var_defs. +-- @param var_defs: a table with string keys and string +-- values, containing variable definitions. +-- @param needed_set: a set where keys are the names of +-- needed variables. +-- @param msg string: the warning message to display. +function util.warn_if_not_used(var_defs, needed_set, msg) + local seen = {} + for _, val in pairs(var_defs) do + for used in val:gmatch(var_format_pattern) do + seen[used] = true + end + end + for var, _ in pairs(needed_set) do + if not seen[var] then + util.warning(msg:format(var)) + end + end +end + +-- Output any entries that might remain in $(XYZ) format, +-- warning the user that substitutions have failed. +-- @param line string: the input string +local function warn_failed_matches(line) + local any_failed = false + if line:match(var_format_pattern) then + for unmatched in line:gmatch(var_format_pattern) do + util.warning("unmatched variable " .. unmatched) + any_failed = true + end + end + return any_failed +end + +--- Perform make-style variable substitutions on string values of a table. +-- For every string value tbl.x which contains a substring of the format +-- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field +-- exists in vars. Only string values are processed; this function +-- does not scan subtables recursively. +-- @param tbl table: Table to have its string values modified. +-- @param vars table: Table containing string-string key-value pairs +-- representing variables to replace in the strings values of tbl. +function util.variable_substitutions(tbl, vars) + assert(type(tbl) == "table") + assert(type(vars) == "table") + + local updated = {} + for k, v in pairs(tbl) do + if type(v) == "string" then + updated[k] = v:gsub(var_format_pattern, vars) + if warn_failed_matches(updated[k]) then + updated[k] = updated[k]:gsub(var_format_pattern, "") + end + end + end + for k, v in pairs(updated) do + tbl[k] = v + end +end + +function util.lua_versions(sort) + local versions = { "5.1", "5.2", "5.3", "5.4" } + local i = 0 + if sort == "descending" then + i = #versions + 1 + return function() + i = i - 1 + return versions[i] + end + else + return function() + i = i + 1 + return versions[i] + end + end +end + +function util.lua_path_variables() + local cfg = require("luarocks.core.cfg") + local lpath_var = "LUA_PATH" + local lcpath_var = "LUA_CPATH" + + local lv = cfg.lua_version:gsub("%.", "_") + if lv ~= "5_1" then + if os.getenv("LUA_PATH_" .. lv) then + lpath_var = "LUA_PATH_" .. lv + end + if os.getenv("LUA_CPATH_" .. lv) then + lcpath_var = "LUA_CPATH_" .. lv + end + end + return lpath_var, lcpath_var +end + +function util.starts_with(s, prefix) + return s:sub(1,#prefix) == prefix +end + +--- Print a line to standard output +function util.printout(...) + io.stdout:write(table.concat({...},"\t")) + io.stdout:write("\n") +end + +function util.title(msg, porcelain, underline) + if porcelain then return end + util.printout() + util.printout(msg) + util.printout((underline or "-"):rep(#msg)) + util.printout() +end + +function util.this_program(default) + local i = 1 + local last, cur = default, default + while i do + local dbg = debug and debug.getinfo(i,"S") + if not dbg then break end + last = cur + cur = dbg.source + i=i+1 + end + local prog = last:sub(1,1) == "@" and last:sub(2) or last + + -- Check if we found the true path of a script that has a wrapper + local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") + if lrdir then + -- Return the wrapper instead + return lrdir .. binpath + end + + return prog +end + +function util.format_rock_name(name, namespace, version) + return (namespace and namespace.."/" or "")..name..(version and " "..version or "") +end + +function util.deps_mode_option(parser, program) + local cfg = require("luarocks.core.cfg") + + parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n".. + "* all - use all trees from the rocks_trees list for finding dependencies\n".. + "* one - use only the current tree (possibly set with --tree)\n".. + "* order - use trees based on order (use the current tree and all ".. + "trees below it on the rocks_trees list)\n".. + "* none - ignore dependencies altogether.\n".. + "The default mode may be set with the deps_mode entry in the configuration file.\n".. + 'The current default is "'..cfg.deps_mode..'".\n'.. + "Type '"..util.this_program(program or "luarocks").."' with no ".. + "arguments to see your list of rocks trees.") + :argname("") + :choices({"all", "one", "order", "none"}) + parser:flag("--nodeps"):hidden(true) +end + +function util.see_help(command, program) + return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." +end + +function util.see_also(text) + local see_also = "See also:\n" + if text then + see_also = see_also..text.."\n" + end + return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration." +end + +function util.announce_install(rockspec) + local cfg = require("luarocks.core.cfg") + local path = require("luarocks.path") + + local suffix = "" + if rockspec.description and rockspec.description.license then + suffix = " (license: "..rockspec.description.license..")" + end + + util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix) + util.printout() +end + +--- Collect rockspecs located in a subdirectory. +-- @param versions table: A table mapping rock names to newest rockspec versions. +-- @param paths table: A table mapping rock names to newest rockspec paths. +-- @param unnamed_paths table: An array of rockspec paths that don't contain rock +-- name and version in regular format. +-- @param subdir string: path to subdirectory. +local function collect_rockspecs(versions, paths, unnamed_paths, subdir) + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local path = require("luarocks.path") + local vers = require("luarocks.core.vers") + + if fs.is_dir(subdir) then + for file in fs.dir(subdir) do + file = dir.path(subdir, file) + + if file:match("rockspec$") and fs.is_file(file) then + local rock, version = path.parse_name(file) + + if rock then + if not versions[rock] or vers.compare_versions(version, versions[rock]) then + versions[rock] = version + paths[rock] = file + end + else + table.insert(unnamed_paths, file) + end + end + end + end +end + +--- Get default rockspec name for commands that take optional rockspec name. +-- @return string or (nil, string): path to the rockspec or nil and error message. +function util.get_default_rockspec() + local versions, paths, unnamed_paths = {}, {}, {} + -- Look for rockspecs in some common locations. + collect_rockspecs(versions, paths, unnamed_paths, ".") + collect_rockspecs(versions, paths, unnamed_paths, "rockspec") + collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") + + if #unnamed_paths > 0 then + -- There are rockspecs not following "name-version.rockspec" format. + -- More than one are ambiguous. + if #unnamed_paths > 1 then + return nil, "Please specify which rockspec file to use." + else + return unnamed_paths[1] + end + else + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local basename = dir.base_name(fs.current_dir()) + + if paths[basename] then + return paths[basename] + end + + local rock = next(versions) + + if rock then + -- If there are rockspecs for multiple rocks it's ambiguous. + if next(versions, rock) then + return nil, "Please specify which rockspec file to use." + else + return paths[rock] + end + else + return nil, "Argument missing: please specify a rockspec to use on current directory." + end + end +end + +-- Quote Lua string, analogous to fs.Q. +-- @param s A string, such as "hello" +-- @return string: A quoted string, such as '"hello"' +function util.LQ(s) + return ("%q"):format(s) +end + +-- Split name and namespace of a package name. +-- @param ns_name a name that may be in "namespace/name" format +-- @return string, string? - name and optionally a namespace +function util.split_namespace(ns_name) + local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") + if p1 then + return p2, p1 + end + return ns_name +end + +--- Argparse action callback for namespaced rock arguments. +function util.namespaced_name_action(args, target, ns_name) + assert(type(args) == "table") + assert(type(target) == "string") + assert(type(ns_name) == "string" or not ns_name) + + if not ns_name then + return + end + + if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then + args[target] = ns_name + else + local name, namespace = util.split_namespace(ns_name) + args[target] = name:lower() + if namespace then + args.namespace = namespace:lower() + end + end +end + +function util.deep_copy(tbl) + local copy = {} + for k, v in pairs(tbl) do + if type(v) == "table" then + copy[k] = util.deep_copy(v) + else + copy[k] = v + end + end + return copy +end + +-- A portable version of fs.exists that can be used at early startup, +-- before the platform has been determined and luarocks.fs has been +-- initialized. +function util.exists(file) + local fd, _, code = io.open(file, "r") + if code == 13 then + -- code 13 means "Permission denied" on both Unix and Windows + -- io.open on folders always fails with code 13 on Windows + return true + end + if fd then + fd:close() + return true + end + return false +end + +do + local function Q(pathname) + if pathname:match("^.:") then + return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' + end + return '"' .. pathname .. '"' + end + + function util.check_lua_version(lua, luaver) + if not util.exists(lua) then + return nil + end + local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') + if lv == "" then + return nil + end + if luaver and luaver ~= lv then + return nil + end + return lv + end + + function util.get_luajit_version() + local cfg = require("luarocks.core.cfg") + if cfg.cache.luajit_version_checked then + return cfg.cache.luajit_version + end + cfg.cache.luajit_version_checked = true + + if not cfg.variables.LUA then + return nil + end + + local ljv + if cfg.lua_version == "5.1" then + -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info" + ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') + if ljv == "nil" then + ljv = nil + end + end + cfg.cache.luajit_version = ljv + return ljv + end + + local find_lua_bindir + do + local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") + + local function insert_lua_variants(names, luaver) + local variants = { + "lua" .. luaver .. exe_suffix, + "lua" .. luaver:gsub("%.", "") .. exe_suffix, + "lua-" .. luaver .. exe_suffix, + "lua-" .. luaver:gsub("%.", "") .. exe_suffix, + } + for _, name in ipairs(variants) do + names[name] = luaver + table.insert(names, name) + end + end + + find_lua_bindir = function(prefix, luaver, verbose) + local names = {} + if luaver then + insert_lua_variants(names, luaver) + else + for v in util.lua_versions("descending") do + insert_lua_variants(names, v) + end + end + if luaver == "5.1" or not luaver then + table.insert(names, "luajit" .. exe_suffix) + end + table.insert(names, "lua" .. exe_suffix) + + local tried = {} + local dir_sep = package.config:sub(1, 1) + for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do + for _, name in ipairs(names) do + local lua = d .. dir_sep .. name + local is_wrapper, err = util.lua_is_wrapper(lua) + if is_wrapper == false then + local lv = util.check_lua_version(lua, luaver) + if lv then + return lua, d, lv + end + elseif is_wrapper == true or err == nil then + table.insert(tried, lua) + else + table.insert(tried, string.format("%-13s (%s)", lua, err)) + end + end + end + local interp = luaver + and ("Lua " .. luaver .. " interpreter") + or "Lua interpreter" + return nil, interp .. " not found at " .. prefix .. "\n" .. + (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") + end + end + + function util.find_lua(prefix, luaver, verbose) + local lua, bindir + lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) + if not lua then + return nil, bindir + end + + return { + lua_version = luaver, + lua = lua, + lua_dir = prefix, + lua_bindir = bindir, + } + end +end + +function util.lua_is_wrapper(interp) + local fd, err = io.open(interp, "r") + if not fd then + return nil, err + end + local data, err = fd:read(1000) + fd:close() + if not data then + return nil, err + end + return not not data:match("LUAROCKS_SYSCONFDIR") +end + +function util.opts_table(type_name, valid_opts) + local opts_mt = {} + + opts_mt.__index = opts_mt + + function opts_mt.type() + return type_name + end + + return function(opts) + for k, v in pairs(opts) do + local tv = type(v) + if not valid_opts[k] then + error("invalid option: "..k) + end + local vo, optional = valid_opts[k]:match("^(.-)(%??)$") + if not (tv == vo or (optional == "?" and tv == nil)) then + error("invalid type option: "..k.." - got "..tv..", expected "..vo) + end + end + for k, v in pairs(valid_opts) do + if (not v:find("?", 1, true)) and opts[k] == nil then + error("missing option: "..k) + end + end + return setmetatable(opts, opts_mt) + end +end + +--- Return a table of modules that are already provided by the VM, which +-- can be specified as dependencies without having to install an actual rock. +-- @param rockspec (optional) a rockspec table, so that rockspec format +-- version compatibility can be checked. If not given, maximum compatibility +-- is assumed. +-- @return a table with rock names as keys and versions and values, +-- specifying modules that are already provided by the VM (including +-- "lua" for the Lua version and, for format 3.0+, "luajit" if detected). +function util.get_rocks_provided(rockspec) + local cfg = require("luarocks.core.cfg") + + if not rockspec and cfg.cache.rocks_provided then + return cfg.cache.rocks_provided + end + + local rocks_provided = {} + + local lv = cfg.lua_version + + rocks_provided["lua"] = lv.."-1" + + if lv == "5.2" then + rocks_provided["bit32"] = lv.."-1" + end + + if lv == "5.3" or lv == "5.4" then + rocks_provided["utf8"] = lv.."-1" + end + + if lv == "5.1" then + local ljv = util.get_luajit_version() + if ljv then + rocks_provided["luabitop"] = ljv.."-1" + if (not rockspec) or rockspec:format_is_at_least("3.0") then + rocks_provided["luajit"] = ljv.."-1" + end + end + end + + if cfg.rocks_provided then + util.deep_merge_under(rocks_provided, cfg.rocks_provided) + end + + if not rockspec then + cfg.cache.rocks_provided = rocks_provided + end + + return rocks_provided +end + +function util.remove_doc_dir(name, version) + local path = require("luarocks.path") + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + + local install_dir = path.install_dir(name, version) + for _, f in ipairs(fs.list_dir(install_dir)) do + local doc_dirs = { "doc", "docs" } + for _, d in ipairs(doc_dirs) do + if f == d then + fs.delete(dir.path(install_dir, f)) + end + end + end +end + +return util diff --git a/src/luarocks/util.tl b/src/luarocks/util.tl index 868e1552..f1447e60 100644 --- a/src/luarocks/util.tl +++ b/src/luarocks/util.tl @@ -7,12 +7,12 @@ local core = require("luarocks.core.util") local cfg = require("luarocks.core.cfg") -local type CompFn = core.CompFn +local type SortBy = table.SortFunction | core.Ordering local record util cleanup_path: function(string, string, string, boolean): string split_string: function(string, string, number): {string} - sortedpairs: function({K : V}, CompFn | {K | {K, S}}): function(): K, V, S + sortedpairs: function(tbl: {K: V}, sort_by: SortBy): function(): K, V, core.Ordering deep_merge: function({any : any}, {any : any}) deep_merge_under: function({any : any}, {any : any}) popen_read: function(string, ?string): string @@ -27,19 +27,18 @@ local record util args: table.PackTable end - record Parser --? + record Parser option: function(Parser, ...: string): Parser argname: function(Parser, string): Parser choices: function(Parser, {string}): Parser flag: function(Parser, string): Parser hidden: function(Parser, boolean): Parser end - end -util.cleanup_path = core.cleanup_path --tlcheck acting funny +util.cleanup_path = core.cleanup_path util.split_string = core.split_string --- util.sortedpairs = core.sortedpairs +util.sortedpairs = core.sortedpairs util.deep_merge = core.deep_merge util.deep_merge_under = core.deep_merge_under util.popen_read = core.popen_read @@ -53,6 +52,7 @@ local type Fn = util.Fn local type Rockspec = cfg.Rockspec local type Parser = util.Parser + local scheduled_functions: {Fn} = {} --? infered from line 48-51 --- Schedule a function to be executed upon program termination. @@ -290,7 +290,6 @@ local function collect_rockspecs(versions: {string: string}, paths: {string: str local dir = require("luarocks.dir") local path = require("luarocks.path") local vers = require("luarocks.core.vers") - if fs.is_dir(subdir) then for file in fs.dir(subdir) do file = dir.path(subdir, file) -- cgit v1.2.3-55-g6feb