moonscript, busted, penlight packages + debugger speed improvements

This commit is contained in:
kepler155c@gmail.com
2020-06-09 18:17:21 -06:00
parent de3d73de70
commit 0f7534d12c
19 changed files with 1065 additions and 1561 deletions

8
pl/.package Normal file
View File

@@ -0,0 +1,8 @@
{
title = 'Penlight',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/pl',
description = [[See: https://github.com/Tieske/Penlight
Penlight brings together a set of generally useful pure Lua modules, focusing on input data handling (such as reading configuration files), functional programming (such as map, reduce, placeholder expressions, etc), and OS path management. Much of the functionality is inspired by the Python standard libraries.]],
license = 'MIT',
}

188
pl/apis/compat.lua Normal file
View File

@@ -0,0 +1,188 @@
----------------
--- Lua 5.1/5.2/5.3 compatibility.
-- Injects `table.pack`, `table.unpack`, and `package.searchpath` in the global
-- environment, to make sure they are available for Lua 5.1 and LuaJIT.
--
-- All other functions are exported as usual in the returned module table.
--
-- NOTE: everything in this module is also available in `pl.utils`.
-- @module pl.compat
local compat = {}
--- boolean flag this is Lua 5.1 (or LuaJIT).
-- @field lua51
compat.lua51 = _VERSION == 'Lua 5.1'
--- boolean flag this is LuaJIT.
-- @field jit
compat.jit = (tostring(assert):match('builtin') ~= nil)
--- boolean flag this is LuaJIT with 5.2 compatibility compiled in.
-- @field jit52
if compat.jit then
-- 'goto' is a keyword when 52 compatibility is enabled in LuaJit
compat.jit52 = not loadstring("local goto = 1")
end
--- the directory separator character for the current platform.
-- @field dir_separator
compat.dir_separator = _ENV.package.config:sub(1,1)
--- boolean flag this is a Windows platform.
-- @field is_windows
compat.is_windows = compat.dir_separator == '\\'
--- execute a shell command, in a compatible and platform independent way.
-- This is a compatibility function that returns the same for Lua 5.1 and
-- Lua 5.2+.
--
-- NOTE: Windows systems can use signed 32bit integer exitcodes. Posix systems
-- only use exitcodes 0-255, anything else is undefined.
-- @param cmd a shell command
-- @return true if successful
-- @return actual return code
function compat.execute(cmd)
local res1,res2,res3 = os.execute(cmd)
if res2 == "No error" and res3 == 0 and compat.is_windows then
-- os.execute bug in Lua 5.2+ not reporting -1 properly on Windows
res3 = -1
end
if compat.lua51 and not compat.jit52 then
if compat.is_windows then
return res1==0,res1
else
res1 = res1 > 255 and res1 / 256 or res1
return res1==0,res1
end
else
if compat.is_windows then
return res3==0,res3
else
return not not res1,res3
end
end
end
----------------
-- Load Lua code as a text or binary chunk (in a Lua 5.2 compatible way).
-- @param ld code string or loader
-- @param[opt] source name of chunk for errors
-- @param[opt] mode 'b', 't' or 'bt'
-- @param[opt] env environment to load the chunk in
-- @function compat.load
---------------
-- Get environment of a function (in a Lua 5.1 compatible way).
-- Not 100% compatible, so with Lua 5.2 it may return nil for a function with no
-- global references!
-- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
-- @param f a function or a call stack reference
-- @function compat.getfenv
---------------
-- Set environment of a function (in a Lua 5.1 compatible way).
-- @param f a function or a call stack reference
-- @param env a table that becomes the new environment of `f`
-- @function compat.setfenv
if compat.lua51 then -- define Lua 5.2 style load()
if not compat.jit then -- but LuaJIT's load _is_ compatible
local lua51_load = load
function compat.load(str,src,mode,env)
local chunk,err
if type(str) == 'string' then
if str:byte(1) == 27 and not (mode or 'bt'):find 'b' then
return nil,"attempt to load a binary chunk"
end
chunk,err = loadstring(str,src)
else
chunk,err = lua51_load(str,src)
end
if chunk and env then setfenv(chunk,env) end
return chunk,err
end
else
compat.load = load
end
compat.setfenv, compat.getfenv = setfenv, getfenv
else
compat.load = load
-- setfenv/getfenv replacements for Lua 5.2
-- by Sergey Rozhenko
-- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
-- Roberto Ierusalimschy notes that it is possible for getfenv to return nil
-- in the case of a function with no globals:
-- http://lua-users.org/lists/lua-l/2010-06/msg00315.html
function compat.setfenv(f, t)
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
local name
local up = 0
repeat
up = up + 1
name = debug.getupvalue(f, up)
until name == '_ENV' or name == nil
if name then
debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue
debug.setupvalue(f, up, t)
end
if f ~= 0 then return f end
end
function compat.getfenv(f)
local f = f or 0
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
local name, val
local up = 0
repeat
up = up + 1
name, val = debug.getupvalue(f, up)
until name == '_ENV' or name == nil
return val
end
end
--- Global exported functions (for Lua 5.1 & LuaJIT)
-- @section lua52
--- pack an argument list into a table.
-- @param ... any arguments
-- @return a table with field n set to the length
-- @function table.pack
if not table.pack then
function table.pack (...) -- luacheck: ignore
return {n=select('#',...); ...}
end
end
--- unpack a table and return the elements.
--
-- NOTE: this version does NOT honor the n field, and hence it is not nil-safe.
-- See `utils.unpack` for a version that is nil-safe.
-- @param t table to unpack
-- @param[opt] i index from which to start unpacking, defaults to 1
-- @param[opt] t index of the last element to unpack, defaults to #t
-- @return multiple return values from the table
-- @function table.unpack
-- @see utils.unpack
if not table.unpack then
table.unpack = unpack -- luacheck: ignore
end
--- return the full path where a Lua module name would be matched.
-- @param mod module name, possibly dotted
-- @param path a path in the same form as package.path or package.cpath
-- @see path.package_path
-- @function package.searchpath
if not package.searchpath then
local sep = package.config:sub(1,1)
function package.searchpath (mod,path) -- luacheck: ignore
mod = mod:gsub('%.',sep)
for m in path:gmatch('[^;]+') do
local nm = m:gsub('?',mod)
local f = io.open(nm,'r')
if f then f:close(); return nm end
end
end
end
return compat

