refactor parallel code
This commit is contained in:
374
openos/ls.lua
Normal file
374
openos/ls.lua
Normal file
@@ -0,0 +1,374 @@
|
||||
local fs = require("openos.filesystem")
|
||||
local shell = require("openos.shell")
|
||||
local tty = require("openos.tty")
|
||||
local unicode = require("openos.unicode")
|
||||
local tx = require("openos.transforms")
|
||||
local text = require("openos.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
|
||||
]])
|
||||
return 0
|
||||
end
|
||||
|
||||
if #dirsArg == 0 then
|
||||
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.fs = fs.get(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,
|
||||
}
|
||||
|
||||
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
|
||||
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)))
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
if not ops.M then
|
||||
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"
|
||||
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]
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
local first_display = true
|
||||
local function display(names)
|
||||
local mt={}
|
||||
local lines = setmetatable({}, mt)
|
||||
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 ""
|
||||
local write_mode = 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
|
||||
mt.__index=function(_, line_index)
|
||||
return setmetatable({},{
|
||||
__len=function()return num_columns end,
|
||||
__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,#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
|
||||
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
|
||||
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
|
||||
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()
|
||||
--end)
|
||||
|
||||
io.output():flush()
|
||||
io.output():setvbuf("no")
|
||||
set_color()
|
||||
|
||||
--assert(ok, msg)
|
||||
|
||||
return ec
|
||||
Reference in New Issue
Block a user