spaces->tab, equipper improvements, supertreefarm rewrite, follow improvements, sensor cleanup, milo multiple items allowed in recipes, remote canvas access
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
return {
|
||||
uptime = os.clock,
|
||||
uptime = os.clock,
|
||||
}
|
||||
@@ -1,68 +1,68 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function get(path)
|
||||
while not fs.exists(path) do
|
||||
path = fs.getDir(path)
|
||||
end
|
||||
while not fs.exists(path) do
|
||||
path = fs.getDir(path)
|
||||
end
|
||||
|
||||
if fs.exists(path) then
|
||||
local proxy = {
|
||||
getLabel = function() return fs.getDrive(path) end,
|
||||
isReadOnly = function() return fs.isReadOnly(path) end,
|
||||
spaceTotal = function() return fs.getSize(path, true) + fs.getFreeSpace(path) end,
|
||||
spaceUsed = function() return fs.getSize(path, true) end,
|
||||
}
|
||||
return proxy, path
|
||||
end
|
||||
if fs.exists(path) then
|
||||
local proxy = {
|
||||
getLabel = function() return fs.getDrive(path) end,
|
||||
isReadOnly = function() return fs.isReadOnly(path) end,
|
||||
spaceTotal = function() return fs.getSize(path, true) + fs.getFreeSpace(path) end,
|
||||
spaceUsed = function() return fs.getSize(path, true) end,
|
||||
}
|
||||
return proxy, path
|
||||
end
|
||||
end
|
||||
|
||||
local function mounts()
|
||||
local t = {
|
||||
[ fs.getDrive('/') ] = '/'
|
||||
}
|
||||
for _,path in pairs(fs.list('/')) do
|
||||
local label = fs.getDrive(path)
|
||||
if not t[label] then
|
||||
t[label] = path
|
||||
end
|
||||
end
|
||||
local t = {
|
||||
[ fs.getDrive('/') ] = '/'
|
||||
}
|
||||
for _,path in pairs(fs.list('/')) do
|
||||
local label = fs.getDrive(path)
|
||||
if not t[label] then
|
||||
t[label] = path
|
||||
end
|
||||
end
|
||||
|
||||
return function()
|
||||
local label, path = next(t)
|
||||
if label then
|
||||
t[label] = nil
|
||||
return get(path)
|
||||
end
|
||||
end
|
||||
return function()
|
||||
local label, path = next(t)
|
||||
if label then
|
||||
t[label] = nil
|
||||
return get(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function list(path)
|
||||
local success, set = pcall(fs.list, path)
|
||||
if success then
|
||||
return function()
|
||||
local key, value = next(set)
|
||||
set[key or false] = nil
|
||||
return value
|
||||
end
|
||||
end
|
||||
return success, set
|
||||
local success, set = pcall(fs.list, path)
|
||||
if success then
|
||||
return function()
|
||||
local key, value = next(set)
|
||||
set[key or false] = nil
|
||||
return value
|
||||
end
|
||||
end
|
||||
return success, set
|
||||
end
|
||||
|
||||
return {
|
||||
canonical = function(...) return ... end,
|
||||
concat = fs.combine,
|
||||
copy = function(...) fs.copy(...) return true end,
|
||||
exists = fs.exists,
|
||||
get = get,
|
||||
isDirectory = fs.isDir,
|
||||
isLink = function() return false end,
|
||||
link = function(s, t) return fs.mount(t, 'linkfs', s) end,
|
||||
list = list,
|
||||
makeDirectory = fs.makeDir,
|
||||
mounts = mounts,
|
||||
name = fs.getName,
|
||||
open = function(n, m) return fs.open(n, m or 'r') end,
|
||||
realPath = function(...) return ... end,
|
||||
remove = function(a) fs.delete(a) return true end,
|
||||
size = fs.getSize,
|
||||
canonical = function(...) return ... end,
|
||||
concat = fs.combine,
|
||||
copy = function(...) fs.copy(...) return true end,
|
||||
exists = fs.exists,
|
||||
get = get,
|
||||
isDirectory = fs.isDir,
|
||||
isLink = function() return false end,
|
||||
link = function(s, t) return fs.mount(t, 'linkfs', s) end,
|
||||
list = list,
|
||||
makeDirectory = fs.makeDir,
|
||||
mounts = mounts,
|
||||
name = fs.getName,
|
||||
open = function(n, m) return fs.open(n, m or 'r') end,
|
||||
realPath = function(...) return ... end,
|
||||
remove = function(a) fs.delete(a) return true end,
|
||||
size = fs.getSize,
|
||||
}
|
||||
|
||||
@@ -2,118 +2,118 @@
|
||||
local M = {_TYPE='module', _NAME='globtopattern', _VERSION='0.2.1.20120406'}
|
||||
|
||||
function M.globtopattern(g)
|
||||
-- Some useful references:
|
||||
-- - apr_fnmatch in Apache APR. For example,
|
||||
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
|
||||
-- which cites POSIX 1003.2-1992, section B.6.
|
||||
-- Some useful references:
|
||||
-- - apr_fnmatch in Apache APR. For example,
|
||||
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
|
||||
-- which cites POSIX 1003.2-1992, section B.6.
|
||||
|
||||
local p = "^" -- pattern being built
|
||||
local i = 0 -- index in g
|
||||
local c -- char at index i in g.
|
||||
local p = "^" -- pattern being built
|
||||
local i = 0 -- index in g
|
||||
local c -- char at index i in g.
|
||||
|
||||
-- unescape glob char
|
||||
local function unescape()
|
||||
if c == '\\' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- unescape glob char
|
||||
local function unescape()
|
||||
if c == '\\' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- escape pattern char
|
||||
local function escape(c)
|
||||
return c:match("^%w$") and c or '%' .. c
|
||||
end
|
||||
-- escape pattern char
|
||||
local function escape(c)
|
||||
return c:match("^%w$") and c or '%' .. c
|
||||
end
|
||||
|
||||
-- Convert tokens at end of charset.
|
||||
local function charset_end()
|
||||
while 1 do
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == ']' then
|
||||
p = p .. ']'
|
||||
break
|
||||
else
|
||||
if not unescape() then break end
|
||||
local c1 = c
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == '-' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == ']' then
|
||||
p = p .. escape(c1) .. '%-]'
|
||||
break
|
||||
else
|
||||
if not unescape() then break end
|
||||
p = p .. escape(c1) .. '-' .. escape(c)
|
||||
end
|
||||
elseif c == ']' then
|
||||
p = p .. escape(c1) .. ']'
|
||||
break
|
||||
else
|
||||
p = p .. escape(c1)
|
||||
i = i - 1 -- put back
|
||||
end
|
||||
end
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- Convert tokens at end of charset.
|
||||
local function charset_end()
|
||||
while 1 do
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == ']' then
|
||||
p = p .. ']'
|
||||
break
|
||||
else
|
||||
if not unescape() then break end
|
||||
local c1 = c
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == '-' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == ']' then
|
||||
p = p .. escape(c1) .. '%-]'
|
||||
break
|
||||
else
|
||||
if not unescape() then break end
|
||||
p = p .. escape(c1) .. '-' .. escape(c)
|
||||
end
|
||||
elseif c == ']' then
|
||||
p = p .. escape(c1) .. ']'
|
||||
break
|
||||
else
|
||||
p = p .. escape(c1)
|
||||
i = i - 1 -- put back
|
||||
end
|
||||
end
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Convert tokens in charset.
|
||||
local function charset()
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' or c == ']' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == '^' or c == '!' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == ']' then
|
||||
-- ignored
|
||||
else
|
||||
p = p .. '[^'
|
||||
if not charset_end() then return false end
|
||||
end
|
||||
else
|
||||
p = p .. '['
|
||||
if not charset_end() then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- Convert tokens in charset.
|
||||
local function charset()
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' or c == ']' then
|
||||
p = '[^]'
|
||||
return false
|
||||
elseif c == '^' or c == '!' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == ']' then
|
||||
-- ignored
|
||||
else
|
||||
p = p .. '[^'
|
||||
if not charset_end() then return false end
|
||||
end
|
||||
else
|
||||
p = p .. '['
|
||||
if not charset_end() then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Convert tokens.
|
||||
while 1 do
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = p .. '$'
|
||||
break
|
||||
elseif c == '?' then
|
||||
p = p .. '.'
|
||||
elseif c == '*' then
|
||||
p = p .. '.*'
|
||||
elseif c == '[' then
|
||||
if not charset() then break end
|
||||
elseif c == '\\' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = p .. '\\$'
|
||||
break
|
||||
end
|
||||
p = p .. escape(c)
|
||||
else
|
||||
p = p .. escape(c)
|
||||
end
|
||||
end
|
||||
return p
|
||||
-- Convert tokens.
|
||||
while 1 do
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = p .. '$'
|
||||
break
|
||||
elseif c == '?' then
|
||||
p = p .. '.'
|
||||
elseif c == '*' then
|
||||
p = p .. '.*'
|
||||
elseif c == '[' then
|
||||
if not charset() then break end
|
||||
elseif c == '\\' then
|
||||
i = i + 1; c = g:sub(i,i)
|
||||
if c == '' then
|
||||
p = p .. '\\$'
|
||||
break
|
||||
end
|
||||
p = p .. escape(c)
|
||||
else
|
||||
p = p .. escape(c)
|
||||
end
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,3 +1,3 @@
|
||||
return {
|
||||
keys = _G.keys,
|
||||
keys = _G.keys,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
local shell = _ENV.shell
|
||||
|
||||
return {
|
||||
execute = function(_, ...) return shell.run(...) end,
|
||||
getLastExitCode = function() return 0 end,
|
||||
execute = function(_, ...) return shell.run(...) end,
|
||||
getLastExitCode = function() return 0 end,
|
||||
}
|
||||
@@ -3,8 +3,8 @@ local Util = require('util')
|
||||
local shell = _ENV.shell
|
||||
|
||||
return {
|
||||
getWorkingDirectory = shell.dir,
|
||||
resolve = shell.resolve,
|
||||
resolveProgram = shell.resolveProgram,
|
||||
parse = Util.parse,
|
||||
getWorkingDirectory = shell.dir,
|
||||
resolve = shell.resolve,
|
||||
resolveProgram = shell.resolveProgram,
|
||||
parse = Util.parse,
|
||||
}
|
||||
|
||||
@@ -7,138 +7,138 @@ text.internal = {}
|
||||
text.syntax = {"^%d?>>?&%d+","^%d?>>?",">>?","<%&%d+","<",";","&&","||?"}
|
||||
|
||||
local function checkArg(n, have, ...)
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)",
|
||||
n, table.concat({...}, " or "), have)
|
||||
error(msg, 3)
|
||||
end
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)",
|
||||
n, table.concat({...}, " or "), have)
|
||||
error(msg, 3)
|
||||
end
|
||||
end
|
||||
|
||||
function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
|
||||
local from = string.match(value, "^%s*()")
|
||||
return from > #value and "" or string.match(value, ".*%S", from)
|
||||
local from = string.match(value, "^%s*()")
|
||||
return from > #value and "" or string.match(value, ".*%S", from)
|
||||
end
|
||||
|
||||
-- used by lib/sh
|
||||
function text.escapeMagic(txt)
|
||||
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
|
||||
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
|
||||
end
|
||||
|
||||
function text.removeEscapes(txt)
|
||||
return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1")
|
||||
return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1")
|
||||
end
|
||||
|
||||
function text.internal.tokenize(value, options)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
local delimiters = options.delimiters
|
||||
local custom = not not options.delimiters
|
||||
delimiters = delimiters or text.syntax
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
local delimiters = options.delimiters
|
||||
local custom = not not options.delimiters
|
||||
delimiters = delimiters or text.syntax
|
||||
|
||||
local words, reason = text.internal.words(value, options)
|
||||
local words, reason = text.internal.words(value, options)
|
||||
|
||||
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
|
||||
if type(words) ~= "table" or
|
||||
#splitter == 0 or
|
||||
not value:find("["..splitter.."]") then
|
||||
return words, reason
|
||||
end
|
||||
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
|
||||
if type(words) ~= "table" or
|
||||
#splitter == 0 or
|
||||
not value:find("["..splitter.."]") then
|
||||
return words, reason
|
||||
end
|
||||
|
||||
return text.internal.splitWords(words, delimiters)
|
||||
return text.internal.splitWords(words, delimiters)
|
||||
end
|
||||
|
||||
-- tokenize input by quotes and whitespace
|
||||
function text.internal.words(input, options)
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
local quotes = options.quotes
|
||||
local show_escapes = options.show_escapes
|
||||
local qr = nil
|
||||
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
|
||||
local function append(dst, txt, _qr)
|
||||
local size = #dst
|
||||
if size == 0 or dst[size].qr ~= _qr then
|
||||
dst[size+1] = {txt=txt, qr=_qr}
|
||||
else
|
||||
dst[size].txt = dst[size].txt..txt
|
||||
end
|
||||
end
|
||||
-- token meta is {string,quote rule}
|
||||
local tokens, token = {}, {}
|
||||
local escaped, start = false, -1
|
||||
for i = 1, unicode.len(input) do
|
||||
local char = unicode.sub(input, i, i)
|
||||
if escaped then -- escaped character
|
||||
escaped = false
|
||||
-- include escape char if show_escapes
|
||||
-- or the followwing are all true
|
||||
-- 1. qr active
|
||||
-- 2. the char escaped is NOT the qr closure
|
||||
-- 3. qr is not literal
|
||||
if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
|
||||
append(token, '\\', qr)
|
||||
end
|
||||
append(token, char, qr)
|
||||
elseif char == "\\" and (not qr or not qr[3]) then
|
||||
escaped = true
|
||||
elseif qr and qr[2] == char then -- end of quoted string
|
||||
-- if string is empty, we can still capture a quoted empty arg
|
||||
if #token == 0 or #token[#token] == 0 then
|
||||
append(token, '', qr)
|
||||
end
|
||||
qr = nil
|
||||
elseif not qr and tx.first(quotes,function(Q)
|
||||
qr=Q[1]==char and Q or nil return qr end) then
|
||||
start = i
|
||||
elseif not qr and string.find(char, "%s") then
|
||||
if #token > 0 then
|
||||
table.insert(tokens, token)
|
||||
end
|
||||
token = {}
|
||||
else -- normal char
|
||||
append(token, char, qr)
|
||||
end
|
||||
end
|
||||
if qr then
|
||||
return nil, "unclosed quote at index " .. start
|
||||
end
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
local quotes = options.quotes
|
||||
local show_escapes = options.show_escapes
|
||||
local qr = nil
|
||||
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
|
||||
local function append(dst, txt, _qr)
|
||||
local size = #dst
|
||||
if size == 0 or dst[size].qr ~= _qr then
|
||||
dst[size+1] = {txt=txt, qr=_qr}
|
||||
else
|
||||
dst[size].txt = dst[size].txt..txt
|
||||
end
|
||||
end
|
||||
-- token meta is {string,quote rule}
|
||||
local tokens, token = {}, {}
|
||||
local escaped, start = false, -1
|
||||
for i = 1, unicode.len(input) do
|
||||
local char = unicode.sub(input, i, i)
|
||||
if escaped then -- escaped character
|
||||
escaped = false
|
||||
-- include escape char if show_escapes
|
||||
-- or the followwing are all true
|
||||
-- 1. qr active
|
||||
-- 2. the char escaped is NOT the qr closure
|
||||
-- 3. qr is not literal
|
||||
if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
|
||||
append(token, '\\', qr)
|
||||
end
|
||||
append(token, char, qr)
|
||||
elseif char == "\\" and (not qr or not qr[3]) then
|
||||
escaped = true
|
||||
elseif qr and qr[2] == char then -- end of quoted string
|
||||
-- if string is empty, we can still capture a quoted empty arg
|
||||
if #token == 0 or #token[#token] == 0 then
|
||||
append(token, '', qr)
|
||||
end
|
||||
qr = nil
|
||||
elseif not qr and tx.first(quotes,function(Q)
|
||||
qr=Q[1]==char and Q or nil return qr end) then
|
||||
start = i
|
||||
elseif not qr and string.find(char, "%s") then
|
||||
if #token > 0 then
|
||||
table.insert(tokens, token)
|
||||
end
|
||||
token = {}
|
||||
else -- normal char
|
||||
append(token, char, qr)
|
||||
end
|
||||
end
|
||||
if qr then
|
||||
return nil, "unclosed quote at index " .. start
|
||||
end
|
||||
|
||||
if #token > 0 then
|
||||
table.insert(tokens, token)
|
||||
end
|
||||
if #token > 0 then
|
||||
table.insert(tokens, token)
|
||||
end
|
||||
|
||||
return tokens
|
||||
return tokens
|
||||
end
|
||||
|
||||
-- separate string value into an array of words delimited by whitespace
|
||||
-- groups by quotes
|
||||
-- options is a table used for internal undocumented purposes
|
||||
function text.tokenize(value, options)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
|
||||
local tokens, reason = text.internal.tokenize(value, options)
|
||||
local tokens, reason = text.internal.tokenize(value, options)
|
||||
|
||||
if type(tokens) ~= "table" then
|
||||
return nil, reason
|
||||
end
|
||||
if type(tokens) ~= "table" then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
if options.doNotNormalize then
|
||||
return tokens
|
||||
end
|
||||
if options.doNotNormalize then
|
||||
return tokens
|
||||
end
|
||||
|
||||
return text.internal.normalize(tokens)
|
||||
return text.internal.normalize(tokens)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -146,41 +146,41 @@ end
|
||||
-- splits input into an array for sub strings delimited by delimiters
|
||||
-- delimiters are included in the result if not dropDelims
|
||||
function text.split(input, delimiters, dropDelims, di)
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, delimiters, "table")
|
||||
checkArg(3, dropDelims, "boolean", "nil")
|
||||
checkArg(4, di, "number", "nil")
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, delimiters, "table")
|
||||
checkArg(3, dropDelims, "boolean", "nil")
|
||||
checkArg(4, di, "number", "nil")
|
||||
|
||||
if #input == 0 then return {} end
|
||||
di = di or 1
|
||||
local result = {input}
|
||||
if di > #delimiters then return result end
|
||||
if #input == 0 then return {} end
|
||||
di = di or 1
|
||||
local result = {input}
|
||||
if di > #delimiters then return result end
|
||||
|
||||
local function add(part, index, r, s, e)
|
||||
local sub = part:sub(s,e)
|
||||
if #sub == 0 then return index end
|
||||
local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
|
||||
for i=1,#subs do
|
||||
table.insert(result, index+i-1, subs[i])
|
||||
end
|
||||
return index+#subs
|
||||
end
|
||||
local function add(part, index, r, s, e)
|
||||
local sub = part:sub(s,e)
|
||||
if #sub == 0 then return index end
|
||||
local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
|
||||
for i=1,#subs do
|
||||
table.insert(result, index+i-1, subs[i])
|
||||
end
|
||||
return index+#subs
|
||||
end
|
||||
|
||||
local i,d=1,delimiters[di]
|
||||
while true do
|
||||
local next = table.remove(result,i)
|
||||
if not next then break end
|
||||
local si,ei = next:find(d)
|
||||
if si and ei and ei~=0 then -- delim found
|
||||
i=add(next, i, di+1, 1, si-1)
|
||||
i=dropDelims and i or add(next, i, false, si, ei)
|
||||
i=add(next, i, di, ei+1)
|
||||
else
|
||||
i=add(next, i, di+1, 1, #next)
|
||||
end
|
||||
end
|
||||
local i,d=1,delimiters[di]
|
||||
while true do
|
||||
local next = table.remove(result,i)
|
||||
if not next then break end
|
||||
local si,ei = next:find(d)
|
||||
if si and ei and ei~=0 then -- delim found
|
||||
i=add(next, i, di+1, 1, si-1)
|
||||
i=dropDelims and i or add(next, i, false, si, ei)
|
||||
i=add(next, i, di, ei+1)
|
||||
else
|
||||
i=add(next, i, di+1, 1, #next)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
return result
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
@@ -189,217 +189,217 @@ end
|
||||
-- delimiters are kept as their own words
|
||||
-- quoted word parts are not split
|
||||
function text.internal.splitWords(words, delimiters)
|
||||
checkArg(1,words,"table")
|
||||
checkArg(2,delimiters,"table")
|
||||
checkArg(1,words,"table")
|
||||
checkArg(2,delimiters,"table")
|
||||
|
||||
local split_words = {}
|
||||
local next_word
|
||||
local function add_part(part)
|
||||
if next_word then
|
||||
split_words[#split_words+1] = {}
|
||||
end
|
||||
table.insert(split_words[#split_words], part)
|
||||
next_word = false
|
||||
end
|
||||
for wi=1,#words do local word = words[wi]
|
||||
next_word = true
|
||||
for pi=1,#word do local part = word[pi]
|
||||
local qr = part.qr
|
||||
if qr then
|
||||
add_part(part)
|
||||
else
|
||||
local part_text_splits = text.split(part.txt, delimiters)
|
||||
tx.foreach(part_text_splits, function(sub_txt)
|
||||
local delim = #text.split(sub_txt, delimiters, true) == 0
|
||||
next_word = next_word or delim
|
||||
add_part({txt=sub_txt,qr=qr})
|
||||
next_word = delim
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
local split_words = {}
|
||||
local next_word
|
||||
local function add_part(part)
|
||||
if next_word then
|
||||
split_words[#split_words+1] = {}
|
||||
end
|
||||
table.insert(split_words[#split_words], part)
|
||||
next_word = false
|
||||
end
|
||||
for wi=1,#words do local word = words[wi]
|
||||
next_word = true
|
||||
for pi=1,#word do local part = word[pi]
|
||||
local qr = part.qr
|
||||
if qr then
|
||||
add_part(part)
|
||||
else
|
||||
local part_text_splits = text.split(part.txt, delimiters)
|
||||
tx.foreach(part_text_splits, function(sub_txt)
|
||||
local delim = #text.split(sub_txt, delimiters, true) == 0
|
||||
next_word = next_word or delim
|
||||
add_part({txt=sub_txt,qr=qr})
|
||||
next_word = delim
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return split_words
|
||||
return split_words
|
||||
end
|
||||
|
||||
function text.internal.normalize(words, omitQuotes)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, omitQuotes, "boolean", "nil")
|
||||
local norms = {}
|
||||
for _,word in ipairs(words) do
|
||||
local norm = {}
|
||||
for _,part in ipairs(word) do
|
||||
norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
|
||||
end
|
||||
norms[#norms+1]=table.concat(norm)
|
||||
end
|
||||
return norms
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, omitQuotes, "boolean", "nil")
|
||||
local norms = {}
|
||||
for _,word in ipairs(words) do
|
||||
local norm = {}
|
||||
for _,part in ipairs(word) do
|
||||
norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
|
||||
end
|
||||
norms[#norms+1]=table.concat(norm)
|
||||
end
|
||||
return norms
|
||||
end
|
||||
|
||||
function text.internal.stream_base(binary)
|
||||
return
|
||||
{
|
||||
binary = binary,
|
||||
plen = binary and string.len or unicode.len,
|
||||
psub = binary and string.sub or unicode.sub,
|
||||
seek = function (handle, whence, to)
|
||||
if not handle.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
to = to or 0
|
||||
local offset = handle:indexbytes()
|
||||
if whence == "cur" then
|
||||
offset = offset + to
|
||||
elseif whence == "set" then
|
||||
offset = to
|
||||
elseif whence == "end" then
|
||||
offset = handle.len + to
|
||||
end
|
||||
offset = math.max(0, math.min(offset, handle.len))
|
||||
handle:byteindex(offset)
|
||||
return offset
|
||||
end,
|
||||
indexbytes = function (handle)
|
||||
return handle.psub(handle.txt, 1, handle.index):len()
|
||||
end,
|
||||
byteindex = function (handle, offset)
|
||||
local sub = string.sub(handle.txt, 1, offset)
|
||||
handle.index = handle.plen(sub)
|
||||
end,
|
||||
}
|
||||
return
|
||||
{
|
||||
binary = binary,
|
||||
plen = binary and string.len or unicode.len,
|
||||
psub = binary and string.sub or unicode.sub,
|
||||
seek = function (handle, whence, to)
|
||||
if not handle.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
to = to or 0
|
||||
local offset = handle:indexbytes()
|
||||
if whence == "cur" then
|
||||
offset = offset + to
|
||||
elseif whence == "set" then
|
||||
offset = to
|
||||
elseif whence == "end" then
|
||||
offset = handle.len + to
|
||||
end
|
||||
offset = math.max(0, math.min(offset, handle.len))
|
||||
handle:byteindex(offset)
|
||||
return offset
|
||||
end,
|
||||
indexbytes = function (handle)
|
||||
return handle.psub(handle.txt, 1, handle.index):len()
|
||||
end,
|
||||
byteindex = function (handle, offset)
|
||||
local sub = string.sub(handle.txt, 1, offset)
|
||||
handle.index = handle.plen(sub)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function text.internal.reader(txt, mode)
|
||||
checkArg(1, txt, "string")
|
||||
local reader = setmetatable(
|
||||
{
|
||||
txt = txt,
|
||||
len = string.len(txt),
|
||||
index = 0,
|
||||
read = function(_, n)
|
||||
checkArg(1, n, "number")
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
if _.index >= _.plen(_.txt) then
|
||||
return nil
|
||||
end
|
||||
local next = _.psub(_.txt, _.index + 1, _.index + n)
|
||||
_.index = _.index + _.plen(next)
|
||||
return next
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base(mode:match("b"))})
|
||||
checkArg(1, txt, "string")
|
||||
local reader = setmetatable(
|
||||
{
|
||||
txt = txt,
|
||||
len = string.len(txt),
|
||||
index = 0,
|
||||
read = function(_, n)
|
||||
checkArg(1, n, "number")
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
if _.index >= _.plen(_.txt) then
|
||||
return nil
|
||||
end
|
||||
local next = _.psub(_.txt, _.index + 1, _.index + n)
|
||||
_.index = _.index + _.plen(next)
|
||||
return next
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base(mode:match("b"))})
|
||||
|
||||
return require("buffer").new("r", reader)
|
||||
return require("buffer").new("r", reader)
|
||||
end
|
||||
|
||||
function text.internal.writer(ostream, mode, append_txt)
|
||||
if type(ostream) == "table" then
|
||||
local mt = getmetatable(ostream) or {}
|
||||
checkArg(1, mt.__call, "function")
|
||||
end
|
||||
checkArg(1, ostream, "function", "table")
|
||||
checkArg(2, append_txt, "string", "nil")
|
||||
local writer = setmetatable(
|
||||
{
|
||||
txt = "",
|
||||
index = 0, -- last location of write
|
||||
len = 0,
|
||||
write = function(_, ...)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
local pre = _.psub(_.txt, 1, _.index)
|
||||
local vs = {}
|
||||
local pos = _.psub(_.txt, _.index + 1)
|
||||
for _,v in ipairs({...}) do
|
||||
table.insert(vs, v)
|
||||
end
|
||||
vs = table.concat(vs)
|
||||
_.index = _.index + _.plen(vs)
|
||||
_.txt = pre .. vs .. pos
|
||||
_.len = string.len(_.txt)
|
||||
return true
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
ostream((append_txt or "") .. _.txt)
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base(mode:match("b"))})
|
||||
if type(ostream) == "table" then
|
||||
local mt = getmetatable(ostream) or {}
|
||||
checkArg(1, mt.__call, "function")
|
||||
end
|
||||
checkArg(1, ostream, "function", "table")
|
||||
checkArg(2, append_txt, "string", "nil")
|
||||
local writer = setmetatable(
|
||||
{
|
||||
txt = "",
|
||||
index = 0, -- last location of write
|
||||
len = 0,
|
||||
write = function(_, ...)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
local pre = _.psub(_.txt, 1, _.index)
|
||||
local vs = {}
|
||||
local pos = _.psub(_.txt, _.index + 1)
|
||||
for _,v in ipairs({...}) do
|
||||
table.insert(vs, v)
|
||||
end
|
||||
vs = table.concat(vs)
|
||||
_.index = _.index + _.plen(vs)
|
||||
_.txt = pre .. vs .. pos
|
||||
_.len = string.len(_.txt)
|
||||
return true
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
ostream((append_txt or "") .. _.txt)
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base(mode:match("b"))})
|
||||
|
||||
return require("buffer").new("w", writer)
|
||||
return require("buffer").new("w", writer)
|
||||
end
|
||||
|
||||
function text.detab(value, tabWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, tabWidth, "number", "nil")
|
||||
tabWidth = tabWidth or 8
|
||||
local function rep(match)
|
||||
local spaces = tabWidth - match:len() % tabWidth
|
||||
return match .. string.rep(" ", spaces)
|
||||
end
|
||||
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
|
||||
return result
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, tabWidth, "number", "nil")
|
||||
tabWidth = tabWidth or 8
|
||||
local function rep(match)
|
||||
local spaces = tabWidth - match:len() % tabWidth
|
||||
return match .. string.rep(" ", spaces)
|
||||
end
|
||||
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
|
||||
return result
|
||||
end
|
||||
|
||||
function text.padLeft(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return string.rep(" ", length - unicode.wlen(value)) .. value
|
||||
end
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return string.rep(" ", length - unicode.wlen(value)) .. value
|
||||
end
|
||||
end
|
||||
|
||||
function text.padRight(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return value .. string.rep(" ", length - unicode.wlen(value))
|
||||
end
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return value .. string.rep(" ", length - unicode.wlen(value))
|
||||
end
|
||||
end
|
||||
|
||||
function text.wrap(value, width, maxWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, width, "number")
|
||||
checkArg(3, maxWidth, "number")
|
||||
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
|
||||
if unicode.wlen(line) > width then -- do we even need to wrap?
|
||||
local partial = unicode.wtrunc(line, width)
|
||||
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
|
||||
if wrapped or unicode.wlen(line) > maxWidth then
|
||||
partial = wrapped or partial
|
||||
return partial, unicode.sub(value, unicode.len(partial) + 1), true
|
||||
else
|
||||
return "", value, true -- write in new line.
|
||||
end
|
||||
end
|
||||
local start = unicode.len(line) + unicode.len(nl) + 1
|
||||
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, width, "number")
|
||||
checkArg(3, maxWidth, "number")
|
||||
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
|
||||
if unicode.wlen(line) > width then -- do we even need to wrap?
|
||||
local partial = unicode.wtrunc(line, width)
|
||||
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
|
||||
if wrapped or unicode.wlen(line) > maxWidth then
|
||||
partial = wrapped or partial
|
||||
return partial, unicode.sub(value, unicode.len(partial) + 1), true
|
||||
else
|
||||
return "", value, true -- write in new line.
|
||||
end
|
||||
end
|
||||
local start = unicode.len(line) + unicode.len(nl) + 1
|
||||
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
|
||||
end
|
||||
|
||||
function text.wrappedLines(value, width, maxWidth)
|
||||
local line
|
||||
return function()
|
||||
if value then
|
||||
line, value = text.wrap(value, width, maxWidth)
|
||||
return line
|
||||
end
|
||||
end
|
||||
local line
|
||||
return function()
|
||||
if value then
|
||||
line, value = text.wrap(value, width, maxWidth)
|
||||
return line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return text
|
||||
@@ -5,269 +5,269 @@ local text = require("shellex.text")
|
||||
local lib = {}
|
||||
|
||||
local function perr(ops, format, ...)
|
||||
if format then
|
||||
io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n")
|
||||
ops.exit_code = 1
|
||||
return 1
|
||||
end
|
||||
if format then
|
||||
io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n")
|
||||
ops.exit_code = 1
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
local function contents_check(arg, options, bMustExist)
|
||||
if arg == "" then
|
||||
return perr(options, "cannot create regular file '' No such file or directory")
|
||||
end
|
||||
local path = shell.resolve(arg)
|
||||
local content_pattern = "^(%.*)(.?)"
|
||||
local contents_of, of_dir = arg:reverse():match(content_pattern)
|
||||
of_dir = of_dir:match("^/?$")
|
||||
local dots = contents_of and contents_of:len() or 0
|
||||
contents_of = of_dir and ({true,true})[dots]
|
||||
if arg == "" then
|
||||
return perr(options, "cannot create regular file '' No such file or directory")
|
||||
end
|
||||
local path = shell.resolve(arg)
|
||||
local content_pattern = "^(%.*)(.?)"
|
||||
local contents_of, of_dir = arg:reverse():match(content_pattern)
|
||||
of_dir = of_dir:match("^/?$")
|
||||
local dots = contents_of and contents_of:len() or 0
|
||||
contents_of = of_dir and ({true,true})[dots]
|
||||
|
||||
if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then
|
||||
perr(options, "'%s' is not a directory", arg)
|
||||
os.exit(1)
|
||||
end
|
||||
if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then
|
||||
perr(options, "'%s' is not a directory", arg)
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
return contents_of, path
|
||||
return contents_of, path
|
||||
end
|
||||
|
||||
local function areEqual(path1, path2)
|
||||
local f1, f2 = fs.open(path1, "rb")
|
||||
local result = true
|
||||
if f1 then
|
||||
f2 = fs.open(path2, "rb")
|
||||
if f2 then
|
||||
local chunkSize = 4 * 1024
|
||||
repeat
|
||||
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
|
||||
if s1 ~= s2 then
|
||||
result = false
|
||||
break
|
||||
end
|
||||
until not s1 or not s2
|
||||
f2:close()
|
||||
end
|
||||
f1:close()
|
||||
end
|
||||
assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2)
|
||||
return result
|
||||
local f1, f2 = fs.open(path1, "rb")
|
||||
local result = true
|
||||
if f1 then
|
||||
f2 = fs.open(path2, "rb")
|
||||
if f2 then
|
||||
local chunkSize = 4 * 1024
|
||||
repeat
|
||||
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
|
||||
if s1 ~= s2 then
|
||||
result = false
|
||||
break
|
||||
end
|
||||
until not s1 or not s2
|
||||
f2:close()
|
||||
end
|
||||
f1:close()
|
||||
end
|
||||
assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2)
|
||||
return result
|
||||
end
|
||||
|
||||
local function status(verbose, from, to)
|
||||
if verbose then
|
||||
to = to and (" -> " .. to) or ""
|
||||
io.write(from .. to .. "\n")
|
||||
end
|
||||
os.sleep(0) -- allow interrupting
|
||||
if verbose then
|
||||
to = to and (" -> " .. to) or ""
|
||||
io.write(from .. to .. "\n")
|
||||
end
|
||||
os.sleep(0) -- allow interrupting
|
||||
end
|
||||
|
||||
local function prompt(message)
|
||||
io.write(message .. " [Y/n] ")
|
||||
local result = io.read()
|
||||
if not result then -- closed pipe
|
||||
os.exit(1)
|
||||
end
|
||||
return result and (result == "" or result:sub(1, 1):lower() == "y")
|
||||
io.write(message .. " [Y/n] ")
|
||||
local result = io.read()
|
||||
if not result then -- closed pipe
|
||||
os.exit(1)
|
||||
end
|
||||
return result and (result == "" or result:sub(1, 1):lower() == "y")
|
||||
end
|
||||
|
||||
local function stat(path, ops, P)
|
||||
local real, reason = fs.realPath(path)
|
||||
if not real and not P then
|
||||
perr(ops, "cannot read '%s': '%s'", path, reason)
|
||||
return false
|
||||
end
|
||||
local isLink, linkTarget = fs.isLink(path)
|
||||
return true,
|
||||
real,
|
||||
reason,
|
||||
isLink,
|
||||
linkTarget,
|
||||
fs.exists(path),
|
||||
fs.get(path),
|
||||
real and fs.isDirectory(real)
|
||||
local real, reason = fs.realPath(path)
|
||||
if not real and not P then
|
||||
perr(ops, "cannot read '%s': '%s'", path, reason)
|
||||
return false
|
||||
end
|
||||
local isLink, linkTarget = fs.isLink(path)
|
||||
return true,
|
||||
real,
|
||||
reason,
|
||||
isLink,
|
||||
linkTarget,
|
||||
fs.exists(path),
|
||||
fs.get(path),
|
||||
real and fs.isDirectory(real)
|
||||
end
|
||||
|
||||
function lib.recurse(fromPath, toPath, options, origin, top)
|
||||
fromPath = fromPath:gsub("/+", "/")
|
||||
toPath = toPath:gsub("/+", "/")
|
||||
local fromPathFull = shell.resolve(fromPath)
|
||||
local toPathFull = shell.resolve(toPath)
|
||||
local mv = options.cmd == "mv"
|
||||
local verbose = options.v and (not mv or top)
|
||||
if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
|
||||
status(verbose, string.format("skipping %s", fromPath))
|
||||
return true
|
||||
end
|
||||
local function release(result, reason)
|
||||
if result and mv and top then
|
||||
local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
|
||||
if not rm_result then
|
||||
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
|
||||
result = false
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
fromPath = fromPath:gsub("/+", "/")
|
||||
toPath = toPath:gsub("/+", "/")
|
||||
local fromPathFull = shell.resolve(fromPath)
|
||||
local toPathFull = shell.resolve(toPath)
|
||||
local mv = options.cmd == "mv"
|
||||
local verbose = options.v and (not mv or top)
|
||||
if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
|
||||
status(verbose, string.format("skipping %s", fromPath))
|
||||
return true
|
||||
end
|
||||
local function release(result, reason)
|
||||
if result and mv and top then
|
||||
local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
|
||||
if not rm_result then
|
||||
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
|
||||
result = false
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
local
|
||||
ok,
|
||||
fromReal,
|
||||
_, --fromError,
|
||||
fromIsLink,
|
||||
fromLinkTarget,
|
||||
fromExists,
|
||||
fromFs,
|
||||
fromIsDir = stat(fromPathFull, options, options.P)
|
||||
if not ok then return nil end
|
||||
local
|
||||
ok,
|
||||
toReal,
|
||||
_,--toError,
|
||||
toIsLink,
|
||||
_,--toLinkTarget,
|
||||
toExists,
|
||||
toFs,
|
||||
toIsDir = stat(toPathFull, options)
|
||||
if not ok then os.exit(1) end
|
||||
if toFs.isReadOnly() then
|
||||
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
|
||||
return
|
||||
end
|
||||
local
|
||||
ok,
|
||||
fromReal,
|
||||
_, --fromError,
|
||||
fromIsLink,
|
||||
fromLinkTarget,
|
||||
fromExists,
|
||||
fromFs,
|
||||
fromIsDir = stat(fromPathFull, options, options.P)
|
||||
if not ok then return nil end
|
||||
local
|
||||
ok,
|
||||
toReal,
|
||||
_,--toError,
|
||||
toIsLink,
|
||||
_,--toLinkTarget,
|
||||
toExists,
|
||||
toFs,
|
||||
toIsDir = stat(toPathFull, options)
|
||||
if not ok then os.exit(1) end
|
||||
if toFs.isReadOnly() then
|
||||
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
|
||||
return
|
||||
end
|
||||
|
||||
local same_path = fromReal == toReal
|
||||
local same_path = fromReal == toReal
|
||||
|
||||
local same_fs = fromFs == toFs
|
||||
local is_mount = origin[fromReal]
|
||||
local same_fs = fromFs == toFs
|
||||
local is_mount = origin[fromReal]
|
||||
|
||||
if mv and is_mount then
|
||||
return false, string.format("cannot move '%s', it is a mount point", fromPath)
|
||||
end
|
||||
if mv and is_mount then
|
||||
return false, string.format("cannot move '%s', it is a mount point", fromPath)
|
||||
end
|
||||
|
||||
if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then
|
||||
if toExists and options.n then
|
||||
return true
|
||||
end
|
||||
fs.remove(toPathFull)
|
||||
if toExists then
|
||||
status(verbose, string.format("removed '%s'", toPath))
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
return release(fs.link(fromLinkTarget, toPathFull))
|
||||
elseif fromIsDir then
|
||||
if not options.r then
|
||||
status(true, string.format("omitting directory '%s'", fromPath))
|
||||
options.exit_code = 1
|
||||
return true
|
||||
end
|
||||
if toExists and not toIsDir then
|
||||
-- my real cp always does this, even with -f, -n or -i.
|
||||
return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'"
|
||||
end
|
||||
if options.x and not top and is_mount then
|
||||
return true
|
||||
end
|
||||
if same_fs then
|
||||
if (toReal.."/"):find(fromReal.."/",1,true) then
|
||||
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
|
||||
end
|
||||
end
|
||||
if mv then
|
||||
if fs.list(toReal)() then -- to is NOT empty
|
||||
return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
end
|
||||
if not toExists then
|
||||
status(verbose, fromPath, toPath)
|
||||
fs.makeDirectory(toPathFull)
|
||||
end
|
||||
for file in fs.list(fromPathFull) do
|
||||
local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false)
|
||||
-- false, no longer top
|
||||
if not result then
|
||||
return false, reason
|
||||
end
|
||||
end
|
||||
return release(true)
|
||||
elseif fromExists then
|
||||
if toExists then
|
||||
if same_path then
|
||||
return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file"
|
||||
end
|
||||
if options.n then
|
||||
return true
|
||||
end
|
||||
if options.u and not toIsDir and areEqual(fromReal, toReal) then
|
||||
return true
|
||||
end
|
||||
if options.i then
|
||||
if not prompt("overwrite '" .. toPath .. "'?") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if toIsDir then
|
||||
return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory"
|
||||
end
|
||||
fs.remove(toReal)
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
return release(fs.copy(fromPathFull, toPathFull))
|
||||
else
|
||||
return nil, "'" .. fromPath .. "': No such file or directory"
|
||||
end
|
||||
if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then
|
||||
if toExists and options.n then
|
||||
return true
|
||||
end
|
||||
fs.remove(toPathFull)
|
||||
if toExists then
|
||||
status(verbose, string.format("removed '%s'", toPath))
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
return release(fs.link(fromLinkTarget, toPathFull))
|
||||
elseif fromIsDir then
|
||||
if not options.r then
|
||||
status(true, string.format("omitting directory '%s'", fromPath))
|
||||
options.exit_code = 1
|
||||
return true
|
||||
end
|
||||
if toExists and not toIsDir then
|
||||
-- my real cp always does this, even with -f, -n or -i.
|
||||
return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'"
|
||||
end
|
||||
if options.x and not top and is_mount then
|
||||
return true
|
||||
end
|
||||
if same_fs then
|
||||
if (toReal.."/"):find(fromReal.."/",1,true) then
|
||||
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
|
||||
end
|
||||
end
|
||||
if mv then
|
||||
if fs.list(toReal)() then -- to is NOT empty
|
||||
return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
end
|
||||
if not toExists then
|
||||
status(verbose, fromPath, toPath)
|
||||
fs.makeDirectory(toPathFull)
|
||||
end
|
||||
for file in fs.list(fromPathFull) do
|
||||
local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false)
|
||||
-- false, no longer top
|
||||
if not result then
|
||||
return false, reason
|
||||
end
|
||||
end
|
||||
return release(true)
|
||||
elseif fromExists then
|
||||
if toExists then
|
||||
if same_path then
|
||||
return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file"
|
||||
end
|
||||
if options.n then
|
||||
return true
|
||||
end
|
||||
if options.u and not toIsDir and areEqual(fromReal, toReal) then
|
||||
return true
|
||||
end
|
||||
if options.i then
|
||||
if not prompt("overwrite '" .. toPath .. "'?") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if toIsDir then
|
||||
return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory"
|
||||
end
|
||||
fs.remove(toReal)
|
||||
end
|
||||
status(verbose, fromPath, toPath)
|
||||
return release(fs.copy(fromPathFull, toPathFull))
|
||||
else
|
||||
return nil, "'" .. fromPath .. "': No such file or directory"
|
||||
end
|
||||
end
|
||||
|
||||
function lib.batch(args, options)
|
||||
options.exit_code = 0
|
||||
options.exit_code = 0
|
||||
|
||||
-- standardized options
|
||||
options.i = options.i and not options.f
|
||||
options.P = options.P or options.r
|
||||
options.skip = text.escapeMagic(options.skip or "")
|
||||
-- standardized options
|
||||
options.i = options.i and not options.f
|
||||
options.P = options.P or options.r
|
||||
options.skip = text.escapeMagic(options.skip or "")
|
||||
|
||||
local origin = {}
|
||||
for dev,path in fs.mounts() do
|
||||
origin[path] = dev
|
||||
end
|
||||
local origin = {}
|
||||
for dev,path in fs.mounts() do
|
||||
origin[path] = dev
|
||||
end
|
||||
|
||||
local toArg = table.remove(args)
|
||||
local _, ok = contents_check(toArg, options)
|
||||
if not ok then
|
||||
return 1
|
||||
end
|
||||
local originalToIsDir = fs.isDirectory(ok)
|
||||
local toArg = table.remove(args)
|
||||
local _, ok = contents_check(toArg, options)
|
||||
if not ok then
|
||||
return 1
|
||||
end
|
||||
local originalToIsDir = fs.isDirectory(ok)
|
||||
|
||||
for _, arg in ipairs(args) do
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, fromArg in pairs(files) do
|
||||
-- a "contents of" copy is where src path ends in . or ..
|
||||
-- a source path ending with . is not sufficient - could be the source filename
|
||||
local contents_of
|
||||
contents_of, ok = contents_check(fromArg, options, true)
|
||||
if ok then
|
||||
-- we do not append fromPath name to toPath in case of contents_of copy
|
||||
local toPath = toArg
|
||||
if contents_of and options.cmd == "mv" then
|
||||
perr(options, "invalid move path '%s'", fromArg)
|
||||
else
|
||||
if not contents_of and originalToIsDir then
|
||||
local fromName = fs.name(fromArg)
|
||||
if fromName then
|
||||
toPath = toPath .. "/" .. fromName
|
||||
end
|
||||
end
|
||||
for _, arg in ipairs(args) do
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, fromArg in pairs(files) do
|
||||
-- a "contents of" copy is where src path ends in . or ..
|
||||
-- a source path ending with . is not sufficient - could be the source filename
|
||||
local contents_of
|
||||
contents_of, ok = contents_check(fromArg, options, true)
|
||||
if ok then
|
||||
-- we do not append fromPath name to toPath in case of contents_of copy
|
||||
local toPath = toArg
|
||||
if contents_of and options.cmd == "mv" then
|
||||
perr(options, "invalid move path '%s'", fromArg)
|
||||
else
|
||||
if not contents_of and originalToIsDir then
|
||||
local fromName = fs.name(fromArg)
|
||||
if fromName then
|
||||
toPath = toPath .. "/" .. fromName
|
||||
end
|
||||
end
|
||||
|
||||
local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
|
||||
local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
|
||||
|
||||
if not result then
|
||||
perr(options, reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not result then
|
||||
perr(options, reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return options.exit_code
|
||||
return options.exit_code
|
||||
end
|
||||
|
||||
return lib
|
||||
@@ -2,198 +2,198 @@ local lib={}
|
||||
lib.internal={}
|
||||
|
||||
local function checkArg(n, have, ...)
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)",
|
||||
n, table.concat({...}, " or "), have)
|
||||
error(msg, 3)
|
||||
end
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)",
|
||||
n, table.concat({...}, " or "), have)
|
||||
error(msg, 3)
|
||||
end
|
||||
end
|
||||
|
||||
function lib.internal.range_adjust(f,l,s)
|
||||
checkArg(1,f,'number','nil')
|
||||
checkArg(2,l,'number','nil')
|
||||
checkArg(3,s,'number')
|
||||
if f==nil then f=1 elseif f<0 then f=s+f+1 end
|
||||
if l==nil then l=s elseif l<0 then l=s+l+1 end
|
||||
return f,l
|
||||
checkArg(1,f,'number','nil')
|
||||
checkArg(2,l,'number','nil')
|
||||
checkArg(3,s,'number')
|
||||
if f==nil then f=1 elseif f<0 then f=s+f+1 end
|
||||
if l==nil then l=s elseif l<0 then l=s+l+1 end
|
||||
return f,l
|
||||
end
|
||||
function lib.internal.table_view(tbl,f,l)
|
||||
return setmetatable({},
|
||||
{
|
||||
__index = function(_, key)
|
||||
return (type(key) ~= 'number' or (key >= f and key <= l)) and tbl[key] or nil
|
||||
end,
|
||||
__len = function(_)
|
||||
return l
|
||||
end,
|
||||
})
|
||||
return setmetatable({},
|
||||
{
|
||||
__index = function(_, key)
|
||||
return (type(key) ~= 'number' or (key >= f and key <= l)) and tbl[key] or nil
|
||||
end,
|
||||
__len = function(_)
|
||||
return l
|
||||
end,
|
||||
})
|
||||
end
|
||||
local adjust=lib.internal.range_adjust
|
||||
local view=lib.internal.table_view
|
||||
|
||||
-- first(p1,p2) searches for the first range in p1 that satisfies p2
|
||||
function lib.first(tbl,pred,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,pred,'function','table')
|
||||
if type(pred)=='table'then
|
||||
local set;set,pred=pred,function(_,fi,tbl)
|
||||
for vi=1,#set do
|
||||
local v=set[vi]
|
||||
if lib.begins(tbl,v,fi) then return true,#v end
|
||||
end
|
||||
end
|
||||
end
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
for i=f,l do
|
||||
local si,ei=pred(tbl[i],i,tbl)
|
||||
if si then
|
||||
return i,i+(ei or 1)-1
|
||||
end
|
||||
end
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,pred,'function','table')
|
||||
if type(pred)=='table'then
|
||||
local set;set,pred=pred,function(_,fi,tbl)
|
||||
for vi=1,#set do
|
||||
local v=set[vi]
|
||||
if lib.begins(tbl,v,fi) then return true,#v end
|
||||
end
|
||||
end
|
||||
end
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
for i=f,l do
|
||||
local si,ei=pred(tbl[i],i,tbl)
|
||||
if si then
|
||||
return i,i+(ei or 1)-1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- returns true if p1 at first p3 equals element for element p2
|
||||
function lib.begins(tbl,v,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,v,'table')
|
||||
local vs=#v
|
||||
f,l=adjust(f,l,#tbl)
|
||||
if vs>(l-f+1)then return end
|
||||
for i=1,vs do
|
||||
if tbl[f+i-1]~=v[i] then return end
|
||||
end
|
||||
return true
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,v,'table')
|
||||
local vs=#v
|
||||
f,l=adjust(f,l,#tbl)
|
||||
if vs>(l-f+1)then return end
|
||||
for i=1,vs do
|
||||
if tbl[f+i-1]~=v[i] then return end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function lib.concat(...)
|
||||
local r,rn,k={},0
|
||||
for _,tbl in ipairs({...})do
|
||||
if type(tbl)~='table'then
|
||||
return nil,'parameter '..tostring(_)..' to concat is not a table'
|
||||
end
|
||||
local n=tbl.n or #tbl
|
||||
k=k or tbl.n
|
||||
for i=1,n do
|
||||
rn=rn+1;r[rn]=tbl[i]
|
||||
end
|
||||
end
|
||||
r.n=k and rn or nil
|
||||
return r
|
||||
local r,rn,k={},0
|
||||
for _,tbl in ipairs({...})do
|
||||
if type(tbl)~='table'then
|
||||
return nil,'parameter '..tostring(_)..' to concat is not a table'
|
||||
end
|
||||
local n=tbl.n or #tbl
|
||||
k=k or tbl.n
|
||||
for i=1,n do
|
||||
rn=rn+1;r[rn]=tbl[i]
|
||||
end
|
||||
end
|
||||
r.n=k and rn or nil
|
||||
return r
|
||||
end
|
||||
|
||||
-- works like string.sub but on elements of an indexed table
|
||||
function lib.sub(tbl,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
local r,s={},#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
l=math.min(l,s)
|
||||
for i=math.max(f,1),l do
|
||||
r[#r+1]=tbl[i]
|
||||
end
|
||||
return r
|
||||
checkArg(1,tbl,'table')
|
||||
local r,s={},#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
l=math.min(l,s)
|
||||
for i=math.max(f,1),l do
|
||||
r[#r+1]=tbl[i]
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
-- Returns a list of subsets of tbl where partitioner acts as a delimiter.
|
||||
function lib.partition(tbl,partitioner,dropEnds,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,partitioner,'function','table')
|
||||
checkArg(3,dropEnds,'boolean','nil')
|
||||
if type(partitioner)=='table'then
|
||||
return lib.partition(tbl,function(_,i,tbl)
|
||||
return lib.first(tbl,partitioner,i)
|
||||
end,dropEnds,f,l)
|
||||
end
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
local cut=view(tbl,f,l)
|
||||
local result={}
|
||||
local need=true
|
||||
local exp=function()if need then result[#result+1]={}need=false end end
|
||||
local i=f
|
||||
while i<=l do
|
||||
local e=cut[i]
|
||||
local ds,de=partitioner(e,i,cut)
|
||||
-- true==partition here
|
||||
if ds==true then ds,de=i,i
|
||||
elseif ds==false then ds,de=nil,nil end
|
||||
if ds~=nil then
|
||||
ds,de=adjust(ds,de,l)
|
||||
ds=ds>=i and ds--no more
|
||||
end
|
||||
if not ds then -- false or nil
|
||||
exp()
|
||||
table.insert(result[#result],e)
|
||||
else
|
||||
local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
|
||||
if #sub>0 then
|
||||
exp()
|
||||
result[#result+math.min(#result[#result],1)]=sub
|
||||
end
|
||||
-- ensure i moves forward
|
||||
local ensured=math.max(math.max(de or ds,ds),i)
|
||||
if de and ds and de<ds and ensured==i then
|
||||
if #result==0 then result[1]={} end
|
||||
table.insert(result[#result],e)
|
||||
end
|
||||
i=ensured
|
||||
need=true
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,partitioner,'function','table')
|
||||
checkArg(3,dropEnds,'boolean','nil')
|
||||
if type(partitioner)=='table'then
|
||||
return lib.partition(tbl,function(_,i,tbl)
|
||||
return lib.first(tbl,partitioner,i)
|
||||
end,dropEnds,f,l)
|
||||
end
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
local cut=view(tbl,f,l)
|
||||
local result={}
|
||||
local need=true
|
||||
local exp=function()if need then result[#result+1]={}need=false end end
|
||||
local i=f
|
||||
while i<=l do
|
||||
local e=cut[i]
|
||||
local ds,de=partitioner(e,i,cut)
|
||||
-- true==partition here
|
||||
if ds==true then ds,de=i,i
|
||||
elseif ds==false then ds,de=nil,nil end
|
||||
if ds~=nil then
|
||||
ds,de=adjust(ds,de,l)
|
||||
ds=ds>=i and ds--no more
|
||||
end
|
||||
if not ds then -- false or nil
|
||||
exp()
|
||||
table.insert(result[#result],e)
|
||||
else
|
||||
local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
|
||||
if #sub>0 then
|
||||
exp()
|
||||
result[#result+math.min(#result[#result],1)]=sub
|
||||
end
|
||||
-- ensure i moves forward
|
||||
local ensured=math.max(math.max(de or ds,ds),i)
|
||||
if de and ds and de<ds and ensured==i then
|
||||
if #result==0 then result[1]={} end
|
||||
table.insert(result[#result],e)
|
||||
end
|
||||
i=ensured
|
||||
need=true
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
|
||||
return result
|
||||
return result
|
||||
end
|
||||
|
||||
-- calls callback(e,i,tbl) for each ith element e in table tbl from first
|
||||
function lib.foreach(tbl,c,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,c,'function','string')
|
||||
local ck=c
|
||||
c=type(c)=="string" and function(e) return e[ck] end or c
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
local r={}
|
||||
for i=f,l do
|
||||
local n,k=c(tbl[i],i,tbl)
|
||||
if n~=nil then
|
||||
if k then r[k]=n
|
||||
else r[#r+1]=n end
|
||||
end
|
||||
end
|
||||
return r
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,c,'function','string')
|
||||
local ck=c
|
||||
c=type(c)=="string" and function(e) return e[ck] end or c
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
local r={}
|
||||
for i=f,l do
|
||||
local n,k=c(tbl[i],i,tbl)
|
||||
if n~=nil then
|
||||
if k then r[k]=n
|
||||
else r[#r+1]=n end
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function lib.where(tbl,p,f,l)
|
||||
return lib.foreach(tbl,
|
||||
function(e,i,tbl)
|
||||
return p(e,i,tbl)and e or nil
|
||||
end,f,l)
|
||||
return lib.foreach(tbl,
|
||||
function(e,i,tbl)
|
||||
return p(e,i,tbl)and e or nil
|
||||
end,f,l)
|
||||
end
|
||||
|
||||
-- works with pairs on tables
|
||||
-- returns the kv pair, or nil and the number of pairs iterated
|
||||
function lib.at(tbl, index)
|
||||
checkArg(1, tbl, "table")
|
||||
checkArg(2, index, "number", "nil")
|
||||
local current_index = 1
|
||||
for k,v in pairs(tbl) do
|
||||
if current_index == index then
|
||||
return k,v
|
||||
end
|
||||
current_index = current_index + 1
|
||||
end
|
||||
return nil, current_index - 1 -- went one too far
|
||||
checkArg(1, tbl, "table")
|
||||
checkArg(2, index, "number", "nil")
|
||||
local current_index = 1
|
||||
for k,v in pairs(tbl) do
|
||||
if current_index == index then
|
||||
return k,v
|
||||
end
|
||||
current_index = current_index + 1
|
||||
end
|
||||
return nil, current_index - 1 -- went one too far
|
||||
end
|
||||
|
||||
return lib
|
||||
@@ -4,31 +4,31 @@ local term = _G.term
|
||||
local w, h = term.getSize()
|
||||
|
||||
local cmap = {
|
||||
[ 0xCC2200 ] = colors.red,
|
||||
[ 0x44CC00 ] = colors.lime,
|
||||
[ 0xB0B00F ] = colors.yellow,
|
||||
[ 0xFFFFFF ] = colors.white,
|
||||
[ 0xCC2200 ] = colors.red,
|
||||
[ 0x44CC00 ] = colors.lime,
|
||||
[ 0xB0B00F ] = colors.yellow,
|
||||
[ 0xFFFFFF ] = colors.white,
|
||||
|
||||
[ 0xb000b0 ] = colors.purple,
|
||||
[ 0x00FF00 ] = colors.green,
|
||||
[ 0xFF0000 ] = colors.red,
|
||||
[ 0x00FFFF ] = colors.cyan,
|
||||
[ 0x000000 ] = colors.black,
|
||||
[ 0xb000b0 ] = colors.purple,
|
||||
[ 0x00FF00 ] = colors.green,
|
||||
[ 0xFF0000 ] = colors.red,
|
||||
[ 0x00FFFF ] = colors.cyan,
|
||||
[ 0x000000 ] = colors.black,
|
||||
}
|
||||
|
||||
return {
|
||||
gpu = function()
|
||||
local current = 0xFFFFFF
|
||||
return {
|
||||
setForeground = function(c)
|
||||
current = c
|
||||
term.setTextColor(cmap[c])
|
||||
end,
|
||||
getForeground = function() return current end,
|
||||
}
|
||||
end,
|
||||
getViewport = term.getSize,
|
||||
window = {
|
||||
width = w, height = h,
|
||||
}
|
||||
gpu = function()
|
||||
local current = 0xFFFFFF
|
||||
return {
|
||||
setForeground = function(c)
|
||||
current = c
|
||||
term.setTextColor(cmap[c])
|
||||
end,
|
||||
getForeground = function() return current end,
|
||||
}
|
||||
end,
|
||||
getViewport = term.getSize,
|
||||
window = {
|
||||
width = w, height = h,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
return {
|
||||
wlen = string.len
|
||||
wlen = string.len
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ local settings = _G.settings
|
||||
settings.set('LS_COLORS', "di=0;36:fi=0:ln=0;33:*.lua=0;32")
|
||||
|
||||
function os.setenv(k, v)
|
||||
settings.set(k, v)
|
||||
settings.set(k, v)
|
||||
end
|
||||
function os.getenv(k)
|
||||
return settings.get(k)
|
||||
return settings.get(k)
|
||||
end
|
||||
|
||||
fs.mount('rom/programs/list.lua', 'linkfs', 'packages/shellex/ls.lua')
|
||||
|
||||
@@ -4,28 +4,28 @@ local fs = require("shellex.filesystem")
|
||||
local args = shell.parse(...)
|
||||
local ec = 0
|
||||
if #args == 0 then
|
||||
args = {"-"}
|
||||
args = {"-"}
|
||||
end
|
||||
|
||||
for i = 1, #args do
|
||||
local arg = shell.resolve(args[i])
|
||||
if fs.isDirectory(arg) then
|
||||
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
|
||||
ec = 1
|
||||
else
|
||||
local file, reason = fs.open(arg)
|
||||
if not file then
|
||||
io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
|
||||
ec = 1
|
||||
else
|
||||
local chunk = file.readAll()
|
||||
file:close()
|
||||
if chunk then
|
||||
io.write(chunk)
|
||||
io.write('\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
local arg = shell.resolve(args[i])
|
||||
if fs.isDirectory(arg) then
|
||||
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
|
||||
ec = 1
|
||||
else
|
||||
local file, reason = fs.open(arg)
|
||||
if not file then
|
||||
io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
|
||||
ec = 1
|
||||
else
|
||||
local chunk = file.readAll()
|
||||
file:close()
|
||||
if chunk then
|
||||
io.write(chunk)
|
||||
io.write('\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ec
|
||||
@@ -4,33 +4,33 @@ local transfer = require("shellex.transfer")
|
||||
local args, options = shell.parse(...)
|
||||
options.h = options.h or options.help
|
||||
if #args < 2 or options.h then
|
||||
io.write([[Usage: cp [OPTIONS] <from...> <to>
|
||||
io.write([[Usage: cp [OPTIONS] <from...> <to>
|
||||
-i: prompt before overwrite (overrides -n option).
|
||||
-n: do not overwrite an existing file.
|
||||
-r: copy directories recursively.
|
||||
-u: copy only when the SOURCE file differs from the destination
|
||||
file or when the destination file is missing.
|
||||
file or when the destination file is missing.
|
||||
-P: preserve attributes, e.g. symbolic links.
|
||||
-v: verbose output.
|
||||
-x: stay on original source file system.
|
||||
--skip=P: skip files matching lua regex P
|
||||
]])
|
||||
return not not options.h
|
||||
return not not options.h
|
||||
end
|
||||
|
||||
-- clean options for copy (as opposed to move)
|
||||
options =
|
||||
{
|
||||
cmd = "cp",
|
||||
i = options.i,
|
||||
f = options.f,
|
||||
n = options.n,
|
||||
r = options.r,
|
||||
u = options.u,
|
||||
P = options.P,
|
||||
v = options.v,
|
||||
x = options.x,
|
||||
skip = options.skip,
|
||||
cmd = "cp",
|
||||
i = options.i,
|
||||
f = options.f,
|
||||
n = options.n,
|
||||
r = options.r,
|
||||
u = options.u,
|
||||
P = options.P,
|
||||
v = options.v,
|
||||
x = options.x,
|
||||
skip = options.skip,
|
||||
}
|
||||
|
||||
return transfer.batch(args, options)
|
||||
|
||||
104
shellex/df.lua
104
shellex/df.lua
@@ -5,72 +5,72 @@ local text = require("shellex.text")
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local function formatSize(size)
|
||||
if not options.h then
|
||||
return tostring(size)
|
||||
elseif type(size) == "string" then
|
||||
return size
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = options.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||
if not options.h then
|
||||
return tostring(size)
|
||||
elseif type(size) == "string" then
|
||||
return size
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = options.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||
end
|
||||
|
||||
local mounts = {}
|
||||
if #args == 0 then
|
||||
for proxy, path in fs.mounts() do
|
||||
if not mounts[proxy] or mounts[proxy]:len() > path:len() then
|
||||
mounts[proxy] = path
|
||||
end
|
||||
end
|
||||
for proxy, path in fs.mounts() do
|
||||
if not mounts[proxy] or mounts[proxy]:len() > path:len() then
|
||||
mounts[proxy] = path
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = 1, #args do
|
||||
local proxy, path = fs.get(shell.resolve(args[i]))
|
||||
if not proxy then
|
||||
io.stderr:write(args[i], ": no such file or directory\n")
|
||||
else
|
||||
mounts[proxy] = path
|
||||
end
|
||||
end
|
||||
for i = 1, #args do
|
||||
local proxy, path = fs.get(shell.resolve(args[i]))
|
||||
if not proxy then
|
||||
io.stderr:write(args[i], ": no such file or directory\n")
|
||||
else
|
||||
mounts[proxy] = path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}}
|
||||
for proxy, path in pairs(mounts) do
|
||||
local label = proxy.getLabel() or proxy.address
|
||||
local used, total = proxy.spaceUsed(), proxy.spaceTotal()
|
||||
local available, percent
|
||||
if total == math.huge then
|
||||
used = used or "N/A"
|
||||
available = "unlimited"
|
||||
percent = "0%"
|
||||
else
|
||||
available = total - used
|
||||
percent = used / total
|
||||
if percent ~= percent then -- NaN
|
||||
available = "N/A"
|
||||
percent = "N/A"
|
||||
else
|
||||
percent = math.ceil(percent * 100) .. "%"
|
||||
end
|
||||
end
|
||||
table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path})
|
||||
local label = proxy.getLabel() or proxy.address
|
||||
local used, total = proxy.spaceUsed(), proxy.spaceTotal()
|
||||
local available, percent
|
||||
if total == math.huge then
|
||||
used = used or "N/A"
|
||||
available = "unlimited"
|
||||
percent = "0%"
|
||||
else
|
||||
available = total - used
|
||||
percent = used / total
|
||||
if percent ~= percent then -- NaN
|
||||
available = "N/A"
|
||||
percent = "N/A"
|
||||
else
|
||||
percent = math.ceil(percent * 100) .. "%"
|
||||
end
|
||||
end
|
||||
table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path})
|
||||
end
|
||||
|
||||
local m = {}
|
||||
for _, row in ipairs(result) do
|
||||
for col, value in ipairs(row) do
|
||||
m[col] = math.max(m[col] or 1, value:len())
|
||||
end
|
||||
for col, value in ipairs(row) do
|
||||
m[col] = math.max(m[col] or 1, value:len())
|
||||
end
|
||||
end
|
||||
|
||||
for _, row in ipairs(result) do
|
||||
for col, value in ipairs(row) do
|
||||
local padding = col == #row and 0 or 2
|
||||
io.write(text.padRight(value, m[col] + padding))
|
||||
end
|
||||
print()
|
||||
for col, value in ipairs(row) do
|
||||
local padding = col == #row and 0 or 2
|
||||
io.write(text.padRight(value, m[col] + padding))
|
||||
end
|
||||
print()
|
||||
end
|
||||
|
||||
@@ -5,28 +5,28 @@ local gpu = tty.gpu()
|
||||
io.write("Press 'Ctrl-C' to exit\n")
|
||||
local events = { }
|
||||
for _, e in pairs(args) do
|
||||
events[e] = true
|
||||
events[e] = true
|
||||
end
|
||||
--pcall(function()
|
||||
repeat
|
||||
local evt = table.pack(os.pullEventRaw())
|
||||
if #args == 0 or events[evt[1]] then
|
||||
gpu.setForeground(0xCC2200)
|
||||
io.write("[" .. math.floor(os.clock("utc")) .. "] ")
|
||||
gpu.setForeground(0x44CC00)
|
||||
io.write(tostring(evt[1]) .. string.rep(" ", math.max(12 - #tostring(evt[1]), 0) + 1))
|
||||
gpu.setForeground(0xB0B00F)
|
||||
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
if evt.n > 2 then
|
||||
for i = 3, evt.n do
|
||||
io.write(" " .. tostring(evt[i]))
|
||||
end
|
||||
end
|
||||
repeat
|
||||
local evt = table.pack(os.pullEventRaw())
|
||||
if #args == 0 or events[evt[1]] then
|
||||
gpu.setForeground(0xCC2200)
|
||||
io.write("[" .. math.floor(os.clock("utc")) .. "] ")
|
||||
gpu.setForeground(0x44CC00)
|
||||
io.write(tostring(evt[1]) .. string.rep(" ", math.max(12 - #tostring(evt[1]), 0) + 1))
|
||||
gpu.setForeground(0xB0B00F)
|
||||
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
if evt.n > 2 then
|
||||
for i = 3, evt.n do
|
||||
io.write(" " .. tostring(evt[i]))
|
||||
end
|
||||
end
|
||||
|
||||
io.write("\n")
|
||||
end
|
||||
until evt[1] == "terminate"
|
||||
io.write("\n")
|
||||
end
|
||||
until evt[1] == "terminate"
|
||||
--end)
|
||||
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
|
||||
152
shellex/du.lua
152
shellex/du.lua
@@ -4,7 +4,7 @@ local shell = require("shellex.shell")
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
if #args == 0 then
|
||||
args[1] = '.'
|
||||
args[1] = '.'
|
||||
end
|
||||
|
||||
local TRY=[[
|
||||
@@ -18,116 +18,116 @@ local HELP=[[
|
||||
Usage: du [OPTION]... [FILE]...
|
||||
Summarize disk usage of each FILE, recursively for directories.
|
||||
|
||||
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)
|
||||
-s, --summarize display only a total for each argument
|
||||
--help display this help and exit
|
||||
--version output version information and exit]]
|
||||
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)
|
||||
-s, --summarize display only a total for each argument
|
||||
--help display this help and exit
|
||||
--version output version information and exit]]
|
||||
|
||||
if options.help then
|
||||
print(HELP)
|
||||
return true
|
||||
print(HELP)
|
||||
return true
|
||||
end
|
||||
|
||||
if options.version then
|
||||
print(VERSION)
|
||||
return true
|
||||
print(VERSION)
|
||||
return true
|
||||
end
|
||||
|
||||
local function addTrailingSlash(path)
|
||||
if path:sub(-1) ~= '/' then
|
||||
return path .. '/'
|
||||
else
|
||||
return path
|
||||
end
|
||||
if path:sub(-1) ~= '/' then
|
||||
return path .. '/'
|
||||
else
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
local function opCheck(shortName, longName)
|
||||
local enabled = options[shortName] or options[longName]
|
||||
options[shortName] = nil
|
||||
options[longName] = nil
|
||||
return enabled
|
||||
local enabled = options[shortName] or options[longName]
|
||||
options[shortName] = nil
|
||||
options[longName] = nil
|
||||
return enabled
|
||||
end
|
||||
|
||||
local bHuman = opCheck('h', 'human-readable')
|
||||
local bSummary = opCheck('s', 'summarize')
|
||||
|
||||
if next(options) then
|
||||
for op in pairs(options) do
|
||||
io.stderr:write(string.format("du: invalid option -- '%s'\n", op))
|
||||
end
|
||||
io.stderr:write(TRY..'\n')
|
||||
return 1
|
||||
for op in pairs(options) do
|
||||
io.stderr:write(string.format("du: invalid option -- '%s'\n", op))
|
||||
end
|
||||
io.stderr:write(TRY..'\n')
|
||||
return 1
|
||||
end
|
||||
|
||||
local function formatSize(size)
|
||||
if not bHuman then
|
||||
return tostring(size)
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = options.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
if not bHuman then
|
||||
return tostring(size)
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = options.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
|
||||
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||
end
|
||||
|
||||
local function printSize(size, rpath)
|
||||
local displaySize = formatSize(size)
|
||||
io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath))
|
||||
local displaySize = formatSize(size)
|
||||
io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath))
|
||||
end
|
||||
|
||||
local function visitor(rpath)
|
||||
local subtotal = 0
|
||||
local dirs = 0
|
||||
local spath = shell.resolve(rpath)
|
||||
local subtotal = 0
|
||||
local dirs = 0
|
||||
local spath = shell.resolve(rpath)
|
||||
|
||||
if fs.isDirectory(spath) then
|
||||
local list_result = fs.list(spath)
|
||||
if list_result then
|
||||
for list_item in list_result do
|
||||
local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
|
||||
subtotal = subtotal + vtotal
|
||||
dirs = dirs + vdirs
|
||||
end
|
||||
end
|
||||
if fs.isDirectory(spath) then
|
||||
local list_result = fs.list(spath)
|
||||
if list_result then
|
||||
for list_item in list_result do
|
||||
local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
|
||||
subtotal = subtotal + vtotal
|
||||
dirs = dirs + vdirs
|
||||
end
|
||||
end
|
||||
|
||||
if dirs == 0 then -- no child dirs
|
||||
if not bSummary then
|
||||
printSize(subtotal, rpath)
|
||||
end
|
||||
end
|
||||
if dirs == 0 then -- no child dirs
|
||||
if not bSummary then
|
||||
printSize(subtotal, rpath)
|
||||
end
|
||||
end
|
||||
|
||||
elseif not fs.isLink(spath) then
|
||||
subtotal = fs.size(spath)
|
||||
end
|
||||
elseif not fs.isLink(spath) then
|
||||
subtotal = fs.size(spath)
|
||||
end
|
||||
|
||||
return subtotal, dirs
|
||||
return subtotal, dirs
|
||||
end
|
||||
|
||||
for _,arg in ipairs(args) do
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
local path = shell.resolve(v)
|
||||
if not fs.exists(path) then
|
||||
io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", v))
|
||||
return 1
|
||||
else
|
||||
if fs.isDirectory(path) then
|
||||
local total = visitor(v)
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
local path = shell.resolve(v)
|
||||
if not fs.exists(path) then
|
||||
io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", v))
|
||||
return 1
|
||||
else
|
||||
if fs.isDirectory(path) then
|
||||
local total = visitor(v)
|
||||
|
||||
if bSummary then
|
||||
printSize(total, v)
|
||||
end
|
||||
elseif fs.isLink(path) then
|
||||
printSize(0, v)
|
||||
else
|
||||
printSize(fs.size(path), v)
|
||||
end
|
||||
end
|
||||
end
|
||||
if bSummary then
|
||||
printSize(total, v)
|
||||
end
|
||||
elseif fs.isLink(path) then
|
||||
printSize(0, v)
|
||||
else
|
||||
printSize(fs.size(path), v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
158
shellex/find.lua
158
shellex/find.lua
@@ -4,26 +4,26 @@ local text = require("shellex.text")
|
||||
|
||||
local USAGE =
|
||||
[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR]
|
||||
--path if not specified, path is assumed to be current working directory
|
||||
--type returns results of a given type, d:directory, f:file, and s:symlinks
|
||||
--name specify the file name pattern. Use quote to include *. iname is
|
||||
case insensitive
|
||||
--help display this help and exit]===]
|
||||
--path if not specified, path is assumed to be current working directory
|
||||
--type returns results of a given type, d:directory, f:file, and s:symlinks
|
||||
--name specify the file name pattern. Use quote to include *. iname is
|
||||
case insensitive
|
||||
--help display this help and exit]===]
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
if (not args or not options) or options.help then
|
||||
print(USAGE)
|
||||
if not options.help then
|
||||
return 1
|
||||
else
|
||||
return -- nil return, meaning no error
|
||||
end
|
||||
print(USAGE)
|
||||
if not options.help then
|
||||
return 1
|
||||
else
|
||||
return -- nil return, meaning no error
|
||||
end
|
||||
end
|
||||
|
||||
if #args > 1 then
|
||||
io.stderr:write(USAGE..'\n')
|
||||
return 1
|
||||
io.stderr:write(USAGE..'\n')
|
||||
return 1
|
||||
end
|
||||
|
||||
local path = #args == 1 and args[1] or "."
|
||||
@@ -36,97 +36,97 @@ local fileNamePattern = ""
|
||||
local bCaseSensitive = true
|
||||
|
||||
if options.iname and options.name then
|
||||
io.stderr:write("find cannot define both iname and name\n")
|
||||
return 1
|
||||
io.stderr:write("find cannot define both iname and name\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
if options.type then
|
||||
bDirs = false
|
||||
bFiles = false
|
||||
bSyms = false
|
||||
bDirs = false
|
||||
bFiles = false
|
||||
bSyms = false
|
||||
|
||||
if options.type == "f" then
|
||||
bFiles = true
|
||||
elseif options.type == "d" then
|
||||
bDirs = true
|
||||
elseif options.type == "s" then
|
||||
bSyms = true
|
||||
else
|
||||
io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type))
|
||||
io.stderr:write(USAGE..'\n')
|
||||
return 1
|
||||
end
|
||||
if options.type == "f" then
|
||||
bFiles = true
|
||||
elseif options.type == "d" then
|
||||
bDirs = true
|
||||
elseif options.type == "s" then
|
||||
bSyms = true
|
||||
else
|
||||
io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type))
|
||||
io.stderr:write(USAGE..'\n')
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
if options.iname or options.name then
|
||||
bCaseSensitive = options.iname ~= nil
|
||||
fileNamePattern = options.iname or options.name
|
||||
bCaseSensitive = options.iname ~= nil
|
||||
fileNamePattern = options.iname or options.name
|
||||
|
||||
if type(fileNamePattern) ~= "string" then
|
||||
io.stderr:write('find: missing argument to `name\'\n')
|
||||
return 1
|
||||
end
|
||||
if type(fileNamePattern) ~= "string" then
|
||||
io.stderr:write('find: missing argument to `name\'\n')
|
||||
return 1
|
||||
end
|
||||
|
||||
if not bCaseSensitive then
|
||||
fileNamePattern = fileNamePattern:lower()
|
||||
end
|
||||
if not bCaseSensitive then
|
||||
fileNamePattern = fileNamePattern:lower()
|
||||
end
|
||||
|
||||
-- prefix any * with . for gnu find glob matching
|
||||
fileNamePattern = text.escapeMagic(fileNamePattern)
|
||||
fileNamePattern = fileNamePattern:gsub("%%%*", ".*")
|
||||
-- prefix any * with . for gnu find glob matching
|
||||
fileNamePattern = text.escapeMagic(fileNamePattern)
|
||||
fileNamePattern = fileNamePattern:gsub("%%%*", ".*")
|
||||
end
|
||||
|
||||
local function isValidType(spath)
|
||||
if not fs.exists(spath) then
|
||||
return false
|
||||
end
|
||||
if not fs.exists(spath) then
|
||||
return false
|
||||
end
|
||||
|
||||
if fileNamePattern:len() > 0 then
|
||||
local fileName = spath:gsub('.*/','')
|
||||
if fileNamePattern:len() > 0 then
|
||||
local fileName = spath:gsub('.*/','')
|
||||
|
||||
if fileName:len() == 0 then
|
||||
return false
|
||||
end
|
||||
if fileName:len() == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local caseFileName = fileName
|
||||
local caseFileName = fileName
|
||||
|
||||
if not bCaseSensitive then
|
||||
caseFileName = caseFileName:lower()
|
||||
end
|
||||
if not bCaseSensitive then
|
||||
caseFileName = caseFileName:lower()
|
||||
end
|
||||
|
||||
local s, e = caseFileName:find(fileNamePattern)
|
||||
if not s or not e then
|
||||
return false
|
||||
end
|
||||
local s, e = caseFileName:find(fileNamePattern)
|
||||
if not s or not e then
|
||||
return false
|
||||
end
|
||||
|
||||
if s ~= 1 or e ~= caseFileName:len() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
if s ~= 1 or e ~= caseFileName:len() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if fs.isDirectory(spath) then
|
||||
return bDirs
|
||||
elseif fs.isLink(spath) then
|
||||
return bSyms
|
||||
else
|
||||
return bFiles
|
||||
end
|
||||
if fs.isDirectory(spath) then
|
||||
return bDirs
|
||||
elseif fs.isLink(spath) then
|
||||
return bSyms
|
||||
else
|
||||
return bFiles
|
||||
end
|
||||
end
|
||||
|
||||
local function visit(rpath)
|
||||
local spath = shell.resolve(rpath)
|
||||
local spath = shell.resolve(rpath)
|
||||
|
||||
if isValidType(spath) then
|
||||
local result = rpath:gsub('/+$','')
|
||||
print(result)
|
||||
end
|
||||
if isValidType(spath) then
|
||||
local result = rpath:gsub('/+$','')
|
||||
print(result)
|
||||
end
|
||||
|
||||
if fs.isDirectory(spath) then
|
||||
local list_result = fs.list(spath)
|
||||
for list_item in list_result do
|
||||
visit(rpath:gsub('/+$', '') .. '/' .. list_item)
|
||||
end
|
||||
end
|
||||
if fs.isDirectory(spath) then
|
||||
local list_result = fs.list(spath)
|
||||
for list_item in list_result do
|
||||
visit(rpath:gsub('/+$', '') .. '/' .. list_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
visit(path)
|
||||
|
||||
416
shellex/grep.lua
416
shellex/grep.lua
@@ -19,11 +19,11 @@ local args, options = shell.parse(...)
|
||||
local gpu = tty.gpu()
|
||||
|
||||
local function printUsage(ostream, msg)
|
||||
local s = ostream or io.stdout
|
||||
if msg then
|
||||
s:write(msg,'\n')
|
||||
end
|
||||
s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
|
||||
local s = ostream or io.stdout
|
||||
if msg then
|
||||
s:write(msg,'\n')
|
||||
end
|
||||
s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
|
||||
Example: grep -i "hello world" menu.lua main.lua
|
||||
for more information, run: man grep
|
||||
]])
|
||||
@@ -32,10 +32,10 @@ end
|
||||
local PATTERNS = { table.remove(args, 1) }
|
||||
local FILES = { }
|
||||
for _, arg in pairs(args) do
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
table.insert(FILES, v)
|
||||
end
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
table.insert(FILES, v)
|
||||
end
|
||||
end
|
||||
|
||||
local LABEL_COLOR = 0xb000b0
|
||||
@@ -44,17 +44,17 @@ local MATCH_COLOR = 0xB0B00F
|
||||
local COLON_COLOR = 0x00FFFF
|
||||
|
||||
local function pop(...)
|
||||
local result
|
||||
for _,key in ipairs({...}) do
|
||||
result = options[key] or result
|
||||
options[key] = nil
|
||||
end
|
||||
return result
|
||||
local result
|
||||
for _,key in ipairs({...}) do
|
||||
result = options[key] or result
|
||||
options[key] = nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Specify the variables for the options
|
||||
local plain = pop('F','fixed-strings')
|
||||
plain = not pop('e','--lua-regexp') and plain
|
||||
plain = not pop('e','--lua-regexp') and plain
|
||||
local pattern_file = pop('file')
|
||||
local match_whole_word = pop('w','word-regexp')
|
||||
local match_whole_line = pop('x','line-regexp')
|
||||
@@ -65,8 +65,8 @@ local invert_match = not not pop('v','invert-match')
|
||||
|
||||
-- no version output, just help
|
||||
if pop('V','version','help') then
|
||||
printUsage()
|
||||
return 0
|
||||
printUsage()
|
||||
return 0
|
||||
end
|
||||
|
||||
local max_matches = tonumber(pop('max-count')) or math.huge
|
||||
@@ -75,40 +75,40 @@ local search_recursively = pop('r','recursive')
|
||||
|
||||
-- Table with patterns to check for
|
||||
if pattern_file then
|
||||
local pattern_file_path = shell.resolve(pattern_file)
|
||||
if not fs.exists(pattern_file_path) then
|
||||
stderr:write('grep: ',pattern_file,': file not found')
|
||||
return 2
|
||||
end
|
||||
table.insert(FILES, 1, PATTERNS[1])
|
||||
PATTERNS = {}
|
||||
for line in io.lines(pattern_file_path) do
|
||||
PATTERNS[#PATTERNS+1] = line
|
||||
end
|
||||
local pattern_file_path = shell.resolve(pattern_file)
|
||||
if not fs.exists(pattern_file_path) then
|
||||
stderr:write('grep: ',pattern_file,': file not found')
|
||||
return 2
|
||||
end
|
||||
table.insert(FILES, 1, PATTERNS[1])
|
||||
PATTERNS = {}
|
||||
for line in io.lines(pattern_file_path) do
|
||||
PATTERNS[#PATTERNS+1] = line
|
||||
end
|
||||
end
|
||||
|
||||
if #PATTERNS == 0 then
|
||||
printUsage(stderr)
|
||||
return 2
|
||||
printUsage(stderr)
|
||||
return 2
|
||||
end
|
||||
|
||||
if #FILES == 0 then
|
||||
FILES = search_recursively and {'.'} or {'-'}
|
||||
FILES = search_recursively and {'.'} or {'-'}
|
||||
end
|
||||
|
||||
if not options.h and search_recursively then
|
||||
options.H = true
|
||||
options.H = true
|
||||
end
|
||||
|
||||
if #FILES < 2 then
|
||||
options.h = true
|
||||
options.h = true
|
||||
end
|
||||
|
||||
local f_only = pop('l','files-with-matches')
|
||||
local no_only = pop('L','files-without-match') and not f_only
|
||||
|
||||
local include_filename = pop('H','with-filename')
|
||||
include_filename = not pop('h','no-filename') or include_filename
|
||||
include_filename = not pop('h','no-filename') or include_filename
|
||||
|
||||
local m_only = pop('o','only-matching')
|
||||
local quiet = pop('q','quiet','silent')
|
||||
@@ -125,212 +125,212 @@ local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
|
||||
local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop
|
||||
|
||||
if next(options) then
|
||||
if not quiet then
|
||||
printUsage(stderr, 'unexpected option: '..next(options))
|
||||
return 2
|
||||
end
|
||||
return 0
|
||||
if not quiet then
|
||||
printUsage(stderr, 'unexpected option: '..next(options))
|
||||
return 2
|
||||
end
|
||||
return 0
|
||||
end
|
||||
-- Resolve the location of a file, without searching the path
|
||||
local function resolve(file)
|
||||
if file:sub(1,1) == '/' then
|
||||
return fs.canonical(file)
|
||||
else
|
||||
if file:sub(1,2) == './' then
|
||||
file = file:sub(3, -1)
|
||||
end
|
||||
return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
|
||||
end
|
||||
if file:sub(1,1) == '/' then
|
||||
return fs.canonical(file)
|
||||
else
|
||||
if file:sub(1,2) == './' then
|
||||
file = file:sub(3, -1)
|
||||
end
|
||||
return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
|
||||
end
|
||||
end
|
||||
|
||||
--- Builds a case insensitive patterns, code from stackoverflow
|
||||
--- (questions/11401890/case-insensitive-lua-pattern-matching)
|
||||
if ignore_case then
|
||||
for i=1,#PATTERNS do
|
||||
-- find an optional '%' (group 1) followed by any character (group 2)
|
||||
PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
|
||||
if percent ~= "" or not letter:match("%a") then
|
||||
-- if the '%' matched, or `letter` is not a letter, return "as is"
|
||||
return percent .. letter
|
||||
else -- case-insensitive
|
||||
return string.format("[%s%s]", letter:lower(), letter:upper())
|
||||
end
|
||||
end)
|
||||
end
|
||||
for i=1,#PATTERNS do
|
||||
-- find an optional '%' (group 1) followed by any character (group 2)
|
||||
PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
|
||||
if percent ~= "" or not letter:match("%a") then
|
||||
-- if the '%' matched, or `letter` is not a letter, return "as is"
|
||||
return percent .. letter
|
||||
else -- case-insensitive
|
||||
return string.format("[%s%s]", letter:lower(), letter:upper())
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function getAllFiles(dir, file_list)
|
||||
for node in fs.list(shell.resolve(dir)) do
|
||||
local rel_path = dir:gsub("/+$","") .. '/' .. node
|
||||
local resolved_path = shell.resolve(rel_path)
|
||||
if fs.isDirectory(resolved_path) then
|
||||
getAllFiles(rel_path, file_list)
|
||||
else
|
||||
file_list[#file_list+1] = rel_path
|
||||
end
|
||||
end
|
||||
for node in fs.list(shell.resolve(dir)) do
|
||||
local rel_path = dir:gsub("/+$","") .. '/' .. node
|
||||
local resolved_path = shell.resolve(rel_path)
|
||||
if fs.isDirectory(resolved_path) then
|
||||
getAllFiles(rel_path, file_list)
|
||||
else
|
||||
file_list[#file_list+1] = rel_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if search_recursively then
|
||||
local files = {}
|
||||
for _,arg in ipairs(FILES) do
|
||||
if fs.isDirectory(shell.resolve(arg)) then
|
||||
getAllFiles(arg, files)
|
||||
else
|
||||
files[#files+1]=arg
|
||||
end
|
||||
end
|
||||
FILES=files
|
||||
local files = {}
|
||||
for _,arg in ipairs(FILES) do
|
||||
if fs.isDirectory(shell.resolve(arg)) then
|
||||
getAllFiles(arg, files)
|
||||
else
|
||||
files[#files+1]=arg
|
||||
end
|
||||
end
|
||||
FILES=files
|
||||
end
|
||||
|
||||
-- Prepare an iterator for reading files
|
||||
local function readLines()
|
||||
local curHand = nil
|
||||
local curFile = nil
|
||||
local meta = nil
|
||||
return function()
|
||||
if not curFile then
|
||||
local file = table.remove(FILES, 1)
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
meta = {line_num=0,hits=0}
|
||||
if file == "-" then
|
||||
curFile = file
|
||||
meta.label = stdin_label
|
||||
curHand = io.input()
|
||||
else
|
||||
meta.label = file
|
||||
local file, reason = resolve(file)
|
||||
if fs.exists(file) then
|
||||
if fs.isDirectory(file) then
|
||||
local msg = string.format("%s: Is a directory", meta.label)
|
||||
stderr:write("grep: ",msg,"\n")
|
||||
return false, 2
|
||||
else
|
||||
curHand, reason = io.open(file, 'r')
|
||||
if not curHand then
|
||||
local msg = string.format("failed to read from %s: %s", meta.label, reason)
|
||||
stderr:write("grep: ",msg,"\n")
|
||||
return false, 2
|
||||
else
|
||||
curFile = meta.label
|
||||
end
|
||||
end
|
||||
else
|
||||
stderr:write("grep: ",file,": file not found\n")
|
||||
return false, 2
|
||||
end
|
||||
end
|
||||
end
|
||||
meta.line = nil
|
||||
if not meta.close and curHand then
|
||||
meta.line_num = meta.line_num + 1
|
||||
meta.line = curHand:read("*l")
|
||||
end
|
||||
if not meta.line then
|
||||
curFile = nil
|
||||
if curHand then
|
||||
curHand:close()
|
||||
end
|
||||
return false, meta
|
||||
else
|
||||
return meta, curFile
|
||||
end
|
||||
end
|
||||
local curHand = nil
|
||||
local curFile = nil
|
||||
local meta = nil
|
||||
return function()
|
||||
if not curFile then
|
||||
local file = table.remove(FILES, 1)
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
meta = {line_num=0,hits=0}
|
||||
if file == "-" then
|
||||
curFile = file
|
||||
meta.label = stdin_label
|
||||
curHand = io.input()
|
||||
else
|
||||
meta.label = file
|
||||
local file, reason = resolve(file)
|
||||
if fs.exists(file) then
|
||||
if fs.isDirectory(file) then
|
||||
local msg = string.format("%s: Is a directory", meta.label)
|
||||
stderr:write("grep: ",msg,"\n")
|
||||
return false, 2
|
||||
else
|
||||
curHand, reason = io.open(file, 'r')
|
||||
if not curHand then
|
||||
local msg = string.format("failed to read from %s: %s", meta.label, reason)
|
||||
stderr:write("grep: ",msg,"\n")
|
||||
return false, 2
|
||||
else
|
||||
curFile = meta.label
|
||||
end
|
||||
end
|
||||
else
|
||||
stderr:write("grep: ",file,": file not found\n")
|
||||
return false, 2
|
||||
end
|
||||
end
|
||||
end
|
||||
meta.line = nil
|
||||
if not meta.close and curHand then
|
||||
meta.line_num = meta.line_num + 1
|
||||
meta.line = curHand:read("*l")
|
||||
end
|
||||
if not meta.line then
|
||||
curFile = nil
|
||||
if curHand then
|
||||
curHand:close()
|
||||
end
|
||||
return false, meta
|
||||
else
|
||||
return meta, curFile
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function write(part, color)
|
||||
local prev_color = color and getc()
|
||||
if color then setc(color) end
|
||||
io.write(part)
|
||||
if color then setc(prev_color) end
|
||||
local prev_color = color and getc()
|
||||
if color then setc(color) end
|
||||
io.write(part)
|
||||
if color then setc(prev_color) end
|
||||
end
|
||||
local flush=(f_only or no_only or print_count) and function(m)
|
||||
if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write('\n')
|
||||
elseif print_count then
|
||||
if include_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
end
|
||||
write(m.hits)
|
||||
write('\n')
|
||||
end
|
||||
if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write('\n')
|
||||
elseif print_count then
|
||||
if include_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
end
|
||||
write(m.hits)
|
||||
write('\n')
|
||||
end
|
||||
end
|
||||
local ec = nil
|
||||
local any_hit_ec = 1
|
||||
local function test(m,p)
|
||||
local empty_line = true
|
||||
local last_index, slen = 1, #m.line
|
||||
local needs_filename, needs_line_num = include_filename, print_line_num
|
||||
local hit_value = 1
|
||||
while last_index <= slen and not m.close do
|
||||
local i, j = m.line:find(p, last_index, plain)
|
||||
local word_fail, line_fail =
|
||||
match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
|
||||
match_whole_line and not (i==1 and j==slen)
|
||||
local matched = not ((m_only or last_index==1) and not i)
|
||||
if (hit_value == 1 and word_fail) or line_fail then
|
||||
matched,i,j = false
|
||||
end
|
||||
if invert_match == matched then break end
|
||||
if max_matches == 0 then os.exit(1) end
|
||||
any_hit_ec = 0
|
||||
m.hits, hit_value = m.hits + hit_value, 0
|
||||
if f_only or no_only then
|
||||
m.close = true
|
||||
end
|
||||
if flush or quiet then return end
|
||||
if needs_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_filename = nil
|
||||
end
|
||||
if needs_line_num then
|
||||
write(m.line_num, LINE_NUM_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_line_num = nil
|
||||
end
|
||||
local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
|
||||
local g=i and m.line:sub(i,j) or ''
|
||||
if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
|
||||
if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
|
||||
write(s)
|
||||
write(g, MATCH_COLOR)
|
||||
empty_line = false
|
||||
last_index = (j or slen)+1
|
||||
if m_only or last_index>slen then
|
||||
write("\n")
|
||||
empty_line = true
|
||||
needs_filename, needs_line_num = include_filename, print_line_num
|
||||
elseif p:find("^^") and not plain then p="^$" end
|
||||
end
|
||||
if not empty_line then write("\n") end
|
||||
if max_matches ~= math.huge and max_matches >= m.hits then
|
||||
m.close = true
|
||||
end
|
||||
local empty_line = true
|
||||
local last_index, slen = 1, #m.line
|
||||
local needs_filename, needs_line_num = include_filename, print_line_num
|
||||
local hit_value = 1
|
||||
while last_index <= slen and not m.close do
|
||||
local i, j = m.line:find(p, last_index, plain)
|
||||
local word_fail, line_fail =
|
||||
match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
|
||||
match_whole_line and not (i==1 and j==slen)
|
||||
local matched = not ((m_only or last_index==1) and not i)
|
||||
if (hit_value == 1 and word_fail) or line_fail then
|
||||
matched,i,j = false
|
||||
end
|
||||
if invert_match == matched then break end
|
||||
if max_matches == 0 then os.exit(1) end
|
||||
any_hit_ec = 0
|
||||
m.hits, hit_value = m.hits + hit_value, 0
|
||||
if f_only or no_only then
|
||||
m.close = true
|
||||
end
|
||||
if flush or quiet then return end
|
||||
if needs_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_filename = nil
|
||||
end
|
||||
if needs_line_num then
|
||||
write(m.line_num, LINE_NUM_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_line_num = nil
|
||||
end
|
||||
local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
|
||||
local g=i and m.line:sub(i,j) or ''
|
||||
if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
|
||||
if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
|
||||
write(s)
|
||||
write(g, MATCH_COLOR)
|
||||
empty_line = false
|
||||
last_index = (j or slen)+1
|
||||
if m_only or last_index>slen then
|
||||
write("\n")
|
||||
empty_line = true
|
||||
needs_filename, needs_line_num = include_filename, print_line_num
|
||||
elseif p:find("^^") and not plain then p="^$" end
|
||||
end
|
||||
if not empty_line then write("\n") end
|
||||
if max_matches ~= math.huge and max_matches >= m.hits then
|
||||
m.close = true
|
||||
end
|
||||
end
|
||||
|
||||
local uptime = computer.uptime
|
||||
local last_sleep = uptime()
|
||||
for meta,status in readLines() do
|
||||
if uptime() - last_sleep > 1 then
|
||||
os.sleep(0)
|
||||
last_sleep = uptime()
|
||||
end
|
||||
if not meta then
|
||||
if type(status) == 'table' then if flush then
|
||||
flush(status) end -- this was the last object, closing out
|
||||
elseif status then
|
||||
ec = status or ec
|
||||
end
|
||||
else
|
||||
for _,p in ipairs(PATTERNS) do
|
||||
test(meta,p)
|
||||
end
|
||||
end
|
||||
if uptime() - last_sleep > 1 then
|
||||
os.sleep(0)
|
||||
last_sleep = uptime()
|
||||
end
|
||||
if not meta then
|
||||
if type(status) == 'table' then if flush then
|
||||
flush(status) end -- this was the last object, closing out
|
||||
elseif status then
|
||||
ec = status or ec
|
||||
end
|
||||
else
|
||||
for _,p in ipairs(PATTERNS) do
|
||||
test(meta,p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ec or any_hit_ec
|
||||
176
shellex/head.lua
176
shellex/head.lua
@@ -4,18 +4,18 @@ local args, options = shell.parse(...)
|
||||
local error_code = 0
|
||||
|
||||
local function pop(key, convert)
|
||||
local result = options[key]
|
||||
options[key] = nil
|
||||
if result and convert then
|
||||
local c = tonumber(result)
|
||||
if not c then
|
||||
io.stderr:write(string.format("use --%s=n where n is a number\n", key))
|
||||
options.help = true
|
||||
error_code = 1
|
||||
end
|
||||
result = c
|
||||
end
|
||||
return result
|
||||
local result = options[key]
|
||||
options[key] = nil
|
||||
if result and convert then
|
||||
local c = tonumber(result)
|
||||
if not c then
|
||||
io.stderr:write(string.format("use --%s=n where n is a number\n", key))
|
||||
options.help = true
|
||||
error_code = 1
|
||||
end
|
||||
result = c
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local bytes = pop('bytes', true)
|
||||
@@ -28,109 +28,109 @@ local help = pop('help')
|
||||
local invalid_key = next(options)
|
||||
|
||||
if bytes and lines then
|
||||
invalid_key = 'bytes and lines both specified'
|
||||
invalid_key = 'bytes and lines both specified'
|
||||
end
|
||||
|
||||
if help or next(options) then
|
||||
local invalid_key = next(options)
|
||||
if invalid_key then
|
||||
invalid_key = string.format('invalid option: %s\n', invalid_key)
|
||||
error_code = 1
|
||||
else
|
||||
invalid_key = ''
|
||||
end
|
||||
print(invalid_key .. [[Usage: head [--lines=n] file
|
||||
local invalid_key = next(options)
|
||||
if invalid_key then
|
||||
invalid_key = string.format('invalid option: %s\n', invalid_key)
|
||||
error_code = 1
|
||||
else
|
||||
invalid_key = ''
|
||||
end
|
||||
print(invalid_key .. [[Usage: head [--lines=n] file
|
||||
Print the first 10 lines of each FILE to stdout.
|
||||
For more info run: man head]])
|
||||
os.exit(error_code)
|
||||
os.exit(error_code)
|
||||
end
|
||||
|
||||
if #args == 0 then
|
||||
args = {'-'}
|
||||
args = {'-'}
|
||||
end
|
||||
|
||||
if quiet and verbose then
|
||||
quiet = false
|
||||
quiet = false
|
||||
end
|
||||
|
||||
local function new_stream()
|
||||
return
|
||||
{
|
||||
open=true,
|
||||
capacity=math.abs(lines or bytes or 10),
|
||||
bytes=bytes,
|
||||
buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '')
|
||||
}
|
||||
return
|
||||
{
|
||||
open=true,
|
||||
capacity=math.abs(lines or bytes or 10),
|
||||
bytes=bytes,
|
||||
buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '')
|
||||
}
|
||||
end
|
||||
|
||||
local function close(stream)
|
||||
if stream.buffer then
|
||||
if type(stream.buffer) == 'table' then
|
||||
stream.buffer = table.concat(stream.buffer)
|
||||
end
|
||||
io.stdout:write(stream.buffer)
|
||||
stream.buffer = nil
|
||||
end
|
||||
stream.open = false
|
||||
if stream.buffer then
|
||||
if type(stream.buffer) == 'table' then
|
||||
stream.buffer = table.concat(stream.buffer)
|
||||
end
|
||||
io.stdout:write(stream.buffer)
|
||||
stream.buffer = nil
|
||||
end
|
||||
stream.open = false
|
||||
end
|
||||
|
||||
local function push(stream, line)
|
||||
if not line then
|
||||
return close(stream)
|
||||
end
|
||||
if not line then
|
||||
return close(stream)
|
||||
end
|
||||
|
||||
local cost = stream.bytes and line:len() or 1
|
||||
stream.capacity = stream.capacity - cost
|
||||
local cost = stream.bytes and line:len() or 1
|
||||
stream.capacity = stream.capacity - cost
|
||||
|
||||
if not stream.buffer then
|
||||
if stream.bytes and stream.capacity < 0 then
|
||||
line = line:sub(1,stream.capacity-1)
|
||||
end
|
||||
io.write(line)
|
||||
if stream.capacity <= 0 then
|
||||
return close(stream)
|
||||
end
|
||||
else
|
||||
if type(stream.buffer) == 'table' then -- line storage
|
||||
stream.buffer[#stream.buffer+1] = line
|
||||
if stream.capacity < 0 then
|
||||
table.remove(stream.buffer, 1)
|
||||
stream.capacity = 0 -- zero out
|
||||
end
|
||||
else -- byte storage
|
||||
stream.buffer = stream.buffer .. line
|
||||
if stream.capacity < 0 then
|
||||
stream.buffer = stream.buffer:sub(-stream.capacity+1)
|
||||
stream.capacity = 0 -- zero out
|
||||
end
|
||||
end
|
||||
end
|
||||
if not stream.buffer then
|
||||
if stream.bytes and stream.capacity < 0 then
|
||||
line = line:sub(1,stream.capacity-1)
|
||||
end
|
||||
io.write(line)
|
||||
if stream.capacity <= 0 then
|
||||
return close(stream)
|
||||
end
|
||||
else
|
||||
if type(stream.buffer) == 'table' then -- line storage
|
||||
stream.buffer[#stream.buffer+1] = line
|
||||
if stream.capacity < 0 then
|
||||
table.remove(stream.buffer, 1)
|
||||
stream.capacity = 0 -- zero out
|
||||
end
|
||||
else -- byte storage
|
||||
stream.buffer = stream.buffer .. line
|
||||
if stream.capacity < 0 then
|
||||
stream.buffer = stream.buffer:sub(-stream.capacity+1)
|
||||
stream.capacity = 0 -- zero out
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for i=1,#args do
|
||||
local arg = args[i]
|
||||
local file
|
||||
if arg == '-' then
|
||||
arg = 'standard input'
|
||||
file = io.stdin
|
||||
else
|
||||
file, reason = io.open(arg, 'r')
|
||||
if not file then
|
||||
io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason))
|
||||
end
|
||||
end
|
||||
if file then
|
||||
if verbose or #args > 1 then
|
||||
io.write(string.format('==> %s <==\n', arg))
|
||||
end
|
||||
local arg = args[i]
|
||||
local file
|
||||
if arg == '-' then
|
||||
arg = 'standard input'
|
||||
file = io.stdin
|
||||
else
|
||||
file, reason = io.open(arg, 'r')
|
||||
if not file then
|
||||
io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason))
|
||||
end
|
||||
end
|
||||
if file then
|
||||
if verbose or #args > 1 then
|
||||
io.write(string.format('==> %s <==\n', arg))
|
||||
end
|
||||
|
||||
local stream = new_stream()
|
||||
local stream = new_stream()
|
||||
|
||||
while stream.open do
|
||||
push(stream, file:read('*L'))
|
||||
end
|
||||
while stream.open do
|
||||
push(stream, file:read('*L'))
|
||||
end
|
||||
|
||||
file:close()
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,13 +5,13 @@ local args = shell.parse(...)
|
||||
local hostname = args[1]
|
||||
|
||||
if hostname then
|
||||
os.setComputerLabel(hostname)
|
||||
os.setComputerLabel(hostname)
|
||||
else
|
||||
hostname = os.getComputerLabel()
|
||||
if hostname then
|
||||
print(hostname)
|
||||
else
|
||||
io.stderr:write("Hostname not set\n")
|
||||
return 1
|
||||
end
|
||||
hostname = os.getComputerLabel()
|
||||
if hostname then
|
||||
print(hostname)
|
||||
else
|
||||
io.stderr:write("Hostname not set\n")
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
216
shellex/less.lua
216
shellex/less.lua
@@ -5,14 +5,14 @@ local term = require("shellex.term") -- using term for negative scroll feature
|
||||
|
||||
local args, ops = shell.parse(...)
|
||||
if #args > 1 then
|
||||
io.write("Usage: ", os.getenv("_"):match("/([^/]+)%.lua$"), " <filename>\n")
|
||||
io.write("- or no args reads stdin\n")
|
||||
return 1
|
||||
io.write("Usage: ", os.getenv("_"):match("/([^/]+)%.lua$"), " <filename>\n")
|
||||
io.write("- or no args reads stdin\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
local cat_cmd = table.concat({"cat", ...}, " ")
|
||||
if not io.output().tty then
|
||||
return os.execute(cat_cmd)
|
||||
return os.execute(cat_cmd)
|
||||
end
|
||||
|
||||
local preader = io.popen(cat_cmd)
|
||||
@@ -23,127 +23,127 @@ local end_of_buffer = false
|
||||
local width, height = term.getViewport()
|
||||
|
||||
local function split(full_line)
|
||||
local index = 1
|
||||
local parts = {}
|
||||
while true do
|
||||
local sub = full_line:sub(index, index + width*3)
|
||||
-- checking #sub < width first is faster, save a unicode call
|
||||
if #sub < width or unicode.wlen(sub) <= width then
|
||||
parts[#parts + 1] = sub
|
||||
break
|
||||
end
|
||||
parts[#parts + 1] = unicode.wtrunc(sub, width + 1)
|
||||
index = index + #parts[#parts]
|
||||
if index > #full_line then
|
||||
break
|
||||
end
|
||||
end
|
||||
return parts
|
||||
local index = 1
|
||||
local parts = {}
|
||||
while true do
|
||||
local sub = full_line:sub(index, index + width*3)
|
||||
-- checking #sub < width first is faster, save a unicode call
|
||||
if #sub < width or unicode.wlen(sub) <= width then
|
||||
parts[#parts + 1] = sub
|
||||
break
|
||||
end
|
||||
parts[#parts + 1] = unicode.wtrunc(sub, width + 1)
|
||||
index = index + #parts[#parts]
|
||||
if index > #full_line then
|
||||
break
|
||||
end
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
local function scan(num)
|
||||
local result = {}
|
||||
local line_count = 0
|
||||
for i=1, num do
|
||||
local lines = {}
|
||||
if scrollback and (bottom + i) <= #scrollback then
|
||||
lines = {scrollback[bottom + i]}
|
||||
else
|
||||
local full_line = preader:read()
|
||||
if not full_line then preader:close() break end
|
||||
-- with buffering, we can buffer ahead too, and read more smoothly
|
||||
local buffering = false
|
||||
for _,line in ipairs(split(full_line)) do
|
||||
if not buffering then
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
if scrollback then
|
||||
buffering = true
|
||||
scrollback[#scrollback + 1] = line
|
||||
end
|
||||
end
|
||||
end
|
||||
local result = {}
|
||||
local line_count = 0
|
||||
for i=1, num do
|
||||
local lines = {}
|
||||
if scrollback and (bottom + i) <= #scrollback then
|
||||
lines = {scrollback[bottom + i]}
|
||||
else
|
||||
local full_line = preader:read()
|
||||
if not full_line then preader:close() break end
|
||||
-- with buffering, we can buffer ahead too, and read more smoothly
|
||||
local buffering = false
|
||||
for _,line in ipairs(split(full_line)) do
|
||||
if not buffering then
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
if scrollback then
|
||||
buffering = true
|
||||
scrollback[#scrollback + 1] = line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,line in ipairs(lines) do
|
||||
result[#result + 1] = line
|
||||
line_count = line_count + 1
|
||||
if #result > height then
|
||||
table.remove(result, 1)
|
||||
end
|
||||
end
|
||||
for _,line in ipairs(lines) do
|
||||
result[#result + 1] = line
|
||||
line_count = line_count + 1
|
||||
if #result > height then
|
||||
table.remove(result, 1)
|
||||
end
|
||||
end
|
||||
|
||||
if line_count >= num then
|
||||
break
|
||||
end
|
||||
end
|
||||
return result, line_count
|
||||
if line_count >= num then
|
||||
break
|
||||
end
|
||||
end
|
||||
return result, line_count
|
||||
end
|
||||
|
||||
local function status()
|
||||
if end_of_buffer then
|
||||
if ops.noback then
|
||||
os.exit()
|
||||
end
|
||||
io.write("(END)")
|
||||
end
|
||||
io.write(":")
|
||||
if end_of_buffer then
|
||||
if ops.noback then
|
||||
os.exit()
|
||||
end
|
||||
io.write("(END)")
|
||||
end
|
||||
io.write(":")
|
||||
end
|
||||
|
||||
local function goback(n)
|
||||
if not scrollback then return end
|
||||
local current_top = bottom - height + 1
|
||||
n = math.min(current_top, n)
|
||||
if n < 1 then return end
|
||||
local top = current_top - n + 1
|
||||
term.scroll(-n)
|
||||
term.setCursor(1, 1)
|
||||
for i=1, n do
|
||||
if i >= height then
|
||||
break
|
||||
end
|
||||
print(scrollback[top + i - 1])
|
||||
end
|
||||
term.setCursor(1, height)
|
||||
bottom = bottom - n
|
||||
end_of_buffer = false
|
||||
if not scrollback then return end
|
||||
local current_top = bottom - height + 1
|
||||
n = math.min(current_top, n)
|
||||
if n < 1 then return end
|
||||
local top = current_top - n + 1
|
||||
term.scroll(-n)
|
||||
term.setCursor(1, 1)
|
||||
for i=1, n do
|
||||
if i >= height then
|
||||
break
|
||||
end
|
||||
print(scrollback[top + i - 1])
|
||||
end
|
||||
term.setCursor(1, height)
|
||||
bottom = bottom - n
|
||||
end_of_buffer = false
|
||||
end
|
||||
|
||||
local function goforward(n)
|
||||
term.clearLine()
|
||||
local update, line_count = scan(n)
|
||||
for _,line in ipairs(update) do
|
||||
print(line)
|
||||
end
|
||||
if line_count < n then
|
||||
end_of_buffer = true
|
||||
end
|
||||
bottom = bottom + line_count
|
||||
term.clearLine()
|
||||
local update, line_count = scan(n)
|
||||
for _,line in ipairs(update) do
|
||||
print(line)
|
||||
end
|
||||
if line_count < n then
|
||||
end_of_buffer = true
|
||||
end
|
||||
bottom = bottom + line_count
|
||||
end
|
||||
|
||||
goforward(height - 1)
|
||||
|
||||
while true do
|
||||
term.clearLine()
|
||||
status()
|
||||
local e, _, _, code = term.pull()
|
||||
if e == "interrupted" then
|
||||
break
|
||||
elseif e == "key_down" then
|
||||
if code == keys.q then
|
||||
term.clearLine()
|
||||
os.exit() -- abort
|
||||
elseif code == keys["end"] then
|
||||
goforward(math.huge)
|
||||
elseif code == keys.space or code == keys.pageDown then
|
||||
goforward(height - 1)
|
||||
elseif code == keys.enter or code == keys.down then
|
||||
goforward(1)
|
||||
elseif code == keys.up then
|
||||
goback(1)
|
||||
elseif code == keys.pageUp then
|
||||
goback(height - 1)
|
||||
elseif code == keys.home then
|
||||
goback(math.huge)
|
||||
end
|
||||
end
|
||||
term.clearLine()
|
||||
status()
|
||||
local e, _, _, code = term.pull()
|
||||
if e == "interrupted" then
|
||||
break
|
||||
elseif e == "key_down" then
|
||||
if code == keys.q then
|
||||
term.clearLine()
|
||||
os.exit() -- abort
|
||||
elseif code == keys["end"] then
|
||||
goforward(math.huge)
|
||||
elseif code == keys.space or code == keys.pageDown then
|
||||
goforward(height - 1)
|
||||
elseif code == keys.enter or code == keys.down then
|
||||
goforward(1)
|
||||
elseif code == keys.up then
|
||||
goback(1)
|
||||
elseif code == keys.pageUp then
|
||||
goback(height - 1)
|
||||
elseif code == keys.home then
|
||||
goback(math.huge)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,8 @@ local shell = require("shellex.shell")
|
||||
|
||||
local args = shell.parse(...)
|
||||
if #args == 0 then
|
||||
io.write("Usage: ln <target> [<name>]\n")
|
||||
return 1
|
||||
io.write("Usage: ln <target> [<name>]\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
local target_name = args[1]
|
||||
@@ -12,23 +12,23 @@ local target = shell.resolve(target_name)
|
||||
|
||||
-- don't link from target if it doesn't exist, unless it is a broken link
|
||||
if not fs.exists(target) and not fs.isLink(target) then
|
||||
io.stderr:write("ln: failed to access '" .. target_name .. "': No such file or directory\n")
|
||||
return 1
|
||||
io.stderr:write("ln: failed to access '" .. target_name .. "': No such file or directory\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
local linkpath
|
||||
if #args > 1 then
|
||||
linkpath = shell.resolve(args[2])
|
||||
linkpath = shell.resolve(args[2])
|
||||
else
|
||||
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
|
||||
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
|
||||
end
|
||||
|
||||
if fs.isDirectory(linkpath) then
|
||||
linkpath = fs.concat(linkpath, fs.name(target))
|
||||
linkpath = fs.concat(linkpath, fs.name(target))
|
||||
end
|
||||
|
||||
local result, reason = fs.link(target, linkpath)
|
||||
if not result then
|
||||
io.stderr:write(reason..'\n')
|
||||
return 1
|
||||
io.stderr:write(reason..'\n')
|
||||
return 1
|
||||
end
|
||||
|
||||
624
shellex/ls.lua
624
shellex/ls.lua
@@ -9,383 +9,383 @@ local text = require("shellex.text")
|
||||
local dirsArg, ops = shell.parse(...)
|
||||
|
||||
if ops.help then
|
||||
print([[Usage: ls [OPTION]... [FILE]...
|
||||
-a, --all do not ignore entries starting with .
|
||||
--full-time with -l, print time in full iso format
|
||||
-h, --human-readable with -l and/or -s, print human readable sizes
|
||||
--si likewise, but use powers of 1000 not 1024
|
||||
-l use a long listing format
|
||||
-r, --reverse reverse order while sorting
|
||||
-R, --recursive list subdirectories recursively
|
||||
-S sort by file size
|
||||
-X sort alphabetically by entry extension
|
||||
-1 list one file per line
|
||||
-p append / indicator to directories
|
||||
-M display Microsoft-style file and directory
|
||||
count after listing
|
||||
--no-color Do not colorize the output (default colorized)
|
||||
--help display this help and exit
|
||||
print([[Usage: ls [OPTION]... [FILE]...
|
||||
-a, --all do not ignore entries starting with .
|
||||
--full-time with -l, print time in full iso format
|
||||
-h, --human-readable with -l and/or -s, print human readable sizes
|
||||
--si likewise, but use powers of 1000 not 1024
|
||||
-l use a long listing format
|
||||
-r, --reverse reverse order while sorting
|
||||
-R, --recursive list subdirectories recursively
|
||||
-S sort by file size
|
||||
-X sort alphabetically by entry extension
|
||||
-1 list one file per line
|
||||
-p append / indicator to directories
|
||||
-M display Microsoft-style file and directory
|
||||
count after listing
|
||||
--no-color Do not colorize the output (default colorized)
|
||||
--help display this help and exit
|
||||
]])
|
||||
return 0
|
||||
return 0
|
||||
end
|
||||
|
||||
if #dirsArg == 0 then
|
||||
table.insert(dirsArg, ".")
|
||||
table.insert(dirsArg, ".")
|
||||
end
|
||||
|
||||
local ec = 0
|
||||
local fOut = true -- tty.isAvailable() and io.output().tty
|
||||
local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
|
||||
local function stat(names, index)
|
||||
local name = names[index]
|
||||
if type(name) == "table" then
|
||||
return name
|
||||
end
|
||||
local info = {}
|
||||
info.key = name
|
||||
info.path = name:sub(1, 1) == "/" and "" or names.path
|
||||
info.full_path = fs.concat(info.path, name)
|
||||
info.isDir = fs.isDirectory(info.full_path)
|
||||
info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "")
|
||||
info.sort_name = info.name:gsub("^%.","")
|
||||
info.isLink, info.link = fs.isLink(info.full_path)
|
||||
info.size = info.isLink and 0 or fs.size(info.full_path)
|
||||
info.ext = info.name:match("(%.[^.]+)$") or ""
|
||||
names[index] = info
|
||||
return info
|
||||
local name = names[index]
|
||||
if type(name) == "table" then
|
||||
return name
|
||||
end
|
||||
local info = {}
|
||||
info.key = name
|
||||
info.path = name:sub(1, 1) == "/" and "" or names.path
|
||||
info.full_path = fs.concat(info.path, name)
|
||||
info.isDir = fs.isDirectory(info.full_path)
|
||||
info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "")
|
||||
info.sort_name = info.name:gsub("^%.","")
|
||||
info.isLink, info.link = fs.isLink(info.full_path)
|
||||
info.size = info.isLink and 0 or fs.size(info.full_path)
|
||||
info.ext = info.name:match("(%.[^.]+)$") or ""
|
||||
names[index] = info
|
||||
return info
|
||||
end
|
||||
local function toArray(i) local r={} for n in i do r[#r+1]=n end return r end
|
||||
local set_color = function() end
|
||||
local function colorize() return end
|
||||
if fOut and not ops["no-color"] then
|
||||
local LSC = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
|
||||
local parts = text.split(e, {"="}, true)
|
||||
return parts[2], parts[1]
|
||||
end)
|
||||
colorize = function(info)
|
||||
return
|
||||
info.isLink and LSC.ln or
|
||||
info.isDir and LSC.di or
|
||||
LSC['*'..info.ext] or
|
||||
LSC.fi
|
||||
end
|
||||
set_color=function(c)
|
||||
local cmap = {
|
||||
[ '30' ] = _G.colors.black,
|
||||
[ '31' ] = _G.colors.red,
|
||||
[ '32' ] = _G.colors.green,
|
||||
[ '33' ] = _G.colors.yellow,
|
||||
[ '34' ] = _G.colors.blue,
|
||||
[ '35' ] = _G.colors.magenta,
|
||||
[ '36' ] = _G.colors.cyan,
|
||||
[ '37' ] = _G.colors.white,
|
||||
}
|
||||
local LSC = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
|
||||
local parts = text.split(e, {"="}, true)
|
||||
return parts[2], parts[1]
|
||||
end)
|
||||
colorize = function(info)
|
||||
return
|
||||
info.isLink and LSC.ln or
|
||||
info.isDir and LSC.di or
|
||||
LSC['*'..info.ext] or
|
||||
LSC.fi
|
||||
end
|
||||
set_color=function(c)
|
||||
local cmap = {
|
||||
[ '30' ] = _G.colors.black,
|
||||
[ '31' ] = _G.colors.red,
|
||||
[ '32' ] = _G.colors.green,
|
||||
[ '33' ] = _G.colors.yellow,
|
||||
[ '34' ] = _G.colors.blue,
|
||||
[ '35' ] = _G.colors.magenta,
|
||||
[ '36' ] = _G.colors.cyan,
|
||||
[ '37' ] = _G.colors.white,
|
||||
}
|
||||
|
||||
if c then
|
||||
c:gsub('(%d+)', function(code)
|
||||
if cmap[code] then
|
||||
term.setTextColor(cmap[code])
|
||||
elseif cmap[code] == '0' then
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
end)
|
||||
else
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
if c then
|
||||
c:gsub('(%d+)', function(code)
|
||||
if cmap[code] then
|
||||
term.setTextColor(cmap[code])
|
||||
elseif cmap[code] == '0' then
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
end)
|
||||
else
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
-- io.write(string.char(0x1b), "[", c or "", "m")
|
||||
end
|
||||
end
|
||||
end
|
||||
local msft={reports=0,proxies={}}
|
||||
function msft.report(files, dirs, used, proxy)
|
||||
local free = proxy.spaceTotal() - proxy.spaceUsed()
|
||||
set_color()
|
||||
local pattern = "%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n"
|
||||
io.write(string.format(pattern, files, tostring(used), dirs, tostring(free)))
|
||||
local free = proxy.spaceTotal() - proxy.spaceUsed()
|
||||
set_color()
|
||||
local pattern = "%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n"
|
||||
io.write(string.format(pattern, files, tostring(used), dirs, tostring(free)))
|
||||
end
|
||||
function msft.tail(names)
|
||||
local fsproxy = fs.get(names.path)
|
||||
if not fsproxy then
|
||||
return
|
||||
end
|
||||
local totalSize, totalFiles, totalDirs = 0, 0, 0
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.isDir then
|
||||
totalDirs = totalDirs + 1
|
||||
else
|
||||
totalFiles = totalFiles + 1
|
||||
end
|
||||
totalSize = totalSize + info.size
|
||||
end
|
||||
msft.report(totalFiles, totalDirs, totalSize, fsproxy)
|
||||
local ps = msft.proxies
|
||||
ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0}
|
||||
local p = ps[fsproxy]
|
||||
p.files = p.files + totalFiles
|
||||
p.dirs = p.dirs + totalDirs
|
||||
p.used = p.used + totalSize
|
||||
msft.reports = msft.reports + 1
|
||||
local fsproxy = fs.get(names.path)
|
||||
if not fsproxy then
|
||||
return
|
||||
end
|
||||
local totalSize, totalFiles, totalDirs = 0, 0, 0
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.isDir then
|
||||
totalDirs = totalDirs + 1
|
||||
else
|
||||
totalFiles = totalFiles + 1
|
||||
end
|
||||
totalSize = totalSize + info.size
|
||||
end
|
||||
msft.report(totalFiles, totalDirs, totalSize, fsproxy)
|
||||
local ps = msft.proxies
|
||||
ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0}
|
||||
local p = ps[fsproxy]
|
||||
p.files = p.files + totalFiles
|
||||
p.dirs = p.dirs + totalDirs
|
||||
p.used = p.used + totalSize
|
||||
msft.reports = msft.reports + 1
|
||||
end
|
||||
function msft.final()
|
||||
if msft.reports < 2 then return end
|
||||
local groups = {}
|
||||
for proxy,report in pairs(msft.proxies) do
|
||||
table.insert(groups, {proxy=proxy,report=report})
|
||||
end
|
||||
set_color()
|
||||
print("Total Files Listed:")
|
||||
for _,pair in ipairs(groups) do
|
||||
local proxy, report = pair.proxy, pair.report
|
||||
if #groups>1 then
|
||||
print("As pertaining to: "..proxy.address)
|
||||
end
|
||||
msft.report(report.files, report.dirs, report.used, proxy)
|
||||
end
|
||||
if msft.reports < 2 then return end
|
||||
local groups = {}
|
||||
for proxy,report in pairs(msft.proxies) do
|
||||
table.insert(groups, {proxy=proxy,report=report})
|
||||
end
|
||||
set_color()
|
||||
print("Total Files Listed:")
|
||||
for _,pair in ipairs(groups) do
|
||||
local proxy, report = pair.proxy, pair.report
|
||||
if #groups>1 then
|
||||
print("As pertaining to: "..proxy.address)
|
||||
end
|
||||
msft.report(report.files, report.dirs, report.used, proxy)
|
||||
end
|
||||
end
|
||||
|
||||
if not ops.M then
|
||||
msft.tail=function()end
|
||||
msft.final=function()end
|
||||
msft.tail=function()end
|
||||
msft.final=function()end
|
||||
end
|
||||
|
||||
local function nod(n)
|
||||
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
|
||||
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
|
||||
end
|
||||
|
||||
local function formatSize(size)
|
||||
if not ops.h and not ops['human-readable'] and not ops.si then
|
||||
return tostring(size)
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = ops.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
return nod(math.floor(size*10)/10)..sizes[unit]
|
||||
if not ops.h and not ops['human-readable'] and not ops.si then
|
||||
return tostring(size)
|
||||
end
|
||||
local sizes = {"", "K", "M", "G"}
|
||||
local unit = 1
|
||||
local power = ops.si and 1000 or 1024
|
||||
while size > power and unit < #sizes do
|
||||
unit = unit + 1
|
||||
size = size / power
|
||||
end
|
||||
return nod(math.floor(size*10)/10)..sizes[unit]
|
||||
end
|
||||
|
||||
local function filter(names)
|
||||
if ops.a then
|
||||
return names
|
||||
end
|
||||
local set = {}
|
||||
for key, value in pairs(names) do
|
||||
if type(key) == "number" then
|
||||
local info = stat(names, key)
|
||||
if fs.name(info.name):sub(1, 1) ~= "." then
|
||||
table.insert(set, names[key])
|
||||
end
|
||||
else
|
||||
set[key] = value
|
||||
end
|
||||
end
|
||||
return set
|
||||
if ops.a then
|
||||
return names
|
||||
end
|
||||
local set = {}
|
||||
for key, value in pairs(names) do
|
||||
if type(key) == "number" then
|
||||
local info = stat(names, key)
|
||||
if fs.name(info.name):sub(1, 1) ~= "." then
|
||||
table.insert(set, names[key])
|
||||
end
|
||||
else
|
||||
set[key] = value
|
||||
end
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
local function sort(names)
|
||||
local once = false
|
||||
local function ni(v)
|
||||
local vname = type(v) == "string" and v or v.key
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.key == vname then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
local function sorter(key)
|
||||
once = true
|
||||
table.sort(names, function(a, b)
|
||||
local ast = stat(names, ni(a))
|
||||
local bst = stat(names, ni(b))
|
||||
return ast[key] > bst[key]
|
||||
end)
|
||||
end
|
||||
if ops.t then sorter("time") end
|
||||
if ops.X then sorter("ext") end
|
||||
if ops.S then sorter("size") end
|
||||
local rev = ops.r or ops.reverse
|
||||
if not once then sorter("sort_name") rev=not rev end
|
||||
if rev then
|
||||
for i=1,#names/2 do
|
||||
names[i], names[#names - i + 1] = names[#names - i + 1], names[i]
|
||||
end
|
||||
end
|
||||
return names
|
||||
local once = false
|
||||
local function ni(v)
|
||||
local vname = type(v) == "string" and v or v.key
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.key == vname then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
local function sorter(key)
|
||||
once = true
|
||||
table.sort(names, function(a, b)
|
||||
local ast = stat(names, ni(a))
|
||||
local bst = stat(names, ni(b))
|
||||
return ast[key] > bst[key]
|
||||
end)
|
||||
end
|
||||
if ops.t then sorter("time") end
|
||||
if ops.X then sorter("ext") end
|
||||
if ops.S then sorter("size") end
|
||||
local rev = ops.r or ops.reverse
|
||||
if not once then sorter("sort_name") rev=not rev end
|
||||
if rev then
|
||||
for i=1,#names/2 do
|
||||
names[i], names[#names - i + 1] = names[#names - i + 1], names[i]
|
||||
end
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local function dig(names, dirs, dir)
|
||||
if ops.R then
|
||||
local di = 1
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.isDir then
|
||||
local path = dir..(dir:sub(-1) == "/" and "" or "/")
|
||||
table.insert(dirs, di, path..info.name)
|
||||
di = di + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return names
|
||||
if ops.R then
|
||||
local di = 1
|
||||
for i=1,#names do
|
||||
local info = stat(names, i)
|
||||
if info.isDir then
|
||||
local path = dir..(dir:sub(-1) == "/" and "" or "/")
|
||||
table.insert(dirs, di, path..info.name)
|
||||
di = di + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local first_display = true
|
||||
local function display(names)
|
||||
local mt={}
|
||||
local lines = setmetatable({}, mt)
|
||||
local columns
|
||||
if ops.l then
|
||||
lines.n = #names
|
||||
local max_size_width = 1
|
||||
for i=1,lines.n do
|
||||
local info = stat(names, i)
|
||||
max_size_width = math.max(max_size_width, formatSize(info.size):len())
|
||||
end
|
||||
mt.__index = function(_, index)
|
||||
local info = stat(names, index)
|
||||
local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f'
|
||||
local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or ""
|
||||
pcall(function()
|
||||
info.fs = fs.get(info.full_path)
|
||||
end)
|
||||
local write_mode = info.fs and info.fs.isReadOnly() and '-' or 'w'
|
||||
local size = formatSize(info.size)
|
||||
local format = "%s-r%s %+"..tostring(max_size_width)..'s '
|
||||
local meta = string.format(format, file_type, write_mode, size)
|
||||
local item = info.name..link_target
|
||||
local mt={}
|
||||
local lines = setmetatable({}, mt)
|
||||
local columns
|
||||
if ops.l then
|
||||
lines.n = #names
|
||||
local max_size_width = 1
|
||||
for i=1,lines.n do
|
||||
local info = stat(names, i)
|
||||
max_size_width = math.max(max_size_width, formatSize(info.size):len())
|
||||
end
|
||||
mt.__index = function(_, index)
|
||||
local info = stat(names, index)
|
||||
local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f'
|
||||
local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or ""
|
||||
pcall(function()
|
||||
info.fs = fs.get(info.full_path)
|
||||
end)
|
||||
local write_mode = info.fs and info.fs.isReadOnly() and '-' or 'w'
|
||||
local size = formatSize(info.size)
|
||||
local format = "%s-r%s %+"..tostring(max_size_width)..'s '
|
||||
local meta = string.format(format, file_type, write_mode, size)
|
||||
local item = info.name..link_target
|
||||
|
||||
return {{name = meta}, {color = colorize(info), name = item}}
|
||||
end
|
||||
elseif ops["1"] or not fOut then
|
||||
lines.n = #names
|
||||
mt.__index = function(_, index)
|
||||
local info = stat(names, index)
|
||||
return {{color = colorize(info), name = info.name}}
|
||||
end
|
||||
else -- columns
|
||||
local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1
|
||||
local function real(x, y)
|
||||
local index = y + ((x-1) * items_per_column)
|
||||
return index <= #names and index or nil
|
||||
end
|
||||
local function max_name(column_index)
|
||||
local max = 0 -- return the width of the max element in column_index
|
||||
for r=1,items_per_column do
|
||||
local ri = real(column_index, r)
|
||||
if not ri then break end
|
||||
local info = stat(names, ri)
|
||||
max = math.max(max, unicode.wlen(info.name))
|
||||
end
|
||||
return max
|
||||
end
|
||||
local function measure()
|
||||
local total = 0
|
||||
for column_index=1,num_columns do
|
||||
total = total + max_name(column_index) + (column_index > 1 and 2 or 0)
|
||||
end
|
||||
return total
|
||||
end
|
||||
while items_per_column<#names do
|
||||
items_per_column = items_per_column + 1
|
||||
num_columns = math.ceil(#names/items_per_column)
|
||||
if measure() < width then
|
||||
break
|
||||
end
|
||||
end
|
||||
lines.n = items_per_column
|
||||
columns = num_columns
|
||||
mt.__index=function(_, line_index)
|
||||
return setmetatable({},{
|
||||
__len=function()return num_columns end, -- no can do in 5.1
|
||||
__index=function(_, column_index)
|
||||
local ri = real(column_index, line_index)
|
||||
if not ri then return end
|
||||
local info = stat(names, ri)
|
||||
local name = info.name
|
||||
return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))}
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
return {{name = meta}, {color = colorize(info), name = item}}
|
||||
end
|
||||
elseif ops["1"] or not fOut then
|
||||
lines.n = #names
|
||||
mt.__index = function(_, index)
|
||||
local info = stat(names, index)
|
||||
return {{color = colorize(info), name = info.name}}
|
||||
end
|
||||
else -- columns
|
||||
local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1
|
||||
local function real(x, y)
|
||||
local index = y + ((x-1) * items_per_column)
|
||||
return index <= #names and index or nil
|
||||
end
|
||||
local function max_name(column_index)
|
||||
local max = 0 -- return the width of the max element in column_index
|
||||
for r=1,items_per_column do
|
||||
local ri = real(column_index, r)
|
||||
if not ri then break end
|
||||
local info = stat(names, ri)
|
||||
max = math.max(max, unicode.wlen(info.name))
|
||||
end
|
||||
return max
|
||||
end
|
||||
local function measure()
|
||||
local total = 0
|
||||
for column_index=1,num_columns do
|
||||
total = total + max_name(column_index) + (column_index > 1 and 2 or 0)
|
||||
end
|
||||
return total
|
||||
end
|
||||
while items_per_column<#names do
|
||||
items_per_column = items_per_column + 1
|
||||
num_columns = math.ceil(#names/items_per_column)
|
||||
if measure() < width then
|
||||
break
|
||||
end
|
||||
end
|
||||
lines.n = items_per_column
|
||||
columns = num_columns
|
||||
mt.__index=function(_, line_index)
|
||||
return setmetatable({},{
|
||||
__len=function()return num_columns end, -- no can do in 5.1
|
||||
__index=function(_, column_index)
|
||||
local ri = real(column_index, line_index)
|
||||
if not ri then return end
|
||||
local info = stat(names, ri)
|
||||
local name = info.name
|
||||
return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))}
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
for line_index=1,lines.n do
|
||||
local line = lines[line_index]
|
||||
for element_index=1,columns or #line do
|
||||
local e = line[element_index]
|
||||
if not e then break end
|
||||
first_display = false
|
||||
set_color(e.color)
|
||||
io.write(e.name)
|
||||
end
|
||||
print()
|
||||
end
|
||||
msft.tail(names)
|
||||
for line_index=1,lines.n do
|
||||
local line = lines[line_index]
|
||||
for element_index=1,columns or #line do
|
||||
local e = line[element_index]
|
||||
if not e then break end
|
||||
first_display = false
|
||||
set_color(e.color)
|
||||
io.write(e.name)
|
||||
end
|
||||
print()
|
||||
end
|
||||
msft.tail(names)
|
||||
end
|
||||
local header = function() end
|
||||
if #dirsArg > 1 or ops.R then
|
||||
header = function(path)
|
||||
if not first_display then print() end
|
||||
set_color()
|
||||
io.write(path,":\n")
|
||||
end
|
||||
header = function(path)
|
||||
if not first_display then print() end
|
||||
set_color()
|
||||
io.write(path,":\n")
|
||||
end
|
||||
end
|
||||
local function displayDirList(dirs)
|
||||
while #dirs > 0 do
|
||||
local dir = table.remove(dirs, 1)
|
||||
header(dir)
|
||||
local path = shell.resolve(dir)
|
||||
local list, reason = fs.list(path)
|
||||
if not list then
|
||||
perr(reason)
|
||||
else
|
||||
local names = toArray(list)
|
||||
names.path = path
|
||||
display(dig(sort(filter(names)), dirs, dir))
|
||||
end
|
||||
end
|
||||
while #dirs > 0 do
|
||||
local dir = table.remove(dirs, 1)
|
||||
header(dir)
|
||||
local path = shell.resolve(dir)
|
||||
local list, reason = fs.list(path)
|
||||
if not list then
|
||||
perr(reason)
|
||||
else
|
||||
local names = toArray(list)
|
||||
names.path = path
|
||||
display(dig(sort(filter(names)), dirs, dir))
|
||||
end
|
||||
end
|
||||
end
|
||||
--[[
|
||||
local dir_set, file_set = {}, {path=shell.getWorkingDirectory()}
|
||||
for _,dir in ipairs(dirsArg) do
|
||||
local path = shell.resolve(dir)
|
||||
local real, why = fs.realPath(path)
|
||||
local access_msg = "cannot access " .. tostring(path) .. ": "
|
||||
if not real then
|
||||
perr(access_msg .. why)
|
||||
elseif not fs.exists(path) then
|
||||
perr(access_msg .. "No such file or directory")
|
||||
elseif fs.isDirectory(path) then
|
||||
table.insert(dir_set, dir)
|
||||
else -- file or link
|
||||
table.insert(file_set, dir)
|
||||
end
|
||||
local path = shell.resolve(dir)
|
||||
local real, why = fs.realPath(path)
|
||||
local access_msg = "cannot access " .. tostring(path) .. ": "
|
||||
if not real then
|
||||
perr(access_msg .. why)
|
||||
elseif not fs.exists(path) then
|
||||
perr(access_msg .. "No such file or directory")
|
||||
elseif fs.isDirectory(path) then
|
||||
table.insert(dir_set, dir)
|
||||
else -- file or link
|
||||
table.insert(file_set, dir)
|
||||
end
|
||||
end
|
||||
]]
|
||||
local dir_set, file_set = {}, {path=shell.getWorkingDirectory()}
|
||||
for _,dir in ipairs(dirsArg) do
|
||||
local path = shell.resolve(dir)
|
||||
if fs.isDirectory(path) then
|
||||
table.insert(dir_set, dir)
|
||||
else
|
||||
local files = glob.matches(shell.getWorkingDirectory(), dir)
|
||||
for _, v in pairs(files) do
|
||||
table.insert(file_set, v)
|
||||
end
|
||||
if #files == 0 then
|
||||
local access_msg = "cannot access " .. tostring(dir) .. ": "
|
||||
perr(access_msg .. "No such file or directory")
|
||||
end
|
||||
end
|
||||
local path = shell.resolve(dir)
|
||||
if fs.isDirectory(path) then
|
||||
table.insert(dir_set, dir)
|
||||
else
|
||||
local files = glob.matches(shell.getWorkingDirectory(), dir)
|
||||
for _, v in pairs(files) do
|
||||
table.insert(file_set, v)
|
||||
end
|
||||
if #files == 0 then
|
||||
local access_msg = "cannot access " .. tostring(dir) .. ": "
|
||||
perr(access_msg .. "No such file or directory")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
io.output():setvbuf("line")
|
||||
|
||||
local ok, msg = pcall(function()
|
||||
if #file_set > 0 then display(sort(file_set)) end
|
||||
displayDirList(dir_set)
|
||||
msft.final()
|
||||
if #file_set > 0 then display(sort(file_set)) end
|
||||
displayDirList(dir_set)
|
||||
msft.final()
|
||||
end)
|
||||
|
||||
io.output():flush()
|
||||
@@ -393,7 +393,7 @@ io.output():setvbuf("no")
|
||||
set_color()
|
||||
|
||||
if not ok then
|
||||
error(msg, 2)
|
||||
error(msg, 2)
|
||||
end
|
||||
assert(ok, msg)
|
||||
|
||||
|
||||
@@ -4,30 +4,30 @@ local transfer = require("shellex.transfer")
|
||||
local args, options = shell.parse(...)
|
||||
options.h = options.h or options.help
|
||||
if #args < 2 or options.h then
|
||||
io.write([[Usage: mv [OPTIONS] <from> <to>
|
||||
-f overwrite without prompt
|
||||
-i prompt before overwriting
|
||||
unless -f
|
||||
-v verbose
|
||||
-n do not overwrite an existing file
|
||||
--skip=P ignore paths matching lua regex P
|
||||
-h, --help show this help
|
||||
io.write([[Usage: mv [OPTIONS] <from> <to>
|
||||
-f overwrite without prompt
|
||||
-i prompt before overwriting
|
||||
unless -f
|
||||
-v verbose
|
||||
-n do not overwrite an existing file
|
||||
--skip=P ignore paths matching lua regex P
|
||||
-h, --help show this help
|
||||
]])
|
||||
return not not options.h
|
||||
return not not options.h
|
||||
end
|
||||
|
||||
-- clean options for move (as opposed to copy)
|
||||
options =
|
||||
{
|
||||
cmd = "mv",
|
||||
f = options.f,
|
||||
i = options.i,
|
||||
v = options.v,
|
||||
n = options.n, -- no clobber
|
||||
skip = options.skip,
|
||||
P = true, -- move operations always preserve
|
||||
r = true, -- move is allowed to move entire dirs
|
||||
x = true, -- cannot move mount points
|
||||
cmd = "mv",
|
||||
f = options.f,
|
||||
i = options.i,
|
||||
v = options.v,
|
||||
n = options.n, -- no clobber
|
||||
skip = options.skip,
|
||||
P = true, -- move operations always preserve
|
||||
r = true, -- move is allowed to move entire dirs
|
||||
x = true, -- cannot move mount points
|
||||
}
|
||||
|
||||
return transfer.batch(args, options)
|
||||
|
||||
176
shellex/rm.lua
176
shellex/rm.lua
@@ -3,20 +3,20 @@ local glob = require('shellex.glob')
|
||||
local shell = require("shellex.shell")
|
||||
|
||||
local function usage()
|
||||
print("Usage: rm [options] <filename1> [<filename2> [...]]"..[[
|
||||
print("Usage: rm [options] <filename1> [<filename2> [...]]"..[[
|
||||
|
||||
-f ignore nonexistent files and arguments, never prompt
|
||||
-r remove directories and their contents recursively
|
||||
-v explain what is being done
|
||||
--help display this help and exit
|
||||
-f ignore nonexistent files and arguments, never prompt
|
||||
-r remove directories and their contents recursively
|
||||
-v explain what is being done
|
||||
--help display this help and exit
|
||||
|
||||
For complete documentation and more options, run: man rm]])
|
||||
end
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
if #args == 0 or options.help then
|
||||
usage()
|
||||
return 1
|
||||
usage()
|
||||
return 1
|
||||
end
|
||||
|
||||
local bRec = options.r or options.R or options.recursive
|
||||
@@ -29,15 +29,15 @@ bVerbose = bVerbose and not bForce
|
||||
promptLevel = bForce and 0 or promptLevel
|
||||
|
||||
local function perr(...)
|
||||
if not bForce then
|
||||
io.stderr:write(...)
|
||||
end
|
||||
if not bForce then
|
||||
io.stderr:write(...)
|
||||
end
|
||||
end
|
||||
|
||||
local function pout(...)
|
||||
if not bForce then
|
||||
io.stdout:write(...)
|
||||
end
|
||||
if not bForce then
|
||||
io.stdout:write(...)
|
||||
end
|
||||
end
|
||||
|
||||
local metas = {}
|
||||
@@ -53,111 +53,111 @@ local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly
|
||||
local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end
|
||||
|
||||
local function createMeta(origin, rel)
|
||||
local m = {origin=origin,rel=rel:gsub("/+$", "")}
|
||||
if _dir(m) then
|
||||
m.rel = m.rel .. '/'
|
||||
end
|
||||
return m
|
||||
local m = {origin=origin,rel=rel:gsub("/+$", "")}
|
||||
if _dir(m) then
|
||||
m.rel = m.rel .. '/'
|
||||
end
|
||||
return m
|
||||
end
|
||||
|
||||
local function unlink(path)
|
||||
fs.remove(path)
|
||||
return true
|
||||
fs.remove(path)
|
||||
return true
|
||||
end
|
||||
|
||||
local function confirm()
|
||||
if bForce then
|
||||
return true
|
||||
end
|
||||
local r = io.read()
|
||||
return r == 'y' or r == 'yes'
|
||||
if bForce then
|
||||
return true
|
||||
end
|
||||
local r = io.read()
|
||||
return r == 'y' or r == 'yes'
|
||||
end
|
||||
|
||||
local function remove_all(parent)
|
||||
if parent == nil or not _dir(parent) or _empty(parent) then
|
||||
return true
|
||||
end
|
||||
if parent == nil or not _dir(parent) or _empty(parent) then
|
||||
return true
|
||||
end
|
||||
|
||||
local all_ok = true
|
||||
if bRec and promptLevel == 1 then
|
||||
pout(string.format("rm: descend into directory `%s'? ", parent.rel))
|
||||
if not confirm() then
|
||||
return false
|
||||
end
|
||||
local all_ok = true
|
||||
if bRec and promptLevel == 1 then
|
||||
pout(string.format("rm: descend into directory `%s'? ", parent.rel))
|
||||
if not confirm() then
|
||||
return false
|
||||
end
|
||||
|
||||
for file in fs.list(_path(parent)) do
|
||||
local child = createMeta(parent.origin, parent.rel .. file)
|
||||
all_ok = remove(child) and all_ok
|
||||
-- uh ?
|
||||
end
|
||||
end
|
||||
for file in fs.list(_path(parent)) do
|
||||
local child = createMeta(parent.origin, parent.rel .. file)
|
||||
all_ok = remove(child) and all_ok
|
||||
-- uh ?
|
||||
end
|
||||
end
|
||||
|
||||
return all_ok
|
||||
return all_ok
|
||||
end
|
||||
|
||||
remove = function(meta)
|
||||
if not remove_all(meta) then
|
||||
return false
|
||||
end
|
||||
if not remove_all(meta) then
|
||||
return false
|
||||
end
|
||||
|
||||
if not _exists(meta) then
|
||||
perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel))
|
||||
return false
|
||||
elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then
|
||||
if not bEmptyDirs then
|
||||
perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel))
|
||||
else
|
||||
perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel))
|
||||
end
|
||||
return false
|
||||
end
|
||||
if not _exists(meta) then
|
||||
perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel))
|
||||
return false
|
||||
elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then
|
||||
if not bEmptyDirs then
|
||||
perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel))
|
||||
else
|
||||
perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel))
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local ok = true
|
||||
if promptLevel == 1 then
|
||||
if _dir(meta) then
|
||||
pout(string.format("rm: remove directory `%s'? ", meta.rel))
|
||||
elseif meta.link then
|
||||
pout(string.format("rm: remove symbolic link `%s'? ", meta.rel))
|
||||
else -- file
|
||||
pout(string.format("rm: remove regular file `%s'? ", meta.rel))
|
||||
end
|
||||
local ok = true
|
||||
if promptLevel == 1 then
|
||||
if _dir(meta) then
|
||||
pout(string.format("rm: remove directory `%s'? ", meta.rel))
|
||||
elseif meta.link then
|
||||
pout(string.format("rm: remove symbolic link `%s'? ", meta.rel))
|
||||
else -- file
|
||||
pout(string.format("rm: remove regular file `%s'? ", meta.rel))
|
||||
end
|
||||
|
||||
ok = confirm()
|
||||
end
|
||||
ok = confirm()
|
||||
end
|
||||
|
||||
if ok then
|
||||
if _readonly(meta) then
|
||||
perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel))
|
||||
return false
|
||||
elseif not unlink(_path(meta)) then
|
||||
perr(meta.rel .. ": failed to be removed\n")
|
||||
ok = false
|
||||
elseif bVerbose then
|
||||
pout("removed '" .. meta.rel .. "'\n");
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
if _readonly(meta) then
|
||||
perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel))
|
||||
return false
|
||||
elseif not unlink(_path(meta)) then
|
||||
perr(meta.rel .. ": failed to be removed\n")
|
||||
ok = false
|
||||
elseif bVerbose then
|
||||
pout("removed '" .. meta.rel .. "'\n");
|
||||
end
|
||||
end
|
||||
|
||||
return ok
|
||||
return ok
|
||||
end
|
||||
|
||||
for _,arg in ipairs(args) do
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
metas[#metas+1] = createMeta(v, v)
|
||||
end
|
||||
local files = glob.matches(shell.getWorkingDirectory(), arg)
|
||||
for _, v in pairs(files) do
|
||||
metas[#metas+1] = createMeta(v, v)
|
||||
end
|
||||
end
|
||||
|
||||
if promptLevel == 3 and #metas > 3 then
|
||||
pout(string.format("rm: remove %i arguments? ", #metas))
|
||||
if not confirm() then
|
||||
return
|
||||
end
|
||||
pout(string.format("rm: remove %i arguments? ", #metas))
|
||||
if not confirm() then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local ok = true
|
||||
for _,meta in ipairs(metas) do
|
||||
local result = remove(meta)
|
||||
ok = ok and result
|
||||
local result = remove(meta)
|
||||
ok = ok and result
|
||||
end
|
||||
|
||||
return bForce or ok
|
||||
|
||||
@@ -5,26 +5,26 @@ local text = require("shellex.text")
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local function usage()
|
||||
print(
|
||||
print(
|
||||
[[Usage: rmdir [OPTION]... DIRECTORY...
|
||||
Removes the DIRECTORY(ies), if they are empty.
|
||||
|
||||
-q, --ignore-fail-on-non-empty
|
||||
ignore failures due solely to non-empty directories
|
||||
-p, --parents remove DIRECTORY and its empty ancestors
|
||||
e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'
|
||||
-v, --verbose output a diagnostic for every directory processed
|
||||
--help display this help and exit]])
|
||||
-q, --ignore-fail-on-non-empty
|
||||
ignore failures due solely to non-empty directories
|
||||
-p, --parents remove DIRECTORY and its empty ancestors
|
||||
e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'
|
||||
-v, --verbose output a diagnostic for every directory processed
|
||||
--help display this help and exit]])
|
||||
end
|
||||
|
||||
if options.help then
|
||||
usage()
|
||||
return 0
|
||||
usage()
|
||||
return 0
|
||||
end
|
||||
|
||||
if #args == 0 then
|
||||
io.stderr:write("rmdir: missing operand\n")
|
||||
return 1
|
||||
io.stderr:write("rmdir: missing operand\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
options.p = options.p or options.parents
|
||||
@@ -33,72 +33,72 @@ options.q = options.q or options['ignore-fail-on-non-empty']
|
||||
|
||||
local ec = 0
|
||||
local function ec_bump()
|
||||
ec = 1
|
||||
return 1
|
||||
ec = 1
|
||||
return 1
|
||||
end
|
||||
|
||||
local function remove(path, ...)
|
||||
-- check to end recursion
|
||||
if path == nil then
|
||||
return true
|
||||
end
|
||||
-- check to end recursion
|
||||
if path == nil then
|
||||
return true
|
||||
end
|
||||
|
||||
if options.v then
|
||||
print(string.format('rmdir: removing directory, %s', path))
|
||||
end
|
||||
if options.v then
|
||||
print(string.format('rmdir: removing directory, %s', path))
|
||||
end
|
||||
|
||||
local rpath = shell.resolve(path)
|
||||
if path == '.' then
|
||||
io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n')
|
||||
return ec_bump()
|
||||
elseif not fs.exists(rpath) then
|
||||
io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n")
|
||||
return ec_bump()
|
||||
elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then
|
||||
io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n")
|
||||
return ec_bump()
|
||||
else
|
||||
local list, reason = fs.list(rpath)
|
||||
local rpath = shell.resolve(path)
|
||||
if path == '.' then
|
||||
io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n')
|
||||
return ec_bump()
|
||||
elseif not fs.exists(rpath) then
|
||||
io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n")
|
||||
return ec_bump()
|
||||
elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then
|
||||
io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n")
|
||||
return ec_bump()
|
||||
else
|
||||
local list, reason = fs.list(rpath)
|
||||
|
||||
if not list then
|
||||
io.stderr:write(tostring(reason)..'\n')
|
||||
return ec_bump()
|
||||
else
|
||||
if list() then
|
||||
if not options.q then
|
||||
io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n")
|
||||
end
|
||||
return ec_bump()
|
||||
else
|
||||
-- path exists and is empty?
|
||||
local ok, reason = fs.remove(rpath)
|
||||
if not ok then
|
||||
io.stderr:write(tostring(reason)..'\n')
|
||||
return ec_bump(), reason
|
||||
end
|
||||
return remove(...) -- the final return of all else
|
||||
end
|
||||
end
|
||||
end
|
||||
if not list then
|
||||
io.stderr:write(tostring(reason)..'\n')
|
||||
return ec_bump()
|
||||
else
|
||||
if list() then
|
||||
if not options.q then
|
||||
io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n")
|
||||
end
|
||||
return ec_bump()
|
||||
else
|
||||
-- path exists and is empty?
|
||||
local ok, reason = fs.remove(rpath)
|
||||
if not ok then
|
||||
io.stderr:write(tostring(reason)..'\n')
|
||||
return ec_bump(), reason
|
||||
end
|
||||
return remove(...) -- the final return of all else
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,path in ipairs(args) do
|
||||
-- clean up the input
|
||||
path = path:gsub('/+', '/')
|
||||
-- clean up the input
|
||||
path = path:gsub('/+', '/')
|
||||
|
||||
local segments = {}
|
||||
if options.p and path:len() > 1 and path:find('/') then
|
||||
local chain = text.split(path, {'/'}, true)
|
||||
local prefix = ''
|
||||
for _,e in ipairs(chain) do
|
||||
table.insert(segments, 1, prefix .. e)
|
||||
prefix = prefix .. e .. '/'
|
||||
end
|
||||
else
|
||||
segments = {path}
|
||||
end
|
||||
local segments = {}
|
||||
if options.p and path:len() > 1 and path:find('/') then
|
||||
local chain = text.split(path, {'/'}, true)
|
||||
local prefix = ''
|
||||
for _,e in ipairs(chain) do
|
||||
table.insert(segments, 1, prefix .. e)
|
||||
prefix = prefix .. e .. '/'
|
||||
end
|
||||
else
|
||||
segments = {path}
|
||||
end
|
||||
|
||||
remove(table.unpack(segments))
|
||||
remove(table.unpack(segments))
|
||||
end
|
||||
|
||||
return ec
|
||||
|
||||
@@ -4,8 +4,8 @@ local sh = require('shellex.sh')
|
||||
local real_before, cpu_before = computer.uptime(), os.clock()
|
||||
local cmd_result = 0
|
||||
if ... then
|
||||
sh.execute(nil, ...)
|
||||
cmd_result = sh.getLastExitCode()
|
||||
sh.execute(nil, ...)
|
||||
cmd_result = sh.getLastExitCode()
|
||||
end
|
||||
local real_after, cpu_after = computer.uptime(), os.clock()
|
||||
|
||||
|
||||
@@ -5,50 +5,50 @@ local fs = require("shellex.filesystem")
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local function usage()
|
||||
print(
|
||||
print(
|
||||
[[Usage: touch [OPTION]... FILE...
|
||||
Update the modification times of each FILE to the current time.
|
||||
A FILE argument that does not exist is created empty, unless -c is supplied.
|
||||
|
||||
-c, --no-create do not create any files
|
||||
--help display this help and exit]])
|
||||
-c, --no-create do not create any files
|
||||
--help display this help and exit]])
|
||||
end
|
||||
|
||||
if options.help then
|
||||
usage()
|
||||
return 0
|
||||
usage()
|
||||
return 0
|
||||
elseif #args == 0 then
|
||||
io.stderr:write("touch: missing operand\n")
|
||||
return 1
|
||||
io.stderr:write("touch: missing operand\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
options.c = options.c or options["no-create"]
|
||||
local errors = 0
|
||||
|
||||
for _,arg in ipairs(args) do
|
||||
local path = shell.resolve(arg)
|
||||
local path = shell.resolve(arg)
|
||||
|
||||
if fs.isDirectory(path) then
|
||||
io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg))
|
||||
else
|
||||
local real, reason = fs.realPath(path)
|
||||
if real then
|
||||
local file
|
||||
if fs.exists(real) or not options.c then
|
||||
file = io.open(real, "a")
|
||||
end
|
||||
if not file then
|
||||
real = options.c
|
||||
reason = "permission denied"
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
if not real then
|
||||
io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason))
|
||||
errors = 1
|
||||
end
|
||||
end
|
||||
if fs.isDirectory(path) then
|
||||
io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg))
|
||||
else
|
||||
local real, reason = fs.realPath(path)
|
||||
if real then
|
||||
local file
|
||||
if fs.exists(real) or not options.c then
|
||||
file = io.open(real, "a")
|
||||
end
|
||||
if not file then
|
||||
real = options.c
|
||||
reason = "permission denied"
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
if not real then
|
||||
io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason))
|
||||
errors = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return errors
|
||||
|
||||
@@ -2,24 +2,24 @@ local shell = require("shellex.shell")
|
||||
|
||||
local args = shell.parse(...)
|
||||
if #args == 0 then
|
||||
io.write("Usage: which <program>\n")
|
||||
return 255
|
||||
io.write("Usage: which <program>\n")
|
||||
return 255
|
||||
end
|
||||
|
||||
for i = 1, #args do
|
||||
local result, reason = shell.resolveProgram(args[i])
|
||||
local result, reason = shell.resolveProgram(args[i])
|
||||
|
||||
if not result then
|
||||
result = shell.getAlias(args[i])
|
||||
if result then
|
||||
result = args[i] .. ": aliased to " .. result
|
||||
end
|
||||
end
|
||||
if not result then
|
||||
result = shell.getAlias(args[i])
|
||||
if result then
|
||||
result = args[i] .. ": aliased to " .. result
|
||||
end
|
||||
end
|
||||
|
||||
if result then
|
||||
print(result)
|
||||
else
|
||||
io.stderr:write(args[i] .. ": " .. reason .. "\n")
|
||||
return 1
|
||||
end
|
||||
if result then
|
||||
print(result)
|
||||
else
|
||||
io.stderr:write(args[i] .. ": " .. reason .. "\n")
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user