refactor parallel code
This commit is contained in:
@@ -233,6 +233,7 @@ local function run(member, point)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function drawContainer(pos)
|
local function drawContainer(pos)
|
||||||
|
if canvas3d then
|
||||||
canvas3d.clear()
|
canvas3d.clear()
|
||||||
|
|
||||||
local function addBox(b)
|
local function addBox(b)
|
||||||
@@ -255,6 +256,7 @@ local function drawContainer(pos)
|
|||||||
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
|
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
|
||||||
addBox(box)
|
addBox(box)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local pauseResume = {
|
local pauseResume = {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
local itemDB = require('core.itemDB')
|
local itemDB = require('core.itemDB')
|
||||||
|
local Tasks = require('milo.taskRunner')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local parallel = _G.parallel
|
|
||||||
local turtle = _G.turtle
|
local turtle = _G.turtle
|
||||||
|
|
||||||
local Craft = {
|
local Craft = {
|
||||||
@@ -37,12 +37,18 @@ end
|
|||||||
|
|
||||||
function Craft.clearGrid(storage)
|
function Craft.clearGrid(storage)
|
||||||
local success = true
|
local success = true
|
||||||
|
local tasks = Tasks()
|
||||||
|
|
||||||
for index, slot in pairs(storage.turtleInventory.adapter.list()) do
|
for index, slot in pairs(storage.turtleInventory.adapter.list()) do
|
||||||
|
tasks:add(function()
|
||||||
if storage:import(storage.turtleInventory, index, slot.count, slot) ~= slot.count then
|
if storage:import(storage.turtleInventory, index, slot.count, slot) ~= slot.count then
|
||||||
success = false
|
success = false
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tasks:run()
|
||||||
|
|
||||||
return success
|
return success
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,10 +139,11 @@ local function turtleCraft(recipe, storage, request, count)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local failed
|
local failed
|
||||||
local fns = { }
|
local tasks = Tasks()
|
||||||
|
|
||||||
for k,v in pairs(recipe.ingredients) do
|
for k,v in pairs(recipe.ingredients) do
|
||||||
local item = splitKey(v)
|
local item = splitKey(v)
|
||||||
table.insert(fns, function()
|
tasks:add(function()
|
||||||
if storage:export(storage.turtleInventory, k, count, item) ~= count then
|
if storage:export(storage.turtleInventory, k, count, item) ~= count then
|
||||||
request.status = 'rescan needed ?'
|
request.status = 'rescan needed ?'
|
||||||
request.statusCode = Craft.STATUS_ERROR
|
request.statusCode = Craft.STATUS_ERROR
|
||||||
@@ -146,7 +153,8 @@ local function turtleCraft(recipe, storage, request, count)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
parallel.waitForAll(table.unpack(fns))
|
tasks:run()
|
||||||
|
|
||||||
if failed then
|
if failed then
|
||||||
Craft.clearGrid(storage)
|
Craft.clearGrid(storage)
|
||||||
return
|
return
|
||||||
|
|||||||
35
milo/apis/taskRunner.lua
Normal file
35
milo/apis/taskRunner.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local class = require('class')
|
||||||
|
|
||||||
|
local parallel = _G.parallel
|
||||||
|
|
||||||
|
local TaskRunner = class()
|
||||||
|
|
||||||
|
function TaskRunner:init(args)
|
||||||
|
self.tasks = { }
|
||||||
|
self.errorMsg = 'Task failed: '
|
||||||
|
|
||||||
|
for k,v in pairs(args or { }) do
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TaskRunner:add(fn)
|
||||||
|
table.insert(self.tasks, function()
|
||||||
|
local s, m = pcall(fn)
|
||||||
|
if not s and m then
|
||||||
|
self:onError(m)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TaskRunner:run()
|
||||||
|
if #self.tasks > 0 then
|
||||||
|
parallel.waitForAll(table.unpack(self.tasks))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TaskRunner:onError(msg)
|
||||||
|
_G._debug(msg.errorMsg .. msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
return TaskRunner
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
local itemDB = require('core.itemDB')
|
local itemDB = require('core.itemDB')
|
||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
|
local Tasks = require('milo.taskRunner')
|
||||||
local parallel = _G.parallel
|
|
||||||
|
|
||||||
local ExportTask = {
|
local ExportTask = {
|
||||||
name = 'exporter',
|
name = 'exporter',
|
||||||
@@ -13,11 +12,12 @@ local function filter(a)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ExportTask:cycle(context)
|
function ExportTask:cycle(context)
|
||||||
local tasks = { }
|
local tasks = Tasks({
|
||||||
|
errorMsg = 'EXPORTER error: ',
|
||||||
|
})
|
||||||
|
|
||||||
for node in context.storage:filterActive('machine', filter) do
|
for node in context.storage:filterActive('machine', filter) do
|
||||||
table.insert(tasks, function()
|
tasks:add(function()
|
||||||
local s, m = pcall(function()
|
|
||||||
for _, entry in pairs(node.exports) do
|
for _, entry in pairs(node.exports) do
|
||||||
|
|
||||||
if not entry.filter then
|
if not entry.filter then
|
||||||
@@ -84,15 +84,9 @@ function ExportTask:cycle(context)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
if not s and m then
|
tasks:run()
|
||||||
_G._debug('EXPORTER error: ' .. m)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
if #tasks > 0 then
|
|
||||||
parallel.waitForAll(table.unpack(tasks))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Milo:registerTask(ExportTask)
|
Milo:registerTask(ExportTask)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
local itemDB = require('core.itemDB')
|
local itemDB = require('core.itemDB')
|
||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
|
local Tasks = require('milo.taskRunner')
|
||||||
local parallel = _G.parallel
|
|
||||||
|
|
||||||
local ImportTask = {
|
local ImportTask = {
|
||||||
name = 'importer',
|
name = 'importer',
|
||||||
@@ -13,11 +12,12 @@ local function filter(a)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ImportTask:cycle(context)
|
function ImportTask:cycle(context)
|
||||||
local tasks = { }
|
local tasks = Tasks({
|
||||||
|
errorMsg = 'IMPORT error: '
|
||||||
|
})
|
||||||
|
|
||||||
for node in context.storage:filterActive('machine', filter) do
|
for node in context.storage:filterActive('machine', filter) do
|
||||||
table.insert(tasks, function()
|
tasks:add(function()
|
||||||
local s, m = pcall(function()
|
|
||||||
for _, entry in pairs(node.imports) do
|
for _, entry in pairs(node.imports) do
|
||||||
|
|
||||||
local function itemMatchesFilter(item)
|
local function itemMatchesFilter(item)
|
||||||
@@ -70,16 +70,9 @@ function ImportTask:cycle(context)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not s and m then
|
|
||||||
_G._debug('IMPORTER error: ' .. m)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #tasks > 0 then
|
tasks:run()
|
||||||
parallel.waitForAll(table.unpack(tasks))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Milo:registerTask(ImportTask)
|
Milo:registerTask(ImportTask)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
|
local Tasks = require('milo.taskRunner')
|
||||||
local parallel = _G.parallel
|
|
||||||
|
|
||||||
local InputChest = {
|
local InputChest = {
|
||||||
name = 'input',
|
name = 'input',
|
||||||
@@ -8,29 +7,24 @@ local InputChest = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InputChest:cycle(context)
|
function InputChest:cycle(context)
|
||||||
local tasks = { }
|
local tasks = Tasks({
|
||||||
|
errorMsg = 'INPUT error: '
|
||||||
|
})
|
||||||
|
|
||||||
for node in context.storage:filterActive('input') do
|
for node in context.storage:filterActive('input') do
|
||||||
table.insert(tasks, function()
|
|
||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
for slot, item in pairs(node.adapter.list()) do
|
for slot, item in pairs(node.adapter.list()) do
|
||||||
local s, m = pcall(function()
|
tasks:add(function()
|
||||||
context.storage:import(node, slot, item.count, item)
|
context.storage:import(node, slot, item.count, item)
|
||||||
end)
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
if not s and m then
|
if not s and m then
|
||||||
_G._debug('INPUT error: ' .. m)
|
_G._debug('INPUT error: ' .. m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|
|
||||||
if not s and m then
|
tasks:run()
|
||||||
_G._debug('INPUT error: ' .. m)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
if #tasks > 0 then
|
|
||||||
parallel.waitForAll(table.unpack(tasks))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Milo:registerTask(InputChest)
|
Milo:registerTask(InputChest)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local Event = require('event')
|
|
||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
|
local Tasks = require('milo.taskRunner')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
@@ -76,16 +76,20 @@ local function filter(a)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function task:cycle(context)
|
function task:cycle(context)
|
||||||
|
local tasks = Tasks()
|
||||||
|
|
||||||
for node in context.storage:filterActive('trashcan', filter) do
|
for node in context.storage:filterActive('trashcan', filter) do
|
||||||
Event.onTimeout(0, function() -- run on a background thread
|
|
||||||
pcall(function()
|
pcall(function()
|
||||||
for k in pairs(node.adapter.list()) do
|
for k in pairs(node.adapter.list()) do
|
||||||
local direction = node.dropDirection or 'down'
|
local direction = node.dropDirection or 'down'
|
||||||
|
tasks:add(function()
|
||||||
node.adapter.drop(k, 64, direction)
|
node.adapter.drop(k, 64, direction)
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
tasks:run()
|
||||||
end
|
end
|
||||||
|
|
||||||
Milo:registerTask(task)
|
Milo:registerTask(task)
|
||||||
|
|||||||
6
openos/.package
Normal file
6
openos/.package
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
title = 'Shell utilities',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/openos',
|
||||||
|
description = [[ Utilties for shell: grep, cat, touch, etc ]],
|
||||||
|
licence = 'MIT',
|
||||||
|
}
|
||||||
3
openos/apis/computer.lua
Normal file
3
openos/apis/computer.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
uptime = os.clock,
|
||||||
|
}
|
||||||
59
openos/apis/filesystem.lua
Normal file
59
openos/apis/filesystem.lua
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
|
local function get(path)
|
||||||
|
local label = fs.getDrive(path)
|
||||||
|
|
||||||
|
if label then
|
||||||
|
local proxy = {
|
||||||
|
getLabel = function() return label 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
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local label, path = next(t)
|
||||||
|
if label then
|
||||||
|
t[label] = nil
|
||||||
|
return get(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function list(path)
|
||||||
|
local set = fs.list(path)
|
||||||
|
return function()
|
||||||
|
local key, value = next(set)
|
||||||
|
set[key or false] = nil
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
canonical = function(...) return ... end,
|
||||||
|
concat = fs.combine,
|
||||||
|
exists = fs.exists,
|
||||||
|
get = get,
|
||||||
|
isDirectory = fs.isDir,
|
||||||
|
isLink = function() return false end,
|
||||||
|
list = list,
|
||||||
|
mounts = mounts,
|
||||||
|
name = fs.getName,
|
||||||
|
open = function(n, m) return fs.open(n, m or 'r') end,
|
||||||
|
realPath = function(...) return ... end,
|
||||||
|
size = fs.getSize,
|
||||||
|
}
|
||||||
3
openos/apis/keyboard.lua
Normal file
3
openos/apis/keyboard.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
keys = _G.keys,
|
||||||
|
}
|
||||||
6
openos/apis/sh.lua
Normal file
6
openos/apis/sh.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
return {
|
||||||
|
execute = function(_, ...) return shell.run(...) end,
|
||||||
|
getLastExitCode = function() return 0 end,
|
||||||
|
}
|
||||||
9
openos/apis/shell.lua
Normal file
9
openos/apis/shell.lua
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
return {
|
||||||
|
getWorkingDirectory = shell.dir,
|
||||||
|
resolve = shell.resolve,
|
||||||
|
parse = Util.parse,
|
||||||
|
}
|
||||||
1
openos/apis/term.lua
Normal file
1
openos/apis/term.lua
Normal file
@@ -0,0 +1 @@
|
|||||||
|
return _G.term
|
||||||
405
openos/apis/text.lua
Normal file
405
openos/apis/text.lua
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
local unicode = require("openos.unicode")
|
||||||
|
local tx = require("openos.transforms")
|
||||||
|
|
||||||
|
local text = {}
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- used by lib/sh
|
||||||
|
function text.escapeMagic(txt)
|
||||||
|
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
|
||||||
|
end
|
||||||
|
|
||||||
|
function text.removeEscapes(txt)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if #token > 0 then
|
||||||
|
table.insert(tokens, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
local tokens, reason = text.internal.tokenize(value, options)
|
||||||
|
|
||||||
|
if type(tokens) ~= "table" then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.doNotNormalize then
|
||||||
|
return tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
return text.internal.normalize(tokens)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- like tokenize, but does not drop any text such as whitespace
|
||||||
|
-- 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")
|
||||||
|
|
||||||
|
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 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
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- splits each word into words at delimiters
|
||||||
|
-- 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")
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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"))})
|
||||||
|
|
||||||
|
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"))})
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
269
openos/apis/transfer.lua
Normal file
269
openos/apis/transfer.lua
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
local fs = require("openos.filesystem")
|
||||||
|
local shell = require("openos.shell")
|
||||||
|
local text = require("openos.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
|
||||||
|
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 (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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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")
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
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_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 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
|
||||||
|
|
||||||
|
-- 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 toArg = table.remove(args)
|
||||||
|
local _, ok = contents_check(toArg, options)
|
||||||
|
if not ok then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
local originalToIsDir = fs.isDirectory(ok)
|
||||||
|
|
||||||
|
for _, fromArg in ipairs(args) 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)
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
perr(options, reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return options.exit_code
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib
|
||||||
200
openos/apis/transforms.lua
Normal file
200
openos/apis/transforms.lua
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib
|
||||||
23
openos/apis/tty.lua
Normal file
23
openos/apis/tty.lua
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
local colors = _G.colors
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
|
local w, h = term.getSize()
|
||||||
|
|
||||||
|
local cmap = {
|
||||||
|
[ 0xCC2200 ] = colors.red,
|
||||||
|
[ 0x44CC00 ] = colors.lime,
|
||||||
|
[ 0xB0B00F ] = colors.yellow,
|
||||||
|
[ 0xFFFFFF ] = colors.white,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
gpu = function()
|
||||||
|
return {
|
||||||
|
setForeground = function(c) term.setTextColor(cmap[c]) end,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
getViewport = term.getSize,
|
||||||
|
window = {
|
||||||
|
width = w, height = h,
|
||||||
|
}
|
||||||
|
}
|
||||||
3
openos/apis/unicode.lua
Normal file
3
openos/apis/unicode.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
wlen = string.len
|
||||||
|
}
|
||||||
11
openos/autorun/startup.lua
Normal file
11
openos/autorun/startup.lua
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
local os = _G.os
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
function os.getenv(k)
|
||||||
|
return settings.get(k)
|
||||||
|
end
|
||||||
30
openos/cat.lua
Normal file
30
openos/cat.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
local shell = require("openos.shell")
|
||||||
|
local fs = require("openos.filesystem")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
local ec = 0
|
||||||
|
if #args == 0 then
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec
|
||||||
36
openos/cp.lua
Normal file
36
openos/cp.lua
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
local shell = require("openos.shell")
|
||||||
|
local transfer = require("openos.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>
|
||||||
|
-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.
|
||||||
|
-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
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transfer.batch(args, options)
|
||||||
76
openos/df.lua
Normal file
76
openos/df.lua
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
local fs = require("openos.filesystem")
|
||||||
|
local shell = require("openos.shell")
|
||||||
|
local text = require("openos.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]
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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})
|
||||||
|
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
|
||||||
|
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()
|
||||||
|
end
|
||||||
33
openos/dmesg.lua
Normal file
33
openos/dmesg.lua
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
local tty = require("openos.tty")
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
local gpu = tty.gpu()
|
||||||
|
io.write("Press 'Ctrl-C' to exit\n")
|
||||||
|
local events = { }
|
||||||
|
for _, e in pairs(args) do
|
||||||
|
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
|
||||||
|
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
until evt[1] == "terminate"
|
||||||
|
--end)
|
||||||
|
|
||||||
|
gpu.setForeground(0xFFFFFF)
|
||||||
|
|
||||||
128
openos/du.lua
Normal file
128
openos/du.lua
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
local shell = require("openos.shell")
|
||||||
|
local fs = require("openos.filesystem")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
args[1] = '.'
|
||||||
|
end
|
||||||
|
|
||||||
|
local TRY=[[
|
||||||
|
Try 'du --help' for more information.]]
|
||||||
|
|
||||||
|
local VERSION=[[
|
||||||
|
du (OpenOS bin) 1.0
|
||||||
|
Written by payonel, patterned after GNU coreutils du]]
|
||||||
|
|
||||||
|
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]]
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
print(HELP)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.version then
|
||||||
|
print(VERSION)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addTrailingSlash(path)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function visitor(rpath)
|
||||||
|
local subtotal = 0
|
||||||
|
local dirs = 0
|
||||||
|
local spath = shell.resolve(rpath)
|
||||||
|
|
||||||
|
if fs.isDirectory(spath) then
|
||||||
|
local list_result = fs.list(spath)
|
||||||
|
for list_item in list_result do
|
||||||
|
local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
|
||||||
|
subtotal = subtotal + vtotal
|
||||||
|
dirs = dirs + vdirs
|
||||||
|
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
|
||||||
|
|
||||||
|
return subtotal, dirs
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
|
||||||
|
if not fs.exists(path) then
|
||||||
|
io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg))
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
if fs.isDirectory(path) then
|
||||||
|
local total = visitor(arg)
|
||||||
|
|
||||||
|
if bSummary then
|
||||||
|
printSize(total, arg)
|
||||||
|
end
|
||||||
|
elseif fs.isLink(path) then
|
||||||
|
printSize(0, arg)
|
||||||
|
else
|
||||||
|
printSize(fs.size(path), arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
132
openos/find.lua
Normal file
132
openos/find.lua
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local text = require("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]===]
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args > 1 then
|
||||||
|
io.stderr:write(USAGE..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = #args == 1 and args[1] or "."
|
||||||
|
|
||||||
|
local bDirs = true
|
||||||
|
local bFiles = true
|
||||||
|
local bSyms = true
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.type then
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.iname or options.name then
|
||||||
|
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 not bCaseSensitive then
|
||||||
|
fileNamePattern = fileNamePattern:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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 fileNamePattern:len() > 0 then
|
||||||
|
local fileName = spath:gsub('.*/','')
|
||||||
|
|
||||||
|
if fileName:len() == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local caseFileName = fileName
|
||||||
|
|
||||||
|
if not bCaseSensitive then
|
||||||
|
caseFileName = caseFileName:lower()
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
visit(path)
|
||||||
323
openos/grep.lua
Normal file
323
openos/grep.lua
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
--[[
|
||||||
|
An adaptation of Wobbo's grep
|
||||||
|
https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua
|
||||||
|
]]--
|
||||||
|
|
||||||
|
-- POSIX grep for OpenComputers
|
||||||
|
-- one difference is that this version uses Lua regex, not POSIX regex.
|
||||||
|
|
||||||
|
local fs = require("openos.filesystem")
|
||||||
|
local shell = require("openos.shell")
|
||||||
|
local tty = require("openos.tty")
|
||||||
|
local computer = require("openos.computer")
|
||||||
|
|
||||||
|
-- Process the command line arguments
|
||||||
|
|
||||||
|
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]...
|
||||||
|
Example: grep -i "hello world" menu.lua main.lua
|
||||||
|
for more information, run: man grep
|
||||||
|
]])
|
||||||
|
end
|
||||||
|
|
||||||
|
local PATTERNS = {args[1]}
|
||||||
|
local FILES = {select(2, table.unpack(args))}
|
||||||
|
|
||||||
|
local LABEL_COLOR = 0xb000b0
|
||||||
|
local LINE_NUM_COLOR = 0x00FF00
|
||||||
|
local MATCH_COLOR = 0xFF0000
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Specify the variables for the options
|
||||||
|
local plain = pop('F','fixed-strings')
|
||||||
|
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')
|
||||||
|
local ignore_case = pop('i','ignore-case')
|
||||||
|
local stdin_label = pop('label') or '(standard input)'
|
||||||
|
local stderr = pop('s','no-messages') and {write=function()end} or io.stderr
|
||||||
|
local invert_match = not not pop('v','invert-match')
|
||||||
|
|
||||||
|
-- no version output, just help
|
||||||
|
if pop('V','version','help') then
|
||||||
|
printUsage()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local max_matches = tonumber(pop('max-count')) or math.huge
|
||||||
|
local print_line_num = pop('n','line-number')
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
if #PATTERNS == 0 then
|
||||||
|
printUsage(stderr)
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
|
||||||
|
if #FILES == 0 then
|
||||||
|
FILES = search_recursively and {'.'} or {'-'}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options.h and search_recursively then
|
||||||
|
options.H = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if #FILES < 2 then
|
||||||
|
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
|
||||||
|
|
||||||
|
local m_only = pop('o','only-matching')
|
||||||
|
local quiet = pop('q','quiet','silent')
|
||||||
|
|
||||||
|
local print_count = pop('c','count')
|
||||||
|
local colorize = pop('color','colour') and io.output().tty and tty.isAvailable()
|
||||||
|
|
||||||
|
local noop = function(...)return ...;end
|
||||||
|
local setc = colorize and gpu.setForeground or noop
|
||||||
|
local getc = colorize and gpu.getForeground or noop
|
||||||
|
|
||||||
|
local trim = pop('t','trim')
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_recursively then
|
||||||
|
local files = {}
|
||||||
|
for _,arg in ipairs(FILES) do
|
||||||
|
if fs.isDirectory(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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec or any_hit_ec
|
||||||
137
openos/head.lua
Normal file
137
openos/head.lua
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
local shell = require("openos.shell")
|
||||||
|
local fs = require("openos.filesystem")
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local bytes = pop('bytes', true)
|
||||||
|
local lines = pop('lines', true)
|
||||||
|
local quiet = {pop('q'), pop('quiet'), pop('silent')}
|
||||||
|
quiet = quiet[1] or quiet[2] or quiet[3]
|
||||||
|
local verbose = {pop('v'), pop('verbose')}
|
||||||
|
verbose = verbose[1] or verbose[2]
|
||||||
|
local help = pop('help')
|
||||||
|
local invalid_key = next(options)
|
||||||
|
|
||||||
|
if bytes and lines then
|
||||||
|
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
|
||||||
|
Print the first 10 lines of each FILE to stdout.
|
||||||
|
For more info run: man head]])
|
||||||
|
os.exit(error_code)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
args = {'-'}
|
||||||
|
end
|
||||||
|
|
||||||
|
if quiet and verbose then
|
||||||
|
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 '')
|
||||||
|
}
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local function push(stream, line)
|
||||||
|
if not line then
|
||||||
|
return close(stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 stream = new_stream()
|
||||||
|
|
||||||
|
while stream.open do
|
||||||
|
push(stream, file:read('*L'))
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
17
openos/hostname.lua
Normal file
17
openos/hostname.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local shell = require("openos.shell")
|
||||||
|
local args = shell.parse(...)
|
||||||
|
local hostname = args[1]
|
||||||
|
|
||||||
|
if hostname then
|
||||||
|
os.setComputerLabel(hostname)
|
||||||
|
else
|
||||||
|
hostname = os.getComputerLabel()
|
||||||
|
if hostname then
|
||||||
|
print(hostname)
|
||||||
|
else
|
||||||
|
io.stderr:write("Hostname not set\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
149
openos/less.lua
Normal file
149
openos/less.lua
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
local keys = require("openos.keyboard").keys
|
||||||
|
local shell = require("openos.shell")
|
||||||
|
local unicode = require("openos.unicode")
|
||||||
|
local term = require("openos.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
|
||||||
|
end
|
||||||
|
|
||||||
|
local cat_cmd = table.concat({"cat", ...}, " ")
|
||||||
|
if not io.output().tty then
|
||||||
|
return os.execute(cat_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
local preader = io.popen(cat_cmd)
|
||||||
|
local scrollback = not ops.noback and {}
|
||||||
|
local bottom = 0
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local function status()
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
35
openos/ln.lua
Normal file
35
openos/ln.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: ln <target> [<name>]\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_name = args[1]
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local linkpath
|
||||||
|
if #args > 1 then
|
||||||
|
linkpath = shell.resolve(args[2])
|
||||||
|
else
|
||||||
|
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
|
||||||
|
end
|
||||||
|
|
||||||
|
if fs.isDirectory(linkpath) then
|
||||||
|
linkpath = fs.concat(linkpath, fs.name(target))
|
||||||
|
end
|
||||||
|
|
||||||
|
local result, reason = fs.link(target_name, linkpath)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(reason..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
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
|
||||||
33
openos/mv.lua
Normal file
33
openos/mv.lua
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local transfer = require("tools/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
|
||||||
|
]])
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return transfer.batch(args, options)
|
||||||
155
openos/ps.lua
Normal file
155
openos/ps.lua
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
local process = require("process")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local event = require("event")
|
||||||
|
local thread = require("thread")
|
||||||
|
local event_mt = getmetatable(event.handlers)
|
||||||
|
|
||||||
|
-- WARNING this code does not use official kernel API and is likely to change
|
||||||
|
|
||||||
|
local data = {}
|
||||||
|
local widths = {}
|
||||||
|
local sorted = {}
|
||||||
|
local moved_indexes = {}
|
||||||
|
|
||||||
|
local elbow = unicode.char(0x2514)
|
||||||
|
|
||||||
|
local function thread_id(t,p)
|
||||||
|
if t then
|
||||||
|
return tostring(t):gsub("^thread: 0x", "")
|
||||||
|
end
|
||||||
|
-- find the parent thread
|
||||||
|
for k,v in pairs(process.list) do
|
||||||
|
if v == p then
|
||||||
|
return thread_id(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
local cols =
|
||||||
|
{
|
||||||
|
{"PID", thread_id},
|
||||||
|
{"EVENTS", function(_,p)
|
||||||
|
local handlers = {}
|
||||||
|
if event_mt.threaded then
|
||||||
|
handlers = rawget(p.data, "handlers") or {}
|
||||||
|
elseif not p.parent then
|
||||||
|
handlers = event.handlers
|
||||||
|
end
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(handlers) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"THREADS", function(_,p)
|
||||||
|
-- threads are handles with mt.close == thread.waitForAll
|
||||||
|
local count = 0
|
||||||
|
for h in pairs(p.data.handles) do
|
||||||
|
local mt = getmetatable(h)
|
||||||
|
if mt and mt.__status then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"PARENT", function(_,p)
|
||||||
|
for _,process_info in pairs(process.list) do
|
||||||
|
for handle in pairs(process_info.data.handles) do
|
||||||
|
local mt = getmetatable(handle)
|
||||||
|
if mt and mt.__status then
|
||||||
|
if mt.process == p then
|
||||||
|
return thread_id(nil, process_info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return thread_id(nil, p.parent)
|
||||||
|
end},
|
||||||
|
{"HANDLES", function(_, p)
|
||||||
|
local count = 0
|
||||||
|
for _,closure in pairs(p.data.handles) do
|
||||||
|
if closure then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"CMD", function(_,p) return p.command end},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function add_field(key, value)
|
||||||
|
if not data[key] then data[key] = {} end
|
||||||
|
table.insert(data[key], value)
|
||||||
|
widths[key] = math.max(widths[key] or 0, #value)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
add_field(key[1], key[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
for thread_handle, process_info in pairs(process.list) do
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
add_field(key[1], key[2](thread_handle, process_info))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent_index
|
||||||
|
for index,set in ipairs(cols) do
|
||||||
|
if set[1] == "PARENT" then
|
||||||
|
parent_index = index
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(parent_index, "did not find a parent column")
|
||||||
|
|
||||||
|
local function move_to_sorted(index)
|
||||||
|
if moved_indexes[index] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local entry = {}
|
||||||
|
for k,v in pairs(data) do
|
||||||
|
entry[k] = v[index]
|
||||||
|
end
|
||||||
|
sorted[#sorted + 1] = entry
|
||||||
|
moved_indexes[index] = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_elbow(depth)
|
||||||
|
return (" "):rep(depth - 1) .. (depth > 0 and elbow or "")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remove COLUMN labels to simplify sort
|
||||||
|
move_to_sorted(1)
|
||||||
|
|
||||||
|
local function update_family(parent, depth)
|
||||||
|
depth = depth or 0
|
||||||
|
parent = parent or "-"
|
||||||
|
for index in ipairs(data.PID) do
|
||||||
|
local this_parent = data[cols[parent_index][1]][index]
|
||||||
|
if this_parent == parent then
|
||||||
|
local dash_cmd = make_elbow(depth) .. data.CMD[index]
|
||||||
|
data.CMD[index] = dash_cmd
|
||||||
|
widths.CMD = math.max(widths.CMD or 0, #dash_cmd)
|
||||||
|
if move_to_sorted(index) then
|
||||||
|
update_family(data.PID[index], depth + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
update_family()
|
||||||
|
table.remove(cols, parent_index) -- don't show parent id
|
||||||
|
|
||||||
|
for _,set in ipairs(sorted) do
|
||||||
|
local split = ""
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
local label = key[1]
|
||||||
|
local format = split .. "%-" .. tostring(widths[label]) .. "s"
|
||||||
|
io.write(string.format(format, set[label]))
|
||||||
|
split = " "
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
|
||||||
158
openos/rm.lua
Normal file
158
openos/rm.lua
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local bRec = options.r or options.R or options.recursive
|
||||||
|
local bForce = options.f or options.force
|
||||||
|
local bVerbose = options.v or options.verbose
|
||||||
|
local bEmptyDirs = options.d or options.dir
|
||||||
|
local promptLevel = (options.I and 3) or (options.i and 1) or 0
|
||||||
|
|
||||||
|
bVerbose = bVerbose and not bForce
|
||||||
|
promptLevel = bForce and 0 or promptLevel
|
||||||
|
|
||||||
|
local function perr(...)
|
||||||
|
if not bForce then
|
||||||
|
io.stderr:write(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pout(...)
|
||||||
|
if not bForce then
|
||||||
|
io.stdout:write(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local metas = {}
|
||||||
|
|
||||||
|
-- promptLevel 3 done before fs.exists
|
||||||
|
-- promptLevel 1 asks for each, displaying fs.exists on hit as it visits
|
||||||
|
|
||||||
|
local function _path(m) return shell.resolve(m.rel) end
|
||||||
|
local function _link(m) return fs.isLink(_path(m)) end
|
||||||
|
local function _exists(m) return _link(m) or fs.exists(_path(m)) end
|
||||||
|
local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end
|
||||||
|
local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unlink(path)
|
||||||
|
os.remove(path)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function confirm()
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return all_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove(meta)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
metas[#metas+1] = createMeta(arg, arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if promptLevel == 3 and #metas > 3 then
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
return bForce or ok
|
||||||
104
openos/rmdir.lua
Normal file
104
openos/rmdir.lua
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
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]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
io.stderr:write("rmdir: missing operand\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
options.p = options.p or options.parents
|
||||||
|
options.v = options.v or options.verbose
|
||||||
|
options.q = options.q or options['ignore-fail-on-non-empty']
|
||||||
|
|
||||||
|
local ec = 0
|
||||||
|
local function ec_bump()
|
||||||
|
ec = 1
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove(path, ...)
|
||||||
|
-- check to end recursion
|
||||||
|
if path == nil then
|
||||||
|
return true
|
||||||
|
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)
|
||||||
|
|
||||||
|
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('/+', '/')
|
||||||
|
|
||||||
|
local segments = {}
|
||||||
|
if options.p and path:len() > 1 and path:find('/') then
|
||||||
|
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))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec
|
||||||
23
openos/set.lua
Normal file
23
openos/set.lua
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
for k,v in pairs(os.getenv()) do
|
||||||
|
io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local count = 0
|
||||||
|
for _, expr in ipairs(args) do
|
||||||
|
local e = expr:find('=')
|
||||||
|
if e then
|
||||||
|
os.setenv(expr:sub(1,e-1), expr:sub(e+1))
|
||||||
|
else
|
||||||
|
if count == 0 then
|
||||||
|
for i = 1, os.getenv('#') do
|
||||||
|
os.setenv(i, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
count = count + 1
|
||||||
|
os.setenv(count, expr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
36
openos/source.lua
Normal file
36
openos/source.lua
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local process = require("process")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if #args ~= 1 then
|
||||||
|
io.stderr:write("specify a single file to source\n");
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local file, open_reason = io.open(args[1], "r")
|
||||||
|
|
||||||
|
if not file then
|
||||||
|
if not options.q then
|
||||||
|
io.stderr:write(string.format("could not source %s because: %s\n", args[1], open_reason));
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = file:lines()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local line = lines()
|
||||||
|
if not line then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local current_data = process.info().data
|
||||||
|
|
||||||
|
local source_proc = process.load((assert(os.getenv("SHELL"), "no $SHELL set")))
|
||||||
|
local source_data = process.list[source_proc].data
|
||||||
|
source_data.aliases = current_data.aliases -- hacks to propogate sub shell env changes
|
||||||
|
source_data.vars = current_data.vars
|
||||||
|
process.internal.continue(source_proc, _ENV, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
18
openos/time.lua
Normal file
18
openos/time.lua
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
local computer = require('openos.computer')
|
||||||
|
local sh = require('openos.sh')
|
||||||
|
|
||||||
|
local real_before, cpu_before = computer.uptime(), os.clock()
|
||||||
|
local cmd_result = 0
|
||||||
|
if ... then
|
||||||
|
sh.execute(nil, ...)
|
||||||
|
cmd_result = sh.getLastExitCode()
|
||||||
|
end
|
||||||
|
local real_after, cpu_after = computer.uptime(), os.clock()
|
||||||
|
|
||||||
|
local real_diff = real_after - real_before
|
||||||
|
local cpu_diff = cpu_after - cpu_before
|
||||||
|
|
||||||
|
print(string.format('real%5dm%.3fs', math.floor(real_diff/60), real_diff%60))
|
||||||
|
print(string.format('cpu %5dm%.3fs', math.floor(cpu_diff/60), cpu_diff%60))
|
||||||
|
|
||||||
|
return cmd_result
|
||||||
54
openos/touch.lua
Normal file
54
openos/touch.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
--[[Lua implementation of the UN*X touch command--]]
|
||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
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]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
elseif #args == 0 then
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
331
openos/tree.lua
Normal file
331
openos/tree.lua
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local tx = require("transforms")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, opts = shell.parse(...)
|
||||||
|
|
||||||
|
local function die(...)
|
||||||
|
io.stderr:write(...)
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
do -- handle cli
|
||||||
|
if opts.help then
|
||||||
|
print([[Usage: tree [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, print human readable sizes
|
||||||
|
--si likewise, but use powers of 1000 not 1024
|
||||||
|
--level=LEVEL descend only LEVEL directories deep
|
||||||
|
--color=WHEN WHEN can be
|
||||||
|
auto - colorize output only if writing to a tty,
|
||||||
|
always - always colorize output,
|
||||||
|
never - never colorize output; (default: auto)
|
||||||
|
-l use a long listing format
|
||||||
|
-f print the full path prefix for each file
|
||||||
|
-i do not print indentation lines
|
||||||
|
-p append "/" indicator to directories
|
||||||
|
-Q, --quote quote filenames with double quotes
|
||||||
|
-r, --reverse reverse order while sorting
|
||||||
|
-S sort by file size
|
||||||
|
-t sort by modification type, newest first
|
||||||
|
-X sort alphabetically by entry extension
|
||||||
|
-C do not count files and directories
|
||||||
|
-R count root directories like other files
|
||||||
|
--help print this help and exit]])
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
table.insert(args, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.level = tonumber(opts.level) or math.huge
|
||||||
|
if opts.level < 1 then
|
||||||
|
die("Invalid level, must be greater than 0")
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.color = opts.color or "auto"
|
||||||
|
if opts.color == "auto" then
|
||||||
|
opts.color = io.stdout.tty and "always" or "never"
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.color ~= "always" and opts.color ~= "never" then
|
||||||
|
die("Invalid value for --color=WHEN option; WHEN should be auto, always or never")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local lastYield = computer.uptime()
|
||||||
|
local function yieldopt()
|
||||||
|
if computer.uptime() - lastYield > 2 then
|
||||||
|
lastYield = computer.uptime()
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function peekable(iterator, state, var1)
|
||||||
|
local nextItem = {iterator(state, var1)}
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
peek = function()
|
||||||
|
return table.unpack(nextItem)
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__call = coroutine.wrap(function()
|
||||||
|
while true do
|
||||||
|
local item = nextItem
|
||||||
|
nextItem = {iterator(state, nextItem[1])}
|
||||||
|
coroutine.yield(table.unpack(item))
|
||||||
|
if nextItem[1] == nil then break end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(entry)
|
||||||
|
return opts.a or entry:sub(1, 1) ~= "."
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stat(path)
|
||||||
|
local st = {}
|
||||||
|
st.path = path
|
||||||
|
st.name = fs.name(path) or "/"
|
||||||
|
st.sortName = st.name:gsub("^%.","")
|
||||||
|
st.time = fs.lastModified(path)
|
||||||
|
st.isLink = fs.isLink(path)
|
||||||
|
st.isDirectory = fs.isDirectory(path)
|
||||||
|
st.size = st.isLink and 0 or fs.size(path)
|
||||||
|
st.extension = st.name:match("(%.[^.]+)$") or ""
|
||||||
|
st.fs = fs.get(path)
|
||||||
|
return st
|
||||||
|
end
|
||||||
|
|
||||||
|
local colorize
|
||||||
|
if opts.color == "always" then
|
||||||
|
-- from /lib/core/full_ls.lua
|
||||||
|
local colors = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
|
||||||
|
local parts = text.split(e, {"="}, true)
|
||||||
|
return parts[2], parts[1]
|
||||||
|
end)
|
||||||
|
|
||||||
|
function colorize(stat)
|
||||||
|
return stat.isLink and colors.ln or
|
||||||
|
stat.isDirectory and colors.di or
|
||||||
|
colors["*" .. stat.extension] or
|
||||||
|
colors.fi
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function list(path)
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
local l = {}
|
||||||
|
for entry in fs.list(path) do
|
||||||
|
if filter(entry) then
|
||||||
|
table.insert(l, stat(fs.concat(path, entry)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.S then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.size < b.size
|
||||||
|
end)
|
||||||
|
elseif opts.t then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.time < b.time
|
||||||
|
end)
|
||||||
|
elseif opts.X then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.extension < b.extension
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.sortName < b.sortName
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = opts.r and #l or 1, opts.r and 1 or #l, opts.r and -1 or 1 do
|
||||||
|
coroutine.yield(l[i])
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function digRoot(rootPath)
|
||||||
|
coroutine.yield(stat(rootPath), {})
|
||||||
|
|
||||||
|
if not fs.isDirectory(rootPath) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local iterStack = {peekable(list(rootPath))}
|
||||||
|
local pathStack = {rootPath}
|
||||||
|
local levelStack = {not not iterStack[#iterStack]:peek()}
|
||||||
|
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local entry = iterStack[#iterStack]()
|
||||||
|
|
||||||
|
if entry then
|
||||||
|
levelStack[#levelStack] = not not iterStack[#iterStack]:peek()
|
||||||
|
|
||||||
|
local path = fs.concat(fs.concat(table.unpack(pathStack)), entry.name)
|
||||||
|
|
||||||
|
coroutine.yield(entry, levelStack)
|
||||||
|
|
||||||
|
if entry.isDirectory and opts.level > #levelStack then
|
||||||
|
table.insert(iterStack, peekable(list(path)))
|
||||||
|
table.insert(pathStack, entry.name)
|
||||||
|
table.insert(levelStack, not not iterStack[#iterStack]:peek())
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.remove(iterStack)
|
||||||
|
table.remove(pathStack)
|
||||||
|
table.remove(levelStack)
|
||||||
|
end
|
||||||
|
until #iterStack == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dig(roots)
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
for _, root in ipairs(roots) do
|
||||||
|
digRoot(root)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function nod(n) -- from /lib/core/full_ls.lua
|
||||||
|
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatFSize(size) -- from /lib/core/full_ls.lua
|
||||||
|
if not opts.h and not opts["human-readable"] and not opts.si then
|
||||||
|
return tostring(size)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sizes = {"", "K", "M", "G"}
|
||||||
|
local unit = 1
|
||||||
|
local power = opts.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 pad(txt) -- from /lib/core/full_ls.lua
|
||||||
|
txt = tostring(txt)
|
||||||
|
return #txt >= 2 and txt or "0" .. txt
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatTime(epochms) -- from /lib/core/full_ls.lua
|
||||||
|
local month_names = {"January","February","March","April","May","June",
|
||||||
|
"July","August","September","October","November","December"}
|
||||||
|
|
||||||
|
if epochms == 0 then return "" end
|
||||||
|
|
||||||
|
local d = os.date("*t", epochms)
|
||||||
|
local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
|
||||||
|
|
||||||
|
if opts["full-time"] then
|
||||||
|
return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
|
||||||
|
else
|
||||||
|
return string.format("%s %+2s %+2s:%+2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeEntry(entry, levelStack)
|
||||||
|
for i, hasNext in ipairs(levelStack) do
|
||||||
|
if opts.i then break end
|
||||||
|
|
||||||
|
if i == #levelStack then
|
||||||
|
if hasNext then
|
||||||
|
io.write("├── ")
|
||||||
|
else
|
||||||
|
io.write("└── ")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if hasNext then
|
||||||
|
io.write("│ ")
|
||||||
|
else
|
||||||
|
io.write(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.l then
|
||||||
|
io.write("[")
|
||||||
|
|
||||||
|
io.write(entry.isDirectory and "d" or entry.isLink and "l" or "f", "-")
|
||||||
|
io.write("r", entry.fs.isReadOnly() and "-" or "w", " ")
|
||||||
|
|
||||||
|
io.write(formatFSize(entry.size), " ")
|
||||||
|
|
||||||
|
io.write(formatTime(entry.time))
|
||||||
|
io.write("] ")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.Q then io.write('"') end
|
||||||
|
|
||||||
|
if opts.color == "always" then
|
||||||
|
io.write("\27[" .. colorize(entry) .. "m")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.f then
|
||||||
|
io.write(entry.path)
|
||||||
|
else
|
||||||
|
io.write(entry.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.color == "always" then
|
||||||
|
io.write("\27[0m")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.p and entry.isDirectory then
|
||||||
|
io.write("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.Q then io.write('"') end
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeCount(dirs, files)
|
||||||
|
io.write("\n")
|
||||||
|
io.write(dirs, " director", dirs == 1 and "y" or "ies")
|
||||||
|
io.write(", ")
|
||||||
|
io.write(files, " file", files == 1 and "" or "s")
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local dirs, files = 0, 0
|
||||||
|
|
||||||
|
local roots = {}
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
local real, reason = fs.realPath(path)
|
||||||
|
if not real then
|
||||||
|
die("cannot access ", path, ": ", reason or "unknown error")
|
||||||
|
elseif not fs.exists(path) then
|
||||||
|
die("cannot access ", path, ":", "No such file or directory")
|
||||||
|
else
|
||||||
|
table.insert(roots, real)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for entry, levelStack in dig(roots) do
|
||||||
|
if opts.R or #levelStack > 0 then
|
||||||
|
if entry.isDirectory then
|
||||||
|
dirs = dirs + 1
|
||||||
|
else
|
||||||
|
files = files + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
writeEntry(entry, levelStack)
|
||||||
|
yieldopt()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not opts.C then
|
||||||
|
writeCount(dirs, files)
|
||||||
|
end
|
||||||
|
|
||||||
19
openos/unalias.lua
Normal file
19
openos/unalias.lua
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: unalias <name>...\n")
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
local e = 0
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
local result = shell.getAlias(arg)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(string.format("unalias: %s: not found\n", arg))
|
||||||
|
e = 1
|
||||||
|
else
|
||||||
|
shell.setAlias(arg, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return e
|
||||||
9
openos/unset.lua
Normal file
9
openos/unset.lua
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: unset <varname>[ <varname2> [...]]\n")
|
||||||
|
else
|
||||||
|
for _, k in ipairs(args) do
|
||||||
|
os.setenv(k, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
25
openos/which.lua
Normal file
25
openos/which.lua
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: which <program>\n")
|
||||||
|
return 255
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
local result, reason = shell.resolve(args[i], "lua")
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user