445
pl/apis/path.lua Normal file
View File

@@ -0,0 +1,445 @@
--- Path manipulation and file queries.
--
-- This is modelled after Python's os.path library (10.1); see @{04-paths.md|the Guide}.
--
-- Dependencies: `pl.utils`, `lfs`
-- @module pl.path
-- imports and locals
local _G = _G
local sub = string.sub
local getenv = os.getenv
local tmpnam = os.tmpname
local attributes, currentdir, link_attrib
local package = package
local append, concat, remove = table.insert, table.concat, table.remove
local utils = require 'pl.utils'
local assert_string,raise = utils.assert_string,utils.raise
local attrib
local path = {}
local lfs = require('lfs')
attributes = lfs.attributes
currentdir = lfs.currentdir
link_attrib = lfs.symlinkattributes
attrib = attributes
path.attrib = attrib
path.link_attrib = link_attrib
--- Lua iterator over the entries of a given directory.
-- Behaves like `lfs.dir`
path.dir = lfs.dir
--- Creates a directory.
path.mkdir = lfs.mkdir
--- Removes a directory.
path.rmdir = lfs.rmdir
---- Get the working directory.
path.currentdir = currentdir
--- Changes the working directory.
path.chdir = lfs.chdir
--- is this a directory?
-- @string P A file path
function path.isdir(P)
assert_string(1,P)
if P:match("\\$") then
P = P:sub(1,-2)
end
return attrib(P,'mode') == 'directory'
end
--- is this a file?.
-- @string P A file path
function path.isfile(P)
assert_string(1,P)
return attrib(P,'mode') == 'file'
end
-- is this a symbolic link?
-- @string P A file path
function path.islink(P)
assert_string(1,P)
if link_attrib then
return link_attrib(P,'mode')=='link'
else
return false
end
end
--- return size of a file.
-- @string P A file path
function path.getsize(P)
assert_string(1,P)
return attrib(P,'size')
end
--- does a path exist?.
-- @string P A file path
-- @return the file path if it exists, nil otherwise
function path.exists(P)
assert_string(1,P)
return attrib(P,'mode') ~= nil and P
end
--- Return the time of last access as the number of seconds since the epoch.
-- @string P A file path
function path.getatime(P)
assert_string(1,P)
return attrib(P,'access')
end
--- Return the time of last modification
-- @string P A file path
function path.getmtime(P)
assert_string(1,P)
return attrib(P,'modification')
end
---Return the system's ctime.
-- @string P A file path
function path.getctime(P)
assert_string(1,P)
return path.attrib(P,'change')
end
local function at(s,i)
return sub(s,i,i)
end
path.is_windows = utils.is_windows
local other_sep
-- !constant sep is the directory separator for this platform.
if path.is_windows then
path.sep = '\\'; other_sep = '/'
path.dirsep = ';'
else
path.sep = '/'
path.dirsep = ':'
end
local sep = path.sep
--- are we running Windows?
-- @class field
-- @name path.is_windows
--- path separator for this platform.
-- @class field
-- @name path.sep
--- separator for PATH for this platform
-- @class field
-- @name path.dirsep
--- given a path, return the directory part and a file part.
-- if there's no directory part, the first value will be empty
-- @string P A file path
function path.splitpath(P)
assert_string(1,P)
local i = #P
local ch = at(P,i)
while i > 0 and ch ~= sep and ch ~= other_sep do
i = i - 1
ch = at(P,i)
end
if i == 0 then
return '',P
else
return sub(P,1,i-1), sub(P,i+1)
end
end
--- return an absolute path.
-- @string P A file path
-- @string[opt] pwd optional start path to use (default is current dir)
function path.abspath(P,pwd)
assert_string(1,P)
if pwd then assert_string(2,pwd) end
local use_pwd = pwd ~= nil
if not use_pwd and not currentdir then return P end
P = P:gsub('[\\/]$','')
pwd = pwd or currentdir()
if not path.isabs(P) then
P = path.join(pwd,P)
elseif path.is_windows and not use_pwd and at(P,2) ~= ':' and at(P,2) ~= '\\' then
P = pwd:sub(1,2)..P -- attach current drive to path like '\\fred.txt'
end
return path.normpath(P)
end
--- given a path, return the root part and the extension part.
-- if there's no extension part, the second value will be empty
-- @string P A file path
-- @treturn string root part
-- @treturn string extension part (maybe empty)
function path.splitext(P)
assert_string(1,P)
local i = #P
local ch = at(P,i)
while i > 0 and ch ~= '.' do
if ch == sep or ch == other_sep then
return P,''
end
i = i - 1
ch = at(P,i)
end
if i == 0 then
return P,''
else
return sub(P,1,i-1),sub(P,i)
end
end
--- return the directory part of a path
-- @string P A file path
function path.dirname(P)
assert_string(1,P)
local p1 = path.splitpath(P)
return p1
end
--- return the file part of a path
-- @string P A file path
function path.basename(P)
assert_string(1,P)
local _,p2 = path.splitpath(P)
return p2
end
--- get the extension part of a path.
-- @string P A file path
function path.extension(P)
assert_string(1,P)
local _,p2 = path.splitext(P)
return p2
end
--- is this an absolute path?.
-- @string P A file path
function path.isabs(P)
assert_string(1,P)
if path.is_windows then
return at(P,1) == '/' or at(P,1)=='\\' or at(P,2)==':'
else
return at(P,1) == '/'
end
end
--- return the path resulting from combining the individual paths.
-- if the second (or later) path is absolute, we return the last absolute path (joined with any non-absolute paths following).
-- empty elements (except the last) will be ignored.
-- @string p1 A file path
-- @string p2 A file path
-- @string ... more file paths
function path.join(p1,p2,...)
assert_string(1,p1)
assert_string(2,p2)
if select('#',...) > 0 then
local p = path.join(p1,p2)
local args = {...}
for i = 1,#args do
assert_string(i,args[i])
p = path.join(p,args[i])
end
return p
end
if path.isabs(p2) then return p2 end
local endc = at(p1,#p1)
if endc ~= path.sep and endc ~= other_sep and endc ~= "" then
p1 = p1..path.sep
end
return p1..p2
end
--- normalize the case of a pathname. On Unix, this returns the path unchanged;
-- for Windows, it converts the path to lowercase, and it also converts forward slashes
-- to backward slashes.
-- @string P A file path
function path.normcase(P)
assert_string(1,P)
if path.is_windows then
return (P:lower():gsub('/','\\'))
else
return P
end
end
--- normalize a path name.
-- `A//B`, `A/./B`, and `A/foo/../B` all become `A/B`.
-- @string P a file path
function path.normpath(P)
assert_string(1,P)
-- Split path into anchor and relative path.
local anchor = ''
if path.is_windows then
if P:match '^\\\\' then -- UNC
anchor = '\\\\'
P = P:sub(3)
elseif at(P, 1) == '/' or at(P, 1) == '\\' then
anchor = '\\'
P = P:sub(2)
elseif at(P, 2) == ':' then
anchor = P:sub(1, 2)
P = P:sub(3)
if at(P, 1) == '/' or at(P, 1) == '\\' then
anchor = anchor..'\\'
P = P:sub(2)
end
end
P = P:gsub('/','\\')
else
-- According to POSIX, in path start '//' and '/' are distinct,
-- but '///+' is equivalent to '/'.
if P:match '^//' and at(P, 3) ~= '/' then
anchor = '//'
P = P:sub(3)
elseif at(P, 1) == '/' then
anchor = '/'
P = P:match '^/*(.*)$'
end
end
local parts = {}
for part in P:gmatch('[^'..sep..']+') do
if part == '..' then
if #parts ~= 0 and parts[#parts] ~= '..' then
remove(parts)
else
append(parts, part)
end
elseif part ~= '.' then
append(parts, part)
end
end
P = anchor..concat(parts, sep)
if P == '' then P = '.' end
return P
end
--- relative path from current directory or optional start point
-- @string P a path
-- @string[opt] start optional start point (default current directory)
function path.relpath (P,start)
assert_string(1,P)
if start then assert_string(2,start) end
local split,min,append = utils.split, math.min, table.insert
P = path.abspath(P,start)
start = start or currentdir()
local compare
if path.is_windows then
P = P:gsub("/","\\")
start = start:gsub("/","\\")
compare = function(v) return v:lower() end
else
compare = function(v) return v end
end
local startl, Pl = split(start,sep), split(P,sep)
local n = min(#startl,#Pl)
if path.is_windows and n > 0 and at(Pl[1],2) == ':' and Pl[1] ~= startl[1] then
return P
end
local k = n+1 -- default value if this loop doesn't bail out!
for i = 1,n do
if compare(startl[i]) ~= compare(Pl[i]) then
k = i
break
end
end
local rell = {}
for i = 1, #startl-k+1 do rell[i] = '..' end
if k <= #Pl then
for i = k,#Pl do append(rell,Pl[i]) end
end
return table.concat(rell,sep)
end
--- Replace a starting '~' with the user's home directory.
-- In windows, if HOME isn't set, then USERPROFILE is used in preference to
-- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows.
-- @string P A file path
function path.expanduser(P)
assert_string(1,P)
if at(P,1) == '~' then
local home = getenv('HOME')
if not home then -- has to be Windows
home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH')
end
return home..sub(P,2)
else
return P
end
end
---Return a suitable full path to a new temporary file name.
-- unlike os.tmpname(), it always gives you a writeable path (uses TEMP environment variable on Windows)
function path.tmpname ()
local res = tmpnam()
-- On Windows if Lua is compiled using MSVC14 os.tmpname
-- already returns an absolute path within TEMP env variable directory,
-- no need to prepend it.
if path.is_windows and not res:find(':') then
res = getenv('TEMP')..res
end
return res
end
--- return the largest common prefix path of two paths.
-- @string path1 a file path
-- @string path2 a file path
function path.common_prefix (path1,path2)
assert_string(1,path1)
assert_string(2,path2)
-- get them in order!
if #path1 > #path2 then path2,path1 = path1,path2 end
local compare
if path.is_windows then
path1 = path1:gsub("/", "\\")
path2 = path2:gsub("/", "\\")
compare = function(v) return v:lower() end
else
compare = function(v) return v end
end
for i = 1,#path1 do
if compare(at(path1,i)) ~= compare(at(path2,i)) then
local cp = path1:sub(1,i-1)
if at(path1,i-1) ~= sep then
cp = path.dirname(cp)
end
return cp
end
end
if at(path2,#path1+1) ~= sep then path1 = path.dirname(path1) end
return path1
--return ''
end
--- return the full path where a particular Lua module would be found.
-- Both package.path and package.cpath is searched, so the result may
-- either be a Lua file or a shared library.
-- @string mod name of the module
-- @return on success: path of module, lua or binary
-- @return on error: nil,error string
function path.package_path(mod)
assert_string(1,mod)
local res
mod = mod:gsub('%.',sep)
res = package.searchpath(mod,package.path)
if res then return res,true end
res = package.searchpath(mod,package.cpath)
if res then return res,false end
return raise 'cannot find module on path'
end
---- finis -----
return path

1
pl/etc/fstab Normal file
View File

@@ -0,0 +1 @@
packages/pl/apis gitfs Tieske/Penlight/master/lua/pl

19
pl/init/6.penlight.lua Normal file
View File

@@ -0,0 +1,19 @@
--[[
local getfenv = _G.getfenv
-- penlight requires a global package to determine path separator
-- some funky things in penlight regarding global access
_G.package = {
config = '/\n:\n?\n!\n-',
}
_G.require = function(module)
for i = 2, 3 do
local env = getfenv(i)
if env ~= _G then
return env.require(module)
end
end
error('invalid environment for require')
end
]]