· 8 years ago · Dec 16, 2017, 03:58 PM
1mnt/6c1/home/.shrc 0000600 00000000000 13215223747 006370 0 mnt/6c1/home 0000700 13215223747 004341 5 mnt/6c1/boot/93_term.lua 0000600 00000004115 13215223664 007441 0 local component = require("component")
2local computer = require("computer")
3local event = require("event")
4local tty = require("tty")
5
6event.listen("gpu_bound", function(_, gpu)
7 gpu = component.proxy(gpu)
8 tty.bind(gpu)
9 computer.pushSignal("term_available")
10end)
11
12local function components_changed(ename, address, type)
13 local window = tty.window
14 if not window then
15 return
16 end
17
18 if ename == "component_available" or ename == "component_unavailable" then
19 type = address
20 end
21
22 if ename == "component_removed" or ename == "component_unavailable" then
23 -- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need
24 if type == "gpu" and window.gpu.address == address then
25 window.gpu = nil
26 window.keyboard = nil
27 elseif type == "keyboard" then
28 -- we could check if this was our keyboard
29 -- i.e. if address == window.keyboard
30 -- but it is also simple for the terminal to
31 -- recheck what kb to use
32 window.keyboard = nil
33 end
34 if (type == "screen" or type == "gpu") and not tty.isAvailable() then
35 computer.pushSignal("term_unavailable")
36 end
37 elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then
38 -- we need to clear the current terminals cached keyboard (if any) when
39 -- a new keyboard becomes available. This is in case the new keyboard was
40 -- attached to the terminal's window. The terminal library has the code to
41 -- determine what the best keyboard to use is, but here we'll just set the
42 -- cache to nil to force term library to reload it. An alternative to this
43 -- method would be to make sure the terminal library doesn't cache the
44 -- wrong keybaord to begin with but, users may actually expect that any
45 -- primary keyboard is a valid keyboard (weird, in my opinion)
46 window.keyboard = nil
47 end
48end
49
50event.listen("component_removed", components_changed)
51event.listen("component_added", components_changed)
52event.listen("component_available", components_changed)
53event.listen("component_unavailable", components_changed)
54
55 mnt/6c1/boot/94_shell.lua 0000600 00000000327 13215223664 007603 0 -- there doesn't seem to be a reason to update $HOSTNAME after the init signal
56-- as user space /etc/profile comes after this point anyways
57loadfile("/bin/hostname.lua")("--update")
58os.setenv("SHELL","/bin/sh.lua")
59 mnt/6c1/boot/00_base.lua 0000600 00000002037 13215223665 007372 0 function loadfile(filename, ...)
60 if filename:sub(1,1) ~= "/" then
61 filename = (os.getenv("PWD") or "/") .. "/" .. filename
62 end
63 local handle, open_reason = require("filesystem").open(filename)
64 if not handle then
65 return nil, open_reason
66 end
67 local buffer = {}
68 while true do
69 local data, reason = handle:read(1024)
70 if not data then
71 handle:close()
72 if reason then
73 return nil, reason
74 end
75 break
76 end
77 buffer[#buffer + 1] = data
78 end
79 return load(table.concat(buffer), "=" .. filename, ...)
80end
81
82function dofile(filename)
83 local program, reason = loadfile(filename)
84 if not program then
85 return error(reason .. ':' .. filename, 0)
86 end
87 return program()
88end
89
90function print(...)
91 local args = table.pack(...)
92 local stdout = io.stdout
93 local old_mode, old_size = stdout:setvbuf()
94 stdout:setvbuf("line")
95 local pre = ""
96 for i = 1, args.n do
97 stdout:write(pre, tostring(args[i]))
98 pre = "\t"
99 end
100 stdout:write("\n")
101 stdout:setvbuf(old_mode, old_size)
102 stdout:flush()
103end
104 mnt/6c1/boot/90_filesystem.lua 0000600 00000003234 13215223664 010654 0 local event = require("event")
105local fs = require("filesystem")
106local shell = require("shell")
107local tmp = require("computer").tmpAddress()
108
109local pendingAutoruns = {}
110
111local function onComponentAdded(_, address, componentType)
112 if componentType == "filesystem" and tmp ~= address then
113 local proxy = fs.proxy(address)
114 if proxy then
115 local name = address:sub(1, 3)
116 while fs.exists(fs.concat("/mnt", name)) and
117 name:len() < address:len() -- just to be on the safe side
118 do
119 name = address:sub(1, name:len() + 1)
120 end
121 name = fs.concat("/mnt", name)
122 fs.mount(proxy, name)
123 if fs.isAutorunEnabled() then
124 local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
125 shell.resolve(fs.concat(name, ".autorun"), "lua")
126 if file then
127 local run = {file, _ENV, proxy}
128 if pendingAutoruns then
129 table.insert(pendingAutoruns, run)
130 else
131 xpcall(shell.execute, event.onError, table.unpack(run))
132 end
133 end
134 end
135 end
136 end
137end
138
139local function onComponentRemoved(_, address, componentType)
140 if componentType == "filesystem" then
141 if fs.get(shell.getWorkingDirectory()).address == address then
142 shell.setWorkingDirectory("/")
143 end
144 fs.umount(address)
145 end
146end
147
148event.listen("init", function()
149 for _, run in ipairs(pendingAutoruns) do
150 xpcall(shell.execute, event.onError, table.unpack(run))
151 end
152 pendingAutoruns = nil
153 return false
154end)
155
156event.listen("component_added", onComponentAdded)
157event.listen("component_removed", onComponentRemoved)
158
159require("package").delay(fs, "/lib/core/full_filesystem.lua")
160 mnt/6c1/boot/03_io.lua 0000600 00000001532 13215223663 007067 0 local buffer = require("buffer")
161local tty_stream = require("tty").stream
162
163local core_stdin = buffer.new("r", tty_stream)
164local core_stdout = buffer.new("w", tty_stream)
165local core_stderr = buffer.new("w", setmetatable(
166{
167 write = function(_, str)
168 return tty_stream:write("\27[31m"..str.."\27[37m")
169 end
170}, {__index=tty_stream}))
171
172core_stdout:setvbuf("no")
173core_stderr:setvbuf("no")
174core_stdin.tty = true
175core_stdout.tty = true
176core_stderr.tty = true
177
178core_stdin.close = tty_stream.close
179core_stdout.close = tty_stream.close
180core_stderr.close = tty_stream.close
181
182local io_mt = getmetatable(io) or {}
183io_mt.__index = function(_, k)
184 return
185 k == 'stdin' and io.input() or
186 k == 'stdout' and io.output() or
187 k == 'stderr' and io.error() or
188 nil
189end
190
191setmetatable(io, io_mt)
192
193io.input(core_stdin)
194io.output(core_stdout)
195io.error(core_stderr)
196 mnt/6c1/boot/01_process.lua 0000600 00000004546 13215223664 010145 0 local process = require("process")
197
198--Initialize coroutine library--
199local _coroutine = coroutine -- real coroutine backend
200
201_G.coroutine = setmetatable(
202 {
203 resume = function(co, ...)
204 local proc = process.info(co)
205 -- proc is nil if the process closed, natural resume will likely complain the coroutine is dead
206 -- but if proc is dead and an aborted coroutine is alive, it doesn't have any proc data like stack info
207 -- if the user really wants to resume it, let them
208 return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...)
209 end
210 },
211 {
212 __index = function(_, key)
213 return assert(process.info(_coroutine.running()), "thread has no proc").data.coroutine_handler[key]
214 end
215 }
216)
217
218package.loaded.coroutine = _G.coroutine
219
220local kernel_load = _G.load
221local intercept_load
222intercept_load = function(source, label, mode, env)
223 if env then
224 local prev_load = env.load or intercept_load
225 local next_load = function(_source, _label, _mode, _env)
226 return prev_load(_source, _label, _mode, _env or env)
227 end
228 if rawget(env, "load") then -- overwrite load
229 env.load = next_load
230 else -- else it must be an __index load, or it didn't have one
231 local env_mt = getmetatable(env) or {}
232 local env_mt_index = env_mt.__index
233 env_mt.__index = function(tbl, key)
234 if key == "load" then
235 return next_load
236 elseif type(env_mt_index) == "table" then
237 return env_mt_index[key]
238 elseif env_mt_index then
239 return env_mt_index(tbl, key)
240 end
241 return nil
242 end
243 setmetatable(env, env_mt)
244 end
245 end
246 return kernel_load(source, label, mode, env or process.info().env)
247end
248_G.load = intercept_load
249
250local kernel_create = _coroutine.create
251_coroutine.create = function(f,standAlone)
252 local co = kernel_create(f)
253 if not standAlone then
254 table.insert(process.findProcess().instances, co)
255 end
256 return co
257end
258
259_coroutine.wrap = function(f)
260 local thread = coroutine.create(f)
261 return function(...)
262 return select(2, coroutine.resume(thread, ...))
263 end
264end
265
266local init_thread = _coroutine.running()
267process.list[init_thread] = {
268 path = "/init.lua",
269 command = "init",
270 env = _ENV,
271 data =
272 {
273 vars={},
274 io={}, --init will populate this
275 coroutine_handler = _coroutine
276 },
277 instances = setmetatable({}, {__mode="v"})
278}
279 mnt/6c1/boot/91_gpu.lua 0000600 00000001264 13215223663 007264 0 local component = require("component")
280local event = require("event")
281
282local function onComponentAvailable(_, componentType)
283 if (componentType == "screen" and component.isAvailable("gpu")) or
284 (componentType == "gpu" and component.isAvailable("screen"))
285 then
286 local gpu, screen = component.gpu, component.screen
287 local screen_address = screen.address
288 if gpu.getScreen() ~= screen_address then
289 gpu.bind(screen_address)
290 end
291 local depth = math.floor(2^(gpu.getDepth()))
292 os.setenv("TERM", "term-"..depth.."color")
293 require("computer").pushSignal("gpu_bound", gpu.address, screen_address)
294 end
295end
296
297event.listen("component_available", onComponentAvailable)
298 mnt/6c1/boot/boot 0000700 13215223663 005314 5 mnt/6c1/boot/99_rc.lua 0000600 00000000201 13215223665 007075 0 -- Run all enabled rc scripts.
299local shell = require("shell")
300local rc = shell.resolve("rc", "lua")
301if rc then
302 dofile(rc)
303end
304 mnt/6c1/boot/04_component.lua 0000600 00000016574 13215223666 010502 0 local component = require("component")
305local computer = require("computer")
306local event = require("event")
307
308local adding = {}
309local primaries = {}
310
311-------------------------------------------------------------------------------
312
313-- This allows writing component.modem.open(123) instead of writing
314-- component.getPrimary("modem").open(123), which may be nicer to read.
315setmetatable(component, {
316 __index = function(_, key)
317 return component.getPrimary(key)
318 end,
319 __pairs = function(self)
320 local parent = false
321 return function(_, key)
322 if parent then
323 return next(primaries, key)
324 else
325 local k, v = next(self, key)
326 if not k then
327 parent = true
328 return next(primaries)
329 else
330 return k, v
331 end
332 end
333 end
334 end
335})
336
337function component.get(address, componentType)
338 checkArg(1, address, "string")
339 checkArg(2, componentType, "string", "nil")
340 for c in component.list(componentType, true) do
341 if c:sub(1, address:len()) == address then
342 return c
343 end
344 end
345 return nil, "no such component"
346end
347
348function component.isAvailable(componentType)
349 checkArg(1, componentType, "string")
350 if not primaries[componentType] and not adding[componentType] then
351 -- This is mostly to avoid out of memory errors preventing proxy
352 -- creation cause confusion by trying to create the proxy again,
353 -- causing the oom error to be thrown again.
354 component.setPrimary(componentType, component.list(componentType, true)())
355 end
356 return primaries[componentType] ~= nil
357end
358
359function component.isPrimary(address)
360 local componentType = component.type(address)
361 if componentType then
362 if component.isAvailable(componentType) then
363 return primaries[componentType].address == address
364 end
365 end
366 return false
367end
368
369function component.getPrimary(componentType)
370 checkArg(1, componentType, "string")
371 assert(component.isAvailable(componentType),
372 "no primary '" .. componentType .. "' available")
373 return primaries[componentType]
374end
375
376function component.setPrimary(componentType, address)
377 checkArg(1, componentType, "string")
378 checkArg(2, address, "string", "nil")
379 if address ~= nil then
380 address = component.get(address, componentType)
381 assert(address, "no such component")
382 end
383
384 local wasAvailable = primaries[componentType]
385 if wasAvailable and address == wasAvailable.address then
386 return
387 end
388 local wasAdding = adding[componentType]
389 if wasAdding and address == wasAdding.address then
390 return
391 end
392 if wasAdding then
393 event.cancel(wasAdding.timer)
394 end
395 primaries[componentType] = nil
396 adding[componentType] = nil
397
398 local primary = address and component.proxy(address) or nil
399 if wasAvailable then
400 computer.pushSignal("component_unavailable", componentType)
401 end
402 if primary then
403 if wasAvailable or wasAdding then
404 adding[componentType] = {
405 address=address,
406 proxy = primary,
407 timer=event.timer(0.1, function()
408 adding[componentType] = nil
409 primaries[componentType] = primary
410 computer.pushSignal("component_available", componentType)
411 end)
412 }
413 else
414 primaries[componentType] = primary
415 computer.pushSignal("component_available", componentType)
416 end
417 end
418end
419
420-------------------------------------------------------------------------------
421
422local function onComponentAdded(_, address, componentType)
423 local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy)
424
425 if prev then
426 -- special handlers -- some components are just better at being primary
427 if componentType == "screen" then
428 --the primary has no keyboards but we do
429 if #prev.getKeyboards() == 0 then
430 local first_kb = component.invoke(address, 'getKeyboards')[1]
431 if first_kb then
432 -- just in case our kb failed to achieve primary
433 -- possible if existing primary keyboard became primary first without a screen
434 -- then prev (a screen) was added without a keyboard
435 -- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary
436 -- also, our kb may fire right after this, which is fine
437 component.setPrimary("keyboard", first_kb)
438 prev = nil -- nil meaning we should take this new one over the previous
439 end
440 end
441 elseif componentType == "keyboard" then
442 -- to reduce signal noise, if this kb is also the prev, we do not need to reset primary
443 if address ~= prev.address then
444 --keyboards never replace primary keyboards unless the are the only keyboard on the primary screen
445 local current_screen = primaries.screen or (adding.screen and adding.screen.proxy)
446 --if there is not yet a screen, do not use this keyboard, it's not any better
447 if current_screen then
448 -- the next phase is complicated
449 -- there is already a screen and there is already a keyboard
450 -- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not
451 -- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first
452 -- but if there are two kbs on a screen, we can give preferred treatment to the first
453 -- thus, assume 2nd is not attached for the purposes of primary kb
454 -- and THUS, whichever (if either) is the 1st kb of the current screen
455 -- this is only possible if
456 -- 1. the only kb on the system (current) has no screen
457 -- 2. a screen is added without a kb
458 -- 3. this kb is added later manually
459
460 -- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning
461 -- when addr is different, and thus it is not the primary keyboard, then we ignore this
462 -- keyboard, and keep the previous
463 -- prev is false means we should take this new keyboard
464 prev = address ~= current_screen.getKeyboards()[1]
465 end
466 end
467 end
468 end
469
470 if not prev then
471 component.setPrimary(componentType, address)
472 end
473end
474
475local function onComponentRemoved(_, address, componentType)
476 if primaries[componentType] and primaries[componentType].address == address or
477 adding[componentType] and adding[componentType].address == address
478 then
479 local next = component.list(componentType, true)()
480 component.setPrimary(componentType, next)
481
482 if componentType == "screen" and next then
483 -- setPrimary already set the proxy (if successful)
484 local proxy = (primaries.screen or (adding.screen and adding.screen.proxy))
485 if proxy then
486 -- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen
487 -- then the `next` screen, if it has a keyboard, should TAKE priority
488 local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often
489 local old_kb = primaries.keyboard or adding.keyboard
490 -- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are
491 -- if there was no previous kb, use the new one
492 if next_kb and (not old_kb or old_kb.address ~= next_kb) then
493 component.setPrimary("keyboard", next_kb)
494 end
495 end
496 end
497 end
498end
499
500event.listen("component_added", onComponentAdded)
501event.listen("component_removed", onComponentRemoved)
502
503if _G.boot_screen then
504 component.setPrimary("screen", _G.boot_screen)
505end
506_G.boot_screen = nil
507 mnt/6c1/boot/92_keyboard.lua 0000600 00000002215 13215223665 010271 0 local component = require("component")
508local event = require("event")
509local keyboard = require("keyboard")
510
511local function onKeyDown(_, address, char, code)
512 if keyboard.pressedChars[address] then
513 keyboard.pressedChars[address][char] = true
514 keyboard.pressedCodes[address][code] = true
515 end
516end
517
518local function onKeyUp(_, address, char, code)
519 if keyboard.pressedChars[address] then
520 keyboard.pressedChars[address][char] = nil
521 keyboard.pressedCodes[address][code] = nil
522 end
523end
524
525local function onComponentAdded(_, address, componentType)
526 if componentType == "keyboard" then
527 keyboard.pressedChars[address] = {}
528 keyboard.pressedCodes[address] = {}
529 end
530end
531
532local function onComponentRemoved(_, address, componentType)
533 if componentType == "keyboard" then
534 keyboard.pressedChars[address] = nil
535 keyboard.pressedCodes[address] = nil
536 end
537end
538
539for address in component.list("keyboard", true) do
540 onComponentAdded("component_added", address, "keyboard")
541end
542
543event.listen("key_down", onKeyDown)
544event.listen("key_up", onKeyUp)
545event.listen("component_added", onComponentAdded)
546event.listen("component_removed", onComponentRemoved)
547 mnt/6c1/boot/02_os.lua 0000600 00000002666 13215223663 007111 0 local computer = require("computer")
548local event = require("event")
549local fs = require("filesystem")
550local shell = require("shell")
551local info = require("process").info
552
553os.execute = function(command)
554 if not command then
555 return type(shell) == "table"
556 end
557 return shell.execute(command)
558end
559
560function os.exit(code)
561 error({reason="terminated", code=code}, 0)
562end
563
564function os.getenv(varname)
565 local env = info().data.vars
566 if not varname then
567 return env
568 elseif varname == '#' then
569 return #env
570 end
571 return env[varname]
572end
573
574function os.setenv(varname, value)
575 checkArg(1, varname, "string", "number")
576 if value ~= nil then
577 value = tostring(value)
578 end
579 info().data.vars[varname] = value
580 return value
581end
582
583os.remove = fs.remove
584os.rename = fs.rename
585
586function os.sleep(timeout)
587 checkArg(1, timeout, "number", "nil")
588 local deadline = computer.uptime() + (timeout or 0)
589 repeat
590 event.pull(deadline - computer.uptime())
591 until computer.uptime() >= deadline
592end
593
594function os.tmpname()
595 local path = os.getenv("TMPDIR") or "/tmp"
596 if fs.exists(path) then
597 for _ = 1, 10 do
598 local name = fs.concat(path, tostring(math.random(1, 0x7FFFFFFF)))
599 if not fs.exists(name) then
600 return name
601 end
602 end
603 end
604end
605
606os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
607os.setenv("TMP", "/tmp") -- Deprecated
608os.setenv("TMPDIR", "/tmp")
609
610if computer.tmpAddress() then
611 fs.mount(computer.tmpAddress(), "/tmp")
612end
613 mnt/6c1/boot/10_devfs.lua 0000600 00000000666 13215223665 007576 0 require("filesystem").mount(
614setmetatable({
615 address = "f5501a9b-9c23-1e7a-4afe-4b65eed9b88a"
616},
617{
618 __index=function(tbl,key)
619 local result =
620 ({
621 getLabel = "devfs",
622 spaceTotal = 0,
623 spaceUsed = 0,
624 isReadOnly = false,
625 })[key]
626
627 if result ~= nil then
628 return function() return result end
629 end
630 local lib = require("devfs")
631 lib.register(tbl)
632 return lib.proxy[key]
633 end
634}), "/dev")
635
636 mnt/6c1/boot 0000700 13215223665 004353 5 mnt/6c1/etc/filesystem.cfg 0000600 00000000015 13215224463 010122 0 autorun=false mnt/6c1/etc/boot 0000700 13215223666 005127 5 mnt/6c1/etc/profile.lua 0000600 00000002277 13215223667 007441 0 local shell = require("shell")
637local tty = require("tty")
638local fs = require("filesystem")
639
640shell.execute("echo profile.luatututututututu")
641
642if tty.isAvailable() then
643 if io.stdout.tty then
644 io.write("\27[40m\27[37m")
645 tty.clear()
646 end
647end
648dofile("/etc/motd")
649
650shell.setAlias("dir", "ls")
651shell.setAlias("move", "mv")
652shell.setAlias("rename", "mv")
653shell.setAlias("copy", "cp")
654shell.setAlias("del", "rm")
655shell.setAlias("md", "mkdir")
656shell.setAlias("cls", "clear")
657shell.setAlias("rs", "redstone")
658shell.setAlias("view", "edit -r")
659shell.setAlias("help", "man")
660shell.setAlias("cp", "cp -i")
661shell.setAlias("l", "ls -lhp")
662shell.setAlias("..", "cd ..")
663shell.setAlias("df", "df -h")
664shell.setAlias("grep", "grep --color")
665
666os.setenv("EDITOR", "/bin/edit")
667os.setenv("HISTSIZE", "10")
668os.setenv("HOME", "/")
669os.setenv("IFS", " ")
670os.setenv("MANPATH", "/usr/man:.")
671os.setenv("PAGER", "/bin/more")
672os.setenv("PS1", "\27[40m\27[31m$HOSTNAME$HOSTNAME_SEPARATOR$PWD # \27[37m")
673os.setenv("LS_COLORS", "di=0;36:fi=0:ln=0;33:*.lua=0;32")
674
675shell.setWorkingDirectory(os.getenv("HOME"))
676
677local home_shrc = shell.resolve(".shrc")
678if fs.exists(home_shrc) then
679 loadfile(shell.resolve("source", "lua"))(home_shrc)
680end mnt/6c1/etc/motd 0000600 00000002645 13215223667 006163 0 local component = require("component")
681local computer = require("computer")
682local unicode = require("unicode")
683local tty = require("tty")
684
685if not component.isAvailable("gpu") then
686 return
687end
688
689local f = io.open("/usr/misc/greetings.txt")
690local lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"}
691local greeting = ""
692if f then
693 local greetings = {}
694 pcall(function()
695 for line in f:lines() do table.insert(greetings, line) end
696 end)
697 f:close()
698 greeting = greetings[math.random(1, math.max(#greetings, 1))] or ""
699end
700local width = math.min(#greeting, tty.getViewport() - 5)
701local maxLine = #lines[1]
702while #greeting > 0 do
703 local si, ei = greeting:sub(1, width):find("%s%S*$")
704 local line = #greeting <= width and greeting or greeting:sub(1, si or width)
705 lines[#lines + 1] = line
706 maxLine = math.max(maxLine, #line)
707 greeting = greeting:sub(#line + 1)
708end
709
710local borders = {{unicode.char(0x2552), unicode.char(0x2550), unicode.char(0x2555)},
711 {unicode.char(0x2502), nil, unicode.char(0x2502)},
712 {unicode.char(0x2514), unicode.char(0x2500), unicode.char(0x2518)}}
713io.write(borders[1][1], string.rep(borders[1][2], maxLine + 2), borders[1][3], "\n")
714for _,line in ipairs(lines) do
715 io.write(borders[2][1], " ", line, (" "):rep(maxLine - #line + 1), borders[2][3], " \n")
716end
717io.write(borders[3][1] .. string.rep(borders[3][2], maxLine + 2) .. borders[3][3] .. "\n")
718 mnt/6c1/etc/edit.cfg 0000600 00000000700 13215223667 006671 0 keybinds={backspace={{"back"}},
719 close={{"control",
720 "w"}},
721 delete={{"delete"}},
722 deleteLine={{"control",
723 "delete"},
724 {"shift",
725 "delete"}},
726 down={{"down"}},
727 eol={{"end"}},
728 find={{"control",
729 "f"}},
730 findnext={{"control",
731 "g"},
732 {"control",
733 "n"},
734 {"f3"}},
735 home={{"home"}},
736 left={{"left"}},
737 newline={{"enter"}},
738 pageDown={{"pageDown"}},
739 pageUp={{"pageUp"}},
740 right={{"right"}},
741 save={{"control",
742 "s"}},
743 up={{"up"}}}
744 mnt/6c1/etc 0000700 13215223667 004165 5 mnt/6c1/init.lua 0000600 00000001750 13215224050 006147 0 do
745 local loadfile = load([[return function(file)
746 local pc,cp = computer or package.loaded.computer, component or package.loaded.component
747 local addr, invoke = pc.getBootAddress(), cp.invoke
748 local handle, reason = invoke(addr, "open", file)
749 assert(handle, reason)
750 local buffer = ""
751 repeat
752 local data, reason = invoke(addr, "read", handle, math.huge)
753 assert(data or not reason, reason)
754 buffer = buffer .. (data or "")
755 until not data
756 invoke(addr, "close", handle)
757 return load(buffer, "=" .. file, "bt", _G)
758 end]], "=loadfile", "bt", _G)()
759 loadfile("/lib/core/boot.lua")(loadfile)
760end
761
762while true do
763 local result, reason = xpcall(require("shell").getShell(), function(msg)
764 return tostring(msg).."\n"..debug.traceback()
765 end)
766 if not result then
767 io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
768 io.write("Press any key to continue.\n")
769 os.sleep(0.5)
770 require("event").pull("key")
771 end
772end
773 mnt/6c1/installAxeOS.lua 0000600 00000000323 13215226604 007554 0 local shell=require("shell")
774tArgs = { ... }
775local path={"bin","boot","etc","lib"}
776for i=1, 4 do
777shell.execute("cp -r /"..path[i].."/ /mnt/"..tArgs[1])
778end
779shell.execute("cp -r /init.lua /mnt/"..tArgs[1].."/") mnt/6c1/bin/ln.lua 0000600 00000001561 13215223701 006367 0 local component = require("component")
780local fs = require("filesystem")
781local shell = require("shell")
782
783local args = shell.parse(...)
784if #args == 0 then
785 io.write("Usage: ln <target> [<name>]\n")
786 return 1
787end
788
789local target_name = args[1]
790local target = shell.resolve(target_name)
791
792-- don't link from target if it doesn't exist, unless it is a broken link
793if not fs.exists(target) and not fs.isLink(target) then
794 io.stderr:write("ln: failed to access '" .. target_name .. "': No such file or directory\n")
795 return 1
796end
797
798local linkpath
799if #args > 1 then
800 linkpath = shell.resolve(args[2])
801else
802 linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
803end
804
805if fs.isDirectory(linkpath) then
806 linkpath = fs.concat(linkpath, fs.name(target))
807end
808
809local result, reason = fs.link(target_name, linkpath)
810if not result then
811 io.stderr:write(reason..'\n')
812 return 1
813end
814 mnt/6c1/bin/flash.lua 0000600 00000004560 13215223670 007062 0 local component = require("component")
815local shell = require("shell")
816local fs = require("filesystem")
817
818local args, options = shell.parse(...)
819
820if #args < 1 and not options.l then
821 io.write("Usage: flash [-qlr] [<bios.lua>] [label]\n")
822 io.write(" q: quiet mode, don't ask questions.\n")
823 io.write(" l: print current contents of installed EEPROM.\n")
824 io.write(" r: save the current contents of installed EEPROM to file.\n")
825 return
826end
827
828local function printRom()
829 local eeprom = component.eeprom
830 io.write(eeprom.get())
831end
832
833local function readRom()
834 local eeprom = component.eeprom
835 fileName = shell.resolve(args[1])
836 if not options.q then
837 if fs.exists(fileName) then
838 io.write("Are you sure you want to overwrite " .. fileName .. "?\n")
839 io.write("Type `y` to confirm.\n")
840 repeat
841 local response = io.read()
842 until response and response:lower():sub(1, 1) == "y"
843 end
844 io.write("Reading EEPROM " .. eeprom.address .. ".\n" )
845 end
846 local bios = eeprom.get()
847 local file = assert(io.open(fileName, "wb"))
848 file:write(bios)
849 file:close()
850 if not options.q then
851 io.write("All done!\nThe label is '" .. eeprom.getLabel() .. "'.\n")
852 end
853end
854
855local function writeRom()
856 local file = assert(io.open(args[1], "rb"))
857 local bios = file:read("*a")
858 file:close()
859
860 if not options.q then
861 io.write("Insert the EEPROM you would like to flash.\n")
862 io.write("When ready to write, type `y` to confirm.\n")
863 repeat
864 local response = io.read()
865 until response and response:lower():sub(1, 1) == "y"
866 io.write("Beginning to flash EEPROM.\n")
867 end
868
869 local eeprom = component.eeprom
870
871 if not options.q then
872 io.write("Flashing EEPROM " .. eeprom.address .. ".\n")
873 io.write("Please do NOT power down or restart your computer during this operation!\n")
874 end
875
876 eeprom.set(bios)
877
878 local label = args[2]
879 if not options.q and not label then
880 io.write("Enter new label for this EEPROM. Leave input blank to leave the label unchanged.\n")
881 label = io.read()
882 end
883 if label and #label > 0 then
884 eeprom.setLabel(label)
885 if not options.q then
886 io.write("Set label to '" .. eeprom.getLabel() .. "'.\n")
887 end
888 end
889
890 if not options.q then
891 io.write("All done! You can remove the EEPROM and re-insert the previous one now.\n")
892 end
893end
894
895if options.l then
896 printRom()
897elseif options.r then
898 readRom()
899else
900 writeRom()
901end
902 mnt/6c1/bin/cd.lua 0000600 00000001752 13215223671 006354 0 local shell = require("shell")
903local fs = require("filesystem")
904
905local args, ops = shell.parse(...)
906local path = nil
907local verbose = false
908
909if ops.help then
910 print(
911[[Usage cd [dir]
912For more options, run: man cd]])
913 return
914end
915
916if #args == 0 then
917 local home = os.getenv("HOME")
918 if not home then
919 io.stderr:write("cd: HOME not set\n")
920 return 1
921 end
922 path = home
923elseif args[1] == '-' then
924 verbose = true
925 local oldpwd = os.getenv("OLDPWD");
926 if not oldpwd then
927 io.stderr:write("cd: OLDPWD not set\n")
928 return 1
929 end
930 path = oldpwd
931else
932 path = args[1]
933end
934
935local resolved = shell.resolve(path)
936if not fs.exists(resolved) then
937 io.stderr:write("cd: ",path,": No such file or directory\n")
938 return 1
939end
940
941path = resolved
942local oldpwd = shell.getWorkingDirectory()
943local result, reason = shell.setWorkingDirectory(path)
944if not result then
945 io.stderr:write("cd: ", path, ": ", reason)
946 return 1
947else
948 os.setenv("OLDPWD", oldpwd)
949end
950if verbose then
951 os.execute("pwd")
952end
953 mnt/6c1/bin/time.lua 0000600 00000001034 13215223674 006720 0 local computer = require('computer')
954local sh = require('sh')
955
956local real_before, cpu_before = computer.uptime(), os.clock()
957local cmd_result = 0
958if ... then
959 sh.execute(nil, ...)
960 cmd_result = sh.getLastExitCode()
961end
962local real_after, cpu_after = computer.uptime(), os.clock()
963
964local real_diff = real_after - real_before
965local cpu_diff = cpu_after - cpu_before
966
967print(string.format('real%5dm%.3fs', math.floor(real_diff/60), real_diff%60))
968print(string.format('cpu %5dm%.3fs', math.floor(cpu_diff/60), cpu_diff%60))
969
970return cmd_result
971 mnt/6c1/bin/hostname.lua 0000600 00000001237 13215223672 007603 0 local shell = require("shell")
972local args, ops = shell.parse(...)
973local hostname = args[1]
974
975if hostname then
976 local file, reason = io.open("/etc/hostname", "w")
977 if not file then
978 io.stderr:write("failed to open for writing: ", reason, "\n")
979 return 1
980 end
981 file:write(hostname)
982 file:close()
983 ops.update = true
984else
985 local file = io.open("/etc/hostname")
986 if file then
987 hostname = file:read("*l")
988 file:close()
989 end
990end
991
992if ops.update then
993 os.setenv("HOSTNAME_SEPARATOR", hostname and #hostname > 0 and ":" or "")
994 os.setenv("HOSTNAME", hostname)
995elseif hostname then
996 print(hostname)
997else
998 io.stderr:write("Hostname not set\n")
999 return 1
1000end
1001 mnt/6c1/bin/date.lua 0000600 00000000041 13215223704 006666 0 io.write(os.date("%F %T").."\n")
1002 mnt/6c1/bin/touch.lua 0000600 00000002431 13215223711 007076 0 --[[Lua implementation of the UN*X touch command--]]
1003local shell = require("shell")
1004local fs = require("filesystem")
1005
1006local args, options = shell.parse(...)
1007
1008local function usage()
1009 print(
1010[[Usage: touch [OPTION]... FILE...
1011Update the modification times of each FILE to the current time.
1012A FILE argument that does not exist is created empty, unless -c is supplied.
1013
1014 -c, --no-create do not create any files
1015 --help display this help and exit]])
1016end
1017
1018if options.help then
1019 usage()
1020 return 0
1021elseif #args == 0 then
1022 io.stderr:write("touch: missing operand\n")
1023 return 1
1024end
1025
1026options.c = options.c or options["no-create"]
1027local errors = 0
1028
1029for _,arg in ipairs(args) do
1030 local path = shell.resolve(arg)
1031
1032 if fs.isDirectory(path) then
1033 io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg))
1034 else
1035 local real, reason = fs.realPath(path)
1036 if real then
1037 local file
1038 if fs.exists(real) or not options.c then
1039 file = io.open(real, "a")
1040 end
1041 if not file then
1042 real = options.c
1043 reason = "permission denied"
1044 else
1045 file:close()
1046 end
1047 end
1048 if not real then
1049 io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason))
1050 errors = 1
1051 end
1052 end
1053end
1054
1055return errors
1056 mnt/6c1/bin/primary.lua 0000600 00000001327 13215223702 007442 0 local component = require("component")
1057local shell = require("shell")
1058
1059local args = shell.parse(...)
1060if #args == 0 then
1061 io.write("Usage: primary <type> [<address>]\n")
1062 io.write("Note that the address may be abbreviated.\n")
1063 return 1
1064end
1065
1066local componentType = args[1]
1067
1068if #args > 1 then
1069 local address = args[2]
1070 if not component.get(address) then
1071 io.stderr:write("no component with this address\n")
1072 return 1
1073 else
1074 component.setPrimary(componentType, address)
1075 os.sleep(0.1) -- allow signals to be processed
1076 end
1077end
1078if component.isAvailable(componentType) then
1079 io.write(component.getPrimary(componentType).address, "\n")
1080else
1081 io.stderr:write("no primary component for this type\n")
1082 return 1
1083end
1084 mnt/6c1/bin/echo.lua 0000600 00000001302 13215223706 006672 0 local args, options = require("shell").parse(...)
1085if options.help then
1086 io.write([[
1087`echo` writes the provided string(s) to the standard output.
1088 -n do not output the trialing newline
1089 -e enable interpretation of backslash escapes
1090 --help display this help and exit
1091]])
1092 return
1093end
1094if options.e then
1095 for index,arg in ipairs(args) do
1096 -- use lua load here to interpret escape sequences such as \27
1097 -- instead of writing my own language to interpret them myself
1098 -- note that in a real terminal, \e is used for \27
1099 args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))()
1100 end
1101end
1102io.write(table.concat(args," "))
1103if not options.n then
1104 io.write("\n")
1105end
1106 mnt/6c1/bin/unalias.lua 0000600 00000000561 13215223711 007412 0 local shell = require("shell")
1107
1108local args = shell.parse(...)
1109if #args < 1 then
1110 io.write("Usage: unalias <name>...\n")
1111 return 2
1112end
1113local e = 0
1114
1115for _,arg in ipairs(args) do
1116 local result = shell.getAlias(arg)
1117 if not result then
1118 io.stderr:write(string.format("unalias: %s: not found\n", arg))
1119 e = 1
1120 else
1121 shell.setAlias(arg, nil)
1122 end
1123end
1124return e
1125 mnt/6c1/bin/df.lua 0000600 00000003573 13215223672 006363 0 local fs = require("filesystem")
1126local shell = require("shell")
1127local text = require("text")
1128
1129local args, options = shell.parse(...)
1130
1131local function formatSize(size)
1132 if not options.h then
1133 return tostring(size)
1134 elseif type(size) == "string" then
1135 return size
1136 end
1137 local sizes = {"", "K", "M", "G"}
1138 local unit = 1
1139 local power = options.si and 1000 or 1024
1140 while size > power and unit < #sizes do
1141 unit = unit + 1
1142 size = size / power
1143 end
1144 return math.floor(size * 10) / 10 .. sizes[unit]
1145end
1146
1147local mounts = {}
1148if #args == 0 then
1149 for proxy, path in fs.mounts() do
1150 if not mounts[proxy] or mounts[proxy]:len() > path:len() then
1151 mounts[proxy] = path
1152 end
1153 end
1154else
1155 for i = 1, #args do
1156 local proxy, path = fs.get(shell.resolve(args[i]))
1157 if not proxy then
1158 io.stderr:write(args[i], ": no such file or directory\n")
1159 else
1160 mounts[proxy] = path
1161 end
1162 end
1163end
1164
1165local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}}
1166for proxy, path in pairs(mounts) do
1167 local label = proxy.getLabel() or proxy.address
1168 local used, total = proxy.spaceUsed(), proxy.spaceTotal()
1169 local available, percent
1170 if total == math.huge then
1171 used = used or "N/A"
1172 available = "unlimited"
1173 percent = "0%"
1174 else
1175 available = total - used
1176 percent = used / total
1177 if percent ~= percent then -- NaN
1178 available = "N/A"
1179 percent = "N/A"
1180 else
1181 percent = math.ceil(percent * 100) .. "%"
1182 end
1183 end
1184 table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path})
1185end
1186
1187local m = {}
1188for _, row in ipairs(result) do
1189 for col, value in ipairs(row) do
1190 m[col] = math.max(m[col] or 1, value:len())
1191 end
1192end
1193
1194for _, row in ipairs(result) do
1195 for col, value in ipairs(row) do
1196 local padding = col == #row and 0 or 2
1197 io.write(text.padRight(value, m[col] + padding))
1198 end
1199 print()
1200end
1201 mnt/6c1/bin/install.lua 0000600 00000002202 13215223706 007422 0 local computer = require("computer")
1202local shell = require("shell")
1203
1204local options
1205
1206do
1207 local basic, reason = loadfile("/lib/core/install_basics.lua", "bt", _G)
1208 if not basic then
1209 io.stderr:write("failed to load install: " .. tostring(reason) .. "\n")
1210 return 1
1211 end
1212 options = basic(...)
1213end
1214
1215if not options then return end
1216
1217if computer.freeMemory() < 50000 then
1218 print("Low memory, collecting garbage")
1219 for i=1,20 do os.sleep(0) end
1220end
1221
1222local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G)
1223assert(cp, reason)
1224
1225local ok, ec = pcall(cp, table.unpack(options.cp_args))
1226assert(ok, ec)
1227
1228if ec ~= nil and ec ~= 0 then
1229 return ec
1230end
1231
1232print("Installation complete!")
1233
1234if options.setlabel then
1235 pcall(options.target.dev.setLabel, options.label)
1236end
1237
1238if options.setboot then
1239 local address = options.target.dev.address
1240 if computer.setBootAddress(address) then
1241 print("Boot address set to " .. address)
1242 end
1243end
1244
1245if options.reboot then
1246 io.write("Reboot now? [Y/n] ")
1247 if ((io.read() or "n").."y"):match("^%s*[Yy]") then
1248 print("\nRebooting now!\n")
1249 computer.shutdown(true)
1250 end
1251end
1252
1253print("Returning to shell.\n")
1254 mnt/6c1/bin/rmdir.lua 0000600 00000005155 13215223674 007107 0 local shell = require("shell")
1255local fs = require("filesystem")
1256local text = require("text")
1257
1258local args, options = shell.parse(...)
1259
1260local function usage()
1261 print(
1262[[Usage: rmdir [OPTION]... DIRECTORY...
1263Removes the DIRECTORY(ies), if they are empty.
1264
1265 -q, --ignore-fail-on-non-empty
1266 ignore failures due solely to non-empty directories
1267 -p, --parents remove DIRECTORY and its empty ancestors
1268 e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'
1269 -v, --verbose output a diagnostic for every directory processed
1270 --help display this help and exit]])
1271end
1272
1273if options.help then
1274 usage()
1275 return 0
1276end
1277
1278if #args == 0 then
1279 io.stderr:write("rmdir: missing operand\n")
1280 return 1
1281end
1282
1283options.p = options.p or options.parents
1284options.v = options.v or options.verbose
1285options.q = options.q or options['ignore-fail-on-non-empty']
1286
1287local ec = 0
1288local function ec_bump()
1289 ec = 1
1290 return 1
1291end
1292
1293local function remove(path, ...)
1294 -- check to end recursion
1295 if path == nil then
1296 return true
1297 end
1298
1299 if options.v then
1300 print(string.format('rmdir: removing directory, %s', path))
1301 end
1302
1303 local rpath = shell.resolve(path)
1304 if path == '.' then
1305 io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n')
1306 return ec_bump()
1307 elseif not fs.exists(rpath) then
1308 io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n")
1309 return ec_bump()
1310 elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then
1311 io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n")
1312 return ec_bump()
1313 else
1314 local list, reason = fs.list(rpath)
1315
1316 if not list then
1317 io.stderr:write(tostring(reason)..'\n')
1318 return ec_bump()
1319 else
1320 if list() then
1321 if not options.q then
1322 io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n")
1323 end
1324 return ec_bump()
1325 else
1326 -- path exists and is empty?
1327 local ok, reason = fs.remove(rpath)
1328 if not ok then
1329 io.stderr:write(tostring(reason)..'\n')
1330 return ec_bump(), reason
1331 end
1332 return remove(...) -- the final return of all else
1333 end
1334 end
1335 end
1336end
1337
1338for _,path in ipairs(args) do
1339 -- clean up the input
1340 path = path:gsub('/+', '/')
1341
1342 local segments = {}
1343 if options.p and path:len() > 1 and path:find('/') then
1344 chain = text.split(path, {'/'}, true)
1345 local prefix = ''
1346 for _,e in ipairs(chain) do
1347 table.insert(segments, 1, prefix .. e)
1348 prefix = prefix .. e .. '/'
1349 end
1350 else
1351 segments = {path}
1352 end
1353
1354 remove(table.unpack(segments))
1355end
1356
1357return ec
1358 mnt/6c1/bin/mv.lua 0000600 00000001540 13215223671 006403 0 local shell = require("shell")
1359local transfer = require("tools/transfer")
1360
1361local args, options = shell.parse(...)
1362options.h = options.h or options.help
1363if #args < 2 or options.h then
1364 io.write([[Usage: mv [OPTIONS] <from> <to>
1365 -f overwrite without prompt
1366 -i prompt before overwriting
1367 unless -f
1368 -v verbose
1369 -n do not overwrite an existing file
1370 --skip=P ignore paths matching lua regex P
1371 -h, --help show this help
1372]])
1373 return not not options.h
1374end
1375
1376-- clean options for move (as opposed to copy)
1377options =
1378{
1379 cmd = "mv",
1380 f = options.f,
1381 i = options.i,
1382 v = options.v,
1383 n = options.n, -- no clobber
1384 skip = options.skip,
1385 P = true, -- move operations always preserve
1386 r = true, -- move is allowed to move entire dirs
1387 x = true, -- cannot move mount points
1388}
1389
1390return transfer.batch(args, options)
1391 mnt/6c1/bin/lua.lua 0000600 00000001254 13215223703 006540 0 local shell = require("shell")
1392local args = shell.parse(...)
1393
1394if #args == 0 then
1395 args = {"/lib/core/lua_shell.lua"}
1396end
1397
1398local filename = args[1]
1399local buffer, script, reason
1400buffer = io.lines(filename, "*a")()
1401if buffer then
1402 buffer = buffer:gsub("^#![^\n]+", "") -- remove shebang if any
1403 script, reason = load(buffer, "="..filename)
1404else
1405 reason = string.format("could not open %s for reading", filename)
1406end
1407
1408if not script then
1409 io.stderr:write(tostring(reason) .. "\n")
1410 os.exit(false)
1411end
1412
1413buffer, reason = pcall(script, table.unpack(args, 2))
1414if not buffer then
1415 io.stderr:write(type(reason) == "table" and reason.reason or tostring(reason), "\n")
1416 os.exit(false)
1417end
1418 mnt/6c1/bin/find.lua 0000600 00000005606 13215223674 006713 0 local shell = require("shell")
1419local fs = require("filesystem")
1420local text = require("text")
1421
1422local USAGE =
1423[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR]
1424 --path if not specified, path is assumed to be current working directory
1425 --type returns results of a given type, d:directory, f:file, and s:symlinks
1426 --name specify the file name pattern. Use quote to include *. iname is
1427 case insensitive
1428 --help display this help and exit]===]
1429
1430local args, options = shell.parse(...)
1431
1432if (not args or not options) or options.help then
1433 print(USAGE)
1434 if not options.help then
1435 return 1
1436 else
1437 return -- nil return, meaning no error
1438 end
1439end
1440
1441if #args > 1 then
1442 io.stderr:write(USAGE..'\n')
1443 return 1
1444end
1445
1446local path = #args == 1 and args[1] or "."
1447
1448local bDirs = true
1449local bFiles = true
1450local bSyms = true
1451
1452local fileNamePattern = ""
1453local bCaseSensitive = true
1454
1455if options.iname and options.name then
1456 io.stderr:write("find cannot define both iname and name\n")
1457 return 1
1458end
1459
1460if options.type then
1461 bDirs = false
1462 bFiles = false
1463 bSyms = false
1464
1465 if options.type == "f" then
1466 bFiles = true
1467 elseif options.type == "d" then
1468 bDirs = true
1469 elseif options.type == "s" then
1470 bSyms = true
1471 else
1472 io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type))
1473 io.stderr:write(USAGE..'\n')
1474 return 1
1475 end
1476end
1477
1478if options.iname or options.name then
1479 bCaseSensitive = options.iname ~= nil
1480 fileNamePattern = options.iname or options.name
1481
1482 if type(fileNamePattern) ~= "string" then
1483 io.stderr:write('find: missing argument to `name\'\n')
1484 return 1
1485 end
1486
1487 if not bCaseSensitive then
1488 fileNamePattern = fileNamePattern:lower()
1489 end
1490
1491 -- prefix any * with . for gnu find glob matching
1492 fileNamePattern = text.escapeMagic(fileNamePattern)
1493 fileNamePattern = fileNamePattern:gsub("%%%*", ".*")
1494end
1495
1496local function isValidType(spath)
1497 if not fs.exists(spath) then
1498 return false
1499 end
1500
1501 if fileNamePattern:len() > 0 then
1502 local fileName = spath:gsub('.*/','')
1503
1504 if fileName:len() == 0 then
1505 return false
1506 end
1507
1508 local caseFileName = fileName
1509
1510 if not bCaseSensitive then
1511 caseFileName = caseFileName:lower()
1512 end
1513
1514 local s, e = caseFileName:find(fileNamePattern)
1515 if not s or not e then
1516 return false
1517 end
1518
1519 if s ~= 1 or e ~= caseFileName:len() then
1520 return false
1521 end
1522 end
1523
1524 if fs.isDirectory(spath) then
1525 return bDirs
1526 elseif fs.isLink(spath) then
1527 return bSyms
1528 else
1529 return bFiles
1530 end
1531end
1532
1533local function visit(rpath)
1534 local spath = shell.resolve(rpath)
1535
1536 if isValidType(spath) then
1537 local result = rpath:gsub('/+$','')
1538 print(result)
1539 end
1540
1541 if fs.isDirectory(spath) then
1542 local list_result = fs.list(spath)
1543 for list_item in list_result do
1544 visit(rpath:gsub('/+$', '') .. '/' .. list_item)
1545 end
1546 end
1547end
1548
1549visit(path)
1550 mnt/6c1/bin/mkdir.lua 0000600 00000001205 13215223677 007073 0 local fs = require("filesystem")
1551local shell = require("shell")
1552
1553local args = shell.parse(...)
1554if #args == 0 then
1555 io.write("Usage: mkdir <dirname1> [<dirname2> [...]]\n")
1556 return 1
1557end
1558
1559local ec = 0
1560for i = 1, #args do
1561 local path = shell.resolve(args[i])
1562 local result, reason = fs.makeDirectory(path)
1563 if not result then
1564 if not reason then
1565 if fs.exists(path) then
1566 reason = "file or folder with that name already exists"
1567 else
1568 reason = "unknown reason"
1569 end
1570 end
1571 io.stderr:write("mkdir: cannot create directory '" .. tostring(args[i]) .. "': " .. reason .. "\n")
1572 ec = 1
1573 end
1574end
1575
1576return ec
1577 mnt/6c1/bin/source.lua 0000600 00000001567 13215223701 007264 0 local shell = require("shell")
1578local process = require("process")
1579
1580local args, options = shell.parse(...)
1581
1582if #args ~= 1 then
1583 io.stderr:write("specify a single file to source\n");
1584 return 1
1585end
1586
1587local file, open_reason = io.open(args[1], "r")
1588
1589if not file then
1590 if not options.q then
1591 io.stderr:write(string.format("could not source %s because: %s\n", args[1], open_reason));
1592 end
1593 return 1
1594end
1595
1596local lines = file:lines()
1597
1598while true do
1599 local line = lines()
1600 if not line then
1601 break
1602 end
1603 local current_data = process.info().data
1604
1605 local source_proc = process.load((assert(os.getenv("SHELL"), "no $SHELL set")))
1606 local source_data = process.list[source_proc].data
1607 source_data.aliases = current_data.aliases -- hacks to propogate sub shell env changes
1608 source_data.vars = current_data.vars
1609 process.internal.continue(source_proc, _ENV, line)
1610end
1611
1612file:close()
1613 mnt/6c1/bin/redstone.lua 0000600 00000005454 13215223677 007622 0 local colors = require("colors")
1614local component = require("component")
1615local shell = require("shell")
1616local sides = require("sides")
1617
1618if not component.isAvailable("redstone") then
1619 io.stderr:write("This program requires a redstone card or redstone I/O block.\n")
1620 return 1
1621end
1622local rs = component.redstone
1623
1624local args, options = shell.parse(...)
1625if #args == 0 and not options.w and not options.f then
1626 io.write("Usage:\n")
1627 io.write(" redstone <side> [<value>]\n")
1628 if rs.setBundledOutput then
1629 io.write(" redstone -b <side> <color> [<value>]\n")
1630 end
1631 if rs.setWirelessOutput then
1632 io.write(" redstone -w [<value>]\n")
1633 io.write(" redstone -f [<frequency>]\n")
1634 end
1635 return
1636end
1637
1638if options.w then
1639 if not rs.setWirelessOutput then
1640 io.stderr:write("wireless redstone not available\n")
1641 return 1
1642 end
1643 if #args > 0 then
1644 local value = args[1]
1645 if tonumber(value) then
1646 value = tonumber(value) > 0
1647 else
1648 value = ({["true"]=true,["on"]=true,["yes"]=true})[value] ~= nil
1649 end
1650 rs.setWirelessOutput(value)
1651 end
1652 io.write("in: " .. tostring(rs.getWirelessInput()) .. "\n")
1653 io.write("out: " .. tostring(rs.getWirelessOutput()) .. "\n")
1654elseif options.f then
1655 if not rs.setWirelessOutput then
1656 io.stderr:write("wireless redstone not available\n")
1657 return 1
1658 end
1659 if #args > 0 then
1660 local value = args[1]
1661 if not tonumber(value) then
1662 io.stderr:write("invalid frequency\n")
1663 return 1
1664 end
1665 rs.setWirelessFrequency(tonumber(value))
1666 end
1667 io.write("freq: " .. tostring(rs.getWirelessFrequency()) .. "\n")
1668else
1669 local side = sides[args[1]]
1670 if not side then
1671 io.stderr:write("invalid side\n")
1672 return 1
1673 end
1674 if type(side) == "string" then
1675 side = sides[side]
1676 end
1677
1678 if options.b then
1679 if not rs.setBundledOutput then
1680 io.stderr:write("bundled redstone not available\n")
1681 return 1
1682 end
1683 local color = colors[args[2]]
1684 if not color then
1685 io.stderr:write("invalid color\n")
1686 return 1
1687 end
1688 if type(color) == "string" then
1689 color = colors[color]
1690 end
1691 if #args > 2 then
1692 local value = args[3]
1693 if tonumber(value) then
1694 value = tonumber(value)
1695 else
1696 value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 255 or 0
1697 end
1698 rs.setBundledOutput(side, color, value)
1699 end
1700 io.write("in: " .. rs.getBundledInput(side, color) .. "\n")
1701 io.write("out: " .. rs.getBundledOutput(side, color) .. "\n")
1702 else
1703 if #args > 1 then
1704 local value = args[2]
1705 if tonumber(value) then
1706 value = tonumber(value)
1707 else
1708 value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0
1709 end
1710 rs.setOutput(side, value)
1711 end
1712 io.write("in: " .. rs.getInput(side) .. "\n")
1713 io.write("out: " .. rs.getOutput(side) .. "\n")
1714 end
1715end
1716 mnt/6c1/bin/reboot.lua 0000600 00000000126 13215223706 007251 0 local computer = require("computer")
1717
1718io.write("Rebooting...")
1719computer.shutdown(true) mnt/6c1/bin/wget.lua 0000600 00000005620 13215223672 006733 0 local component = require("component")
1720local fs = require("filesystem")
1721local internet = require("internet")
1722local shell = require("shell")
1723local text = require("text")
1724
1725if not component.isAvailable("internet") then
1726 io.stderr:write("This program requires an internet card to run.")
1727 return
1728end
1729
1730local args, options = shell.parse(...)
1731options.q = options.q or options.Q
1732
1733if #args < 1 then
1734 io.write("Usage: wget [-fq] <url> [<filename>]\n")
1735 io.write(" -f: Force overwriting existing files.\n")
1736 io.write(" -q: Quiet mode - no status messages.\n")
1737 io.write(" -Q: Superquiet mode - no error messages.")
1738 return
1739end
1740
1741local url = text.trim(args[1])
1742local filename = args[2]
1743if not filename then
1744 filename = url
1745 local index = string.find(filename, "/[^/]*$")
1746 if index then
1747 filename = string.sub(filename, index + 1)
1748 end
1749 index = string.find(filename, "?", 1, true)
1750 if index then
1751 filename = string.sub(filename, 1, index - 1)
1752 end
1753end
1754filename = text.trim(filename)
1755if filename == "" then
1756 if not options.Q then
1757 io.stderr:write("could not infer filename, please specify one")
1758 end
1759 return nil, "missing target filename" -- for programs using wget as a function
1760end
1761filename = shell.resolve(filename)
1762
1763local preexisted
1764if fs.exists(filename) then
1765 preexisted = true
1766 if not options.f then
1767 if not options.Q then
1768 io.stderr:write("file already exists")
1769 end
1770 return nil, "file already exists" -- for programs using wget as a function
1771 end
1772end
1773
1774local f, reason = io.open(filename, "a")
1775if not f then
1776 if not options.Q then
1777 io.stderr:write("failed opening file for writing: " .. reason)
1778 end
1779 return nil, "failed opening file for writing: " .. reason -- for programs using wget as a function
1780end
1781f:close()
1782f = nil
1783
1784if not options.q then
1785 io.write("Downloading... ")
1786end
1787local result, response = pcall(internet.request, url)
1788if result then
1789 local result, reason = pcall(function()
1790 for chunk in response do
1791 if not f then
1792 f, reason = io.open(filename, "wb")
1793 assert(f, "failed opening file for writing: " .. tostring(reason))
1794 end
1795 f:write(chunk)
1796 end
1797 end)
1798 if not result then
1799 if not options.q then
1800 io.stderr:write("failed.\n")
1801 end
1802 if f then
1803 f:close()
1804 if not preexisted then
1805 fs.remove(filename)
1806 end
1807 end
1808 if not options.Q then
1809 io.stderr:write("HTTP request failed: " .. reason .. "\n")
1810 end
1811 return nil, reason -- for programs using wget as a function
1812 end
1813 if not options.q then
1814 io.write("success.\n")
1815 end
1816
1817 if f then
1818 f:close()
1819 end
1820
1821 if not options.q then
1822 io.write("Saved data to " .. filename .. "\n")
1823 end
1824else
1825 if not options.q then
1826 io.write("failed.\n")
1827 end
1828 if not options.Q then
1829 io.stderr:write("HTTP request failed: " .. response .. "\n")
1830 end
1831 return nil, response -- for programs using wget as a function
1832end
1833return true -- for programs using wget as a function
1834 mnt/6c1/bin/alias.lua 0000600 00000002415 13215223706 007053 0 local shell = require("shell")
1835local args, options = shell.parse(...)
1836
1837local ec, error_prefix = 0, "alias:"
1838
1839if options.help then
1840 print(string.format("Usage: alias: [name[=value] ... ]"))
1841 return
1842end
1843
1844local function validAliasName(k)
1845 return k:match("[/%$`=|&;%(%)<> \t]") == nil
1846end
1847
1848local function setAlias(k, v)
1849 if not validAliasName(k) then
1850 io.stderr:write(string.format("%s `%s': invalid alias name\n", error_prefix, k))
1851 else
1852 shell.setAlias(k, v)
1853 end
1854end
1855
1856local function printAlias(k)
1857 local v = shell.getAlias(k)
1858 if not v then
1859 io.stderr:write(string.format("%s %s: not found\n", error_prefix, k))
1860 ec = 1
1861 else
1862 io.write(string.format("alias %s='%s'\n", k, v))
1863 end
1864end
1865
1866local function splitPair(arg)
1867 local matchBegin, matchEnd = arg:find("=")
1868 if matchBegin == nil or matchBegin == 1 then
1869 return arg
1870 else
1871 return arg:sub(1, matchBegin - 1), arg:sub(matchEnd + 1)
1872 end
1873end
1874
1875local function handlePair(k, v)
1876 if v then
1877 return setAlias(k, v)
1878 else
1879 return printAlias(k)
1880 end
1881end
1882
1883if not next(args) then -- no args
1884 -- print all aliases
1885 for k,v in shell.aliases() do
1886 print(string.format("alias %s='%s'", k, v))
1887 end
1888else
1889 for _,v in ipairs(args) do
1890 checkArg(1,v,"string")
1891 handlePair(splitPair(v))
1892 end
1893end
1894
1895return ec
1896 mnt/6c1/bin/umount.lua 0000600 00000001473 13215223700 007306 0 local fs = require("filesystem")
1897local shell = require("shell")
1898
1899local args, options = shell.parse(...)
1900
1901if #args < 1 then
1902 io.write("Usage: umount [-a] <mount>\n")
1903 io.write(" -a Remove any mounts by file system label or address instead of by path. Note that the address may be abbreviated.\n")
1904 return 1
1905end
1906
1907local proxy, reason
1908if options.a then
1909 proxy, reason = fs.proxy(args[1])
1910 if proxy then
1911 proxy = proxy.address
1912 end
1913else
1914 local path = shell.resolve(args[1])
1915 proxy, reason = fs.get(path)
1916 if proxy then
1917 proxy = reason -- = path
1918 if proxy ~= path then
1919 io.stderr:write("not a mount point\n")
1920 return 1
1921 end
1922 end
1923end
1924if not proxy then
1925 io.stderr:write(tostring(reason)..'\n')
1926 return 1
1927end
1928
1929if not fs.umount(proxy) then
1930 io.stderr:write("nothing to unmount here\n")
1931 return 1
1932end
1933 mnt/6c1/bin/sh.lua 0000600 00000002600 13215223711 006364 0 local event = require("event")
1934local shell = require("shell")
1935local tty = require("tty")
1936local text = require("text")
1937local sh = require("sh")
1938
1939local args, options = shell.parse(...)
1940
1941shell.prime()
1942local needs_profile = io.input().tty
1943local has_prompt = needs_profile and io.output().tty and not options.c
1944local input_handler = {hint = sh.hintHandler}
1945
1946if #args == 0 then
1947 while true do
1948 if has_prompt then
1949 while not tty.isAvailable() do
1950 event.pull("term_available")
1951 end
1952 if needs_profile then -- first time run AND interactive
1953 needs_profile = nil
1954 dofile("/etc/profile.lua")
1955 end
1956 io.write(sh.expand(os.getenv("PS1") or "$ "))
1957 end
1958 local command = tty.read(input_handler)
1959 if command then
1960 command = text.trim(command)
1961 if command == "exit" then
1962 return
1963 elseif command ~= "" then
1964 local result, reason = sh.execute(_ENV, command)
1965 if not result then
1966 io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n")
1967 end
1968 end
1969 elseif command == nil then -- false only means the input was interrupted
1970 return -- eof
1971 end
1972 if has_prompt and tty.getCursor() > 1 then
1973 io.write("\n")
1974 end
1975 end
1976else
1977 -- execute command.
1978 local result = table.pack(sh.execute(...))
1979 if not result[1] then
1980 error(result[2], 0)
1981 end
1982 return table.unpack(result, 2)
1983end
1984 mnt/6c1/bin/more.lua 0000600 00000003661 13215223677 006737 0 local buffer = require("buffer")
1985local keyboard = require("keyboard")
1986local shell = require("shell")
1987local tty = require("tty")
1988
1989local args = shell.parse(...)
1990if #args > 1 then
1991 io.write("Usage: more <filename>\n")
1992 io.write("- or no args reads stdin\n")
1993 return 1
1994end
1995
1996local function clear_line()
1997 tty.window.x = 1 -- move cursor to start of line
1998 io.write("\27[2K") -- clear line
1999end
2000
2001if io.output().tty then
2002 io.write("\27[2J\27[H")
2003
2004 local intercept_active = true
2005 local original_stream = io.stdout.stream
2006 local custom_stream = setmetatable({
2007 scroll = function(...)
2008 local _, height, _, _, _, y = tty.getViewport()
2009 local lines_below = height - y
2010 if intercept_active and lines_below < 1 then
2011 intercept_active = false
2012 original_stream.scroll(-lines_below) -- if zero no scroll action is made [good]
2013 tty.setCursor(1, height) -- move to end
2014 clear_line()
2015 io.write(":") -- status
2016 local _, _, _, code = original_stream:pull(nil, "key_down") -- nil timeout is math.huge
2017 if code == keyboard.keys.q then
2018 clear_line()
2019 os.exit(1) -- abort
2020 elseif code == keyboard.keys["end"] then
2021 io.stdout.stream.scroll = nil -- remove handler
2022 elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
2023 io.write("\27[2J\27[H") -- clear whole screen, get new page drawn; move cursor to 1,1
2024 elseif code == keyboard.keys.enter or code == keyboard.keys.down then
2025 clear_line() -- remove status bar
2026 original_stream.scroll(1) -- move everything up one
2027 tty.setCursor(1, height - 1)
2028 end
2029 intercept_active = true
2030 end
2031 return original_stream.scroll(...)
2032 end
2033 }, {__index=original_stream})
2034
2035 local custom_output_buffer = buffer.new("w", custom_stream)
2036 custom_output_buffer:setvbuf("no")
2037 io.output(custom_output_buffer)
2038end
2039
2040return loadfile(shell.resolve("cat", "lua"))(...)
2041 mnt/6c1/bin/clear.lua 0000600 00000000046 13215223700 007040 0 local tty = require("tty")
2042tty.clear() mnt/6c1/bin/unset.lua 0000600 00000000243 13215223704 007113 0 local args = {...}
2043
2044if #args < 1 then
2045 io.write("Usage: unset <varname>[ <varname2> [...]]\n")
2046else
2047 for _, k in ipairs(args) do
2048 os.setenv(k, nil)
2049 end
2050end
2051 mnt/6c1/bin/userdel.lua 0000600 00000000410 13215223675 007423 0 local computer = require("computer")
2052local shell = require("shell")
2053
2054local args = shell.parse(...)
2055if #args ~= 1 then
2056 io.write("Usage: userdel <name>\n")
2057 return 1
2058end
2059
2060if not computer.removeUser(args[1]) then
2061 io.stderr:write("no such user\n")
2062 return 1
2063end
2064 mnt/6c1/bin/ps.lua 0000600 00000007166 13215223705 006413 0 local process = require("process")
2065local unicode = require("unicode")
2066local event = require("event")
2067local event_mt = getmetatable(event.handlers)
2068
2069-- WARNING this code does not use official kernel API and is likely to change
2070
2071local data = {}
2072local widths = {}
2073local sorted = {}
2074local moved_indexes = {}
2075
2076local elbow = unicode.char(0x2514)
2077
2078local function thread_id(t,p)
2079 if t then
2080 return tostring(t):gsub("^thread: 0x", "")
2081 end
2082 -- find the parent thread
2083 for k,v in pairs(process.list) do
2084 if v == p then
2085 return thread_id(k)
2086 end
2087 end
2088 return "-"
2089end
2090
2091local cols =
2092{
2093 {"PID", thread_id},
2094 {"EVENTS", function(_,p)
2095 local handlers = {}
2096 if event_mt.threaded then
2097 handlers = rawget(p.data, "handlers") or {}
2098 elseif not p.parent then
2099 handlers = event.handlers
2100 end
2101 local count = 0
2102 for _ in pairs(handlers) do
2103 count = count + 1
2104 end
2105 return count == 0 and "-" or tostring(count)
2106 end},
2107 {"THREADS", function(_,p)
2108 -- threads are handles with mt.close
2109 local count = 0
2110 for _,h in pairs(p.data.handles or {}) do
2111 local mt = getmetatable(h)
2112 if mt and mt.__index and mt.__index.close then
2113 count = count + #h
2114 break -- there is only one thread handle manager
2115 end
2116 end
2117 return count == 0 and "-" or tostring(count)
2118 end},
2119 {"PARENT", function(_,p)
2120 for _,process_info in pairs(process.list) do
2121 for _,handle in pairs(process_info.data.handles or {}) do
2122 local mt = getmetatable(handle)
2123 if mt and mt.__index and mt.__index.close then
2124 for _,ht in ipairs(handle) do
2125 local ht_mt = getmetatable(ht)
2126 if ht_mt.process == p then
2127 return thread_id(nil,process_info)
2128 end
2129 end
2130 break
2131 end
2132 end
2133 end
2134 return thread_id(nil,p.parent)
2135 end},
2136 {"CMD", function(_,p) return p.command end},
2137}
2138
2139local function add_field(key, value)
2140 if not data[key] then data[key] = {} end
2141 table.insert(data[key], value)
2142 widths[key] = math.max(widths[key] or 0, #value)
2143end
2144
2145for _,key in ipairs(cols) do
2146 add_field(key[1], key[1])
2147end
2148
2149for thread_handle, process_info in pairs(process.list) do
2150 for _,key in ipairs(cols) do
2151 add_field(key[1], key[2](thread_handle, process_info))
2152 end
2153end
2154
2155local parent_index
2156for index,set in ipairs(cols) do
2157 if set[1] == "PARENT" then
2158 parent_index = index
2159 break
2160 end
2161end
2162assert(parent_index, "did not find a parent column")
2163
2164local function move_to_sorted(index)
2165 if moved_indexes[index] then
2166 return false
2167 end
2168 local entry = {}
2169 for k,v in pairs(data) do
2170 entry[k] = v[index]
2171 end
2172 sorted[#sorted + 1] = entry
2173 moved_indexes[index] = true
2174 return true
2175end
2176
2177local function make_elbow(depth)
2178 return (" "):rep(depth - 1) .. (depth > 0 and elbow or "")
2179end
2180
2181-- remove COLUMN labels to simplify sort
2182move_to_sorted(1)
2183
2184local function update_family(parent, depth)
2185 depth = depth or 0
2186 parent = parent or "-"
2187 for index in ipairs(data.PID) do
2188 local this_parent = data[cols[parent_index][1]][index]
2189 if this_parent == parent then
2190 local dash_cmd = make_elbow(depth) .. data.CMD[index]
2191 data.CMD[index] = dash_cmd
2192 widths.CMD = math.max(widths.CMD or 0, #dash_cmd)
2193 if move_to_sorted(index) then
2194 update_family(data.PID[index], depth + 1)
2195 end
2196 end
2197 end
2198end
2199
2200update_family()
2201table.remove(cols, parent_index) -- don't show parent id
2202
2203for _,set in ipairs(sorted) do
2204 local split = ""
2205 for _,key in ipairs(cols) do
2206 local label = key[1]
2207 local format = split .. "%-" .. tostring(widths[label]) .. "s"
2208 io.write(string.format(format, set[label]))
2209 split = " "
2210 end
2211 print()
2212end
2213
2214 mnt/6c1/bin/pastebin.lua 0000600 00000010206 13215223670 007564 0 --[[ This program allows downloading and uploading from and to pastebin.com.
2215 Authors: Sangar, Vexatos ]]
2216local component = require("component")
2217local fs = require("filesystem")
2218local internet = require("internet")
2219local shell = require("shell")
2220
2221if not component.isAvailable("internet") then
2222 io.stderr:write("This program requires an internet card to run.")
2223 return
2224end
2225
2226local args, options = shell.parse(...)
2227
2228-- This gets code from the website and stores it in the specified file.
2229local function get(pasteId, filename)
2230 local f, reason = io.open(filename, "w")
2231 if not f then
2232 io.stderr:write("Failed opening file for writing: " .. reason)
2233 return
2234 end
2235
2236 io.write("Downloading from pastebin.com... ")
2237 local url = "https://pastebin.com/raw/" .. pasteId
2238 local result, response = pcall(internet.request, url)
2239 if result then
2240 io.write("success.\n")
2241 for chunk in response do
2242 if not options.k then
2243 string.gsub(chunk, "\r\n", "\n")
2244 end
2245 f:write(chunk)
2246 end
2247
2248 f:close()
2249 io.write("Saved data to " .. filename .. "\n")
2250 else
2251 io.write("failed.\n")
2252 f:close()
2253 fs.remove(filename)
2254 io.stderr:write("HTTP request failed: " .. response .. "\n")
2255 end
2256end
2257
2258-- This makes a string safe for being used in a URL.
2259function encode(code)
2260 if code then
2261 code = string.gsub(code, "([^%w ])", function (c)
2262 return string.format("%%%02X", string.byte(c))
2263 end)
2264 code = string.gsub(code, " ", "+")
2265 end
2266 return code
2267end
2268
2269-- This stores the program in a temporary file, which it will
2270-- delete after the program was executed.
2271function run(pasteId, ...)
2272 local tmpFile = os.tmpname()
2273 get(pasteId, tmpFile)
2274 io.write("Running...\n")
2275
2276 local success, reason = shell.execute(tmpFile, nil, ...)
2277 if not success then
2278 io.stderr:write(reason)
2279 end
2280 fs.remove(tmpFile)
2281end
2282
2283-- Uploads the specified file as a new paste to pastebin.com.
2284function put(path)
2285 local config = {}
2286 local configFile = loadfile("/etc/pastebin.conf", "t", config)
2287 if configFile then
2288 local result, reason = pcall(configFile)
2289 if not result then
2290 io.stderr:write("Failed loading config: " .. reason)
2291 end
2292 end
2293 config.key = config.key or "fd92bd40a84c127eeb6804b146793c97"
2294 local file, reason = io.open(path, "r")
2295
2296 if not file then
2297 io.stderr:write("Failed opening file for reading: " .. reason)
2298 return
2299 end
2300
2301 local data = file:read("*a")
2302 file:close()
2303
2304 io.write("Uploading to pastebin.com... ")
2305 local result, response = pcall(internet.request,
2306 "https://pastebin.com/api/api_post.php",
2307 "api_option=paste&" ..
2308 "api_dev_key=" .. config.key .. "&" ..
2309 "api_paste_format=lua&" ..
2310 "api_paste_expire_date=N&" ..
2311 "api_paste_name=" .. encode(fs.name(path)) .. "&" ..
2312 "api_paste_code=" .. encode(data))
2313
2314 if result then
2315 local info = ""
2316 for chunk in response do
2317 info = info .. chunk
2318 end
2319 if string.match(info, "^Bad API request, ") then
2320 io.write("failed.\n")
2321 io.write(info)
2322 else
2323 io.write("success.\n")
2324 local pasteId = string.match(info, "[^/]+$")
2325 io.write("Uploaded as " .. info .. "\n")
2326 io.write('Run "pastebin get ' .. pasteId .. '" to download anywhere.')
2327 end
2328 else
2329 io.write("failed.\n")
2330 io.stderr:write(response)
2331 end
2332end
2333
2334local command = args[1]
2335if command == "put" then
2336 if #args == 2 then
2337 put(shell.resolve(args[2]))
2338 return
2339 end
2340elseif command == "get" then
2341 if #args == 3 then
2342 local path = shell.resolve(args[3])
2343 if fs.exists(path) then
2344 if not options.f or not os.remove(path) then
2345 io.stderr:write("file already exists")
2346 return
2347 end
2348 end
2349 get(args[2], path)
2350 return
2351 end
2352elseif command == "run" then
2353 if #args >= 2 then
2354 run(args[2], table.unpack(args, 3))
2355 return
2356 end
2357end
2358
2359-- If we come here there was some invalid input.
2360io.write("Usages:\n")
2361io.write("pastebin put [-f] <file>\n")
2362io.write("pastebin get [-f] <id> <file>\n")
2363io.write("pastebin run [-f] <id> [<arguments...>]\n")
2364io.write(" -f: Force overwriting existing files.\n")
2365io.write(" -k: keep line endings as-is (will convert\n")
2366io.write(" Windows line endings to Unix otherwise).") mnt/6c1/bin/edit.lua 0000600 00000043576 13215223677 006733 0 local fs = require("filesystem")
2367local keyboard = require("keyboard")
2368local shell = require("shell")
2369local term = require("term") -- TODO use tty and cursor position instead of global area and gpu
2370local text = require("text")
2371local unicode = require("unicode")
2372
2373if not term.isAvailable() then
2374 return
2375end
2376local gpu = term.gpu()
2377local args, options = shell.parse(...)
2378if #args == 0 then
2379 io.write("Usage: edit <filename>")
2380 return
2381end
2382
2383local filename = shell.resolve(args[1])
2384local file_parentpath = fs.path(filename)
2385
2386if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then
2387 io.stderr:write(string.format("Not a directory: %s\n", file_parentpath))
2388 return 1
2389end
2390
2391local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
2392
2393if fs.isDirectory(filename) then
2394 io.stderr:write("file is a directory\n")
2395 return 1
2396elseif not fs.exists(filename) and readonly then
2397 io.stderr:write("file system is read only\n")
2398 return 1
2399end
2400
2401local function loadConfig()
2402 -- Try to load user settings.
2403 local env = {}
2404 local config = loadfile("/etc/edit.cfg", nil, env)
2405 if config then
2406 pcall(config)
2407 end
2408 -- Fill in defaults.
2409 env.keybinds = env.keybinds or {
2410 left = {{"left"}},
2411 right = {{"right"}},
2412 up = {{"up"}},
2413 down = {{"down"}},
2414 home = {{"home"}},
2415 eol = {{"end"}},
2416 pageUp = {{"pageUp"}},
2417 pageDown = {{"pageDown"}},
2418
2419 backspace = {{"back"}},
2420 delete = {{"delete"}},
2421 deleteLine = {{"control", "delete"}, {"shift", "delete"}},
2422 newline = {{"enter"}},
2423
2424 save = {{"control", "s"}},
2425 close = {{"control", "w"}},
2426 find = {{"control", "f"}},
2427 findnext = {{"control", "g"}, {"control", "n"}, {"f3"}}
2428 }
2429 -- Generate config file if it didn't exist.
2430 if not config then
2431 local root = fs.get("/")
2432 if root and not root.isReadOnly() then
2433 fs.makeDirectory("/etc")
2434 local f = io.open("/etc/edit.cfg", "w")
2435 if f then
2436 local serialization = require("serialization")
2437 for k, v in pairs(env) do
2438 f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
2439 end
2440 f:close()
2441 end
2442 end
2443 end
2444 return env
2445end
2446
2447term.clear()
2448term.setCursorBlink(true)
2449
2450local running = true
2451local buffer = {}
2452local scrollX, scrollY = 0, 0
2453local config = loadConfig()
2454
2455local getKeyBindHandler -- forward declaration for refind()
2456
2457local function helpStatusText()
2458 local function prettifyKeybind(label, command)
2459 local keybind = type(config.keybinds) == "table" and config.keybinds[command]
2460 if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
2461 local alt, control, shift, key
2462 for _, value in ipairs(keybind[1]) do
2463 if value == "alt" then alt = true
2464 elseif value == "control" then control = true
2465 elseif value == "shift" then shift = true
2466 else key = value end
2467 end
2468 if not key then return "" end
2469 return label .. ": [" ..
2470 (control and "Ctrl+" or "") ..
2471 (alt and "Alt+" or "") ..
2472 (shift and "Shift+" or "") ..
2473 unicode.upper(key) ..
2474 "] "
2475 end
2476 return prettifyKeybind("Save", "save") ..
2477 prettifyKeybind("Close", "close") ..
2478 prettifyKeybind("Find", "find")
2479end
2480
2481-------------------------------------------------------------------------------
2482
2483local function setStatus(value)
2484 local x, y, w, h = term.getGlobalArea()
2485 value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
2486 value = text.padRight(value, w - 10)
2487 gpu.set(x, y + h - 1, value)
2488end
2489
2490local function getArea()
2491 local x, y, w, h = term.getGlobalArea()
2492 return x, y, w, h - 1
2493end
2494
2495local function removePrefix(line, length)
2496 if length >= unicode.wlen(line) then
2497 return ""
2498 else
2499 local prefix = unicode.wtrunc(line, length + 1)
2500 local suffix = unicode.sub(line, unicode.len(prefix) + 1)
2501 length = length - unicode.wlen(prefix)
2502 if length > 0 then
2503 suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2)
2504 end
2505 return suffix
2506 end
2507end
2508
2509local function lengthToChars(line, length)
2510 if length > unicode.wlen(line) then
2511 return unicode.len(line) + 1
2512 else
2513 local prefix = unicode.wtrunc(line, length)
2514 return unicode.len(prefix) + 1
2515 end
2516end
2517
2518
2519local function isWideAtPosition(line, x)
2520 local index = lengthToChars(line, x)
2521 if index > unicode.len(line) then
2522 return false, false
2523 end
2524 local prefix = unicode.sub(line, 1, index)
2525 local char = unicode.sub(line, index, index)
2526 --isWide, isRight
2527 return unicode.isWide(char), unicode.wlen(prefix) == x
2528end
2529
2530local function drawLine(x, y, w, h, lineNr)
2531 local yLocal = lineNr - scrollY
2532 if yLocal > 0 and yLocal <= h then
2533 local str = removePrefix(buffer[lineNr] or "", scrollX)
2534 str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
2535 str = text.padRight(str, w)
2536 gpu.set(x, y - 1 + lineNr - scrollY, str)
2537 end
2538end
2539
2540local function getCursor()
2541 local cx, cy = term.getCursor()
2542 return cx + scrollX, cy + scrollY
2543end
2544
2545local function line()
2546 local cbx, cby = getCursor()
2547 return buffer[cby]
2548end
2549
2550local function getNormalizedCursor()
2551 local cbx, cby = getCursor()
2552 local wide, right = isWideAtPosition(buffer[cby], cbx)
2553 if wide and right then
2554 cbx = cbx - 1
2555 end
2556 return cbx, cby
2557end
2558
2559local function setCursor(nbx, nby)
2560 local x, y, w, h = getArea()
2561 nby = math.max(1, math.min(#buffer, nby))
2562
2563 local ncy = nby - scrollY
2564 if ncy > h then
2565 term.setCursorBlink(false)
2566 local sy = nby - h
2567 local dy = math.abs(scrollY - sy)
2568 scrollY = sy
2569 if h > dy then
2570 gpu.copy(x, y + dy, w, h - dy, 0, -dy)
2571 end
2572 for lineNr = nby - (math.min(dy, h) - 1), nby do
2573 drawLine(x, y, w, h, lineNr)
2574 end
2575 elseif ncy < 1 then
2576 term.setCursorBlink(false)
2577 local sy = nby - 1
2578 local dy = math.abs(scrollY - sy)
2579 scrollY = sy
2580 if h > dy then
2581 gpu.copy(x, y, w, h - dy, 0, dy)
2582 end
2583 for lineNr = nby, nby + (math.min(dy, h) - 1) do
2584 drawLine(x, y, w, h, lineNr)
2585 end
2586 end
2587 term.setCursor(term.getCursor(), nby - scrollY)
2588
2589 nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx))
2590 local wide, right = isWideAtPosition(line(), nbx)
2591 local ncx = nbx - scrollX
2592 if ncx > w or (ncx + 1 > w and wide and not right) then
2593 term.setCursorBlink(false)
2594 scrollX = nbx - w + ((wide and not right) and 1 or 0)
2595 for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
2596 drawLine(x, y, w, h, lineNr)
2597 end
2598 elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then
2599 term.setCursorBlink(false)
2600 scrollX = nbx - 1 - ((wide and right) and 1 or 0)
2601 for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
2602 drawLine(x, y, w, h, lineNr)
2603 end
2604 end
2605 term.setCursor(nbx - scrollX, nby - scrollY)
2606 --update with term lib
2607 nbx, nby = getCursor()
2608 gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
2609end
2610
2611local function highlight(bx, by, length, enabled)
2612 local x, y, w, h = getArea()
2613 local cx, cy = bx - scrollX, by - scrollY
2614 cx = math.max(1, math.min(w, cx))
2615 cy = math.max(1, math.min(h, cy))
2616 length = math.max(1, math.min(w - cx, length))
2617
2618 local fg, fgp = gpu.getForeground()
2619 local bg, bgp = gpu.getBackground()
2620 if enabled then
2621 gpu.setForeground(bg, bgp)
2622 gpu.setBackground(fg, fgp)
2623 end
2624 local indexFrom = lengthToChars(buffer[by], bx)
2625 local value = unicode.sub(buffer[by], indexFrom)
2626 if unicode.wlen(value) > length then
2627 value = unicode.wtrunc(value, length + 1)
2628 end
2629 gpu.set(x - 1 + cx, y - 1 + cy, value)
2630 if enabled then
2631 gpu.setForeground(fg, fgp)
2632 gpu.setBackground(bg, bgp)
2633 end
2634end
2635
2636local function home()
2637 local cbx, cby = getCursor()
2638 setCursor(1, cby)
2639end
2640
2641local function ende()
2642 local cbx, cby = getCursor()
2643 setCursor(unicode.wlen(line()) + 1, cby)
2644end
2645
2646local function left()
2647 local cbx, cby = getNormalizedCursor()
2648 if cbx > 1 then
2649 local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1)
2650 if wideTarget and rightTarget then
2651 setCursor(cbx - 2, cby)
2652 else
2653 setCursor(cbx - 1, cby)
2654 end
2655 return true -- for backspace
2656 elseif cby > 1 then
2657 setCursor(cbx, cby - 1)
2658 ende()
2659 return true -- again, for backspace
2660 end
2661end
2662
2663local function right(n)
2664 n = n or 1
2665 local cbx, cby = getNormalizedCursor()
2666 local be = unicode.wlen(line()) + 1
2667 local wide, right = isWideAtPosition(line(), cbx + n)
2668 if wide and right then
2669 n = n + 1
2670 end
2671 if cbx + n <= be then
2672 setCursor(cbx + n, cby)
2673 elseif cby < #buffer then
2674 setCursor(1, cby + 1)
2675 end
2676end
2677
2678local function up(n)
2679 n = n or 1
2680 local cbx, cby = getCursor()
2681 if cby > 1 then
2682 setCursor(cbx, cby - n)
2683 end
2684end
2685
2686local function down(n)
2687 n = n or 1
2688 local cbx, cby = getCursor()
2689 if cby < #buffer then
2690 setCursor(cbx, cby + n)
2691 end
2692end
2693
2694local function delete(fullRow)
2695 local cx, cy = term.getCursor()
2696 local cbx, cby = getCursor()
2697 local x, y, w, h = getArea()
2698 local function deleteRow(row)
2699 local content = table.remove(buffer, row)
2700 local rcy = cy + (row - cby)
2701 if rcy <= h then
2702 gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
2703 drawLine(x, y, w, h, row + (h - rcy))
2704 end
2705 return content
2706 end
2707 if fullRow then
2708 term.setCursorBlink(false)
2709 if #buffer > 1 then
2710 deleteRow(cby)
2711 else
2712 buffer[cby] = ""
2713 gpu.fill(x, y - 1 + cy, w, 1, " ")
2714 end
2715 setCursor(1, cby)
2716 elseif cbx <= unicode.wlen(line()) then
2717 term.setCursorBlink(false)
2718 local index = lengthToChars(line(), cbx)
2719 buffer[cby] = unicode.sub(line(), 1, index - 1) ..
2720 unicode.sub(line(), index + 1)
2721 drawLine(x, y, w, h, cby)
2722 elseif cby < #buffer then
2723 term.setCursorBlink(false)
2724 local append = deleteRow(cby + 1)
2725 buffer[cby] = buffer[cby] .. append
2726 drawLine(x, y, w, h, cby)
2727 else
2728 return
2729 end
2730 setStatus(helpStatusText())
2731end
2732
2733local function insert(value)
2734 if not value or unicode.len(value) < 1 then
2735 return
2736 end
2737 term.setCursorBlink(false)
2738 local cx, cy = term.getCursor()
2739 local cbx, cby = getCursor()
2740 local x, y, w, h = getArea()
2741 local index = lengthToChars(line(), cbx)
2742 buffer[cby] = unicode.sub(line(), 1, index - 1) ..
2743 value ..
2744 unicode.sub(line(), index)
2745 drawLine(x, y, w, h, cby)
2746 right(unicode.wlen(value))
2747 setStatus(helpStatusText())
2748end
2749
2750local function enter()
2751 term.setCursorBlink(false)
2752 local cx, cy = term.getCursor()
2753 local cbx, cby = getCursor()
2754 local x, y, w, h = getArea()
2755 local index = lengthToChars(line(), cbx)
2756 table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index))
2757 buffer[cby] = unicode.sub(buffer[cby], 1, index - 1)
2758 drawLine(x, y, w, h, cby)
2759 if cy < h then
2760 if cy < h - 1 then
2761 gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
2762 end
2763 drawLine(x, y, w, h, cby + 1)
2764 end
2765 setCursor(1, cby + 1)
2766 setStatus(helpStatusText())
2767end
2768
2769local findText = ""
2770
2771local function find()
2772 local x, y, w, h = getArea()
2773 local cx, cy = term.getCursor()
2774 local cbx, cby = getCursor()
2775 local ibx, iby = cbx, cby
2776 while running do
2777 if unicode.len(findText) > 0 then
2778 local sx, sy
2779 for syo = 1, #buffer do -- iterate lines with wraparound
2780 sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
2781 sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
2782 if sx and (sx >= ibx or syo > 1) then
2783 break
2784 end
2785 end
2786 if not sx then -- special case for single matches
2787 sy = iby
2788 sx = string.find(buffer[sy], findText, nil, true)
2789 end
2790 if sx then
2791 sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1
2792 cbx, cby = sx, sy
2793 setCursor(cbx, cby)
2794 highlight(cbx, cby, unicode.wlen(findText), true)
2795 end
2796 end
2797 term.setCursor(7 + unicode.wlen(findText), h + 1)
2798 setStatus("Find: " .. findText)
2799
2800 local _, address, char, code = term.pull("key_down")
2801 if address == term.keyboard() then
2802 local handler, name = getKeyBindHandler(code)
2803 highlight(cbx, cby, unicode.wlen(findText), false)
2804 if name == "newline" then
2805 break
2806 elseif name == "close" then
2807 handler()
2808 elseif name == "backspace" then
2809 findText = unicode.sub(findText, 1, -2)
2810 elseif name == "find" or name == "findnext" then
2811 ibx = cbx + 1
2812 iby = cby
2813 elseif not keyboard.isControl(char) then
2814 findText = findText .. unicode.char(char)
2815 end
2816 end
2817 end
2818 setCursor(cbx, cby)
2819 setStatus(helpStatusText())
2820end
2821
2822-------------------------------------------------------------------------------
2823
2824local keyBindHandlers = {
2825 left = left,
2826 right = right,
2827 up = up,
2828 down = down,
2829 home = home,
2830 eol = ende,
2831 pageUp = function()
2832 local x, y, w, h = getArea()
2833 up(h - 1)
2834 end,
2835 pageDown = function()
2836 local x, y, w, h = getArea()
2837 down(h - 1)
2838 end,
2839
2840 backspace = function()
2841 if not readonly and left() then
2842 delete()
2843 end
2844 end,
2845 delete = function()
2846 if not readonly then
2847 delete()
2848 end
2849 end,
2850 deleteLine = function()
2851 if not readonly then
2852 delete(true)
2853 end
2854 end,
2855 newline = function()
2856 if not readonly then
2857 enter()
2858 end
2859 end,
2860
2861 save = function()
2862 if readonly then return end
2863 local new = not fs.exists(filename)
2864 local backup
2865 if not new then
2866 backup = filename .. "~"
2867 for i = 1, math.huge do
2868 if not fs.exists(backup) then
2869 break
2870 end
2871 backup = filename .. "~" .. i
2872 end
2873 fs.copy(filename, backup)
2874 end
2875 if not fs.exists(file_parentpath) then
2876 fs.makeDirectory(file_parentpath)
2877 end
2878 local f, reason = io.open(filename, "w")
2879 if f then
2880 local chars, firstLine = 0, true
2881 for _, line in ipairs(buffer) do
2882 if not firstLine then
2883 line = "\n" .. line
2884 end
2885 firstLine = false
2886 f:write(line)
2887 chars = chars + unicode.len(line)
2888 end
2889 f:close()
2890 local format
2891 if new then
2892 format = [["%s" [New] %dL,%dC written]]
2893 else
2894 format = [["%s" %dL,%dC written]]
2895 end
2896 setStatus(string.format(format, fs.name(filename), #buffer, chars))
2897 else
2898 setStatus(reason)
2899 end
2900 if not new then
2901 fs.remove(backup)
2902 end
2903 end,
2904 close = function()
2905 -- TODO ask to save if changed
2906 running = false
2907 end,
2908 find = function()
2909 findText = ""
2910 find()
2911 end,
2912 findnext = find
2913}
2914
2915getKeyBindHandler = function(code)
2916 if type(config.keybinds) ~= "table" then return end
2917 -- Look for matches, prefer more 'precise' keybinds, e.g. prefer
2918 -- ctrl+del over del.
2919 local result, resultName, resultWeight = nil, nil, 0
2920 for command, keybinds in pairs(config.keybinds) do
2921 if type(keybinds) == "table" and keyBindHandlers[command] then
2922 for _, keybind in ipairs(keybinds) do
2923 if type(keybind) == "table" then
2924 local alt, control, shift, key
2925 for _, value in ipairs(keybind) do
2926 if value == "alt" then alt = true
2927 elseif value == "control" then control = true
2928 elseif value == "shift" then shift = true
2929 else key = value end
2930 end
2931 local keyboardAddress = term.keyboard()
2932 if (not alt or keyboard.isAltDown(keyboardAddress)) and
2933 (not control or keyboard.isControlDown(keyboardAddress)) and
2934 (not shift or keyboard.isShiftDown(keyboardAddress)) and
2935 code == keyboard.keys[key] and
2936 #keybind > resultWeight
2937 then
2938 resultWeight = #keybind
2939 resultName = command
2940 result = keyBindHandlers[command]
2941 end
2942 end
2943 end
2944 end
2945 end
2946 return result, resultName
2947end
2948
2949-------------------------------------------------------------------------------
2950
2951local function onKeyDown(char, code)
2952 local handler = getKeyBindHandler(code)
2953 if handler then
2954 handler()
2955 elseif readonly and code == keyboard.keys.q then
2956 running = false
2957 elseif not readonly then
2958 if not keyboard.isControl(char) then
2959 insert(unicode.char(char))
2960 elseif unicode.char(char) == "\t" then
2961 insert(" ")
2962 end
2963 end
2964end
2965
2966local function onClipboard(value)
2967 value = value:gsub("\r\n", "\n")
2968 local cbx, cby = getCursor()
2969 local start = 1
2970 local l = value:find("\n", 1, true)
2971 if l then
2972 repeat
2973 local line = string.sub(value, start, l - 1)
2974 line = text.detab(line, 2)
2975 insert(line)
2976 enter()
2977 start = l + 1
2978 l = value:find("\n", start, true)
2979 until not l
2980 end
2981 insert(string.sub(value, start))
2982end
2983
2984local function onClick(x, y)
2985 setCursor(x + scrollX, y + scrollY)
2986end
2987
2988local function onScroll(direction)
2989 local cbx, cby = getCursor()
2990 setCursor(cbx, cby - direction * 12)
2991end
2992
2993-------------------------------------------------------------------------------
2994
2995do
2996 local f = io.open(filename)
2997 if f then
2998 local x, y, w, h = getArea()
2999 local chars = 0
3000 for line in f:lines() do
3001 table.insert(buffer, line)
3002 chars = chars + unicode.len(line)
3003 if #buffer <= h then
3004 drawLine(x, y, w, h, #buffer)
3005 end
3006 end
3007 f:close()
3008 if #buffer == 0 then
3009 table.insert(buffer, "")
3010 end
3011 local format
3012 if readonly then
3013 format = [["%s" [readonly] %dL,%dC]]
3014 else
3015 format = [["%s" %dL,%dC]]
3016 end
3017 setStatus(string.format(format, fs.name(filename), #buffer, chars))
3018 else
3019 table.insert(buffer, "")
3020 setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
3021 end
3022 setCursor(1, 1)
3023end
3024
3025while running do
3026 local event, address, arg1, arg2, arg3 = term.pull()
3027 if address == term.keyboard() or address == term.screen() then
3028 local blink = true
3029 if event == "key_down" then
3030 onKeyDown(arg1, arg2)
3031 elseif event == "clipboard" and not readonly then
3032 onClipboard(arg1)
3033 elseif event == "touch" or event == "drag" then
3034 local x, y, w, h = getArea()
3035 arg1 = arg1 - x + 1
3036 arg2 = arg2 - y + 1
3037 if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then
3038 onClick(arg1, arg2)
3039 end
3040 elseif event == "scroll" then
3041 onScroll(arg3)
3042 else
3043 blink = false
3044 end
3045 if blink then
3046 term.setCursorBlink(true)
3047 end
3048 end
3049end
3050
3051term.clear()
3052term.setCursorBlink(true)
3053 mnt/6c1/bin/pwd.lua 0000600 00000000512 13215223705 006547 0 local shell = require("shell")
3054local fs = require("filesystem")
3055local _,op = shell.parse(...)
3056
3057local path, why = shell.getWorkingDirectory(), ""
3058if op.P then
3059 path, why = fs.realPath(path)
3060end
3061if not path then
3062 io.stderr:write(string.format("error retrieving current directory: %s", why))
3063 os.exit(1)
3064end
3065
3066io.write(path, "\n")
3067 mnt/6c1/bin/ls.lua 0000600 00000000614 13215223675 006404 0 -- load complex, if we can (might be low on memory)
3068
3069local ok, why = pcall(function(...)
3070 return loadfile("/lib/core/full_ls.lua", "bt", _G)(...)
3071end, ...)
3072
3073if not ok then
3074 if type(why) == "table" then
3075 if why.code == 0 then
3076 return
3077 end
3078 why = why.reason
3079 end
3080 io.stderr:write(tostring(why) .. "\nFor low memory systems, try using `list` instead\n")
3081 return 1
3082end
3083
3084return why
3085 mnt/6c1/bin/address.lua 0000600 00000000107 13215223700 007375 0 local computer = require("computer")
3086io.write(computer.address(),"\n")
3087 mnt/6c1/bin/tree.lua 0000600 00000020526 13215223711 006720 0 local computer = require("computer")
3088local shell = require("shell")
3089local fs = require("filesystem")
3090local tx = require("transforms")
3091local text = require("text")
3092
3093local args, opts = shell.parse(...)
3094
3095local function die(...)
3096 io.stderr:write(...)
3097 os.exit(1)
3098end
3099
3100do -- handle cli
3101 if opts.help then
3102 print([[Usage: tree [OPTION]... [FILE]...
3103 -a, --all do not ignore entries starting with .
3104 --full-time with -l, print time in full iso format
3105 -h, --human-readable with -l, print human readable sizes
3106 --si likewise, but use powers of 1000 not 1024
3107 --level=LEVEL descend only LEVEL directories deep
3108 --color=WHEN WHEN can be
3109 auto - colorize output only if writing to a tty,
3110 always - always colorize output,
3111 never - never colorize output; (default: auto)
3112 -l use a long listing format
3113 -f print the full path prefix for each file
3114 -i do not print indentation lines
3115 -p append "/" indicator to directories
3116 -Q, --quote quote filenames with double quotes
3117 -r, --reverse reverse order while sorting
3118 -S sort by file size
3119 -t sort by modification type, newest first
3120 -X sort alphabetically by entry extension
3121 -C do not count files and directories
3122 -R count root directories like other files
3123 --help print this help and exit]])
3124 return 0
3125 end
3126
3127 if #args == 0 then
3128 table.insert(args, ".")
3129 end
3130
3131 opts.level = tonumber(opts.level) or math.huge
3132 if opts.level < 1 then
3133 die("Invalid level, must be greater than 0")
3134 end
3135
3136 opts.color = opts.color or "auto"
3137 if opts.color == "auto" then
3138 opts.color = io.stdout.tty and "always" or "never"
3139 end
3140
3141 if opts.color ~= "always" and opts.color ~= "never" then
3142 die("Invalid value for --color=WHEN option; WHEN should be auto, always or never")
3143 end
3144end
3145
3146local lastYield = computer.uptime()
3147local function yieldopt()
3148 if computer.uptime() - lastYield > 2 then
3149 lastYield = computer.uptime()
3150 os.sleep(0)
3151 end
3152end
3153
3154local function peekable(iterator, state, var1)
3155 local nextItem = {iterator(state, var1)}
3156
3157 return setmetatable({
3158 peek = function()
3159 return table.unpack(nextItem)
3160 end
3161 }, {
3162 __call = coroutine.wrap(function()
3163 while true do
3164 local item = nextItem
3165 nextItem = {iterator(state, nextItem[1])}
3166 coroutine.yield(table.unpack(item))
3167 if nextItem[1] == nil then break end
3168 end
3169 end)
3170 })
3171end
3172
3173local function filter(entry)
3174 return opts.a or entry:sub(1, 1) ~= "."
3175end
3176
3177local function stat(path)
3178 local st = {}
3179 st.path = path
3180 st.name = fs.name(path) or "/"
3181 st.sortName = st.name:gsub("^%.","")
3182 st.time = fs.lastModified(path)
3183 st.isLink = fs.isLink(path)
3184 st.isDirectory = fs.isDirectory(path)
3185 st.size = st.isLink and 0 or fs.size(path)
3186 st.extension = st.name:match("(%.[^.]+)$") or ""
3187 st.fs = fs.get(path)
3188 return st
3189end
3190
3191local colorize
3192if opts.color == "always" then
3193 -- from /lib/core/full_ls.lua
3194 local colors = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
3195 local parts = text.split(e, {"="}, true)
3196 return parts[2], parts[1]
3197 end)
3198
3199 function colorize(stat)
3200 return stat.isLink and colors.ln or
3201 stat.isDirectory and colors.di or
3202 colors["*" .. stat.extension] or
3203 colors.fi
3204 end
3205end
3206
3207local function list(path)
3208 return coroutine.wrap(function()
3209 local l = {}
3210 for entry in fs.list(path) do
3211 if filter(entry) then
3212 table.insert(l, stat(fs.concat(path, entry)))
3213 end
3214 end
3215
3216 if opts.S then
3217 table.sort(l, function(a, b)
3218 return a.size < b.size
3219 end)
3220 elseif opts.t then
3221 table.sort(l, function(a, b)
3222 return a.time < b.time
3223 end)
3224 elseif opts.X then
3225 table.sort(l, function(a, b)
3226 return a.extension < b.extension
3227 end)
3228 else
3229 table.sort(l, function(a, b)
3230 return a.sortName < b.sortName
3231 end)
3232 end
3233
3234 for i = opts.r and #l or 1, opts.r and 1 or #l, opts.r and -1 or 1 do
3235 coroutine.yield(l[i])
3236 end
3237 end)
3238end
3239
3240local function digRoot(rootPath)
3241 coroutine.yield(stat(rootPath), {})
3242
3243 if not fs.isDirectory(rootPath) then
3244 return
3245 end
3246 local iterStack = {peekable(list(rootPath))}
3247 local pathStack = {rootPath}
3248 local levelStack = {not not iterStack[#iterStack]:peek()}
3249
3250
3251 repeat
3252 local entry = iterStack[#iterStack]()
3253
3254 if entry then
3255 levelStack[#levelStack] = not not iterStack[#iterStack]:peek()
3256
3257 local path = fs.concat(fs.concat(table.unpack(pathStack)), entry.name)
3258
3259 coroutine.yield(entry, levelStack)
3260
3261 if entry.isDirectory and opts.level > #levelStack then
3262 table.insert(iterStack, peekable(list(path)))
3263 table.insert(pathStack, entry.name)
3264 table.insert(levelStack, not not iterStack[#iterStack]:peek())
3265 end
3266 else
3267 table.remove(iterStack)
3268 table.remove(pathStack)
3269 table.remove(levelStack)
3270 end
3271 until #iterStack == 0
3272end
3273
3274local function dig(roots)
3275 return coroutine.wrap(function()
3276 for _, root in ipairs(roots) do
3277 digRoot(root)
3278 end
3279 end)
3280end
3281
3282local function nod(n) -- from /lib/core/full_ls.lua
3283 return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
3284end
3285
3286local function formatFSize(size) -- from /lib/core/full_ls.lua
3287 if not opts.h and not opts["human-readable"] and not opts.si then
3288 return tostring(size)
3289 end
3290
3291 local sizes = {"", "K", "M", "G"}
3292 local unit = 1
3293 local power = opts.si and 1000 or 1024
3294
3295 while size > power and unit < #sizes do
3296 unit = unit + 1
3297 size = size / power
3298 end
3299
3300 return nod(math.floor(size*10)/10)..sizes[unit]
3301end
3302
3303local function pad(txt) -- from /lib/core/full_ls.lua
3304 txt = tostring(txt)
3305 return #txt >= 2 and txt or "0" .. txt
3306end
3307
3308local function formatTime(epochms) -- from /lib/core/full_ls.lua
3309 local month_names = {"January","February","March","April","May","June",
3310 "July","August","September","October","November","December"}
3311
3312 if epochms == 0 then return "" end
3313
3314 local d = os.date("*t", epochms)
3315 local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
3316
3317 if opts["full-time"] then
3318 return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
3319 else
3320 return string.format("%s %+2s %+2s:%+2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
3321 end
3322end
3323
3324local function writeEntry(entry, levelStack)
3325 for i, hasNext in ipairs(levelStack) do
3326 if opts.i then break end
3327
3328 if i == #levelStack then
3329 if hasNext then
3330 io.write("вâ€ÑšÐ²â€Ð‚вâ€Ð‚ ")
3331 else
3332 io.write("вâ€â€Ð²â€Ð‚вâ€Ð‚ ")
3333 end
3334 else
3335 if hasNext then
3336 io.write("вâ€â€šÐ’ В ")
3337 else
3338 io.write(" ")
3339 end
3340 end
3341 end
3342
3343 if opts.l then
3344 io.write("[")
3345
3346 io.write(entry.isDirectory and "d" or entry.isLink and "l" or "f", "-")
3347 io.write("r", entry.fs.isReadOnly() and "-" or "w", " ")
3348
3349 io.write(formatFSize(entry.size), " ")
3350
3351 io.write(formatTime(entry.time))
3352 io.write("] ")
3353 end
3354
3355 if opts.Q then io.write('"') end
3356
3357 if opts.color == "always" then
3358 io.write("\27[" .. colorize(entry) .. "m")
3359 end
3360
3361 if opts.f then
3362 io.write(entry.path)
3363 else
3364 io.write(entry.name)
3365 end
3366
3367 if opts.color == "always" then
3368 io.write("\27[0m")
3369 end
3370
3371 if opts.p and entry.isDirectory then
3372 io.write("/")
3373 end
3374
3375 if opts.Q then io.write('"') end
3376 io.write("\n")
3377end
3378
3379local function writeCount(dirs, files)
3380 io.write("\n")
3381 io.write(dirs, " director", dirs == 1 and "y" or "ies")
3382 io.write(", ")
3383 io.write(files, " file", files == 1 and "" or "s")
3384 io.write("\n")
3385end
3386
3387local dirs, files = 0, 0
3388
3389local roots = {}
3390for _, arg in ipairs(args) do
3391 local path = shell.resolve(arg)
3392 local real, reason = fs.realPath(path)
3393 if not real then
3394 die("cannot access ", path, ": ", reason or "unknown error")
3395 elseif not fs.exists(path) then
3396 die("cannot access ", path, ":", "No such file or directory")
3397 else
3398 table.insert(roots, real)
3399 end
3400end
3401
3402for entry, levelStack in dig(roots) do
3403 if opts.R or #levelStack > 0 then
3404 if entry.isDirectory then
3405 dirs = dirs + 1
3406 else
3407 files = files + 1
3408 end
3409 end
3410 writeEntry(entry, levelStack)
3411 yieldopt()
3412end
3413
3414if not opts.C then
3415 writeCount(dirs, files)
3416end
3417
3418 mnt/6c1/bin/sleep.lua 0000600 00000003050 13215223673 007071 0 local shell = require("shell")
3419local tty = require("tty")
3420local args, options = shell.parse(...)
3421
3422if options.help then
3423 print([[Usage: sleep NUMBER[SUFFIX]...
3424Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
3425'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
3426that require NUMBER be an integer, here NUMBER may be an arbitrary floating
3427point number. Given two or more arguments, pause for the amount of time
3428specified by the sum of their values.]])
3429end
3430
3431local function help(bad_arg)
3432 print("sleep: invalid option -- '"..tostring(bad_arg).."'")
3433 print("Try 'sleep --help' for more information.")
3434end
3435
3436local function time_type_multiplier(time_type)
3437 if not time_type or #time_type == 0 or time_type == 's' then
3438 return 1
3439 elseif time_type == 'm' then
3440 return 60
3441 elseif time_type == 'h' then
3442 return 60 * 60
3443 elseif time_type == 'd' then
3444 return 60 * 60 * 24
3445 end
3446
3447 -- weird error, my bad
3448 assert(false,'bug parsing parameter:'..tostring(time_type))
3449end
3450
3451options.help = nil
3452if next(options) then
3453 help(next(options))
3454 return 1
3455end
3456
3457local total_time = 0
3458
3459for _,v in ipairs(args) do
3460 local interval, time_type = v:match('^([%d%.]+)([smhd]?)$')
3461 interval = tonumber(interval)
3462
3463 if not interval or interval < 0 then
3464 help(v)
3465 return 1
3466 end
3467
3468 total_time = total_time + time_type_multiplier(time_type) * interval
3469end
3470
3471local ins = io.stdin.stream
3472local pull = ins.pull
3473local start = 1
3474if not pull then
3475 pull = require("event").pull
3476 start = 2
3477end
3478pull(select(start, ins, total_time, "interrupted"))
3479 mnt/6c1/bin/rc.lua 0000600 00000007105 13215223673 006372 0 local rc = require("rc")
3480local fs = require("filesystem")
3481
3482local function loadConfig()
3483 local env = {}
3484 local result, reason = loadfile('/etc/rc.cfg', 't', env)
3485 if result then
3486 result, reason = xpcall(result, debug.traceback)
3487 if result then
3488 return env
3489 end
3490 end
3491 return nil, reason
3492end
3493
3494local function saveConfig(conf)
3495 local file, reason = io.open('/etc/rc.cfg', 'w')
3496 if not file then
3497 return nil, reason
3498 end
3499 for key, value in pairs(conf) do
3500 file:write(tostring(key) .. " = " .. require("serialization").serialize(value) .. "\n")
3501 end
3502
3503 file:close()
3504 return true
3505end
3506
3507local function load(name, args)
3508 if rc.loaded[name] then
3509 return rc.loaded[name]
3510 end
3511 local fileName = fs.concat('/etc/rc.d/', name .. '.lua')
3512 local env = setmetatable({args = args}, {__index = _G})
3513 local result, reason = loadfile(fileName, 't', env)
3514 if result then
3515 result, reason = xpcall(result, debug.traceback)
3516 if result then
3517 rc.loaded[name] = env
3518 return env
3519 end
3520 end
3521 return nil, reason
3522end
3523
3524function rc.unload(name)
3525 rc.loaded[name] = nil
3526end
3527
3528local function rawRunCommand(conf, name, cmd, args, ...)
3529 local result, what = load(name, args)
3530 if result then
3531 if not cmd then
3532 io.output():write("Commands for service " .. name .. "\n")
3533 for command, val in pairs(result) do
3534 if type(val) == "function" then
3535 io.output():write(tostring(command) .. " ")
3536 end
3537 end
3538 return true
3539 elseif type(result[cmd]) == "function" then
3540 result, what = xpcall(result[cmd], debug.traceback, ...)
3541 if result then
3542 return true
3543 end
3544 elseif cmd == "restart" and type(result["stop"]) == "function" and type(result["start"]) == "function" then
3545 local daemon = result
3546 result, what = xpcall(daemon["stop"], debug.traceback, ...)
3547 if result then
3548 result, what = xpcall(daemon["start"], debug.traceback, ...)
3549 if result then
3550 return true
3551 end
3552 end
3553 elseif cmd == "enable" then
3554 conf.enabled = conf.enabled or {}
3555 for _, _name in ipairs(conf.enabled) do
3556 if name == _name then
3557 return nil, "Service already enabled"
3558 end
3559 end
3560 conf.enabled[#conf.enabled + 1] = name
3561 return saveConfig(conf)
3562 elseif cmd == "disable" then
3563 conf.enabled = conf.enabled or {}
3564 for n, _name in ipairs(conf.enabled) do
3565 if name == _name then
3566 table.remove(conf.enabled, n)
3567 end
3568 end
3569 return saveConfig(conf)
3570 else
3571 what = "Command '" .. cmd .. "' not found in daemon '" .. name .. "'"
3572 end
3573 end
3574 return nil, what
3575end
3576
3577local function runCommand(name, cmd, ...)
3578 local conf, reason = loadConfig()
3579 if not conf then
3580 return nil, reason
3581 end
3582 return rawRunCommand(conf, name, cmd, conf[name], ...)
3583end
3584
3585local function allRunCommand(cmd, ...)
3586 local conf, reason = loadConfig()
3587 if not conf then
3588 return nil, reason
3589 end
3590 local results = {}
3591 for _, name in ipairs(conf.enabled or {}) do
3592 results[name] = table.pack(rawRunCommand(conf, name, cmd, conf[name], ...))
3593 end
3594 return results
3595end
3596
3597if select("#", ...) == 0 then
3598 local results,reason = allRunCommand("start")
3599 if not results then
3600 local msg = "rc failed to start:"..tostring(reason)
3601 io.stderr:write(msg)
3602 require("event").onError(msg)
3603 return
3604 end
3605 for _, result in pairs(results) do
3606 local ok, reason = table.unpack(result)
3607 if not ok then
3608 io.stderr:write(reason, "\n")
3609 end
3610 end
3611else
3612 local result, reason = runCommand(...)
3613 if not result then
3614 io.stderr:write(reason, "\n")
3615 return 1
3616 end
3617end
3618 mnt/6c1/bin/cat.lua 0000600 00000001626 13215223706 006534 0 local shell = require("shell")
3619local fs = require("filesystem")
3620
3621local args = shell.parse(...)
3622local ec = 0
3623if #args == 0 then
3624 args = {"-"}
3625end
3626
3627local input_method, input_param = "read", require("tty").window.width
3628
3629for i = 1, #args do
3630 local arg = shell.resolve(args[i])
3631 if fs.isDirectory(arg) then
3632 io.stderr:write(string.format('cat %s: Is a directory\n', arg))
3633 ec = 1
3634 else
3635 local file, reason
3636 if args[i] == "-" then
3637 file, reason = io.stdin, "missing stdin"
3638 input_method, input_param = "readLine", false
3639 else
3640 file, reason = fs.open(arg)
3641 end
3642 if not file then
3643 io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
3644 ec = 1
3645 else
3646 repeat
3647 local chunk = file[input_method](file, input_param)
3648 if chunk then
3649 io.write(chunk)
3650 end
3651 until not chunk
3652 file:close()
3653 end
3654 end
3655end
3656
3657return ec
3658 mnt/6c1/bin/yes.lua 0000600 00000001404 13215223707 006560 0 --[[Lua implementation of the UN*X yes command--]]
3659local shell = require("shell")
3660
3661local args, options = shell.parse(...)
3662
3663if options.V or options.version then
3664 io.write("yes v:1.0-3\n")
3665 io.write("Inspired by functionality of yes from GNU coreutils\n")
3666 return 0
3667end
3668
3669if options.h or options.help then
3670 io.write("Usage: yes [string]...\n")
3671 io.write("OR: yes [-V/h]\n")
3672 io.write("\n")
3673 io.write("yes prints the command line arguments, or 'y', until is killed.\n")
3674 io.write("\n")
3675 io.write("Options:\n")
3676 io.write(" -V, --version Version\n")
3677 io.write(" -h, --help This help\n")
3678 return 0
3679end
3680
3681local msg = #args == 0 and 'y' or table.concat(args, ' ')
3682msg = msg .. '\n'
3683
3684while io.write(msg) do
3685 if io.stdout.tty then
3686 os.sleep(0)
3687 end
3688end
3689return 0
3690 mnt/6c1/bin/dmesg.lua 0000600 00000002113 13215223704 007052 0 local event = require("event")
3691local tty = require("tty")
3692
3693local args = {...}
3694local gpu = tty.gpu()
3695local interactive = io.output().tty
3696local color, isPal, evt
3697if interactive then
3698 color, isPal = gpu.getForeground()
3699end
3700io.write("Press 'Ctrl-C' to exit\n")
3701pcall(function()
3702 repeat
3703 if #args > 0 then
3704 evt = table.pack(event.pullMultiple("interrupted", table.unpack(args)))
3705 else
3706 evt = table.pack(event.pull())
3707 end
3708 if interactive then gpu.setForeground(0xCC2200) end
3709 io.write("[" .. os.date("%T") .. "] ")
3710 if interactive then gpu.setForeground(0x44CC00) end
3711 io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1))
3712 if interactive then gpu.setForeground(0xB0B00F) end
3713 io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
3714 if interactive then gpu.setForeground(0xFFFFFF) end
3715 if evt.n > 2 then
3716 for i = 3, evt.n do
3717 io.write(" " .. tostring(evt[i]))
3718 end
3719 end
3720
3721 io.write("\n")
3722 until evt[1] == "interrupted"
3723end)
3724if interactive then
3725 gpu.setForeground(color, isPal)
3726end
3727
3728 mnt/6c1/bin/useradd.lua 0000600 00000000437 13215223707 007414 0 local computer = require("computer")
3729local shell = require("shell")
3730
3731local args = shell.parse(...)
3732if #args ~= 1 then
3733 io.write("Usage: useradd <name>\n")
3734 return 1
3735end
3736
3737local result, reason = computer.addUser(args[1])
3738if not result then
3739 io.stderr:write(reason..'\n')
3740 return 1
3741end
3742 mnt/6c1/bin/man.lua 0000600 00000001111 13215223673 006530 0 local fs = require("filesystem")
3743local shell = require("shell")
3744
3745local args = shell.parse(...)
3746if #args == 0 then
3747 io.write("Usage: man <topic>\n")
3748 io.write("Where `topic` will usually be the name of a program or library.\n")
3749 return 1
3750end
3751
3752local topic = args[1]
3753for path in string.gmatch(os.getenv("MANPATH"), "[^:]+") do
3754 path = shell.resolve(fs.concat(path, topic), "man")
3755 if path and fs.exists(path) and not fs.isDirectory(path) then
3756 os.execute(os.getenv("PAGER") .. " " .. path)
3757 os.exit()
3758 end
3759end
3760io.stderr:write("No manual entry for " .. topic .. '\n')
3761return 1
3762 mnt/6c1/bin/du.lua 0000600 00000005533 13215223671 006377 0 local shell = require("shell")
3763local fs = require("filesystem")
3764
3765local args, options, reason = shell.parse(...)
3766if #args == 0 then
3767 args[1] = '.'
3768end
3769
3770local TRY=[[
3771Try 'du --help' for more information.]]
3772
3773local VERSION=[[
3774du (OpenOS bin) 1.0
3775Written by payonel, patterned after GNU coreutils du]]
3776
3777local HELP=[[
3778Usage: du [OPTION]... [FILE]...
3779Summarize disk usage of each FILE, recursively for directories.
3780
3781 -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)
3782 -s, --summarize display only a total for each argument
3783 --help display this help and exit
3784 --version output version information and exit]]
3785
3786if options.help then
3787 print(HELP)
3788 return true
3789end
3790
3791if options.version then
3792 print(VERSION)
3793 return true
3794end
3795
3796local function addTrailingSlash(path)
3797 if path:sub(-1) ~= '/' then
3798 return path .. '/'
3799 else
3800 return path
3801 end
3802end
3803
3804local function opCheck(shortName, longName)
3805 local enabled = options[shortName] or options[longName]
3806 options[shortName] = nil
3807 options[longName] = nil
3808 return enabled
3809end
3810
3811local bHuman = opCheck('h', 'human-readable')
3812local bSummary = opCheck('s', 'summarize')
3813
3814if next(options) then
3815 for op,v in pairs(options) do
3816 io.stderr:write(string.format("du: invalid option -- '%s'\n", op))
3817 end
3818 io.stderr:write(TRY..'\n')
3819 return 1
3820end
3821
3822local function formatSize(size)
3823 if not bHuman then
3824 return tostring(size)
3825 end
3826 local sizes = {"", "K", "M", "G"}
3827 local unit = 1
3828 local power = options.si and 1000 or 1024
3829 while size > power and unit < #sizes do
3830 unit = unit + 1
3831 size = size / power
3832 end
3833
3834 return math.floor(size * 10) / 10 .. sizes[unit]
3835end
3836
3837local function printSize(size, rpath)
3838 local displaySize = formatSize(size)
3839 io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath))
3840end
3841
3842local function visitor(rpath)
3843 local subtotal = 0
3844 local dirs = 0
3845 local spath = shell.resolve(rpath)
3846
3847 if fs.isDirectory(spath) then
3848 local list_result = fs.list(spath)
3849 for list_item in list_result do
3850 local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
3851 subtotal = subtotal + vtotal
3852 dirs = dirs + vdirs
3853 end
3854
3855 if dirs == 0 then -- no child dirs
3856 if not bSummary then
3857 printSize(subtotal, rpath)
3858 end
3859 end
3860
3861 elseif not fs.isLink(spath) then
3862 subtotal = fs.size(spath)
3863 end
3864
3865 return subtotal, dirs
3866end
3867
3868for i,arg in ipairs(args) do
3869 local path = shell.resolve(arg)
3870
3871 if not fs.exists(path) then
3872 io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg))
3873 return 1
3874 else
3875 if fs.isDirectory(path) then
3876 local total = visitor(arg)
3877
3878 if bSummary then
3879 printSize(total, arg)
3880 end
3881 elseif fs.isLink(path) then
3882 printSize(0, arg)
3883 else
3884 printSize(fs.size(path), arg)
3885 end
3886 end
3887end
3888
3889return true
3890 mnt/6c1/bin/less.lua 0000600 00000020376 13215223710 006731 0 local keyboard = require("keyboard")
3891local shell = require("shell")
3892local term = require("term") -- using term for negative scroll feature
3893local text = require("text")
3894local unicode = require("unicode")
3895local computer = require("computer")
3896local tx = require("transforms")
3897
3898if not io.output().tty then
3899 return loadfile(shell.resolve("cat", "lua"), "bt", _G)(...)
3900end
3901
3902local args = shell.parse(...)
3903if #args > 1 then
3904 io.write("Usage: more <filename>\n")
3905 io.write("- or no args reads stdin\n")
3906 return 1
3907end
3908local arg = args[1] or "-"
3909
3910local initial_offset
3911
3912-- test validity of args
3913do
3914 if arg == "-" then
3915 if not io.stdin then
3916 io.stderr:write("this process has no stdin\n")
3917 return 1
3918 end
3919 -- stdin may not be core_stdin
3920 initial_offset = io.stdin:seek("cur")
3921 else
3922 local file, reason = io.open(shell.resolve(arg))
3923 if not file then
3924 io.stderr:write(reason,'\n')
3925 return 1
3926 end
3927 initial_offset = file:seek("cur")
3928 file:close()
3929 end
3930end
3931
3932local width, height = term.getViewport()
3933local max_display = height - 1
3934
3935-- mgr is the data manager, it keeps track of what has been loaded
3936-- keeps a reasonable buffer, and keeps track of file handles
3937local mgr
3938mgr =
3939{
3940 lines = {}, -- current buffer
3941 chunk, -- temp from last read line that hasn't finished wrapping
3942 lines_released = 0,
3943 can_seek = initial_offset,
3944 capacity = math.max(1, math.min(max_display * 10, computer.freeMemory() / 2 / width)),
3945 size = 0,
3946 file = nil,
3947 path = arg ~= "-" and shell.resolve(arg) or nil,
3948 open = function()
3949 mgr.file = mgr.path and io.open(mgr.path) or io.stdin
3950 end,
3951 top_of_file = max_display,
3952 total_lines = nil, -- nil means unknown
3953 latest_line = nil, -- used for status improvements
3954 rollback = function()
3955 if not mgr.can_seek then
3956 return false
3957 end
3958 if not mgr.file then
3959 mgr.open()
3960 elseif not mgr.file:seek("set", 0) then
3961 mgr.close()
3962 return false
3963 end
3964 mgr.lines_released = 0
3965 mgr.lines = {}
3966 mgr.size = 0
3967 return true
3968 end,
3969 at = function(line_number)
3970 local index = line_number - mgr.lines_released
3971 if index < 1 then
3972 index = index + mgr.capacity
3973 if #mgr.lines ~= mgr.capacity or index <= mgr.size then
3974 return nil
3975 end
3976 elseif index > mgr.size then
3977 return nil
3978 end
3979 return mgr.lines[index] -- cached
3980 end,
3981 load = function(line_number)
3982 local index = line_number - mgr.lines_released
3983 if mgr.total_lines and mgr.total_lines < line_number then
3984 return nil
3985 end
3986 if mgr.at(line_number) then
3987 return true
3988 end
3989 -- lines[index] is line (lines_released + index) in the file
3990 -- thus index == line_number - lines_released
3991 if index <= 0 then
3992 -- we have previously freed some of the buffer, and now the user wants it back
3993 if not mgr.rollback() then
3994 -- TODO how to nicely fail if can_seek == false
3995 -- or if no more buffers
3996 error("cannot load prior data")
3997 end
3998 return mgr.load(line_number) -- retry
3999 end
4000 if mgr.read_next() then
4001 return mgr.load(line_number) -- retry
4002 end
4003 -- ran out of file, could not reach line_number
4004 end,
4005 write = function(line_number)
4006 local line = mgr.at(line_number)
4007 if not line then return false end
4008 term.write(line)
4009 end,
4010 close = function()
4011 if mgr.file then
4012 mgr.file:close()
4013 mgr.file = nil
4014 end
4015 end,
4016 last = function()
4017 -- return the last line_number available right now in the cache
4018 return mgr.size + mgr.lines_released
4019 end,
4020 check_capacity = function(release)
4021 -- if we have reached capacity
4022 if mgr.size >= mgr.capacity then
4023 if release then
4024 mgr.lines_released = mgr.lines_released + mgr.size
4025 mgr.size = 0
4026 end
4027 return true
4028 end
4029 end,
4030 insert = function(line)
4031 if mgr.check_capacity() then return false end
4032 mgr.size = mgr.size + 1
4033 mgr.lines[mgr.size] = line
4034 -- latest_line is not used for computation, just for status reports
4035 mgr.latest_line = math.max(mgr.latest_line or 0, mgr.size + mgr.lines_released)
4036 return true
4037 end,
4038 read_next = function()
4039 -- total_lines indicates we've reached the end previously
4040 -- but have we just prior to this reached the end?
4041 if mgr.last() == mgr.total_lines then
4042 -- then there is no more after that point
4043 return nil
4044 end
4045 if not mgr.file then
4046 mgr.open()
4047 end
4048 mgr.check_capacity(true)
4049 if not mgr.chunk then
4050 mgr.chunk = mgr.file:read("*l")
4051 if not mgr.chunk then
4052 mgr.total_lines = mgr.size + mgr.lines_released -- now file length is known
4053 mgr.close()
4054 end
4055 end
4056 while mgr.chunk do
4057 local wrapped, next = text.wrap(text.detab(mgr.chunk), width, width)
4058 -- insert fails if capacity is full
4059 if not mgr.insert(wrapped) then
4060 return mgr.last()
4061 end
4062 mgr.chunk = next
4063 end
4064
4065 return mgr.last()
4066 end,
4067 scroll = function(num)
4068 if num < 0 then
4069 num = math.max(num, mgr.top_of_file)
4070 if num >= 0 then
4071 return true -- nothing to scroll
4072 end
4073 end
4074
4075 term.setCursor(1, height)
4076 local y = height
4077 term.clearLine()
4078
4079 if num < 0 then
4080 term.scroll(num) -- push text down
4081 mgr.top_of_file = mgr.top_of_file - num
4082 y = 1
4083 term.setCursor(1, y) -- ready to write lines above
4084 num = -num -- now print forward
4085 end
4086
4087 local range
4088 while num > 0 do
4089 -- trigger load of data if needed
4090 local line_number = y - mgr.top_of_file
4091
4092 if not mgr.load(line_number) then -- nothing more to read from the file
4093 return range ~= nil -- first time it is nil
4094 end
4095
4096 -- print num range of what is available, scroll to show it (if bottom of screen)
4097 range = math.min(num, mgr.last() - line_number + 1)
4098
4099 if y == height then
4100 range = math.min(range, max_display)
4101 term.scroll(range)
4102 y = y - range
4103 term.setCursor(1, y)
4104 mgr.top_of_file = mgr.top_of_file - range
4105 end
4106
4107 for i=1,range do
4108 mgr.write(line_number + i - 1)
4109 term.setCursor(1, y + i)
4110 end
4111 y = y + range
4112
4113 num = num - range
4114 end
4115
4116 return true
4117 end,
4118 print_status = function()
4119 local first = mgr.top_of_file >= 1 and 1 or 1 - mgr.top_of_file
4120 local perc = not mgr.total_lines and "--" or tostring((max_display - mgr.top_of_file) / mgr.total_lines * 100):gsub("%..*","")
4121 local last_plus = mgr.total_lines and "" or "+"
4122 local status = string.format("%s lines %d-%d/%s %s%%", mgr.path or "-", first, max_display - mgr.top_of_file, tostring(mgr.total_lines or mgr.latest_line)..last_plus, perc)
4123
4124 local gpu = term.gpu()
4125 local sf, sb = gpu.setForeground, gpu.setBackground
4126 local b_color, b_is_palette = gpu.getBackground()
4127 local f_color, f_is_palette = gpu.getForeground()
4128 sf(b_color, b_is_palette)
4129 sb(f_color, f_is_palette)
4130 term.write(status)
4131 sb(b_color, b_is_palette)
4132 sf(f_color, f_is_palette)
4133 end
4134}
4135
4136local function update(num)
4137 -- unexpected
4138 if num == 0 then
4139 return
4140 end
4141
4142 -- if this a positive direction, and we didn't previously know this was the end of the stream, give the user a once chance
4143 local end_is_known = mgr.total_lines
4144 -- clear buttom line, for status
4145 local ok = mgr.scroll(num or max_display)
4146
4147 -- print status
4148 term.setCursor(1, height)
4149 -- we have to clear again in case we scrolled up
4150 term.clearLine()
4151 mgr.print_status()
4152 return not end_is_known or ok
4153end
4154
4155if not update() then
4156 return
4157end
4158
4159while true do
4160 local ename, address, char, code, dy = term.pull()
4161 local num
4162 if ename == "scroll" then
4163 if dy < 0 then
4164 num = 3
4165 else
4166 num = -3
4167 end
4168 elseif ename == "key_down" then
4169 num = 0
4170 if code == keyboard.keys.q or code == keyboard.keys.d and keyboard.isControlDown() then
4171 break
4172 elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
4173 num = max_display
4174 elseif code == keyboard.keys.pageUp then
4175 num = -max_display
4176 elseif code == keyboard.keys.enter or code == keyboard.keys.down then
4177 num = 1
4178 elseif code == keyboard.keys.up then
4179 num = -1
4180 elseif code == keyboard.keys.home then
4181 num = -math.huge
4182 elseif code == keyboard.keys["end"] then
4183 num = math.huge
4184 end
4185 elseif ename == "interrupted" then
4186 break
4187 end
4188 if num then
4189 update(num)
4190 end
4191end
4192
4193term.clearLine()
4194 mnt/6c1/bin/uptime.lua 0000600 00000000543 13215223673 007270 0 local computer = require("computer")
4195
4196local seconds = math.floor(computer.uptime())
4197local minutes, hours = 0, 0
4198if seconds >= 60 then
4199 minutes = math.floor(seconds / 60)
4200 seconds = seconds % 60
4201end
4202if minutes >= 60 then
4203 hours = math.floor(minutes / 60)
4204 minutes = minutes % 60
4205end
4206io.write(string.format("%02d:%02d:%02d\n", hours, minutes, seconds))
4207 mnt/6c1/bin/mount.lua 0000600 00000004651 13215223705 007127 0 local fs = require("filesystem")
4208local shell = require("shell")
4209
4210local function usage()
4211 io.stderr:write([==[
4212Usage: mount [OPTIONS] [device path]")
4213 If no args are given, all current mount points are printed.
4214 <Options> Note that multiple options can be used together
4215 -r, --ro Mount the filesystem read only
4216 --bind Create a mount bind point, folder to folder
4217 <Args>
4218 device Specify filesystem device by one of:
4219 a. label
4220 b. address (can be abbreviated)
4221 c. folder path (requires --bind)
4222 path Target folder path to mount to
4223
4224See `man mount` for more details
4225 ]==])
4226 os.exit(1)
4227end
4228
4229-- smart parse, follow arg after -o
4230local args, opts = shell.parse(...)
4231opts.readonly = opts.r or opts.readonly
4232
4233if opts.h or opts.help then
4234 usage()
4235end
4236
4237local function print_mounts()
4238 -- for each mount
4239 local mounts = {}
4240
4241 for proxy,path in fs.mounts() do
4242 local device = {}
4243
4244 device.dev_path = proxy.address
4245 device.mount_path = path
4246 device.rw_ro = proxy.isReadOnly() and "ro" or "rw"
4247 device.fs_label = proxy.getLabel() or proxy.address
4248
4249 mounts[device.dev_path] = mounts[device.dev_path] or {}
4250 local dev_mounts = mounts[device.dev_path]
4251 table.insert(dev_mounts, device)
4252 end
4253
4254 local smounts = {}
4255 for key,value in pairs(mounts) do
4256 smounts[#smounts+1] = {key, value}
4257 end
4258 table.sort(smounts, function(a,b) return a[1] < b[1] end)
4259
4260 for _, dev in ipairs(smounts) do
4261 local dev_path, dev_mounts = table.unpack(dev)
4262 for _,device in ipairs(dev_mounts) do
4263 local rw_ro = "(" .. device.rw_ro .. ")"
4264 local fs_label = "\"" .. device.fs_label .. "\""
4265
4266 io.write(string.format("%-8s on %-10s %s %s\n",
4267 dev_path:sub(1,8),
4268 device.mount_path,
4269 rw_ro,
4270 fs_label))
4271 end
4272 end
4273end
4274
4275local function do_mount()
4276 -- bind converts a path to a proxy
4277 local proxy, reason = fs.proxy(args[1], opts)
4278 if not proxy then
4279 io.stderr:write("Failed to mount: ", tostring(reason), "\n")
4280 os.exit(1)
4281 end
4282
4283 local result, mount_failure = fs.mount(proxy, shell.resolve(args[2]))
4284 if not result then
4285 io.stderr:write(mount_failure, "\n")
4286 os.exit(2) -- error code
4287 end
4288end
4289
4290if #args == 0 then
4291 if next(opts) then
4292 io.stderr:write("Missing argument\n")
4293 usage()
4294 else
4295 print_mounts()
4296 end
4297elseif #args == 2 then
4298 do_mount()
4299else
4300 io.stderr:write("wrong number of arguments: ", #args, "\n")
4301 usage()
4302end
4303 mnt/6c1/bin/components.lua 0000600 00000002415 13215223667 010155 0 local component = require("component")
4304local shell = require("shell")
4305local text = require("text")
4306
4307local args, options = shell.parse(...)
4308local count = tonumber(options.limit) or math.huge
4309
4310local components = {}
4311local padTo = 1
4312
4313if #args == 0 then -- get all components if no filters given.
4314 args[1] = ""
4315end
4316for _, filter in ipairs(args) do
4317 for address, name in component.list(filter) do
4318 if name:len() > padTo then
4319 padTo = name:len() + 2
4320 end
4321 components[address] = name
4322 end
4323end
4324
4325padTo = padTo + 8 - padTo % 8
4326for address, name in pairs(components) do
4327 io.write(text.padRight(name, padTo) .. address .. '\n')
4328
4329 if options.l then
4330 local proxy = component.proxy(address)
4331 local padTo = 1
4332 local methods = {}
4333 for name, member in pairs(proxy) do
4334 if type(member) == "table" or type(member) == "function" then
4335 if name:len() > padTo then
4336 padTo = name:len() + 2
4337 end
4338 table.insert(methods, name)
4339 end
4340 end
4341 table.sort(methods)
4342 padTo = padTo + 8 - padTo % 8
4343
4344 for _, name in ipairs(methods) do
4345 local doc = component.doc(address, name) or tostring(proxy[name])
4346 io.write(" " .. text.padRight(name, padTo) .. doc .. '\n')
4347 end
4348 end
4349
4350 count = count - 1
4351 if count <= 0 then
4352 break
4353 end
4354end
4355 mnt/6c1/bin/shutdown.lua 0000600 00000000140 13215223704 007624 0 local computer = require("computer")
4356local tty = require("tty")
4357
4358tty.clear()
4359computer.shutdown() mnt/6c1/bin/free.lua 0000600 00000000407 13215223677 006711 0 local computer = require("computer")
4360local total = computer.totalMemory()
4361local max = 0
4362for _=1,40 do
4363 max = math.max(max, computer.freeMemory())
4364 os.sleep(0) -- invokes gc
4365end
4366io.write(string.format("Total%12d\nUsed%13d\nFree%13d\n", total, total - max, max))
4367 mnt/6c1/bin/label.lua 0000600 00000001777 13215223674 007057 0 local shell = require("shell")
4368local devfs = require("devfs")
4369local comp = require("component")
4370
4371local args, options = shell.parse(...)
4372if #args < 1 then
4373 io.write("Usage: label [-a] <device> [<label>]\n")
4374 io.write(" -a Device is specified via label or address instead of by path.\n")
4375 return 1
4376end
4377
4378local filter = args[1]
4379local label = args[2]
4380
4381local proxy, reason
4382
4383if options.a then
4384 for addr in comp.list() do
4385 if addr:sub(1, filter:len()) == filter then
4386 proxy, reason = comp.proxy(addr)
4387 break
4388 end
4389 local tmp_proxy = comp.proxy(addr)
4390 local tmp_label = devfs.getDeviceLabel(tmp_proxy)
4391 if tmp_label == filter then
4392 proxy = tmp_proxy
4393 break
4394 end
4395 end
4396else
4397 proxy, reason = devfs.getDevice(args[1])
4398end
4399
4400if not proxy then
4401 io.stderr:write(reason..'\n')
4402 return 1
4403end
4404
4405if #args < 2 then
4406 local label = devfs.getDeviceLabel(proxy)
4407 if label then
4408 print(label)
4409 else
4410 io.stderr:write("no label\n")
4411 return 1
4412 end
4413else
4414 devfs.setDeviceLabel(proxy, args[2])
4415end
4416 mnt/6c1/bin/set.lua 0000600 00000000746 13215223671 006563 0 local args = {...}
4417
4418if #args < 1 then
4419 for k,v in pairs(os.getenv()) do
4420 io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n")
4421 end
4422else
4423 local count = 0
4424 for _, expr in ipairs(args) do
4425 local e = expr:find('=')
4426 if e then
4427 os.setenv(expr:sub(1,e-1), expr:sub(e+1))
4428 else
4429 if count == 0 then
4430 for i = 1, os.getenv('#') do
4431 os.setenv(i, nil)
4432 end
4433 end
4434 count = count + 1
4435 os.setenv(count, expr)
4436 end
4437 end
4438end
4439 mnt/6c1/bin/head.lua 0000600 00000006076 13215223703 006667 0 local shell = require("shell")
4440local fs = require("filesystem")
4441
4442local args, options = shell.parse(...)
4443local error_code = 0
4444
4445local function pop(key, convert)
4446 local result = options[key]
4447 options[key] = nil
4448 if result and convert then
4449 local c = tonumber(result)
4450 if not c then
4451 io.stderr:write(string.format("use --%s=n where n is a number\n", key))
4452 options.help = true
4453 error_code = 1
4454 end
4455 result = c
4456 end
4457 return result
4458end
4459
4460local bytes = pop('bytes', true)
4461local lines = pop('lines', true)
4462local quiet = {pop('q'), pop('quiet'), pop('silent')}
4463quiet = quiet[1] or quiet[2] or quiet[3]
4464local verbose = {pop('v'), pop('verbose')}
4465verbose = verbose[1] or verbose[2]
4466local help = pop('help')
4467local invalid_key = next(options)
4468
4469if bytes and lines then
4470 invalid_key = 'bytes and lines both specified'
4471end
4472
4473if help or next(options) then
4474 local invalid_key = next(options)
4475 if invalid_key then
4476 invalid_key = string.format('invalid option: %s\n', invalid_key)
4477 error_code = 1
4478 else
4479 invalid_key = ''
4480 end
4481 print(invalid_key .. [[Usage: head [--lines=n] file
4482Print the first 10 lines of each FILE to stdout.
4483For more info run: man head]])
4484 os.exit(error_code)
4485end
4486
4487if #args == 0 then
4488 args = {'-'}
4489end
4490
4491if quiet and verbose then
4492 quiet = false
4493end
4494
4495local function new_stream()
4496 return
4497 {
4498 open=true,
4499 capacity=math.abs(lines or bytes or 10),
4500 bytes=bytes,
4501 buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '')
4502 }
4503end
4504
4505local function close(stream)
4506 if stream.buffer then
4507 if type(stream.buffer) == 'table' then
4508 stream.buffer = table.concat(stream.buffer)
4509 end
4510 io.stdout:write(stream.buffer)
4511 stream.buffer = nil
4512 end
4513 stream.open = false
4514end
4515
4516local function push(stream, line)
4517 if not line then
4518 return close(stream)
4519 end
4520
4521 local cost = stream.bytes and line:len() or 1
4522 stream.capacity = stream.capacity - cost
4523
4524 if not stream.buffer then
4525 if stream.bytes and stream.capacity < 0 then
4526 line = line:sub(1,stream.capacity-1)
4527 end
4528 io.write(line)
4529 if stream.capacity <= 0 then
4530 return close(stream)
4531 end
4532 else
4533 if type(stream.buffer) == 'table' then -- line storage
4534 stream.buffer[#stream.buffer+1] = line
4535 if stream.capacity < 0 then
4536 table.remove(stream.buffer, 1)
4537 stream.capacity = 0 -- zero out
4538 end
4539 else -- byte storage
4540 stream.buffer = stream.buffer .. line
4541 if stream.capacity < 0 then
4542 stream.buffer = stream.buffer:sub(-stream.capacity+1)
4543 stream.capacity = 0 -- zero out
4544 end
4545 end
4546 end
4547
4548end
4549
4550for i=1,#args do
4551 local arg = args[i]
4552 local file
4553 if arg == '-' then
4554 arg = 'standard input'
4555 file = io.stdin
4556 else
4557 file, reason = io.open(arg, 'r')
4558 if not file then
4559 io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason))
4560 end
4561 end
4562 if file then
4563 if verbose or #args > 1 then
4564 io.write(string.format('==> %s <==\n', arg))
4565 end
4566
4567 local stream = new_stream()
4568
4569 while stream.open do
4570 push(stream, file:read('*L'))
4571 end
4572
4573 file:close()
4574 end
4575end
4576 mnt/6c1/bin/list.lua 0000600 00000001273 13215223711 006732 0 local fs = require("filesystem")
4577local shell = require("shell")
4578
4579local args, ops = shell.parse(...)
4580if #args == 0 then
4581 table.insert(args, ".")
4582end
4583
4584local arg = args[1]
4585local path = shell.resolve(arg)
4586
4587if ops.help then
4588 io.write([[Usage: list [path]
4589 path:
4590 optional argument (defaults to ./)
4591 Displays a list of files in the given path with no added formatting
4592 Intended for low memory systems
4593]])
4594 return 0
4595end
4596
4597local real, why = fs.realPath(path)
4598if real and not fs.exists(real) then
4599 why = "no such file or directory"
4600end
4601if why then
4602 io.stderr:write(string.format("cannot access '%s': %s", arg, tostring(why)))
4603 return 1
4604end
4605
4606for item in fs.list(real) do
4607 io.write(item, '\n')
4608end
4609 mnt/6c1/bin/resolution.lua 0000600 00000001147 13215223671 010167 0 local shell = require("shell")
4610local tty = require("tty")
4611
4612local args = shell.parse(...)
4613local gpu = tty.gpu()
4614
4615if #args == 0 then
4616 local w, h = gpu.getViewport()
4617 io.write(w," ",h,"\n")
4618 return
4619end
4620
4621if #args ~= 2 then
4622 print("Usage: resolution [<width> <height>]")
4623 return
4624end
4625
4626local w = tonumber(args[1])
4627local h = tonumber(args[2])
4628if not w or not h then
4629 io.stderr:write("invalid width or height\n")
4630 return 1
4631end
4632
4633local result, reason = gpu.setResolution(w, h)
4634if not result then
4635 if reason then -- otherwise we didn't change anything
4636 io.stderr:write(reason..'\n')
4637 end
4638 return 1
4639end
4640tty.clear()
4641 mnt/6c1/bin/cp.lua 0000600 00000001627 13215223670 006370 0 local shell = require("shell")
4642local transfer = require("tools/transfer")
4643
4644local args, options = shell.parse(...)
4645options.h = options.h or options.help
4646if #args < 2 or options.h then
4647 io.write([[Usage: cp [OPTIONS] <from...> <to>
4648 -i: prompt before overwrite (overrides -n option).
4649 -n: do not overwrite an existing file.
4650 -r: copy directories recursively.
4651 -u: copy only when the SOURCE file differs from the destination
4652 file or when the destination file is missing.
4653 -P: preserve attributes, e.g. symbolic links.
4654 -v: verbose output.
4655 -x: stay on original source file system.
4656 --skip=P: skip files matching lua regex P
4657]])
4658 return not not options.h
4659end
4660
4661-- clean options for copy (as opposed to move)
4662options =
4663{
4664 cmd = "cp",
4665 i = options.i,
4666 n = options.n,
4667 r = options.r,
4668 u = options.u,
4669 P = options.P,
4670 v = options.v,
4671 x = options.x,
4672 skip = options.skip,
4673}
4674
4675return transfer.batch(args, options)
4676 mnt/6c1/bin/grep.lua 0000600 00000021310 13215223701 006705 0 --[[
4677An adaptation of Wobbo's grep
4678https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua
4679]]--
4680
4681-- POSIX grep for OpenComputers
4682-- one difference is that this version uses Lua regex, not POSIX regex.
4683
4684local fs = require("filesystem")
4685local shell = require("shell")
4686local tty = require("tty")
4687local computer = require("computer")
4688
4689-- Process the command line arguments
4690
4691local args, options = shell.parse(...)
4692
4693local gpu = tty.gpu()
4694
4695local function printUsage(ostream, msg)
4696 local s = ostream or io.stdout
4697 if msg then
4698 s:write(msg,'\n')
4699 end
4700 s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
4701Example: grep -i "hello world" menu.lua main.lua
4702for more information, run: man grep
4703]])
4704end
4705
4706local PATTERNS = {args[1]}
4707local FILES = {select(2, table.unpack(args))}
4708
4709local LABEL_COLOR = 0xb000b0
4710local LINE_NUM_COLOR = 0x00FF00
4711local MATCH_COLOR = 0xFF0000
4712local COLON_COLOR = 0x00FFFF
4713
4714local function pop(...)
4715 local result
4716 for _,key in ipairs({...}) do
4717 result = options[key] or result
4718 options[key] = nil
4719 end
4720 return result
4721end
4722
4723-- Specify the variables for the options
4724local plain = pop('F','fixed-strings')
4725 plain = not pop('e','--lua-regexp') and plain
4726local pattern_file = pop('file')
4727local match_whole_word = pop('w','word-regexp')
4728local match_whole_line = pop('x','line-regexp')
4729local ignore_case = pop('i','ignore-case')
4730local stdin_label = pop('label') or '(standard input)'
4731local stderr = pop('s','no-messages') and {write=function()end} or io.stderr
4732local invert_match = not not pop('v','invert-match')
4733
4734-- no version output, just help
4735if pop('V','version','help') then
4736 printUsage()
4737 return 0
4738end
4739
4740local max_matches = tonumber(pop('max-count')) or math.huge
4741local print_line_num = pop('n','line-number')
4742local search_recursively = pop('r','recursive')
4743
4744-- Table with patterns to check for
4745if pattern_file then
4746 local pattern_file_path = shell.resolve(pattern_file)
4747 if not fs.exists(pattern_file_path) then
4748 stderr:write('grep: ',pattern_file,': file not found')
4749 return 2
4750 end
4751 table.insert(FILES, 1, PATTERNS[1])
4752 PATTERNS = {}
4753 for line in io.lines(pattern_file_path) do
4754 PATTERNS[#PATTERNS+1] = line
4755 end
4756end
4757
4758if #PATTERNS == 0 then
4759 printUsage(stderr)
4760 return 2
4761end
4762
4763if #FILES == 0 then
4764 FILES = search_recursively and {'.'} or {'-'}
4765end
4766
4767if not options.h and search_recursively then
4768 options.H = true
4769end
4770
4771if #FILES < 2 then
4772 options.h = true
4773end
4774
4775local f_only = pop('l','files-with-matches')
4776local no_only = pop('L','files-without-match') and not f_only
4777
4778local include_filename = pop('H','with-filename')
4779 include_filename = not pop('h','no-filename') or include_filename
4780
4781local m_only = pop('o','only-matching')
4782local quiet = pop('q','quiet','silent')
4783
4784local print_count = pop('c','count')
4785local colorize = pop('color','colour') and io.output().tty and tty.isAvailable()
4786
4787local noop = function(...)return ...;end
4788local setc = colorize and gpu.setForeground or noop
4789local getc = colorize and gpu.getForeground or noop
4790
4791local trim = pop('t','trim')
4792local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
4793local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop
4794
4795if next(options) then
4796 if not quiet then
4797 printUsage(stderr, 'unexpected option: '..next(options))
4798 return 2
4799 end
4800 return 0
4801end
4802-- Resolve the location of a file, without searching the path
4803local function resolve(file)
4804 if file:sub(1,1) == '/' then
4805 return fs.canonical(file)
4806 else
4807 if file:sub(1,2) == './' then
4808 file = file:sub(3, -1)
4809 end
4810 return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
4811 end
4812end
4813
4814--- Builds a case insensitive patterns, code from stackoverflow
4815--- (questions/11401890/case-insensitive-lua-pattern-matching)
4816if ignore_case then
4817 for i=1,#PATTERNS do
4818 -- find an optional '%' (group 1) followed by any character (group 2)
4819 PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
4820 if percent ~= "" or not letter:match("%a") then
4821 -- if the '%' matched, or `letter` is not a letter, return "as is"
4822 return percent .. letter
4823 else -- case-insensitive
4824 return string.format("[%s%s]", letter:lower(), letter:upper())
4825 end
4826 end)
4827 end
4828end
4829
4830local function getAllFiles(dir, file_list)
4831 for node in fs.list(shell.resolve(dir)) do
4832 local rel_path = dir:gsub("/+$","") .. '/' .. node
4833 local resolved_path = shell.resolve(rel_path)
4834 if fs.isDirectory(resolved_path) then
4835 getAllFiles(rel_path, file_list)
4836 else
4837 file_list[#file_list+1] = rel_path
4838 end
4839 end
4840end
4841
4842if search_recursively then
4843 local files = {}
4844 for i,arg in ipairs(FILES) do
4845 if fs.isDirectory(arg) then
4846 getAllFiles(arg, files)
4847 else
4848 files[#files+1]=arg
4849 end
4850 end
4851 FILES=files
4852end
4853
4854-- Prepare an iterator for reading files
4855local function readLines()
4856 local curHand = nil
4857 local curFile = nil
4858 local meta = nil
4859 return function()
4860 if not curFile then
4861 local file = table.remove(FILES, 1)
4862 if not file then
4863 return
4864 end
4865 meta = {line_num=0,hits=0}
4866 if file == "-" then
4867 curFile = file
4868 meta.label = stdin_label
4869 curHand = io.input()
4870 else
4871 meta.label = file
4872 local file, reason = resolve(file)
4873 if fs.exists(file) then
4874 curHand, reason = io.open(file, 'r')
4875 if not curHand then
4876 local msg = string.format("failed to read from %s: %s", meta.label, reason)
4877 stderr:write("grep: ",msg,"\n")
4878 return false, 2
4879 else
4880 curFile = meta.label
4881 end
4882 else
4883 stderr:write("grep: ",file,": file not found\n")
4884 return false, 2
4885 end
4886 end
4887 end
4888 meta.line = nil
4889 if not meta.close and curHand then
4890 meta.line_num = meta.line_num + 1
4891 meta.line = curHand:read("*l")
4892 end
4893 if not meta.line then
4894 curFile = nil
4895 if curHand then
4896 curHand:close()
4897 end
4898 return false, meta
4899 else
4900 return meta, curFile
4901 end
4902 end
4903end
4904
4905local function write(part, color)
4906 local prev_color = color and getc()
4907 if color then setc(color) end
4908 io.write(part)
4909 if color then setc(prev_color) end
4910end
4911local flush=(f_only or no_only or print_count) and function(m)
4912 if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
4913 write(m.label, LABEL_COLOR)
4914 write('\n')
4915 elseif print_count then
4916 if include_filename then
4917 write(m.label, LABEL_COLOR)
4918 write(':', COLON_COLOR)
4919 end
4920 write(m.hits)
4921 write('\n')
4922 end
4923end
4924local ec = nil
4925local any_hit_ec = 1
4926local function test(m,p)
4927 local empty_line = true
4928 local last_index, slen = 1, #m.line
4929 local needs_filename, needs_line_num = include_filename, print_line_num
4930 local hit_value = 1
4931 while last_index <= slen and not m.close do
4932 local i, j = m.line:find(p, last_index, plain)
4933 local word_fail, line_fail =
4934 match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
4935 match_whole_line and not (i==1 and j==slen)
4936 local matched = not ((m_only or last_index==1) and not i)
4937 if (hit_value == 1 and word_fail) or line_fail then
4938 matched,i,j = false
4939 end
4940 if invert_match == matched then break end
4941 if max_matches == 0 then os.exit(1) end
4942 any_hit_ec = 0
4943 m.hits, hit_value = m.hits + hit_value, 0
4944 if f_only or no_only then
4945 m.close = true
4946 end
4947 if flush or quiet then return end
4948 if needs_filename then
4949 write(m.label, LABEL_COLOR)
4950 write(':', COLON_COLOR)
4951 needs_filename = nil
4952 end
4953 if needs_line_num then
4954 write(m.line_num, LINE_NUM_COLOR)
4955 write(':', COLON_COLOR)
4956 needs_line_num = nil
4957 end
4958 local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
4959 local g=i and m.line:sub(i,j) or ''
4960 if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
4961 if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
4962 write(s)
4963 write(g, MATCH_COLOR)
4964 empty_line = false
4965 last_index = (j or slen)+1
4966 if m_only or last_index>slen then
4967 write("\n")
4968 empty_line = true
4969 needs_filename, needs_line_num = include_filename, print_line_num
4970 elseif p:find("^^") and not plain then p="^$" end
4971 end
4972 if not empty_line then write("\n") end
4973 if max_matches ~= math.huge and max_matches >= m.hits then
4974 m.close = true
4975 end
4976end
4977
4978local uptime = computer.uptime
4979local last_sleep = uptime()
4980for meta,status in readLines() do
4981 if uptime() - last_sleep > 1 then
4982 os.sleep(0)
4983 last_sleep = uptime()
4984 end
4985 if not meta then
4986 if type(status) == 'table' then if flush then
4987 flush(status) end -- this was the last object, closing out
4988 elseif status then
4989 ec = status or ec
4990 end
4991 else
4992 for _,p in ipairs(PATTERNS) do
4993 test(meta,p)
4994 end
4995 end
4996end
4997
4998return ec or any_hit_ec
4999 mnt/6c1/bin/mktmp.lua 0000600 00000003167 13215223672 007121 0 local fs = require("filesystem")
5000local shell = require("shell")
5001local sh = require("sh")
5002
5003local touch = loadfile(shell.resolve("touch", "lua"))
5004local mkdir = loadfile(shell.resolve("mkdir", "lua"))
5005
5006if not touch then
5007 local errorMessage = "missing tools for mktmp"
5008 io.stderr:write(errorMessage .. '\n')
5009 return false, errorMessage
5010end
5011
5012local args, ops = shell.parse(...)
5013
5014local function pop(key)
5015 local result = ops[key]
5016 ops[key] = nil
5017 return result
5018end
5019
5020local directory = pop('d')
5021local verbose = pop('v')
5022verbose = pop('verbose') or verbose
5023local quiet = pop('q') or quiet
5024quiet = pop('quiet') or quiet
5025
5026if pop('help') or #args > 1 or next(ops) then
5027 print([[Usage: mktmp [OPTION] [PATH]
5028Create a new file with a random name in $TMPDIR or PATH argument if given
5029 -d create a directory instead of a file
5030 -v, --verbose print result to stdout, even if no tty
5031 -q, --quiet do not print results to stdout, even if tty (verbose overrides)
5032 --help print this help message]])
5033 if next(ops) then
5034 io.stderr:write("invalid option: " .. (next(ops)) .. '\n')
5035 return 1
5036 end
5037 return
5038end
5039
5040if not verbose then
5041 if not quiet then
5042 if io.stdout.tty then
5043 verbose = true
5044 end
5045 end
5046end
5047
5048local prefix = args[1] or os.getenv("TMPDIR") .. '/'
5049if not fs.exists(prefix) then
5050 io.stderr:write(
5051 string.format(
5052 "cannot create tmp file or directory at %s, it does not exist\n",
5053 prefix))
5054 return 1
5055end
5056
5057local tmp = os.tmpname()
5058local ok, reason = (directory and mkdir or touch)(tmp)
5059
5060if sh.internal.command_passed(ok) then
5061 if verbose then
5062 print(tmp)
5063 end
5064 return tmp
5065end
5066
5067return ok, reason
5068 mnt/6c1/bin/which.lua 0000600 00000000740 13215223704 007061 0 local shell = require("shell")
5069
5070local args = shell.parse(...)
5071if #args == 0 then
5072 io.write("Usage: which <program>\n")
5073 return 255
5074end
5075
5076for i = 1, #args do
5077 local result, reason = shell.resolve(args[i], "lua")
5078
5079 if not result then
5080 result = shell.getAlias(args[i])
5081 if result then
5082 result = args[i] .. ": aliased to " .. result
5083 end
5084 end
5085
5086 if result then
5087 print(result)
5088 else
5089 io.stderr:write(args[i] .. ": " .. reason .. "\n")
5090 return 1
5091 end
5092end
5093 mnt/6c1/bin/rm.lua 0000600 00000007635 13215223703 006406 0 local fs = require("filesystem")
5094local shell = require("shell")
5095
5096local function usage()
5097 print("Usage: rm [options] <filename1> [<filename2> [...]]"..[[
5098
5099 -f ignore nonexistent files and arguments, never prompt
5100 -r remove directories and their contents recursively
5101 -v explain what is being done
5102 --help display this help and exit
5103
5104For complete documentation and more options, run: man rm]])
5105end
5106
5107local args, options = shell.parse(...)
5108if #args == 0 or options.help then
5109 usage()
5110 return 1
5111end
5112
5113local bRec = options.r or options.R or options.recursive
5114local bForce = options.f or options.force
5115local bVerbose = options.v or options.verbose
5116local bEmptyDirs = options.d or options.dir
5117local promptLevel = (options.I and 3) or (options.i and 1) or 0
5118
5119bVerbose = bVerbose and not bForce
5120promptLevel = bForce and 0 or promptLevel
5121
5122local function perr(...)
5123 if not bForce then
5124 io.stderr:write(...)
5125 end
5126end
5127
5128local function pout(...)
5129 if not bForce then
5130 io.stdout:write(...)
5131 end
5132end
5133
5134local metas = {}
5135
5136-- promptLevel 3 done before fs.exists
5137-- promptLevel 1 asks for each, displaying fs.exists on hit as it visits
5138
5139local function _path(m) return shell.resolve(m.rel) end
5140local function _link(m) return fs.isLink(_path(m)) end
5141local function _exists(m) return _link(m) or fs.exists(_path(m)) end
5142local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end
5143local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end
5144local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end
5145
5146local function createMeta(origin, rel)
5147 local m = {origin=origin,rel=rel:gsub("/+$", "")}
5148 if _dir(m) then
5149 m.rel = m.rel .. '/'
5150 end
5151 return m
5152end
5153
5154local function unlink(path)
5155 os.remove(path)
5156 return true
5157end
5158
5159local function confirm()
5160 if bForce then
5161 return true
5162 end
5163 local r = io.read()
5164 return r == 'y' or r == 'yes'
5165end
5166
5167local function remove_all(parent)
5168 if parent == nil or not _dir(parent) or _empty(parent) then
5169 return true
5170 end
5171
5172 local all_ok = true
5173 if bRec and promptLevel == 1 then
5174 pout(string.format("rm: descend into directory `%s'? ", parent.rel))
5175 if not confirm() then
5176 return false
5177 end
5178
5179 for file in fs.list(_path(parent)) do
5180 local child = createMeta(parent.origin, parent.rel .. file)
5181 all_ok = remove(child) and all_ok
5182 end
5183 end
5184
5185 return all_ok
5186end
5187
5188local function remove(meta)
5189 if not remove_all(meta) then
5190 return false
5191 end
5192
5193 if not _exists(meta) then
5194 perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel))
5195 return false
5196 elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then
5197 if not bEmptyDirs then
5198 perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel))
5199 else
5200 perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel))
5201 end
5202 return false
5203 end
5204
5205 local ok = true
5206 if promptLevel == 1 then
5207 if _dir(meta) then
5208 pout(string.format("rm: remove directory `%s'? ", meta.rel))
5209 elseif meta.link then
5210 pout(string.format("rm: remove symbolic link `%s'? ", meta.rel))
5211 else -- file
5212 pout(string.format("rm: remove regular file `%s'? ", meta.rel))
5213 end
5214
5215 ok = confirm()
5216 end
5217
5218 if ok then
5219 if _readonly(meta) then
5220 perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel))
5221 return false
5222 elseif not unlink(_path(meta)) then
5223 perr(meta.rel .. ": failed to be removed\n")
5224 ok = false
5225 elseif bVerbose then
5226 pout("removed '" .. meta.rel .. "'\n");
5227 end
5228 end
5229
5230 return ok
5231end
5232
5233for _,arg in ipairs(args) do
5234 metas[#metas+1] = createMeta(arg, arg)
5235end
5236
5237if promptLevel == 3 and #metas > 3 then
5238 pout(string.format("rm: remove %i arguments? ", #metas))
5239 if not confirm() then
5240 return
5241 end
5242end
5243
5244local ok = true
5245for _,meta in ipairs(metas) do
5246 local result = remove(meta)
5247 ok = ok and result
5248end
5249
5250return bForce or ok
5251 mnt/6c1/bin/lshw.lua 0000600 00000002272 13215223705 006737 0 local computer = require("computer")
5252local shell = require("shell")
5253local text = require("text")
5254
5255local args, options = shell.parse(...)
5256
5257local devices = computer.getDeviceInfo()
5258local columns = {}
5259
5260if not next(options, nil) then
5261 options.t = true
5262 options.d = true
5263 options.p = true
5264end
5265if options.t then table.insert(columns, "Class") end
5266if options.d then table.insert(columns, "Description") end
5267if options.p then table.insert(columns, "Product") end
5268if options.v then table.insert(columns, "Vendor") end
5269if options.c then table.insert(columns, "Capacity") end
5270if options.w then table.insert(columns, "Width") end
5271if options.s then table.insert(columns, "Clock") end
5272
5273local m = {}
5274for address, info in pairs(devices) do
5275 for col, name in ipairs(columns) do
5276 m[col] = math.max(m[col] or 1, (info[name:lower()] or ""):len())
5277 end
5278end
5279
5280io.write(text.padRight("Address", 10))
5281for col, name in ipairs(columns) do
5282 io.write(text.padRight(name, m[col] + 2))
5283end
5284io.write("\n")
5285
5286for address, info in pairs(devices) do
5287 io.write(text.padRight(address:sub(1, 5).."...", 10))
5288 for col, name in ipairs(columns) do
5289 io.write(text.padRight(info[name:lower()] or "", m[col] + 2))
5290 end
5291 io.write("\n")
5292end
5293 mnt/6c1/bin 0000700 13215223711 004150 5 mnt/6c1/lib/tty.lua 0000600 00000034153 13215223745 006607 0 local unicode = require("unicode")
5294local event = require("event")
5295local kb = require("keyboard")
5296local component = require("component")
5297local computer = require("computer")
5298local keys = kb.keys
5299
5300local tty = {}
5301tty.window =
5302{
5303 fullscreen = true,
5304 blink = true,
5305 dx = 0,
5306 dy = 0,
5307 x = 1,
5308 y = 1,
5309}
5310
5311tty.stream = {}
5312
5313function tty.key_down_handler(handler, cursor, char, code)
5314 local data = cursor.data
5315 local c = false
5316 local backup_cache = handler.cache
5317 handler.cache = nil
5318 local ctrl = kb.isControlDown(tty.keyboard())
5319 if ctrl and code == keys.d then
5320 return --close
5321 elseif code == keys.tab then
5322 handler.cache = backup_cache
5323 tty.on_tab(handler, cursor)
5324 elseif code == keys.enter or code == keys.numpadenter then
5325 cursor:move(math.huge)
5326 cursor:draw("\n")
5327 if data:find("%S") and data ~= handler[1] then
5328 table.insert(handler, 1, data)
5329 handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil
5330 end
5331 handler[0]=nil
5332 return nil, data .. "\n"
5333 elseif code == keys.up or code == keys.down then
5334 local ni = handler.index + (code == keys.up and 1 or -1)
5335 if ni >= 0 and ni <= #handler then
5336 handler[handler.index] = data
5337 handler.index = ni
5338 cursor:clear()
5339 cursor:update(handler[ni])
5340 end
5341 elseif code == keys.left or code == keys.back or code == keys.w and ctrl then
5342 local value = ctrl and ((unicode.sub(data, 1, cursor.index):find("%s[^%s]+%s*$") or 0) - cursor.index) or -1
5343 if code == keys.left then
5344 cursor:move(value)
5345 else
5346 c = value
5347 end
5348 elseif code == keys.right then cursor:move(ctrl and ((data:find("%s[^%s]", cursor.index + 1) or math.huge) - cursor.index) or 1)
5349 elseif code == keys.home then cursor:move(-math.huge)
5350 elseif code == keys["end"] then cursor:move( math.huge)
5351 elseif code == keys.delete then c = 1
5352 elseif char >= 32 then c = unicode.char(char)
5353 else handler.cache = backup_cache -- ignored chars shouldn't clear hint cache
5354 end
5355 return c
5356end
5357
5358local screen_cache = {}
5359local function screen_reset(gpu, addr)
5360 screen_cache[addr or gpu.getScreen() or false] = nil
5361end
5362
5363event.listen("screen_resized", screen_reset)
5364
5365function tty.getViewport()
5366 local window = tty.window
5367 local screen = tty.screen()
5368 if window.fullscreen and screen and not screen_cache[screen] then
5369 screen_cache[screen] = true
5370 window.width, window.height = window.gpu.getViewport()
5371 end
5372
5373 return window.width, window.height, window.dx, window.dy, window.x, window.y
5374end
5375
5376function tty.setViewport(width, height, dx, dy, x, y)
5377 local window = tty.window
5378 dx, dy, x, y = dx or 0, dy or 0, x or 1, y or 1
5379 window.width, window.height, window.dx, window.dy, window.x, window.y = width, height, dx, dy, x, y
5380end
5381
5382function tty.gpu()
5383 return tty.window.gpu
5384end
5385
5386function tty.clear()
5387 tty.stream.scroll(math.huge)
5388 tty.setCursor(1, 1)
5389end
5390
5391function tty.isAvailable()
5392 local gpu = tty.gpu()
5393 return not not (gpu and gpu.getScreen())
5394end
5395
5396function tty.stream:pull(timeout, ...)
5397 timeout = timeout or math.huge
5398 local blink_timeout = tty.window.blink and .5 or math.huge
5399
5400 local width, height, dx, dy, x, y = tty.getViewport()
5401 local gpu = tty.gpu()
5402 if x < 1 or x > width or y < 1 or y > height then
5403 gpu = nil
5404 end
5405 local char_at_cursor
5406 local blinked
5407 if gpu then
5408 blinked, char_at_cursor = pcall(gpu.get, x + dx, y + dy)
5409 if not blinked then
5410 return nil, "interrupted"
5411 end
5412 io.write("\0277\27[7m", char_at_cursor, "\0278")
5413 end
5414
5415 -- get the next event
5416 while true do
5417 local signal = table.pack(event.pull(math.min(blink_timeout, timeout), ...))
5418
5419 timeout = timeout - blink_timeout
5420 local done = signal.n > 1 or timeout < blink_timeout
5421 if gpu then
5422 if not blinked and not done then
5423 io.write("\0277\27[7m", char_at_cursor, "\0278")
5424 blinked = true
5425 elseif blinked and (done or tty.window.blink) then
5426 io.write("\0277", char_at_cursor, "\0278")
5427 blinked = false
5428 end
5429 end
5430
5431 if done then
5432 return table.unpack(signal, 1, signal.n)
5433 end
5434 end
5435end
5436
5437function tty.split(cursor)
5438 local data, index = cursor.data, cursor.index
5439 local dlen = unicode.len(data)
5440 index = math.max(0, math.min(index, dlen))
5441 local tail = dlen - index
5442 return unicode.sub(data, 1, index), tail == 0 and "" or unicode.sub(data, -tail)
5443end
5444
5445function tty.build_vertical_reader()
5446 return
5447 {
5448 promptx = tty.window.x,
5449 prompty = tty.window.y,
5450 index = 0,
5451 data = "",
5452 sy = 0,
5453 scroll = function(self, goback, prev_x, prev_y)
5454 local width, x = tty.window.width, tty.getCursor() - 1
5455 tty.setCursor(x % width + 1, tty.window.y + math.floor(x / width))
5456 self:draw("")
5457 if goback then
5458 tty.setCursor(prev_x, prev_y - self.sy)
5459 end
5460 end,
5461 move = function(self, n)
5462 local win = tty.window
5463 self.index = math.min(math.max(0, self.index + n), unicode.len(self.data))
5464 local s1, s2 = tty.split(self)
5465 s2 = unicode.sub(s2.." ", 1, 1)
5466 local data_remaining = ("_"):rep(self.promptx - 1)..s1..s2
5467 win.y = self.prompty - self.sy
5468 while true do
5469 local wlen_remaining = unicode.wlen(data_remaining)
5470 if wlen_remaining > win.width then
5471 local line_cut = unicode.wtrunc(data_remaining, win.width + 1)
5472 data_remaining = unicode.sub(data_remaining, unicode.len(line_cut) + 1)
5473 win.y = win.y + 1
5474 else
5475 win.x = wlen_remaining - unicode.wlen(s2) + 1
5476 break
5477 end
5478 end
5479 end,
5480 clear_tail = function(self)
5481 local oi, width, _, dx, dy, ox, oy = self.index, tty.getViewport()
5482 self:move(math.huge)
5483 self:move(-1)
5484 local _, ey = tty.getCursor()
5485 tty.setCursor(ox, oy)
5486 self.index = oi
5487 local cx = oy == ey and ox or 1
5488 tty.gpu().fill(cx + dx, ey + dy, width - cx + 1, 1, " ")
5489 end,
5490 update = function(self, arg)
5491 local s1, s2 = tty.split(self)
5492 if type(arg) == "string" then
5493 self.data = s1 .. arg .. s2
5494 self.index = self.index + unicode.len(arg)
5495 self:draw(arg)
5496 else -- number
5497 if arg < 0 then
5498 -- backspace? ignore if at start
5499 if self.index <= 0 then return end
5500 self:move(arg)
5501 s1 = unicode.sub(s1, 1, -1 + arg)
5502 else
5503 -- forward? ignore if at end
5504 if self.index >= unicode.len(self.data) then return end
5505 s2 = unicode.sub(s2, 1 + arg)
5506 end
5507 self:clear_tail()
5508 self.data = s1 .. s2
5509 end
5510
5511 -- redraw suffix
5512 local prev_x, prev_y = tty.getCursor()
5513 prev_y = prev_y + self.sy -- scroll will remove it
5514 self:draw(s2)
5515 self:scroll(s2 ~= "", prev_x, prev_y)
5516 end,
5517 clear = function(self)
5518 self:move(-math.huge)
5519 self:draw((" "):rep(unicode.wlen(self.data)))
5520 self:move(-math.huge)
5521 self.index = 0
5522 self.data = ""
5523 end,
5524 draw = function(self, text)
5525 self.sy = self.sy + tty.stream:write(text)
5526 end
5527 }
5528end
5529
5530function tty.read(handler)
5531 tty.window.handler = handler
5532
5533 local stdin = io.stdin
5534 local result = table.pack(pcall(stdin.readLine, stdin, false))
5535 tty.window.handler = nil
5536 return select(2, assert(table.unpack(result)))
5537end
5538
5539-- PLEASE do not use this method directly, use io.read or term.read
5540function tty.stream:read()
5541 local handler = tty.window.handler or {}
5542 local cursor = handler.cursor or tty.build_vertical_reader()
5543
5544 tty.window.handler = nil
5545 handler.index = 0
5546
5547 while true do
5548 local name, address, char, code = self:pull()
5549 -- we may have lost tty during the pull
5550 if not tty.isAvailable() then
5551 return
5552 end
5553
5554 -- we have to keep checking what kb is active in case it is switching during use
5555 -- we could have multiple screens, each with keyboards active
5556 local main_kb = tty.keyboard()
5557 local main_sc = tty.screen()
5558 if name == "interrupted" then
5559 self:write("^C\n")
5560 return false, name
5561 elseif address == main_kb or address == main_sc then
5562 local handler_method = handler[name] or
5563 -- this handler listing hack is to delay load tty
5564 ({key_down=1, touch=1, drag=1, clipboard=1})[name] and tty[name .. "_handler"]
5565 if handler_method then
5566 -- nil to end (close)
5567 -- false to ignore
5568 -- true-thy updates cursor
5569 local c, ret = handler_method(handler, cursor, char, code)
5570 if c == nil then
5571 return ret
5572 elseif c then
5573 -- if we obtained something (c) to handle
5574 cursor:update(c)
5575 end
5576 end
5577 end
5578 end
5579end
5580
5581function tty.getCursor()
5582 local window = tty.window
5583 return window.x, window.y
5584end
5585
5586function tty.setCursor(x, y)
5587 local window = tty.window
5588 window.x, window.y = x, y
5589end
5590
5591-- PLEASE do not use this method directly, use io.write or term.write
5592function tty.stream:write(value)
5593 local gpu = tty.gpu()
5594 if not gpu then
5595 return
5596 end
5597 local window = tty.window
5598 local sy = 0
5599 local beeped
5600 local uptime = computer.uptime
5601 local last_sleep = uptime()
5602 while true do
5603 if uptime() - last_sleep > 1 then
5604 os.sleep(0)
5605 last_sleep = uptime()
5606 end
5607
5608 local ansi_print = ""
5609 if window.ansi_escape then
5610 -- parse the instruction in segment
5611 -- [ (%d+;)+ %d+m
5612 window.ansi_escape = window.ansi_escape .. value
5613 value, ansi_print = require("vt100").parse(window)
5614 end
5615
5616 -- scroll before parsing next line
5617 -- the value may only have been a newline
5618 sy = sy + self.scroll()
5619 -- we may have needed to scroll one last time [nowrap adjustments]
5620 if #value == 0 then
5621 break
5622 end
5623
5624 local x, y = tty.getCursor()
5625
5626 local _, ei, delim = unicode.sub(value, 1, window.width):find("([\27\t\r\n\a])", #ansi_print + 1)
5627 local segment = ansi_print .. (ei and value:sub(1, ei - 1) or value)
5628
5629 if segment ~= "" then
5630 local gpu_x, gpu_y = x + window.dx, y + window.dy
5631 local tail = ""
5632 local wlen_needed = unicode.wlen(segment)
5633 local wlen_remaining = window.width - x + 1
5634 if wlen_remaining < wlen_needed then
5635 segment = unicode.wtrunc(segment, wlen_remaining + 1)
5636 local wlen_used = unicode.wlen(segment)
5637 -- we can clear the line because we already know remaining < needed
5638 tail = (" "):rep(wlen_remaining - wlen_used)
5639 if not window.nowrap then
5640 -- we have to reparse the delimeter
5641 ei = #segment
5642 -- fake a newline
5643 delim = "\n"
5644 wlen_needed = wlen_used
5645 end
5646 end
5647 gpu.set(gpu_x, gpu_y, segment..tail)
5648 x = x + wlen_needed
5649 end
5650
5651 value = ei and value:sub(ei + 1) or ""
5652
5653 if delim == "\t" then
5654 x = ((x-1) - ((x-1) % 8)) + 9
5655 elseif delim == "\r" or (delim == "\n" and not window.cr_last) then
5656 x = 1
5657 y = y + 1
5658 elseif delim == "\a" and not beeped then
5659 computer.beep()
5660 beeped = true
5661 elseif delim == "\27" then -- ansi escape
5662 window.ansi_escape = ""
5663 end
5664
5665 tty.setCursor(x, y)
5666 window.cr_last = delim == "\r"
5667 end
5668 return sy
5669end
5670
5671local gpu_intercept = {}
5672function tty.bind(gpu)
5673 checkArg(1, gpu, "table")
5674 if not gpu_intercept[gpu] then
5675 gpu_intercept[gpu] = true -- only override a gpu once
5676 -- the gpu can change resolution before we get a chance to call events and handle screen_resized
5677 -- unfortunately, we have to handle viewport changes by intercept
5678 local setr, setv = gpu.setResolution, gpu.setViewport
5679 gpu.setResolution = function(...)
5680 screen_reset(gpu)
5681 return setr(...)
5682 end
5683 gpu.setViewport = function(...)
5684 screen_reset(gpu)
5685 return setv(...)
5686 end
5687 end
5688 local window = tty.window
5689 if not window.gpu or window.gpu == gpu then
5690 window.gpu = gpu
5691 window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st)
5692 tty.getViewport()
5693 end
5694 screen_reset(gpu)
5695end
5696
5697function tty.keyboard()
5698 -- this method needs to be safe even if there is no terminal window (e.g. no gpu)
5699 local window = tty.window
5700
5701 if window.keyboard then
5702 return window.keyboard
5703 end
5704
5705 local system_keyboard = component.isAvailable("keyboard") and component.keyboard
5706 system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard"
5707
5708 local screen = tty.screen()
5709
5710 if not screen then
5711 -- no screen, no known keyboard, use system primary keyboard if any
5712 return system_keyboard
5713 end
5714
5715 -- if we are using a gpu bound to the primary scren, then use the primary keyboard
5716 if component.isAvailable("screen") and component.screen.address == screen then
5717 window.keyboard = system_keyboard
5718 else
5719 -- calling getKeyboards() on the screen is costly (time)
5720 -- changes to this design should avoid this on every key hit
5721
5722 -- this is expensive (slow!)
5723 window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard
5724 end
5725
5726 return window.keyboard
5727end
5728
5729function tty.screen()
5730 local gpu = tty.gpu()
5731 if not gpu then
5732 return nil
5733 end
5734 return gpu.getScreen()
5735end
5736
5737function tty.stream.scroll(lines)
5738 local gpu = tty.gpu()
5739 if not gpu then
5740 return 0
5741 end
5742 local width, height, dx, dy, x, y = tty.getViewport()
5743
5744 -- nil lines indicates a request to auto scroll
5745 -- auto scroll is when the cursor has gone below the bottom on the terminal
5746 -- and the text is scroll up, pulling the cursor back into view
5747
5748 -- lines<0 scrolls up (text down)
5749 -- lines>0 scrolls down (text up)
5750
5751 -- no lines count given, the user is asking to auto scroll y back into view
5752 if not lines then
5753 if y < 1 then
5754 lines = y - 1 -- y==0 scrolls back -1
5755 elseif y > height then
5756 lines = y - height -- y==height+1 scroll forward 1
5757 else
5758 return 0 -- do nothing
5759 end
5760 end
5761
5762 lines = math.min(lines, height)
5763 lines = math.max(lines,-height)
5764
5765 -- scroll request can be too large
5766 local abs_lines = math.abs(lines)
5767 local box_height = height - abs_lines
5768 local fill_top = dy + 1 + (lines < 0 and 0 or box_height)
5769
5770 gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines)
5771 gpu.fill(dx + 1, fill_top, width, abs_lines, ' ')
5772
5773 tty.setCursor(x, math.max(1, math.min(y, height)))
5774
5775 return lines
5776end
5777
5778-- stream methods
5779local function bfd() return nil, "tty: invalid operation" end
5780tty.stream.close = bfd
5781tty.stream.seek = bfd
5782tty.stream.handle = "tty"
5783
5784require("package").delay(tty, "/lib/core/full_tty.lua")
5785
5786return tty
5787 mnt/6c1/lib/shell.lua 0000600 00000007646 13215223722 007100 0 local fs = require("filesystem")
5788local unicode = require("unicode")
5789local process = require("process")
5790
5791local shell = {}
5792
5793-- Cache loaded shells for command execution. This puts the requirement on
5794-- shells that they do not keep a global state, since they may be called
5795-- multiple times, but reduces memory usage a lot.
5796local shells = setmetatable({}, {__mode="v"})
5797
5798function shell.getShell()
5799 local shellPath = os.getenv("SHELL") or "/bin/sh"
5800 local shellName, reason = shell.resolve(shellPath, "lua")
5801 if not shellName then
5802 return nil, "cannot resolve shell `" .. shellPath .. "': " .. reason
5803 end
5804 if shells[shellName] then
5805 return shells[shellName]
5806 end
5807 local sh, load_reason = loadfile(shellName)
5808 if sh then
5809 shells[shellName] = sh
5810 end
5811 return sh, load_reason
5812end
5813
5814-------------------------------------------------------------------------------
5815
5816function shell.prime()
5817 local data = process.info().data
5818 for _,key in ipairs({'aliases','vars'}) do
5819 -- first time get need to populate
5820 local raw = rawget(data, key)
5821 if not raw then
5822 -- current process does not have the key
5823 local current = data[key]
5824 data[key] = {}
5825 if current then
5826 for k,v in pairs(current) do
5827 data[key][k] = v
5828 end
5829 end
5830 end
5831 end
5832end
5833
5834function shell.getAlias(alias)
5835 return process.info().data.aliases[alias]
5836end
5837
5838function shell.setAlias(alias, value)
5839 checkArg(1, alias, "string")
5840 checkArg(2, value, "string", "nil")
5841 process.info().data.aliases[alias] = value
5842end
5843
5844function shell.getWorkingDirectory()
5845 -- if no env PWD default to /
5846 return os.getenv("PWD") or "/"
5847end
5848
5849function shell.setWorkingDirectory(dir)
5850 checkArg(1, dir, "string")
5851 -- ensure at least /
5852 -- and remove trailing /
5853 dir = fs.canonical(dir):gsub("^$", "/"):gsub("(.)/$", "%1")
5854 if fs.isDirectory(dir) then
5855 os.setenv("PWD", dir)
5856 return true
5857 else
5858 return nil, "not a directory"
5859 end
5860end
5861
5862function shell.resolve(path, ext)
5863 checkArg(1, path, "string")
5864
5865 local dir = path
5866 if dir:find("/") ~= 1 then
5867 dir = fs.concat(shell.getWorkingDirectory(), dir)
5868 end
5869 local name = fs.name(path)
5870 dir = fs[name and "path" or "canonical"](dir)
5871 local fullname = fs.concat(dir, name or "")
5872
5873 if not ext then
5874 return fullname
5875 elseif name then
5876 checkArg(2, ext, "string")
5877 -- search for name in PATH if no dir was given
5878 -- no dir was given if path has no /
5879 local search_in = path:find("/") and dir or os.getenv("PATH")
5880 for search_path in string.gmatch(search_in, "[^:]+") do
5881 -- resolve search_path because they may be relative
5882 local search_name = fs.concat(shell.resolve(search_path), name)
5883 if not fs.exists(search_name) then
5884 search_name = search_name .. "." .. ext
5885 end
5886 -- extensions are provided when the caller is looking for a file
5887 if fs.exists(search_name) and not fs.isDirectory(search_name) then
5888 return search_name
5889 end
5890 end
5891 end
5892
5893 return nil, "file not found"
5894end
5895
5896function shell.parse(...)
5897 local params = table.pack(...)
5898 local args = {}
5899 local options = {}
5900 local doneWithOptions = false
5901 for i = 1, params.n do
5902 local param = params[i]
5903 if not doneWithOptions and type(param) == "string" then
5904 if param == "--" then
5905 doneWithOptions = true -- stop processing options at `--`
5906 elseif param:sub(1, 2) == "--" then
5907 local key, value = param:match("%-%-(.-)=(.*)")
5908 if not key then
5909 key, value = param:sub(3), true
5910 end
5911 options[key] = value
5912 elseif param:sub(1, 1) == "-" and param ~= "-" then
5913 for j = 2, unicode.len(param) do
5914 options[unicode.sub(param, j, j)] = true
5915 end
5916 else
5917 table.insert(args, param)
5918 end
5919 else
5920 table.insert(args, param)
5921 end
5922 end
5923 return args, options
5924end
5925
5926-------------------------------------------------------------------------------
5927
5928require("package").delay(shell, "/lib/core/full_shell.lua")
5929
5930return shell
5931 mnt/6c1/lib/colors.lua 0000600 00000000716 13215223721 007260 0 local colors = {
5932 [0] = "white",
5933 [1] = "orange",
5934 [2] = "magenta",
5935 [3] = "lightblue",
5936 [4] = "yellow",
5937 [5] = "lime",
5938 [6] = "pink",
5939 [7] = "gray",
5940 [8] = "silver",
5941 [9] = "cyan",
5942 [10] = "purple",
5943 [11] = "blue",
5944 [12] = "brown",
5945 [13] = "green",
5946 [14] = "red",
5947 [15] = "black"
5948}
5949
5950do
5951 local keys = {}
5952 for k in pairs(colors) do
5953 table.insert(keys, k)
5954 end
5955 for _, k in pairs(keys) do
5956 colors[colors[k]] = k
5957 end
5958end
5959
5960return colors mnt/6c1/lib/devfs.lua 0000600 00000021203 13215223723 007062 0 local fs = require("filesystem")
5961local text = require("text")
5962
5963local api = {}
5964
5965local function new_node(proxy)
5966 local node = {proxy=proxy}
5967 if not proxy or not proxy.list then
5968 node.children = {}
5969 end
5970 return node
5971end
5972
5973local function array_read(array, separator)
5974 separator = separator or " "
5975 local builder = {}
5976 for _,value in ipairs(array) do
5977 table.insert(builder, tostring(value))
5978 end
5979 return table.concat(builder, separator)
5980end
5981
5982local function child_iterator(node)
5983 -- a node can either list or have children, but not both (see add_child)
5984 -- a node can be a file, which has a proxy, but no children
5985 local listed = {}
5986 if node then
5987 if node.proxy and node.proxy.list then
5988 -- list should return a table, not another iterator
5989 -- the elements in the list are not nodes, but proxies
5990 -- we have to wrap each entry with a virtual node (a node that is not in a child-parent tree)
5991 -- list can be a function that returns a table, or the table already
5992 local list = node.proxy.list
5993 listed = type(list) == "table" and list or list()
5994 elseif node.children then
5995 listed = node.children
5996 end
5997 end
5998 local availables = {}
5999 for name, item in pairs(listed) do
6000 if name:len() > 0 then
6001 if not item.proxy then item = new_node(item) end
6002 if not item.proxy.isAvailable or item.proxy.isAvailable() then
6003 availables[name] = item
6004 end
6005 end
6006 end
6007 return pairs(availables)
6008end
6009
6010local function get_child(node, name)
6011 for child_name, child in child_iterator(node) do
6012 if child_name == name then
6013 return child
6014 end
6015 end
6016end
6017
6018local function add_child(node, name, proxy)
6019 if not node or node.proxy and node.proxy.list then
6020 return nil, "cannot add child to listing proxy"
6021 end
6022
6023 local child = new_node(proxy)
6024 node.children[name] = child
6025 return child
6026end
6027
6028local function findNode(path, bCreate)
6029 local segments = fs.segments(path)
6030 local node = api.root
6031 while #segments > 0 do
6032 local name = table.remove(segments, 1)
6033 local next = get_child(node, name)
6034 if not next then
6035 if bCreate then
6036 if not add_child(node, name) then
6037 return nil, "cannot create child node"
6038 end
6039 else
6040 return nil, "no such file or directory"
6041 end
6042 end
6043 node = next or get_child(node, name)
6044 end
6045 return node
6046end
6047
6048-- devfs api
6049
6050api.root = new_node()
6051
6052function api.create(path, proxy)
6053 checkArg(1, path, "string")
6054 checkArg(2, proxy, "table", "nil")
6055 local pwd = fs.path(path)
6056 local name = fs.name(path)
6057 if not name then return nil, "invalid devfs path" end
6058 local pnode, why = findNode(pwd, true)
6059 if not pnode then
6060 return nil, why
6061 end
6062
6063 if get_child(pnode, name) then
6064 return nil, "file or directory exists"
6065 end
6066
6067 return add_child(pnode, name, proxy)
6068end
6069
6070-- the filesystem object as seen from the system mount interface
6071api.proxy = {}
6072
6073-- forward declare injector
6074local inject_dynamic_pairs
6075local function dynamic_list(path, fsnode)
6076 local nodes, links, dirs = {}, {}, {}
6077 local node = findNode(path)
6078 if node then
6079 for name,cnode in child_iterator(node) do
6080 if cnode.proxy and cnode.proxy.link then
6081 links[name] = cnode.proxy.link
6082 elseif cnode.proxy and cnode.proxy.list then
6083 local child = {name=name,parent=fsnode}
6084 local child_path = path .. "/" .. name
6085 inject_dynamic_pairs(child, child_path, true)
6086 dirs[name] = child
6087 else
6088 nodes[name] = cnode
6089 end
6090 end
6091 end
6092 return nodes, links, dirs
6093end
6094
6095inject_dynamic_pairs = function(fsnode, path, bStoreUse)
6096 if getmetatable(fsnode) then return end
6097 fsnode.children = nil
6098 fsnode.links = nil
6099 setmetatable(fsnode,
6100 {
6101 __index = function(tbl, key)
6102 local bLinks = key == "links"
6103 local bChildren = key == "children"
6104 if not bLinks and not bChildren then return end
6105 local _, links, dirs = dynamic_list(path, tbl)
6106 if bStoreUse then
6107 tbl.children = dirs
6108 tbl.links = links
6109 end
6110 return bLinks and links or dirs
6111 end
6112 })
6113end
6114
6115local label_lib = dofile("/lib/core/device_labeling.lua")
6116label_lib.loadRules()
6117api.getDeviceLabel = label_lib.getDeviceLabel
6118api.setDeviceLabel = label_lib.setDeviceLabel
6119
6120local registered = false
6121function api.register(public_proxy)
6122 if registered then return end
6123 registered = true
6124
6125 local start_path = "/lib/core/devfs/"
6126 for starter in fs.list(start_path) do
6127 local full_path = start_path .. starter
6128 local _,matched = starter:gsub("%.lua$","")
6129 if matched > 0 then
6130 local data = dofile(full_path)
6131 for name, entry in pairs(data) do
6132 api.create(name, entry)
6133 end
6134 end
6135 end
6136
6137 if rawget(public_proxy, "fsnode") then
6138 inject_dynamic_pairs(public_proxy.fsnode, "")
6139 end
6140end
6141
6142function api.proxy.list(path)
6143 local result = {}
6144 for name in pairs(dynamic_list(path, false, false)) do
6145 table.insert(result, name)
6146 end
6147 return result
6148end
6149
6150function api.proxy.isDirectory(path)
6151 local node = findNode(path)
6152 return node and node.proxy and node.proxy.list
6153end
6154
6155function api.proxy.size(path)
6156 checkArg(1, path, "string")
6157 local node = findNode(path)
6158 if not node or not node.proxy then
6159 return 0
6160 end
6161
6162 local proxy = node.proxy
6163 if proxy.list then return 0 end
6164 if proxy.size then return proxy.size() end
6165 if proxy.open then return 0 end
6166 if proxy.read then return proxy.read():len() end
6167 if proxy[1] ~= nil then return array_read(proxy):len() end
6168 return 0
6169end
6170
6171function api.proxy.lastModified()
6172 return 0
6173end
6174
6175function api.proxy.exists(path)
6176 checkArg(1, path, "string")
6177 return not not findNode(path)
6178end
6179
6180function api.getDevice(path)
6181 checkArg(1, path, "string")
6182 local device
6183 local reason = "no such device"
6184 local real, why = fs.realPath(require("shell").resolve(path))
6185 if not real then return nil, why end
6186 if fs.exists(real) then
6187 -- we don't have a good way of knowing where dev is mounted still
6188 -- similar hack in api.proxy.open
6189 real = fs.path(real) .. (fs.name(real) or "")
6190 local part, subbed = real:gsub("^/dev/", "")
6191 if subbed > 0 and part:len() > 0 then
6192 local node = findNode(part)
6193 if node and node.proxy then
6194 -- must be a special device node
6195 device = node.proxy.device
6196 end
6197 if not device then
6198 reason = "not a device"
6199 end
6200 else
6201 device, reason = fs.get(real)
6202 end
6203 end
6204 return device, reason
6205end
6206
6207function api.proxy.open(path, mode)
6208 checkArg(1, path, "string")
6209 checkArg(2, mode, "string", "nil")
6210
6211 mode = mode or "r"
6212 local bRead = mode:match("[ra]")
6213 local bWrite = mode:match("[wa]")
6214
6215 if not bRead and not bWrite then
6216 return nil, "invalid mode"
6217 end
6218
6219 local node, why = findNode(path)
6220 if not node then
6221 return nil, why
6222 elseif not node.proxy or node.proxy.list then
6223 return nil, "is a directory"
6224 end
6225
6226 local proxy = node.proxy
6227
6228 -- in case someone tries to open a link directly, refer them back to fs
6229 -- this is an unfortunate pathing hack due to optimizations for memory
6230 if proxy.link then
6231 return fs.open("/dev/"..path, mode)
6232 end
6233
6234 -- special (but common) simple readonly cases
6235 if proxy[1] ~= nil then -- contains special readonly value
6236 local array = proxy
6237 proxy.read = function()return array_read(array) end
6238 end
6239
6240 if proxy.open then
6241 return proxy.open(mode)
6242 end
6243
6244 if bRead and not proxy.read then
6245 return nil, "cannot open for read"
6246 elseif bWrite and not proxy.write then
6247 return nil, "cannot open for write"
6248 end
6249
6250 local txtRead = bRead and proxy.read()
6251
6252 if bWrite then
6253 return text.internal.writer(proxy.write, mode, txtRead)
6254 end
6255
6256 return text.internal.reader(txtRead, mode)
6257end
6258
6259-- as long as the fsnode hack is used, fs.isLink is not needed here
6260-- function api.proxy.isLink(path) end
6261
6262local function checked_invoke(handle, method, ...)
6263 checkArg(1, handle, "table")
6264 checkArg(2, method, "string")
6265 checkArg(3, handle[method], "function", "table", "nil")
6266 local m = handle[method]
6267 if not m then
6268 return nil, "bad file handle"
6269 elseif type(m) == "table" then
6270 local mm = getmetatable(m)
6271 assert(mm and mm.__call, string.format("FILE handle [%s] method defined, but is not callable", tostring(method)))
6272 end
6273 return m(handle, ...)
6274end
6275
6276function api.proxy.read(h, ...)
6277 return checked_invoke(h, "read", ...)
6278end
6279
6280function api.proxy.close(h, ...)
6281 return checked_invoke(h, "close", ...)
6282end
6283
6284function api.proxy.write(h, ...)
6285 return checked_invoke(h, "write", ...)
6286end
6287
6288function api.proxy.seek(h, ...)
6289 return checked_invoke(h, "seek", ...)
6290end
6291
6292function api.proxy.remove()
6293 return nil, "cannot remove file or directory"
6294end
6295
6296function api.proxy.makeDirectory()
6297 return nil, "use create in the devfs api"
6298end
6299
6300function api.proxy.setLabel()
6301 return nil, "cannot set label on devfs"
6302end
6303
6304return api
6305 mnt/6c1/lib/uuid.lua 0000600 00000000657 13215223722 006732 0 local uuid = {}
6306
6307function uuid.next()
6308 -- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
6309 -- 8-4-4-4-12 (halved sizes because bytes make hex pairs)
6310 local sets = {4, 2, 2, 2, 6}
6311 local result = ""
6312
6313 for _,set in ipairs(sets) do
6314 if result:len() > 0 then
6315 result = result .. "-"
6316 end
6317 for i = 1,set do
6318 result = result .. string.format("%02x", math.random(0, 255))
6319 end
6320 end
6321
6322 return result
6323end
6324
6325return uuid
6326 mnt/6c1/lib/transforms.lua 0000600 00000003450 13215223720 010152 0 local lib={}
6327lib.internal={}
6328function lib.internal.range_adjust(f,l,s)
6329 checkArg(1,f,'number','nil')
6330 checkArg(2,l,'number','nil')
6331 checkArg(3,s,'number')
6332 if f==nil then f=1 elseif f<0 then f=s+f+1 end
6333 if l==nil then l=s elseif l<0 then l=s+l+1 end
6334 return f,l
6335end
6336function lib.internal.table_view(tbl,f,l)
6337 return setmetatable({},
6338 {
6339 __index = function(_, key)
6340 return (type(key) ~= 'number' or (key >= f and key <= l)) and tbl[key] or nil
6341 end,
6342 __len = function(_)
6343 return l
6344 end,
6345 })
6346end
6347local adjust=lib.internal.range_adjust
6348local view=lib.internal.table_view
6349
6350-- first(p1,p2) searches for the first range in p1 that satisfies p2
6351function lib.first(tbl,pred,f,l)
6352 checkArg(1,tbl,'table')
6353 checkArg(2,pred,'function','table')
6354 if type(pred)=='table'then
6355 local set;set,pred=pred,function(e,fi,tbl)
6356 for vi=1,#set do
6357 local v=set[vi]
6358 if lib.begins(tbl,v,fi) then return true,#v end
6359 end
6360 end
6361 end
6362 local s=#tbl
6363 f,l=adjust(f,l,s)
6364 tbl=view(tbl,f,l)
6365 for i=f,l do
6366 local si,ei=pred(tbl[i],i,tbl)
6367 if si then
6368 return i,i+(ei or 1)-1
6369 end
6370 end
6371end
6372
6373-- returns true if p1 at first p3 equals element for element p2
6374function lib.begins(tbl,v,f,l)
6375 checkArg(1,tbl,'table')
6376 checkArg(2,v,'table')
6377 local vs=#v
6378 f,l=adjust(f,l,#tbl)
6379 if vs>(l-f+1)then return end
6380 for i=1,vs do
6381 if tbl[f+i-1]~=v[i] then return end
6382 end
6383 return true
6384end
6385
6386function lib.concat(...)
6387 local r,rn,k={},0
6388 for _,tbl in ipairs({...})do
6389 if type(tbl)~='table'then
6390 return nil,'parameter '..tostring(_)..' to concat is not a table'
6391 end
6392 local n=tbl.n or #tbl
6393 k=k or tbl.n
6394 for i=1,n do
6395 rn=rn+1;r[rn]=tbl[i]
6396 end
6397 end
6398 r.n=k and rn or nil
6399 return r
6400end
6401
6402require("package").delay(lib, "/lib/core/full_transforms.lua")
6403
6404return lib
6405 mnt/6c1/lib/text.lua 0000600 00000006115 13215223726 006747 0 local unicode = require("unicode")
6406local tx = require("transforms")
6407
6408local text = {}
6409text.internal = {}
6410
6411text.syntax = {"^%d?>>?&%d+","^%d?>>?",">>?","<%&%d+","<",";","&&","||?"}
6412
6413function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
6414 local from = string.match(value, "^%s*()")
6415 return from > #value and "" or string.match(value, ".*%S", from)
6416end
6417
6418-- used by lib/sh
6419function text.escapeMagic(txt)
6420 return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
6421end
6422
6423function text.removeEscapes(txt)
6424 return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1")
6425end
6426
6427function text.internal.tokenize(value, options)
6428 checkArg(1, value, "string")
6429 checkArg(2, options, "table", "nil")
6430 options = options or {}
6431 local delimiters = options.delimiters
6432 local custom = not not options.delimiters
6433 delimiters = delimiters or text.syntax
6434
6435 local words, reason = text.internal.words(value, options)
6436
6437 local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
6438 if type(words) ~= "table" or
6439 #splitter == 0 or
6440 not value:find("["..splitter.."]") then
6441 return words, reason
6442 end
6443
6444 return text.internal.splitWords(words, delimiters)
6445end
6446
6447-- tokenize input by quotes and whitespace
6448function text.internal.words(input, options)
6449 checkArg(1, input, "string")
6450 checkArg(2, options, "table", "nil")
6451 options = options or {}
6452 local quotes = options.quotes
6453 local show_escapes = options.show_escapes
6454 local qr = nil
6455 quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
6456 local function append(dst, txt, _qr)
6457 local size = #dst
6458 if size == 0 or dst[size].qr ~= _qr then
6459 dst[size+1] = {txt=txt, qr=_qr}
6460 else
6461 dst[size].txt = dst[size].txt..txt
6462 end
6463 end
6464 -- token meta is {string,quote rule}
6465 local tokens, token = {}, {}
6466 local escaped, start = false, -1
6467 for i = 1, unicode.len(input) do
6468 local char = unicode.sub(input, i, i)
6469 if escaped then -- escaped character
6470 escaped = false
6471 -- include escape char if show_escapes
6472 -- or the followwing are all true
6473 -- 1. qr active
6474 -- 2. the char escaped is NOT the qr closure
6475 -- 3. qr is not literal
6476 if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
6477 append(token, '\\', qr)
6478 end
6479 append(token, char, qr)
6480 elseif char == "\\" and (not qr or not qr[3]) then
6481 escaped = true
6482 elseif qr and qr[2] == char then -- end of quoted string
6483 -- if string is empty, we can still capture a quoted empty arg
6484 if #token == 0 or #token[#token] == 0 then
6485 append(token, '', qr)
6486 end
6487 qr = nil
6488 elseif not qr and tx.first(quotes,function(Q)
6489 qr=Q[1]==char and Q or nil return qr end) then
6490 start = i
6491 elseif not qr and string.find(char, "%s") then
6492 if #token > 0 then
6493 table.insert(tokens, token)
6494 end
6495 token = {}
6496 else -- normal char
6497 append(token, char, qr)
6498 end
6499 end
6500 if qr then
6501 return nil, "unclosed quote at index " .. start
6502 end
6503
6504 if #token > 0 then
6505 table.insert(tokens, token)
6506 end
6507
6508 return tokens
6509end
6510
6511require("package").delay(text, "/lib/core/full_text.lua")
6512
6513return text
6514 mnt/6c1/lib/io.lua 0000600 00000004636 13215223712 006373 0 local io = {}
6515
6516-------------------------------------------------------------------------------
6517
6518function io.close(file)
6519 return (file or io.output()):close()
6520end
6521
6522function io.flush()
6523 return io.output():flush()
6524end
6525
6526function io.lines(filename, ...)
6527 if filename then
6528 local file, reason = io.open(filename)
6529 if not file then
6530 error(reason, 2)
6531 end
6532 local args = table.pack(...)
6533 return function()
6534 local result = table.pack(file:read(table.unpack(args, 1, args.n)))
6535 if not result[1] then
6536 if result[2] then
6537 error(result[2], 2)
6538 else -- eof
6539 file:close()
6540 return nil
6541 end
6542 end
6543 return table.unpack(result, 1, result.n)
6544 end
6545 else
6546 return io.input():lines()
6547 end
6548end
6549
6550function io.open(path, mode)
6551 -- These requires are not on top because this is a bootstrapped file.
6552 local resolved_path = require("shell").resolve(path)
6553 local stream, result = require("filesystem").open(resolved_path, mode)
6554 if stream then
6555 return require("buffer").new(mode, stream)
6556 else
6557 return nil, result
6558 end
6559end
6560
6561function io.stream(fd,file,mode)
6562 checkArg(1,fd,'number')
6563 assert(fd>=0,'fd must be >= 0. 0 is input, 1 is stdout, 2 is stderr')
6564 local dio = require("process").info().data.io
6565 if file then
6566 if type(file) == "string" then
6567 local result, reason = io.open(file, mode)
6568 if not result then
6569 error(reason, 2)
6570 end
6571 file = result
6572 elseif not io.type(file) then
6573 error("bad argument #1 (string or file expected, got " .. type(file) .. ")", 2)
6574 end
6575 dio[fd] = file
6576 end
6577 return dio[fd]
6578end
6579
6580function io.input(file)
6581 return io.stream(0, file, 'r')
6582end
6583
6584function io.output(file)
6585 return io.stream(1, file,'w')
6586end
6587
6588function io.error(file)
6589 return io.stream(2, file,'w')
6590end
6591
6592function io.popen(prog, mode, env)
6593 return require("pipe").popen(prog, mode, env)
6594end
6595
6596function io.read(...)
6597 return io.input():read(...)
6598end
6599
6600function io.tmpfile()
6601 local name = os.tmpname()
6602 if name then
6603 return io.open(name, "a")
6604 end
6605end
6606
6607function io.type(object)
6608 if type(object) == "table" then
6609 if getmetatable(object) == "file" then
6610 if object.stream.handle then
6611 return "file"
6612 else
6613 return "closed file"
6614 end
6615 end
6616 end
6617 return nil
6618end
6619
6620function io.write(...)
6621 return io.output():write(...)
6622end
6623
6624-------------------------------------------------------------------------------
6625
6626return io
6627 mnt/6c1/lib/filesystem.lua 0000600 00000022237 13215223715 010150 0 local component = require("component")
6628local unicode = require("unicode")
6629
6630local filesystem = {}
6631local isAutorunEnabled = nil
6632local mtab = {name="", children={}, links={}}
6633local fstab = {}
6634
6635local function segments(path)
6636 local parts = {}
6637 for part in path:gmatch("[^\\/]+") do
6638 local current, up = part:find("^%.?%.$")
6639 if current then
6640 if up == 2 then
6641 table.remove(parts)
6642 end
6643 else
6644 table.insert(parts, part)
6645 end
6646 end
6647 return parts
6648end
6649
6650local function saveConfig()
6651 local root = filesystem.get("/")
6652 if root and not root.isReadOnly() then
6653 local f = filesystem.open("/etc/filesystem.cfg", "w")
6654 if f then
6655 f:write("autorun="..tostring(isAutorunEnabled))
6656 f:close()
6657 end
6658 end
6659end
6660
6661local function findNode(path, create, resolve_links)
6662 checkArg(1, path, "string")
6663 local visited = {}
6664 local parts = segments(path)
6665 local ancestry = {}
6666 local node = mtab
6667 local index = 1
6668 while index <= #parts do
6669 local part = parts[index]
6670 ancestry[index] = node
6671 if not node.children[part] then
6672 local link_path = node.links[part]
6673 if link_path then
6674 if not resolve_links and #parts == index then break end
6675
6676 if visited[path] then
6677 return nil, string.format("link cycle detected '%s'", path)
6678 end
6679 -- the previous parts need to be conserved in case of future ../.. link cuts
6680 visited[path] = index
6681 local pst_path = "/" .. table.concat(parts, "/", index + 1)
6682 local pre_path
6683
6684 if link_path:match("^[^/]") then
6685 pre_path = table.concat(parts, "/", 1, index - 1) .. "/"
6686 local link_parts = segments(link_path)
6687 local join_parts = segments(pre_path .. link_path)
6688 local back = (index - 1 + #link_parts) - #join_parts
6689 index = index - back
6690 node = ancestry[index]
6691 else
6692 pre_path = ""
6693 index = 1
6694 node = mtab
6695 end
6696
6697 path = pre_path .. link_path .. pst_path
6698 parts = segments(path)
6699 part = nil -- skip node movement
6700 elseif create then
6701 node.children[part] = {name=part, parent=node, children={}, links={}}
6702 else
6703 break
6704 end
6705 end
6706 if part then
6707 node = node.children[part]
6708 index = index + 1
6709 end
6710 end
6711
6712 local vnode, vrest = node, #parts >= index and table.concat(parts, "/", index)
6713 local rest = vrest
6714 while node and not node.fs do
6715 rest = rest and filesystem.concat(node.name, rest) or node.name
6716 node = node.parent
6717 end
6718 return node, rest, vnode, vrest
6719end
6720
6721-------------------------------------------------------------------------------
6722
6723function filesystem.isAutorunEnabled()
6724 if isAutorunEnabled == nil then
6725 local env = {}
6726 local config = loadfile("/etc/filesystem.cfg", nil, env)
6727 if config then
6728 pcall(config)
6729 isAutorunEnabled = not not env.autorun
6730 else
6731 isAutorunEnabled = true
6732 end
6733 saveConfig()
6734 end
6735 return isAutorunEnabled
6736end
6737
6738function filesystem.setAutorunEnabled(value)
6739 checkArg(1, value, "boolean")
6740 isAutorunEnabled = value
6741 saveConfig()
6742end
6743
6744function filesystem.canonical(path)
6745 local result = table.concat(segments(path), "/")
6746 if unicode.sub(path, 1, 1) == "/" then
6747 return "/" .. result
6748 else
6749 return result
6750 end
6751end
6752
6753function filesystem.concat(...)
6754 local set = table.pack(...)
6755 for index, value in ipairs(set) do
6756 checkArg(index, value, "string")
6757 end
6758 return filesystem.canonical(table.concat(set, "/"))
6759end
6760
6761function filesystem.get(path)
6762 local node = findNode(path)
6763 if node.fs then
6764 local proxy = node.fs
6765 path = ""
6766 while node and node.parent do
6767 path = filesystem.concat(node.name, path)
6768 node = node.parent
6769 end
6770 path = filesystem.canonical(path)
6771 if path ~= "/" then
6772 path = "/" .. path
6773 end
6774 return proxy, path
6775 end
6776 return nil, "no such file system"
6777end
6778
6779function filesystem.realPath(path)
6780 checkArg(1, path, "string")
6781 local node, rest = findNode(path, false, true)
6782 if not node then return nil, rest end
6783 local parts = {rest or nil}
6784 repeat
6785 table.insert(parts, 1, node.name)
6786 node = node.parent
6787 until not node
6788 return table.concat(parts, "/")
6789end
6790
6791function filesystem.mount(fs, path)
6792 checkArg(1, fs, "string", "table")
6793 if type(fs) == "string" then
6794 fs = filesystem.proxy(fs)
6795 end
6796 assert(type(fs) == "table", "bad argument #1 (file system proxy or address expected)")
6797 checkArg(2, path, "string")
6798
6799 local real
6800 if not mtab.fs then
6801 if path == "/" then
6802 real = path
6803 else
6804 return nil, "rootfs must be mounted first"
6805 end
6806 else
6807 local why
6808 real, why = filesystem.realPath(path)
6809 if not real then
6810 return nil, why
6811 end
6812
6813 if filesystem.exists(real) and not filesystem.isDirectory(real) then
6814 return nil, "mount point is not a directory"
6815 end
6816 end
6817
6818 local fsnode
6819 if fstab[real] then
6820 return nil, "another filesystem is already mounted here"
6821 end
6822 for _,node in pairs(fstab) do
6823 if node.fs.address == fs.address then
6824 fsnode = node
6825 break
6826 end
6827 end
6828
6829 if not fsnode then
6830 fsnode = select(3, findNode(real, true))
6831 -- allow filesystems to intercept their own nodes
6832 fs.fsnode = fsnode
6833 else
6834 local pwd = filesystem.path(real)
6835 local parent = select(3, findNode(pwd, true))
6836 local name = filesystem.name(real)
6837 fsnode = setmetatable({name=name,parent=parent},{__index=fsnode})
6838 parent.children[name] = fsnode
6839 end
6840
6841 fsnode.fs = fs
6842 fstab[real] = fsnode
6843
6844 return true
6845end
6846
6847function filesystem.path(path)
6848 local parts = segments(path)
6849 local result = table.concat(parts, "/", 1, #parts - 1) .. "/"
6850 if unicode.sub(path, 1, 1) == "/" and unicode.sub(result, 1, 1) ~= "/" then
6851 return "/" .. result
6852 else
6853 return result
6854 end
6855end
6856
6857function filesystem.name(path)
6858 checkArg(1, path, "string")
6859 local parts = segments(path)
6860 return parts[#parts]
6861end
6862
6863function filesystem.proxy(filter, options)
6864 checkArg(1, filter, "string")
6865 if not component.list("filesystem")[filter] or next(options or {}) then
6866 -- if not, load fs full library, it has a smarter proxy that also supports options
6867 return filesystem.internal.proxy(filter, options)
6868 end
6869 return component.proxy(filter) -- it might be a perfect match
6870end
6871
6872function filesystem.exists(path)
6873 if not filesystem.realPath(filesystem.path(path)) then
6874 return false
6875 end
6876 local node, rest, vnode, vrest = findNode(path)
6877 if not vrest or vnode.links[vrest] then -- virtual directory or symbolic link
6878 return true
6879 elseif node and node.fs then
6880 return node.fs.exists(rest)
6881 end
6882 return false
6883end
6884
6885function filesystem.isDirectory(path)
6886 local real, reason = filesystem.realPath(path)
6887 if not real then return nil, reason end
6888 local node, rest, vnode, vrest = findNode(real)
6889 if not vnode.fs and not vrest then
6890 return true -- virtual directory (mount point)
6891 end
6892 if node.fs then
6893 return not rest or node.fs.isDirectory(rest)
6894 end
6895 return false
6896end
6897
6898function filesystem.list(path)
6899 local node, rest, vnode, vrest = findNode(path, false, true)
6900 local result = {}
6901 if node then
6902 result = node.fs and node.fs.list(rest or "") or {}
6903 -- `if not vrest` indicates that vnode reached the end of path
6904 -- in other words, vnode[children, links] represent path
6905 if not vrest then
6906 for k,n in pairs(vnode.children) do
6907 if not n.fs or fstab[filesystem.concat(path, k)] then
6908 table.insert(result, k .. "/")
6909 end
6910 end
6911 for k in pairs(vnode.links) do
6912 table.insert(result, k)
6913 end
6914 end
6915 end
6916 local set = {}
6917 for _,name in ipairs(result) do
6918 set[filesystem.canonical(name)] = name
6919 end
6920 return function()
6921 local key, value = next(set)
6922 set[key or false] = nil
6923 return value
6924 end
6925end
6926
6927function filesystem.remove(path)
6928 return require("tools/fsmod").remove(path, findNode)
6929end
6930
6931function filesystem.rename(oldPath, newPath)
6932 return require("tools/fsmod").rename(oldPath, newPath, findNode)
6933end
6934
6935function filesystem.open(path, mode)
6936 checkArg(1, path, "string")
6937 mode = tostring(mode or "r")
6938 checkArg(2, mode, "string")
6939
6940 assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
6941 "bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")")
6942
6943 local node, rest = findNode(path, false, true)
6944 if not node then
6945 return nil, rest
6946 end
6947 if not node.fs or not rest or (({r=true,rb=true})[mode] and not node.fs.exists(rest)) then
6948 return nil, "file not found"
6949 end
6950
6951 local handle, reason = node.fs.open(rest, mode)
6952 if not handle then
6953 return nil, reason
6954 end
6955
6956 local function create_handle_method(key)
6957 return function(self, ...)
6958 if not self.handle then
6959 return nil, "file is closed"
6960 end
6961 return self.fs[key](self.handle, ...)
6962 end
6963 end
6964
6965 local stream =
6966 {
6967 fs = node.fs,
6968 handle = handle,
6969 close = function(self)
6970 if self.handle then
6971 self.fs.close(self.handle)
6972 self.handle = nil
6973 end
6974 end
6975 }
6976 stream.read = create_handle_method("read")
6977 stream.seek = create_handle_method("seek")
6978 stream.write = create_handle_method("write")
6979 return stream
6980end
6981
6982filesystem.findNode = findNode
6983filesystem.segments = segments
6984filesystem.fstab = fstab
6985
6986-------------------------------------------------------------------------------
6987
6988return filesystem
6989 mnt/6c1/lib/sides.lua 0000600 00000001770 13215223712 007067 0 local sides = {
6990 [0] = "bottom",
6991 [1] = "top",
6992 [2] = "back",
6993 [3] = "front",
6994 [4] = "right",
6995 [5] = "left",
6996 [6] = "unknown",
6997
6998 bottom = 0,
6999 top = 1,
7000 back = 2,
7001 front = 3,
7002 right = 4,
7003 left = 5,
7004 unknown = 6,
7005
7006 down = 0,
7007 up = 1,
7008 north = 2,
7009 south = 3,
7010 west = 4,
7011 east = 5,
7012
7013 negy = 0,
7014 posy = 1,
7015 negz = 2,
7016 posz = 3,
7017 negx = 4,
7018 posx = 5,
7019
7020 forward = 3
7021}
7022
7023local metatable = getmetatable(sides) or {}
7024
7025-- sides[0..5] are mapped to itertable[1..6].
7026local itertable = {
7027 sides[0],
7028 sides[1],
7029 sides[2],
7030 sides[3],
7031 sides[4],
7032 sides[5]
7033}
7034
7035-- Future-proofing against the possible introduction of additional
7036-- logical sides (e.g. [7] = "all", [8] = "none", etc.).
7037function metatable.__len(sides)
7038 return #itertable
7039end
7040
7041-- Allow `sides` to be iterated over like a normal (1-based) array.
7042function metatable.__ipairs(sides)
7043 return ipairs(itertable)
7044end
7045
7046setmetatable(sides, metatable)
7047
7048-------------------------------------------------------------------------------
7049
7050return sides
7051 mnt/6c1/lib/note.lua 0000600 00000006552 13215223721 006730 0 --Provides all music notes in range of computer.beep in MIDI and frequency form
7052--Author: Vexatos
7053local computer = require("computer")
7054
7055local note = {}
7056--The table that maps note names to their respective MIDI codes
7057local notes = {}
7058--The reversed table "notes"
7059local reverseNotes = {}
7060
7061do
7062 --All the base notes
7063 local tempNotes = {
7064 "c",
7065 "c#",
7066 "d",
7067 "d#",
7068 "e",
7069 "f",
7070 "f#",
7071 "g",
7072 "g#",
7073 "a",
7074 "a#",
7075 "b"
7076 }
7077 --The table containing all the standard notes and # semitones in correct order, temporarily
7078 local sNotes = {}
7079 --The table containing all the b semitones
7080 local bNotes = {}
7081
7082 --Registers all possible notes in order
7083 do
7084 table.insert(sNotes,"a0")
7085 table.insert(sNotes,"a#0")
7086 table.insert(bNotes,"bb0")
7087 table.insert(sNotes,"b0")
7088 for i = 1,6 do
7089 for _,v in ipairs(tempNotes) do
7090 table.insert(sNotes,v..tostring(i))
7091 if #v == 1 and v ~= "c" and v ~= "f" then
7092 table.insert(bNotes,v.."b"..tostring(i))
7093 end
7094 end
7095 end
7096 end
7097 for i=21,95 do
7098 notes[sNotes[i-20]]=tostring(i)
7099 end
7100
7101 --Reversing the whole table in reverseNotes, used for note.get
7102 do
7103 for k,v in pairs(notes) do
7104 reverseNotes[tonumber(v)]=k
7105 end
7106 end
7107
7108 --This is registered after reverseNotes to avoid conflicts
7109 for k,v in ipairs(bNotes) do
7110 notes[v]=tostring(notes[string.gsub(v,"(.)b(.)","%1%2")]-1)
7111 end
7112end
7113
7114--Converts string or frequency into MIDI code
7115function note.midi(n)
7116 if type(n) == "string" then
7117 n = string.lower(n)
7118 if tonumber(notes[n])~=nil then
7119 return tonumber(notes[n])
7120 else
7121 error("Wrong input "..tostring(n).." given to note.midi, needs to be <note>[semitone sign]<octave>, e.g. A#0 or Gb4")
7122 end
7123 elseif type(n) == "number" then
7124 return math.floor((12*math.log(n/440,2))+69)
7125 else
7126 error("Wrong input "..tostring(n).." given to note.midi, needs to be a number or a string")
7127 end
7128end
7129
7130--Converts String or MIDI code into frequency
7131function note.freq(n)
7132 if type(n) == "string" then
7133 n = string.lower(n)
7134 if tonumber(notes[n])~=nil then
7135 return math.pow(2,(tonumber(notes[n])-69)/12)*440
7136 else
7137 error("Wrong input "..tostring(n).." given to note.freq, needs to be <note>[semitone sign]<octave>, e.g. A#0 or Gb4",2)
7138 end
7139 elseif type(n) == "number" then
7140 return math.pow(2,(n-69)/12)*440
7141 else
7142 error("Wrong input "..tostring(n).." given to note.freq, needs to be a number or a string",2)
7143 end
7144end
7145
7146--Converts a MIDI value back into a string
7147function note.name(n)
7148 n = tonumber(n)
7149 if reverseNotes[n] then
7150 return string.upper(string.match(reverseNotes[n],"^(.)"))..string.gsub(reverseNotes[n],"^.(.*)","%1")
7151 else
7152 error("Attempt to get a note for a non-exsisting MIDI code",2)
7153 end
7154end
7155
7156--Converts Note block ticks (0-24) to MIDI code (34-58) and vice-versa
7157function note.ticks(n)
7158 if type(n) == "number" then
7159 if n>=0 and n<=24 then
7160 return n+34
7161 elseif n>=34 and n<=58 then
7162 return n-34
7163 else
7164 error("Wrong input "..tostring(n).." given to note.ticks, needs to be a number [0-24 or 34-58]",2)
7165 end
7166 else
7167 error("Wrong input "..tostring(n).." given to note.ticks, needs to be a number",2)
7168 end
7169end
7170
7171--Plays a tone, input is either the note as a string or the MIDI code as well as the duration of the tone
7172function note.play(tone,duration)
7173 computer.beep(note.freq(tone),duration)
7174end
7175
7176return note
7177 mnt/6c1/lib/serialization.lua 0000600 00000010701 13215223743 010633 0 local serialization = {}
7178
7179-- delay loaded tables fail to deserialize cross [C] boundaries (such as when having to read files that cause yields)
7180local local_pairs = function(tbl)
7181 local mt = getmetatable(tbl)
7182 return (mt and mt.__pairs or pairs)(tbl)
7183end
7184
7185-- Important: pretty formatting will allow presenting non-serializable values
7186-- but may generate output that cannot be unserialized back.
7187function serialization.serialize(value, pretty)
7188 local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
7189 ["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true,
7190 ["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true,
7191 ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
7192 ["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true,
7193 ["until"]=true, ["while"]=true}
7194 local id = "^[%a_][%w_]*$"
7195 local ts = {}
7196 local result_pack = {}
7197 local function recurse(current_value, depth)
7198 local t = type(current_value)
7199 if t == "number" then
7200 if current_value ~= current_value then
7201 table.insert(result_pack, "0/0")
7202 elseif current_value == math.huge then
7203 table.insert(result_pack, "math.huge")
7204 elseif current_value == -math.huge then
7205 table.insert(result_pack, "-math.huge")
7206 else
7207 table.insert(result_pack, tostring(current_value))
7208 end
7209 elseif t == "string" then
7210 table.insert(result_pack, (string.format("%q", current_value):gsub("\\\n","\\n")))
7211 elseif
7212 t == "nil" or
7213 t == "boolean" or
7214 pretty and (t ~= "table" or (getmetatable(current_value) or {}).__tostring) then
7215 table.insert(result_pack, tostring(current_value))
7216 elseif t == "table" then
7217 if ts[current_value] then
7218 if pretty then
7219 table.insert(result_pack, "recursion")
7220 return
7221 else
7222 error("tables with cycles are not supported")
7223 end
7224 end
7225 ts[current_value] = true
7226 local f
7227 if pretty then
7228 local ks, sks, oks = {}, {}, {}
7229 for k in local_pairs(current_value) do
7230 if type(k) == "number" then
7231 table.insert(ks, k)
7232 elseif type(k) == "string" then
7233 table.insert(sks, k)
7234 else
7235 table.insert(oks, k)
7236 end
7237 end
7238 table.sort(ks)
7239 table.sort(sks)
7240 for _, k in ipairs(sks) do
7241 table.insert(ks, k)
7242 end
7243 for _, k in ipairs(oks) do
7244 table.insert(ks, k)
7245 end
7246 local n = 0
7247 f = table.pack(function()
7248 n = n + 1
7249 local k = ks[n]
7250 if k ~= nil then
7251 return k, current_value[k]
7252 else
7253 return nil
7254 end
7255 end)
7256 else
7257 f = table.pack(local_pairs(current_value))
7258 end
7259 local i = 1
7260 local first = true
7261 table.insert(result_pack, "{")
7262 for k, v in table.unpack(f) do
7263 if not first then
7264 table.insert(result_pack, ",")
7265 if pretty then
7266 table.insert(result_pack, "\n" .. string.rep(" ", depth))
7267 end
7268 end
7269 first = nil
7270 local tk = type(k)
7271 if tk == "number" and k == i then
7272 i = i + 1
7273 recurse(v, depth + 1)
7274 else
7275 if tk == "string" and not kw[k] and string.match(k, id) then
7276 table.insert(result_pack, k)
7277 else
7278 table.insert(result_pack, "[")
7279 recurse(k, depth + 1)
7280 table.insert(result_pack, "]")
7281 end
7282 table.insert(result_pack, "=")
7283 recurse(v, depth + 1)
7284 end
7285 end
7286 ts[current_value] = nil -- allow writing same table more than once
7287 table.insert(result_pack, "}")
7288 else
7289 error("unsupported type: " .. t)
7290 end
7291 end
7292 recurse(value, 1)
7293 local result = table.concat(result_pack)
7294 if pretty then
7295 local limit = type(pretty) == "number" and pretty or 10
7296 local truncate = 0
7297 while limit > 0 and truncate do
7298 truncate = string.find(result, "\n", truncate + 1, true)
7299 limit = limit - 1
7300 end
7301 if truncate then
7302 return result:sub(1, truncate) .. "..."
7303 end
7304 end
7305 return result
7306end
7307
7308function serialization.unserialize(data)
7309 checkArg(1, data, "string")
7310 local result, reason = load("return " .. data, "=data", nil, {math={huge=math.huge}})
7311 if not result then
7312 return nil, reason
7313 end
7314 local ok, output = pcall(result)
7315 if not ok then
7316 return nil, output
7317 end
7318 return output
7319end
7320
7321return serialization
7322 mnt/6c1/lib/tools/fsmod.lua 0000600 00000004242 13215223724 010230 0 local filesystem = require("filesystem")
7323
7324local lib = {}
7325function lib.remove(path, findNode)
7326 local function removeVirtual()
7327 local _, _, vnode, vrest = findNode(filesystem.path(path), false, true)
7328 -- vrest represents the remaining path beyond vnode
7329 -- vrest is nil if vnode reaches the full path
7330 -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
7331 if not vrest then
7332 local name = filesystem.name(path)
7333 if vnode.children[name] or vnode.links[name] then
7334 vnode.children[name] = nil
7335 vnode.links[name] = nil
7336 while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do
7337 vnode.parent.children[vnode.name] = nil
7338 vnode = vnode.parent
7339 end
7340 return true
7341 end
7342 end
7343 -- return false even if vrest is nil because this means it was a expected
7344 -- to be a real file
7345 return false
7346 end
7347 local function removePhysical()
7348 local node, rest = findNode(path)
7349 if node.fs and rest then
7350 return node.fs.remove(rest)
7351 end
7352 return false
7353 end
7354 local success = removeVirtual()
7355 success = removePhysical() or success -- Always run.
7356 if success then return true
7357 else return nil, "no such file or directory"
7358 end
7359end
7360
7361function lib.rename(oldPath, newPath, findNode)
7362 if filesystem.isLink(oldPath) then
7363 local _, _, vnode, _ = findNode(filesystem.path(oldPath))
7364 local target = vnode.links[filesystem.name(oldPath)]
7365 local result, reason = filesystem.link(target, newPath)
7366 if result then
7367 filesystem.remove(oldPath)
7368 end
7369 return result, reason
7370 else
7371 local oldNode, oldRest = findNode(oldPath)
7372 local newNode, newRest = findNode(newPath)
7373 if oldNode.fs and oldRest and newNode.fs and newRest then
7374 if oldNode.fs.address == newNode.fs.address then
7375 return oldNode.fs.rename(oldRest, newRest)
7376 else
7377 local result, reason = filesystem.copy(oldPath, newPath)
7378 if result then
7379 return filesystem.remove(oldPath)
7380 else
7381 return nil, reason
7382 end
7383 end
7384 end
7385 return nil, "trying to read from or write to virtual directory"
7386 end
7387end
7388
7389return lib
7390 mnt/6c1/lib/tools/programLocations.lua 0000600 00000001544 13215223724 012445 0 local computer = require("computer")
7391local fs = require("filesystem")
7392local shell = require("shell")
7393local lib = {}
7394
7395function lib.locate(path)
7396 for _,lookup in ipairs(computer.getProgramLocations()) do
7397 if lookup[1] == path then
7398 return lookup[2]
7399 end
7400 end
7401end
7402
7403function lib.reportNotFound(path, reason)
7404 checkArg(1, path, "string")
7405 if fs.isDirectory(shell.resolve(path)) then
7406 io.stderr:write(path .. ": is a directory\n")
7407 return 126
7408 end
7409 local loot = lib.locate(path)
7410 if loot then
7411 io.stderr:write("The program '" .. path .. "' is currently not installed. To install it:\n" ..
7412 "1. Craft the '" .. loot .. "' floppy disk and insert it into this computer.\n" ..
7413 "2. Run `install " .. loot .. "`")
7414 elseif type(reason) == "string" then
7415 io.stderr:write(path .. ": " .. reason .. "\n")
7416 end
7417 return 127
7418end
7419
7420return lib
7421 mnt/6c1/lib/tools/transfer.lua 0000600 00000016573 13215223725 010757 0 local fs = require("filesystem")
7422local shell = require("shell")
7423local text = require("text")
7424local lib = {}
7425
7426local function perr(ops, format, ...)
7427 if format then
7428 io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n")
7429 ops.exit_code = 1
7430 return 1
7431 end
7432end
7433
7434local function contents_check(arg, options, bMustExist)
7435 if arg == "" then
7436 return perr(options, "cannot create regular file '' No such file or directory")
7437 end
7438 local path = shell.resolve(arg)
7439 local content_pattern = "^(%.*)(.?)"
7440 local contents_of, of_dir = arg:reverse():match(content_pattern)
7441 of_dir = of_dir:match("^/?$")
7442 local dots = contents_of and contents_of:len() or 0
7443 contents_of = of_dir and ({true,true})[dots]
7444
7445 if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then
7446 perr(options, "'%s' is not a directory", arg)
7447 os.exit(1)
7448 end
7449
7450 return contents_of, path
7451end
7452
7453local function areEqual(path1, path2)
7454 local f1, f2 = fs.open(path1, "rb")
7455 local result = true
7456 if f1 then
7457 f2 = fs.open(path2, "rb")
7458 if f2 then
7459 local chunkSize = 4 * 1024
7460 repeat
7461 local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
7462 if s1 ~= s2 then
7463 result = false
7464 break
7465 end
7466 until not s1 or not s2
7467 f2:close()
7468 end
7469 f1:close()
7470 end
7471 assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2)
7472 return result
7473end
7474
7475local function status(verbose, from, to)
7476 if verbose then
7477 to = to and (" -> " .. to) or ""
7478 io.write(from .. to .. "\n")
7479 end
7480 os.sleep(0) -- allow interrupting
7481end
7482
7483local function prompt(message)
7484 io.write(message .. " [Y/n] ")
7485 local result = io.read()
7486 if not result then -- closed pipe
7487 os.exit(1)
7488 end
7489 return result and (result == "" or result:sub(1, 1):lower() == "y")
7490end
7491
7492local function stat(path, ops, P)
7493 local real, reason = fs.realPath(path)
7494 if not real and not P then
7495 perr(ops, "cannot read '%s': '%s'", path, reason)
7496 return false
7497 end
7498 local isLink, linkTarget = fs.isLink(path)
7499 return true,
7500 real,
7501 reason,
7502 isLink,
7503 linkTarget,
7504 fs.exists(path),
7505 fs.get(path),
7506 real and fs.isDirectory(real)
7507end
7508
7509function lib.recurse(fromPath, toPath, options, origin, top)
7510 fromPath = fromPath:gsub("/+", "/")
7511 toPath = toPath:gsub("/+", "/")
7512 local fromPathFull = shell.resolve(fromPath)
7513 local toPathFull = shell.resolve(toPath)
7514 local mv = options.cmd == "mv"
7515 local verbose = options.v and (not mv or top)
7516 if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
7517 status(verbose, string.format("skipping %s", fromPath))
7518 return true
7519 end
7520 local function release(result, reason)
7521 if result and mv and top then
7522 local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
7523 if not rm_result then
7524 perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
7525 result = false
7526 end
7527 end
7528 return result, reason
7529 end
7530
7531 local
7532 ok,
7533 fromReal,
7534 _, --fromError,
7535 fromIsLink,
7536 fromLinkTarget,
7537 fromExists,
7538 fromFs,
7539 fromIsDir = stat(fromPathFull, options, options.P)
7540 if not ok then return nil end
7541 local
7542 ok,
7543 toReal,
7544 _,--toError,
7545 toIsLink,
7546 _,--toLinkTarget,
7547 toExists,
7548 toFs,
7549 toIsDir = stat(toPathFull, options)
7550 if not ok then os.exit(1) end
7551 if toFs.isReadOnly() then
7552 perr(options, "cannot create target '%s': filesystem is readonly", toPath)
7553 return
7554 end
7555
7556 local same_path = fromReal == toReal
7557
7558 local same_fs = fromFs == toFs
7559 local is_mount = origin[fromReal]
7560
7561 if mv and is_mount then
7562 return false, string.format("cannot move '%s', it is a mount point", fromPath)
7563 end
7564
7565 if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then
7566 if toExists and options.n then
7567 return true
7568 end
7569 fs.remove(toPathFull)
7570 if toExists then
7571 status(verbose, string.format("removed '%s'", toPath))
7572 end
7573 status(verbose, fromPath, toPath)
7574 return release(fs.link(fromLinkTarget, toPathFull))
7575 elseif fromIsDir then
7576 if not options.r then
7577 status(true, string.format("omitting directory '%s'", fromPath))
7578 options.exit_code = 1
7579 return true
7580 end
7581 if toExists and not toIsDir then
7582 -- my real cp always does this, even with -f, -n or -i.
7583 return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'"
7584 end
7585 if options.x and not top and is_mount then
7586 return true
7587 end
7588 if same_fs then
7589 if (toReal.."/"):find(fromReal.."/",1,true) then
7590 return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
7591 end
7592 end
7593 if mv then
7594 if fs.list(toReal)() then -- to is NOT empty
7595 return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
7596 end
7597 status(verbose, fromPath, toPath)
7598 end
7599 if not toExists then
7600 status(verbose, fromPath, toPath)
7601 fs.makeDirectory(toPathFull)
7602 end
7603 for file in fs.list(fromPathFull) do
7604 local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false) -- false, no longer top
7605 if not result then
7606 return false, reason
7607 end
7608 end
7609 return release(true)
7610 elseif fromExists then
7611 if toExists then
7612 if same_path then
7613 return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file"
7614 end
7615 if options.n then
7616 return true
7617 end
7618 if options.u and not toIsDir and areEqual(fromReal, toReal) then
7619 return true
7620 end
7621 if options.i then
7622 if not prompt("overwrite '" .. toPath .. "'?") then
7623 return true
7624 end
7625 end
7626 if toIsDir then
7627 return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory"
7628 end
7629 fs.remove(toReal)
7630 end
7631 status(verbose, fromPath, toPath)
7632 return release(fs.copy(fromPathFull, toPathFull))
7633 else
7634 return nil, "'" .. fromPath .. "': No such file or directory"
7635 end
7636end
7637
7638function lib.batch(args, options)
7639 options.exit_code = 0
7640
7641 -- standardized options
7642 options.i = options.i and not options.f
7643 options.P = options.P or options.r
7644 options.skip = text.escapeMagic(options.skip or "")
7645
7646 local origin = {}
7647 for dev,path in fs.mounts() do
7648 origin[path] = dev
7649 end
7650
7651 local toArg = table.remove(args)
7652 local _, ok = contents_check(toArg, options)
7653 if not ok then
7654 return 1
7655 end
7656 local originalToIsDir = fs.isDirectory(ok)
7657
7658 for _, fromArg in ipairs(args) do
7659 -- a "contents of" copy is where src path ends in . or ..
7660 -- a source path ending with . is not sufficient - could be the source filename
7661 local contents_of
7662 contents_of, ok = contents_check(fromArg, options, true)
7663 if ok then
7664 -- we do not append fromPath name to toPath in case of contents_of copy
7665 local toPath = toArg
7666 if contents_of and options.cmd == "mv" then
7667 perr(options, "invalid move path '%s'", fromArg)
7668 else
7669 if not contents_of and originalToIsDir then
7670 local fromName = fs.name(fromArg)
7671 if fromName then
7672 toPath = toPath .. "/" .. fromName
7673 end
7674 end
7675
7676 local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
7677
7678 if not result then
7679 perr(options, reason)
7680 end
7681 end
7682 end
7683 end
7684
7685 return options.exit_code
7686end
7687
7688return lib mnt/6c1/lib/tools 0000700 13215223724 005312 5 mnt/6c1/lib/rc.lua 0000600 00000000223 13215223723 006356 0 -- Keeps track of loaded scripts to retain local values between invocation
7689-- of their command callbacks.
7690local rc = {}
7691rc.loaded = {}
7692
7693return rc
7694
7695 mnt/6c1/lib/term.lua 0000600 00000015602 13215223713 006727 0 local tty = require("tty")
7696local unicode = require("unicode")
7697local computer = require("computer")
7698local process = require("process")
7699
7700local kb = require("keyboard")
7701local keys = kb.keys
7702
7703-- tty is bisected into a delay loaded library
7704-- term indexing will fail to use full_tty unless tty is fully loaded
7705-- accessing tty.full_tty [a nonexistent field] will cause that full load
7706local term = setmetatable({internal={},tty.full_tty}, {__index=tty})
7707
7708function term.internal.window()
7709 return process.info().data.window
7710end
7711
7712local function as_window(window, func, ...)
7713 local data = process.info().data
7714 if not data.window then
7715 return func(...)
7716 end
7717 local prev = rawget(data, "window")
7718 data.window = window
7719 local ret = table.pack(func(...))
7720 data.window = prev
7721 return table.unpack(ret, 1, ret.n)
7722end
7723
7724function term.internal.open(...)
7725 local dx, dy, w, h = ...
7726 local window = {fullscreen=select("#",...) == 0, blink = true}
7727
7728 -- support legacy code using direct manipulation of w and h
7729 -- (e.g. wocchat) instead of using setViewport
7730 setmetatable(window,
7731 {
7732 __index = function(tbl, key)
7733 key = key == "w" and "width" or key == "h" and "height" or key
7734 return rawget(tbl, key)
7735 end,
7736 __newindex = function(tbl, key, value)
7737 key = key == "w" and "width" or key == "h" and "height" or key
7738 return rawset(tbl, key, value)
7739 end
7740 })
7741
7742 -- first time we open a pty the current tty.window must become the process window
7743 if not term.internal.window() then
7744 local init_index = 2
7745 while process.info(init_index) do
7746 init_index = init_index + 1
7747 end
7748 process.info(init_index - 1).data.window = tty.window
7749 tty.window = nil
7750 setmetatable(tty,
7751 {
7752 __index = function(_, key)
7753 if key == "window" then
7754 return term.internal.window()
7755 end
7756 end
7757 })
7758 end
7759
7760 as_window(window, tty.setViewport, w, h, dx, dy, 1, 1)
7761 return window
7762end
7763
7764local function build_horizontal_reader(cursor)
7765 cursor.clear_tail = function(self)
7766 local w,_,dx,dy,x,y = tty.getViewport()
7767 local _,s2=tty.split(self)
7768 local wlen = math.min(unicode.wlen(s2),w-x+1)
7769 tty.gpu().fill(x+dx,y+dy,wlen,1," ")
7770 end
7771 cursor.move = function(self, n)
7772 local win = tty.window
7773 local a = self.index
7774 local b = math.max(0,math.min(unicode.len(self.data), self.index+n))
7775 self.index = b
7776 a, b = a < b and a or b, a < b and b or a
7777 local wlen_moved = unicode.wlen(unicode.sub(self.data, a + 1, b))
7778 win.x = win.x + wlen_moved * (n<0 and -1 or 1)
7779 self:scroll()
7780 end
7781 cursor.draw = function(_, text)
7782 local nowrap = tty.window.nowrap
7783 tty.window.nowrap = true
7784 tty.stream:write(text)
7785 tty.window.nowrap = nowrap
7786 end
7787 cursor.scroll = function(self, goback, prev_x)
7788 local win = tty.window
7789 win.x = goback and prev_x or win.x
7790 local x = win.x
7791 local w = win.width
7792 local data,px,i = self.data, self.promptx, self.index
7793 local available = w-px+1
7794 if x > w then
7795 local blank
7796 if i == unicode.len(data) then
7797 available,blank=available-1," "
7798 else
7799 i,blank=i+1,""
7800 end
7801 data = unicode.sub(data,1,i)
7802 local rev = unicode.reverse(data)
7803 local ending = unicode.wtrunc(rev, available+1)
7804 data = unicode.reverse(ending)
7805 win.x = self.promptx
7806 self:draw(data..blank)
7807 -- wide chars may place the cursor not exactly at the end
7808 win.x = math.min(w, self.promptx + unicode.wlen(data))
7809 -- x could be negative, we scroll it back into view
7810 elseif x < self.promptx then
7811 data = unicode.sub(data, self.index+1)
7812 if unicode.wlen(data) > available then
7813 data = unicode.wtrunc(data,available+1)
7814 end
7815 win.x = self.promptx
7816 self:draw(data)
7817 win.x = math.max(px, math.min(w, x))
7818 end
7819 end
7820 cursor.clear = function(self)
7821 local win = tty.window
7822 local gpu, px = win.gpu, self.promptx
7823 local w,_,dx,dy,_,y = tty.getViewport()
7824 self.index, self.data, win.x = 0, "", px
7825 gpu.fill(px+dx,y+dy,w-px+1-dx,1," ")
7826 end
7827end
7828
7829local function inject_filter(handler, filter)
7830 if filter then
7831 if type(filter) == "string" then
7832 local filter_text = filter
7833 filter = function(text)
7834 return text:match(filter_text)
7835 end
7836 end
7837
7838 handler.key_down = function(self, cursor, char, code)
7839 if code == keys.enter or code == keys.numpadenter then
7840 if not filter(cursor.data) then
7841 computer.beep(2000, 0.1)
7842 return false -- ignore
7843 end
7844 end
7845 return tty.key_down_handler(self, cursor, char, code)
7846 end
7847 end
7848end
7849
7850local function inject_mask(cursor, dobreak, pwchar)
7851 if not pwchar and dobreak ~= false then
7852 return
7853 end
7854
7855 if pwchar then
7856 if type(pwchar) == "string" then
7857 local pwchar_text = pwchar
7858 pwchar = function(text)
7859 return text:gsub(".", pwchar_text)
7860 end
7861 end
7862 end
7863
7864 local cursor_draw = cursor.draw
7865 cursor.draw = function(self, text)
7866 local pre, newline = text:match("(.-)(\n?)$")
7867 if dobreak == false then
7868 newline = ""
7869 end
7870 if pwchar then
7871 pre = pwchar(pre)
7872 end
7873 return cursor_draw(self, pre .. newline)
7874 end
7875end
7876
7877-- cannot use term.write = io.write because io.write invokes metatable
7878function term.write(value, wrap)
7879 local previous_nowrap = tty.window.nowrap
7880 tty.window.nowrap = wrap == false
7881 io.write(value)
7882 io.stdout:flush()
7883 tty.window.nowrap = previous_nowrap
7884end
7885
7886function term.read(history, dobreak, hint, pwchar, filter)
7887 history = history or {}
7888 local handler = history
7889 handler.hint = handler.hint or hint
7890
7891 local cursor = tty.build_vertical_reader()
7892 if handler.nowrap then
7893 build_horizontal_reader(cursor)
7894 end
7895
7896 inject_filter(handler, filter)
7897 inject_mask(cursor, dobreak, pwchar or history.pwchar)
7898 handler.cursor = cursor
7899
7900 return tty.read(handler)
7901end
7902
7903function term.getGlobalArea(window)
7904 local w,h,dx,dy = as_window(window, tty.getViewport)
7905 return dx+1,dy+1,w,h
7906end
7907
7908function term.clearLine(window)
7909 window = window or tty.window
7910 local w, h, dx, dy, _, y = as_window(window, tty.getViewport)
7911 window.gpu.fill(dx + 1, dy + math.max(1, math.min(y, h)), w, 1, " ")
7912 window.x = 1
7913end
7914
7915function term.setCursorBlink(enabled)
7916 tty.window.blink = enabled
7917end
7918
7919function term.getCursorBlink()
7920 return tty.window.blink
7921end
7922
7923function term.pull(...)
7924 local args = table.pack(...)
7925 local timeout = nil
7926 if type(args[1]) == "number" then
7927 timeout = table.remove(args, 1)
7928 args.n = args.n - 1
7929 end
7930 local stdin_stream = io.stdin.stream
7931 if stdin_stream.pull then
7932 return stdin_stream:pull(timeout, table.unpack(args, 1, args.n))
7933 end
7934 -- if stdin does not have pull() we can build the result
7935 local result = io.read(1)
7936 if result then
7937 return "clipboard", nil, result
7938 end
7939end
7940
7941function term.bind(gpu, window)
7942 return as_window(window, tty.bind, gpu)
7943end
7944
7945function term.scroll(...)
7946 if io.stdout.tty then
7947 return io.stdout.stream.scroll(...)
7948 end
7949end
7950
7951term.internal.run_in_window = as_window
7952
7953return term
7954 mnt/6c1/lib/vt100.lua 0000600 00000006215 13215223742 006634 0 local text = require("text")
7955
7956local rules = {}
7957local vt100 = {rules=rules}
7958local full
7959
7960-- colors, blinking, and reverse
7961-- [%d+;%d+;..%d+m
7962-- cost: 2,250
7963rules[{"%[", "[%d;]*", "m"}] = function(window, _, number_text)
7964 -- prefix and suffix ; act as reset
7965 -- e.g. \27[41;m is actually 41 followed by a reset
7966 local colors = {0x0,0xff0000,0x00ff00,0xffff00,0x0000ff,0xff00ff,0x00B6ff,0xffffff}
7967 local fg, bg = window.gpu.setForeground, window.gpu.setBackground
7968 if window.flip then
7969 fg, bg = bg, fg
7970 end
7971 number_text = " _ " .. number_text:gsub("^;$", ""):gsub(";", " _ ") .. " _ "
7972 local parts = text.internal.tokenize(number_text)
7973 local last_was_break
7974 for _,part in ipairs(parts) do
7975 local num = tonumber(part[1].txt)
7976 last_was_break, num = not num, num or last_was_break and 0
7977
7978 if num == 7 then
7979 if not window.flip then
7980 fg(bg(window.gpu.getForeground()))
7981 fg, bg = bg, fg
7982 end
7983 window.flip = true
7984 elseif num == 5 then
7985 window.blink = true
7986 elseif num == 0 then
7987 bg(colors[1])
7988 fg(colors[8])
7989 elseif num then
7990 num = num - 29
7991 local set = fg
7992 if num > 10 then
7993 num = num - 10
7994 set = bg
7995 end
7996 local color = colors[num]
7997 if color then
7998 set(color)
7999 end
8000 end
8001 end
8002end
8003
8004local function save_attributes(window, seven, s)
8005 if seven == "7" or s == "s" then
8006 window.saved =
8007 {
8008 window.x,
8009 window.y,
8010 {window.gpu.getBackground()},
8011 {window.gpu.getForeground()},
8012 window.flip,
8013 window.blink
8014 }
8015 else
8016 local data = window.saved or {1, 1, {0x0}, {0xffffff}, window.flip, window.blink}
8017 window.x = data[1]
8018 window.y = data[2]
8019 window.gpu.setBackground(table.unpack(data[3]))
8020 window.gpu.setForeground(table.unpack(data[4]))
8021 window.flip = data[5]
8022 window.blink = data[6]
8023 end
8024end
8025
8026-- 7 save cursor position and attributes
8027-- 8 restore cursor position and attributes
8028rules[{"[78]"}] = save_attributes
8029
8030-- s save cursor position
8031-- u restore cursor position
8032rules[{"%[", "[su]"}] = save_attributes
8033
8034-- returns 2 values
8035-- value: parsed text
8036-- ansi_print: failed to parse
8037function vt100.parse(window)
8038 local ansi = window.ansi_escape
8039 window.ansi_escape = nil
8040 local any_valid
8041
8042 for rule,action in pairs(rules) do
8043 local last_index = 0
8044 local captures = {}
8045 for _,pattern in ipairs(rule) do
8046 if last_index >= #ansi then
8047 any_valid = true
8048 break
8049 end
8050 local si, ei, capture = ansi:find("^(" .. pattern .. ")", last_index + 1)
8051 if not si then
8052 break
8053 end
8054 captures[#captures + 1] = capture
8055 last_index = ei
8056 end
8057
8058 if #captures == #rule then
8059 action(window, table.unpack(captures))
8060 return ansi:sub(last_index + 1), ""
8061 end
8062 end
8063
8064 if not full then
8065 -- maybe it did satisfy a rule, load more rules
8066 full = true
8067 dofile("/lib/core/full_vt.lua")
8068 window.ansi_escape = ansi
8069 return vt100.parse(window)
8070 end
8071
8072 if not any_valid then
8073 -- malformed
8074 return ansi, "\27"
8075 end
8076
8077 -- else, still consuming
8078 window.ansi_escape = ansi
8079 return "", ""
8080end
8081
8082return vt100
8083 mnt/6c1/lib/thread.lua 0000600 00000023600 13215223714 007225 0 local pipe = require("pipe")
8084local event = require("event")
8085local process = require("process")
8086local computer = require("computer")
8087
8088local thread = {}
8089local init_thread
8090
8091local function waitForDeath(threads, timeout, all)
8092 checkArg(1, threads, "table")
8093 checkArg(2, timeout, "number", "nil")
8094 checkArg(3, all, "boolean")
8095 timeout = timeout or math.huge
8096 local mortician = {}
8097 local timed_out = true
8098 local deadline = computer.uptime() + timeout
8099 while deadline > computer.uptime() do
8100 local dieing = {}
8101 local living = false
8102 for _,t in ipairs(threads) do
8103 local mt = getmetatable(t)
8104 local result = mt.attached.data.result
8105 local proc_ok = type(result) ~= "table" or result[1]
8106 local ready_to_die = t:status() ~= "running" -- suspended is considered dead to exit
8107 or not proc_ok -- the thread is killed if its attached process has a non zero exit
8108 if ready_to_die then
8109 dieing[#dieing + 1] = t
8110 mortician[t] = true
8111 else
8112 living = true
8113 end
8114 end
8115
8116 if all and not living or not all and #dieing > 0 then
8117 timed_out = false
8118 break
8119 end
8120
8121 -- resume each non dead thread
8122 -- we KNOW all threads are event.pull blocked
8123 event.pull(deadline - computer.uptime())
8124 end
8125
8126 for t in pairs(mortician) do
8127 t:kill()
8128 end
8129
8130 if timed_out then
8131 return nil, "thread join timed out"
8132 end
8133 return true
8134end
8135
8136function thread.waitForAny(threads, timeout)
8137 return waitForDeath(threads, timeout, false)
8138end
8139
8140function thread.waitForAll(threads, timeout)
8141 return waitForDeath(threads, timeout, true)
8142end
8143
8144local box_thread = {}
8145local box_thread_list = {close = thread.waitForAll}
8146
8147local function get_process_threads(proc, bCreate)
8148 local handles = proc.data.handles
8149 for _,next_handle in ipairs(handles) do
8150 local handle_mt = getmetatable(next_handle)
8151 if handle_mt and handle_mt.__index == box_thread_list then
8152 return next_handle
8153 end
8154 end
8155 if bCreate then
8156 local btm = setmetatable({}, {__index = box_thread_list})
8157 table.insert(handles, btm)
8158 return btm
8159 end
8160end
8161
8162function box_thread:resume()
8163 local mt = getmetatable(self)
8164 if mt.__status ~= "suspended" then
8165 return nil, "cannot resume " .. mt.__status .. " thread"
8166 end
8167 mt.__status = "running"
8168 -- register the thread to wake up
8169 if coroutine.status(self.pco.root) == "suspended" and not mt.reg then
8170 mt.register(0)
8171 end
8172 return true
8173end
8174
8175function box_thread:suspend()
8176 local mt = getmetatable(self)
8177 if mt.__status ~= "running" then
8178 return nil, "cannot suspend " .. mt.__status .. " thread"
8179 end
8180 mt.__status = "suspended"
8181 local pco_status = coroutine.status(self.pco.root)
8182 if pco_status == "running" or pco_status == "normal" then
8183 mt.coma()
8184 end
8185 return true
8186end
8187
8188function box_thread:status()
8189 return getmetatable(self).__status
8190end
8191
8192function box_thread:join(timeout)
8193 return waitForDeath({self}, timeout, true)
8194end
8195
8196function box_thread:kill()
8197 getmetatable(self).close()
8198end
8199
8200function box_thread:detach()
8201 return self:attach(init_thread)
8202end
8203
8204function box_thread:attach(parent)
8205 checkArg(1, parent, "thread", "number", "nil")
8206 local mt = assert(getmetatable(self), "thread panic: no metadata")
8207 local proc = process.info(parent)
8208 if not proc then return nil, "thread failed to attach, process not found" end
8209 if mt.attached == proc then return self end -- already attached
8210
8211 if mt.attached then
8212 local prev_threads = assert(get_process_threads(mt.attached), "thread panic: no thread handle")
8213 for index,t_in_list in ipairs(prev_threads) do
8214 if t_in_list == self then
8215 table.remove(prev_threads, index)
8216 break
8217 end
8218 end
8219 end
8220
8221 -- registration happens on the attached proc, unregister before reparenting
8222 local waiting_handler = mt.unregister()
8223
8224 -- attach to parent or the current process
8225 mt.attached = proc
8226
8227 -- this process may not have a box_thread list
8228 local threads = get_process_threads(proc, true)
8229 table.insert(threads, self)
8230
8231 -- register on the new parent
8232 if waiting_handler then -- event-waiting
8233 mt.register(waiting_handler.timeout - computer.uptime())
8234 end
8235
8236 return self
8237end
8238
8239function thread.current()
8240 local proc = process.findProcess()
8241 local thread_root
8242 while proc do
8243 if thread_root then
8244 for _,bt in ipairs(get_process_threads(proc) or {}) do
8245 if bt.pco.root == thread_root then
8246 return bt
8247 end
8248 end
8249 else
8250 thread_root = proc.data.coroutine_handler.root
8251 end
8252 proc = proc.parent
8253 end
8254end
8255
8256function thread.create(fp, ...)
8257 checkArg(1, fp, "function")
8258
8259 local t = {}
8260 local mt = {__status="suspended",__index=box_thread}
8261 setmetatable(t, mt)
8262 t.pco = pipe.createCoroutineStack(function(...)
8263 mt.__status = "running"
8264 local fp_co = t.pco.create(fp)
8265 -- run fp_co until dead
8266 -- pullSignal will yield_past this point
8267 -- but yield will return here, we pullSignal from here to yield_past
8268 local args = table.pack(...)
8269 while true do
8270 local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n)))
8271 if t.pco.status(fp_co) == "dead" then
8272 -- this error handling is VERY much like process.lua
8273 -- maybe one day it'll merge
8274 if not result[1] then
8275 local exit_code
8276 local msg = result[2]
8277 -- msg can be a custom error object
8278 local reason = "crashed"
8279 if type(msg) == "table" then
8280 if type(msg.reason) == "string" then
8281 reason = msg.reason
8282 end
8283 exit_code = tonumber(msg.code)
8284 elseif type(msg) == "string" then
8285 reason = msg
8286 end
8287 if not exit_code then
8288 pcall(event.onError, string.format("[thread] %s", reason))
8289 exit_code = 1
8290 end
8291 os.exit(exit_code)
8292 end
8293 break
8294 end
8295 args = table.pack(event.pull(table.unpack(result, 2, result.n)))
8296 end
8297 end, nil, "thread")
8298
8299 --special resume to keep track of process death
8300 function mt.private_resume(...)
8301 mt.unregister()
8302 -- this thread may have been killed
8303 if t:status() == "dead" then return end
8304 local result = table.pack(t.pco.resume(t.pco.root, ...))
8305 if t.pco.status(t.pco.root) == "dead" then
8306 mt.close()
8307 end
8308 return table.unpack(result, 1, result.n)
8309 end
8310
8311 mt.process = process.list[t.pco.root]
8312 mt.process.data.handlers = {}
8313
8314 function mt.register(timeout)
8315 -- register a timeout handler
8316 mt.id = event.register(
8317 nil, -- nil key matches anything, timers use false keys
8318 mt.private_resume,
8319 timeout, -- wait for the time specified by the caller
8320 1, -- we only want this thread to wake up once
8321 mt.attached.data.handlers) -- optional arg, to specify our own handlers
8322 mt.reg = mt.attached.data.handlers[mt.id]
8323 end
8324
8325 function mt.unregister()
8326 local id = mt.id
8327 local reg = mt.reg
8328 mt.id = nil
8329 mt.reg = nil
8330 -- before just removing a handler, make sure it is still ours
8331 if id and mt.attached and mt.attached.data.handlers[id] == reg then
8332 mt.attached.data.handlers[id] = nil
8333 return reg
8334 end
8335 end
8336
8337 function mt.coma()
8338 mt.unregister() -- we should not wake up again (until resumed)
8339 while mt.__status == "suspended" do
8340 t.pco.yield_past(t.pco.root, 0)
8341 end
8342 end
8343
8344 function mt.process.data.pull(_, timeout)
8345 --[==[
8346 yield_past(root) will yield until out of this thread
8347 registration puts in a callback to resume this thread
8348
8349 Subsequent registrations are necessary in case the thread is suspended
8350 This thread yields when suspended, entering a coma state
8351 -> coma state: yield without registration
8352
8353 resume will regsiter a wakeup call, breaks coma
8354
8355 subsequent yields need not specify a timeout because
8356 we already legitimately resumed only to find out we had been suspended
8357
8358 3 places register for wake up
8359 1. computer.pullSignal [this path]
8360 2. t:attach(proc) will unregister and re-register
8361 3. t:resume() of a suspended thread
8362 ]==]
8363 mt.register(timeout)
8364 local event_data = table.pack(t.pco.yield_past(t.pco.root, timeout))
8365 mt.coma()
8366 return table.unpack(event_data, 1, event_data.n)
8367 end
8368
8369 function mt.close()
8370 if t:status() == "dead" then
8371 return
8372 end
8373 local threads = get_process_threads(mt.attached)
8374 for index,t_in_list in ipairs(threads) do
8375 if t_in_list == t then
8376 table.remove(threads, index)
8377 break
8378 end
8379 end
8380 mt.__status = "dead"
8381 event.push("thread_exit")
8382 end
8383
8384 t:attach() -- the current process
8385 mt.private_resume(...) -- threads start out running
8386
8387 return t
8388end
8389
8390do
8391 local handlers = event.handlers
8392 local handlers_mt = getmetatable(handlers)
8393 -- the event library sets a metatable on handlers, but we set threaded=true
8394 if not handlers_mt.threaded then
8395 -- find the root process
8396 local root_data
8397 for t,p in pairs(process.list) do
8398 if not p.parent then
8399 init_thread = t
8400 root_data = p.data
8401 break
8402 end
8403 end
8404 assert(init_thread, "thread library panic: no init thread")
8405 handlers_mt.threaded = true
8406 -- handles might be optimized out for memory
8407 root_data.handles = root_data.handles or {}
8408 -- if we don't separate root handlers from thread handlers we see double dispatch
8409 -- because the thread calls dispatch on pull as well
8410 root_data.handlers = {} -- root handlers
8411 root_data.pull = handlers_mt.__call -- the real computer.pullSignal
8412 while true do
8413 local key, value = next(handlers)
8414 if not key then break end
8415 root_data.handlers[key] = value
8416 handlers[key] = nil
8417 end
8418 handlers_mt.__index = function(_, key)
8419 return process.info().data.handlers[key]
8420 end
8421 handlers_mt.__newindex = function(_, key, value)
8422 process.info().data.handlers[key] = value
8423 end
8424 handlers_mt.__pairs = function(_, ...)
8425 return pairs(process.info().data.handlers, ...)
8426 end
8427 handlers_mt.__call = function(tbl, ...)
8428 return process.info().data.pull(tbl, ...)
8429 end
8430 end
8431end
8432
8433return thread
8434 mnt/6c1/lib/buffer.lua 0000600 00000007707 13215223721 007237 0 local computer = require("computer")
8435local unicode = require("unicode")
8436
8437local buffer = {}
8438local metatable = {
8439 __index = buffer,
8440 __metatable = "file"
8441}
8442
8443function buffer.new(mode, stream)
8444 local result = {
8445 closed = false,
8446 tty = false,
8447 mode = {},
8448 stream = stream,
8449 bufferRead = "",
8450 bufferWrite = "",
8451 bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)),
8452 bufferMode = "full",
8453 readTimeout = math.huge
8454 }
8455 mode = mode or "r"
8456 for i = 1, unicode.len(mode) do
8457 result.mode[unicode.sub(mode, i, i)] = true
8458 end
8459 return setmetatable(result, metatable)
8460end
8461
8462function buffer:close()
8463 if self.mode.w or self.mode.a then
8464 self:flush()
8465 end
8466 self.closed = true
8467 return self.stream:close()
8468end
8469
8470function buffer:flush()
8471 if #self.bufferWrite > 0 then
8472 local tmp = self.bufferWrite
8473 self.bufferWrite = ""
8474 local result, reason = self.stream:write(tmp)
8475 if not result then
8476 return nil, reason or "bad file descriptor"
8477 end
8478 end
8479
8480 return self
8481end
8482
8483function buffer:lines(...)
8484 local args = table.pack(...)
8485 return function()
8486 local result = table.pack(self:read(table.unpack(args, 1, args.n)))
8487 if not result[1] and result[2] then
8488 error(result[2])
8489 end
8490 return table.unpack(result, 1, result.n)
8491 end
8492end
8493
8494local function readChunk(self)
8495 if computer.uptime() > self.timeout then
8496 error("timeout")
8497 end
8498 local result, reason = self.stream:read(math.max(1,self.bufferSize))
8499 if result then
8500 self.bufferRead = self.bufferRead .. result
8501 return self
8502 else -- error or eof
8503 return result, reason
8504 end
8505end
8506
8507function buffer:readLine(chop, timeout)
8508 self.timeout = timeout or (computer.uptime() + self.readTimeout)
8509 local start = 1
8510 while true do
8511 local buf = self.bufferRead
8512 local i = buf:find("[\r\n]", start)
8513 local c = i and buf:sub(i,i)
8514 local is_cr = c == "\r"
8515 if i and (not is_cr or i < #buf) then
8516 local n = buf:sub(i+1,i+1)
8517 if is_cr and n == "\n" then
8518 c = c .. n
8519 end
8520 local result = buf:sub(1, i - 1) .. (chop and "" or c)
8521 self.bufferRead = buf:sub(i + #c)
8522 return result
8523 else
8524 start = #self.bufferRead - (is_cr and 1 or 0)
8525 local result, reason = readChunk(self)
8526 if not result then
8527 if reason then
8528 return result, reason
8529 else -- eof
8530 result = #self.bufferRead > 0 and self.bufferRead or nil
8531 self.bufferRead = ""
8532 return result
8533 end
8534 end
8535 end
8536 end
8537end
8538
8539function buffer:read(...)
8540 if not self.mode.r then
8541 return nil, "read mode was not enabled for this stream"
8542 end
8543
8544 if self.mode.w or self.mode.a then
8545 self:flush()
8546 end
8547
8548 if select("#", ...) == 0 then
8549 return self:readLine(true)
8550 end
8551 return self:formatted_read(readChunk, ...)
8552end
8553
8554function buffer:setvbuf(mode, size)
8555 mode = mode or self.bufferMode
8556 size = size or self.bufferSize
8557
8558 assert(mode == "no" or mode == "full" or mode == "line",
8559 "bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
8560 assert(mode == "no" or type(size) == "number",
8561 "bad argument #2 (number expected, got " .. type(size) .. ")")
8562
8563 self.bufferMode = mode
8564 self.bufferSize = size
8565
8566 return self.bufferMode, self.bufferSize
8567end
8568
8569function buffer:write(...)
8570 if self.closed then
8571 return nil, "bad file descriptor"
8572 end
8573 if not self.mode.w and not self.mode.a then
8574 return nil, "write mode was not enabled for this stream"
8575 end
8576 local args = table.pack(...)
8577 for i = 1, args.n do
8578 if type(args[i]) == "number" then
8579 args[i] = tostring(args[i])
8580 end
8581 checkArg(i, args[i], "string")
8582 end
8583
8584 for i = 1, args.n do
8585 local arg = args[i]
8586 local result, reason
8587
8588 if self.bufferMode == "no" then
8589 result, reason = self.stream:write(arg)
8590 else
8591 result, reason = self:buffered_write(arg)
8592 end
8593
8594 if not result then
8595 return nil, reason
8596 end
8597 end
8598
8599 return self
8600end
8601
8602require("package").delay(buffer, "/lib/core/full_buffer.lua")
8603
8604return buffer
8605 mnt/6c1/lib/keyboard.lua 0000600 00000005565 13215223742 007571 0 local keyboard = {pressedChars = {}, pressedCodes = {}}
8606
8607-- these key definitions are only a subset of all the defined keys
8608-- __index loads all key data from /lib/tools/keyboard_full.lua (only once)
8609-- new key metadata should be added here if required for boot
8610keyboard.keys = {
8611 c = 0x2E,
8612 d = 0x20,
8613 q = 0x10,
8614 w = 0x11,
8615 back = 0x0E, -- backspace
8616 delete = 0xD3,
8617 down = 0xD0,
8618 enter = 0x1C,
8619 home = 0xC7,
8620 lcontrol = 0x1D,
8621 left = 0xCB,
8622 lmenu = 0x38, -- left Alt
8623 lshift = 0x2A,
8624 pageDown = 0xD1,
8625 rcontrol = 0x9D,
8626 right = 0xCD,
8627 rmenu = 0xB8, -- right Alt
8628 rshift = 0x36,
8629 space = 0x39,
8630 tab = 0x0F,
8631 up = 0xC8,
8632 ["end"] = 0xCF,
8633 enter = 0x1C,
8634 tab = 0x0F,
8635 numpadenter = 0x9C,
8636}
8637
8638-------------------------------------------------------------------------------
8639
8640local function getKeyboardAddress(address)
8641 return address or require("tty").keyboard()
8642end
8643
8644local function getPressedCodes(address)
8645 address = getKeyboardAddress(address)
8646 return address and keyboard.pressedCodes[address] or false
8647end
8648
8649local function getPressedChars(address)
8650 address = getKeyboardAddress(address)
8651 return address and keyboard.pressedChars[address] or false
8652end
8653
8654function keyboard.isAltDown(address)
8655 checkArg(1, address, "string", "nil")
8656 local pressedCodes = getPressedCodes(address)
8657 return pressedCodes and (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil
8658end
8659
8660function keyboard.isControl(char)
8661 return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
8662end
8663
8664function keyboard.isControlDown(address)
8665 checkArg(1, address, "string", "nil")
8666 local pressedCodes = getPressedCodes(address)
8667 return pressedCodes and (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil
8668end
8669
8670function keyboard.isKeyDown(charOrCode, address)
8671 checkArg(1, charOrCode, "string", "number")
8672 checkArg(2, address, "string", "nil")
8673 if type(charOrCode) == "string" then
8674 local pressedChars = getPressedChars(address)
8675 return pressedChars and pressedChars[utf8 and utf8.codepoint(charOrCode) or charOrCode:byte()]
8676 elseif type(charOrCode) == "number" then
8677 local pressedCodes = getPressedCodes(address)
8678 return pressedCodes and pressedCodes[charOrCode]
8679 end
8680end
8681
8682function keyboard.isShiftDown(address)
8683 checkArg(1, address, "string", "nil")
8684 local pressedCodes = getPressedCodes(address)
8685 return pressedCodes and (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil
8686end
8687
8688-------------------------------------------------------------------------------
8689
8690require("package").delay(keyboard.keys, "/lib/core/full_keyboard.lua")
8691
8692return keyboard
8693 mnt/6c1/lib/core/device_labeling.lua 0000600 00000005076 13215223735 012014 0 local fs = require("filesystem")
8694
8695local lib = {}
8696
8697local rules_path = "/etc/udev/rules.d/"
8698local auto_rules = "autogenerated.lua"
8699
8700local function fs_key(dir, filename)
8701 local long_name = dir .. '/' .. filename
8702 local segments = fs.segments(long_name)
8703 local result = '/' .. table.concat(segments, '/')
8704 return result
8705end
8706
8707function lib.loadRules(root_dir)
8708 checkArg(1, root_dir, "string", "nil")
8709 root_dir = (root_dir or rules_path)
8710 lib.rules = {}
8711 lib.rules[fs_key(root_dir, auto_rules)] = {}
8712
8713 for file in fs.list(root_dir) do
8714 if file:match("%.lua$") then
8715 local path = fs_key(root_dir, file)
8716 local file_handle = io.open(path)
8717 if file_handle then
8718 local load_rule = load("return {" .. file_handle:read("*a") .. "}")
8719 file_handle:close()
8720 if load_rule then
8721 local ok, rule = pcall(load_rule)
8722 if ok and type(rule) == "table" then
8723 local irule = {}
8724 lib.rules[path] = irule
8725 for _,v in ipairs(rule) do
8726 if type(v) == "table" then
8727 table.insert(irule, v)
8728 end
8729 -- else invalid rule
8730 end
8731 end
8732 end
8733 end
8734 end
8735 end
8736end
8737
8738function lib.saveRule(rule_set, path)
8739 checkArg(1, rule_set, "table")
8740 checkArg(2, path, "string")
8741 local file = io.open(path, "w")
8742 if not file then return end -- fs may be read only, totally fine, this just won't persist
8743 for index, irule in ipairs(rule_set) do
8744 file:write(require("serialization").serialize(irule), ",\n")
8745 end
8746 file:close()
8747end
8748
8749function lib.saveRules(rules)
8750 for path, rule_set in pairs(rules) do
8751 lib.saveRule(rule_set, path)
8752 end
8753end
8754
8755local function getIRule(proxy)
8756 checkArg(1, proxy, "table")
8757 for path,rule_set in pairs(lib.rules) do
8758 for index, irule in ipairs(rule_set) do
8759 if irule.address == proxy.address then
8760 return irule, index, rule_set, path
8761 end
8762 end
8763 end
8764end
8765
8766function lib.getDeviceLabel(proxy)
8767 local irule = getIRule(proxy)
8768 if irule and irule.label then
8769 return irule.label
8770 elseif proxy.getLabel then
8771 return proxy.getLabel()
8772 end
8773end
8774
8775function lib.setDeviceLabel(proxy, label)
8776 local irule, index, rule_set, path = getIRule(proxy)
8777 if not irule then
8778 -- if the device supports labels, use it instead
8779 if proxy.setLabel then
8780 return proxy.setLabel(label)
8781 end
8782 path = fs_key(rules_path, auto_rules)
8783 rule_set = lib.rules[path]
8784 index = #rule_set + 1
8785 irule = {address=proxy.address}
8786 table.insert(rule_set, irule)
8787 end
8788 irule.label = label
8789 lib.saveRule(rule_set, path)
8790end
8791
8792return lib
8793
8794 mnt/6c1/lib/core/full_tty.lua 0000600 00000005522 13215223730 010551 0 local tty = require("tty")
8795local unicode = require("unicode")
8796local kb = require("keyboard")
8797
8798function tty.touch_handler(_, cursor, gx, gy)
8799 if cursor.data == "" then
8800 return false
8801 end
8802 cursor:move(-math.huge)
8803 local win = tty.window
8804 gx, gy = gx - win.dx, gy - win.dy
8805 local x2, y2, d = win.x, win.y, win.width
8806 local char_width_to_move = ((gy*d+gx)-(y2*d+x2))
8807 if char_width_to_move <= 0 then
8808 return false
8809 end
8810 local total_wlen = unicode.wlen(cursor.data)
8811 if char_width_to_move >= total_wlen then
8812 cursor:move(math.huge)
8813 else
8814 local chars_to_move = unicode.wtrunc(cursor.data, char_width_to_move + 1)
8815 cursor:move(unicode.len(chars_to_move))
8816 end
8817 -- fake white space can make the index off, redo adjustment for alignment
8818 x2, y2, d = win.x, win.y, win.width
8819 char_width_to_move = ((gy*d+gx)-(y2*d+x2))
8820 if (char_width_to_move < 0) then
8821 -- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up
8822 local up_to_cursor = unicode.sub(cursor.data, cursor.index+char_width_to_move, cursor.index)
8823 local full_wlen = unicode.wlen(up_to_cursor)
8824 local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1)
8825 local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail)
8826 cursor:move(-chars_cut)
8827 end
8828 return false -- no further cursor update
8829end
8830tty.drag_handler = tty.touch_handler
8831
8832function tty.clipboard_handler(handler, _, char, _)
8833 handler.cache = nil
8834 local first_line, end_index = char:find("\13?\10")
8835 if first_line then
8836 local after = char:sub(end_index + 1)
8837 if after ~= "" then
8838 -- todo look at postponing the text on cursor
8839 require("computer").pushSignal("key_down", tty.keyboard(), 13, 28)
8840 require("computer").pushSignal("clipboard", tty.keyboard(), after)
8841 end
8842 char = char:sub(1, first_line - 1)
8843 end
8844 return char
8845end
8846
8847function tty.on_tab(handler, cursor)
8848 local hints = handler.hint
8849 if not hints then return end
8850 local main_kb = tty.keyboard()
8851 -- tty may not have a keyboard
8852 -- in which case, we shouldn't be handling tab events
8853 if not main_kb then
8854 return
8855 end
8856 if not handler.cache then
8857 handler.cache = type(hints) == "table" and hints or hints(cursor.data, cursor.index + 1) or {}
8858 handler.cache.i = -1
8859 end
8860
8861 local cache = handler.cache
8862
8863 if #cache == 1 and cache.i == 0 then
8864 -- there was only one solution, and the user is asking for the next
8865 handler.cache = hints(cache[1], cursor.index + 1)
8866 if not handler.cache then return end
8867 handler.cache.i = -1
8868 cache = handler.cache
8869 end
8870
8871 local change = kb.isShiftDown(main_kb) and -1 or 1
8872 cache.i = (cache.i + change) % math.max(#cache, 1)
8873 local next = cache[cache.i + 1]
8874 if next then
8875 local tail = unicode.len(cursor.data) - cursor.index
8876 cursor:clear()
8877 cursor:update(next)
8878 cursor:move(-tail)
8879 end
8880end
8881 mnt/6c1/lib/core/full_event.lua 0000600 00000002472 13215223734 011057 0 local event = require("event")
8882
8883local function createMultipleFilter(...)
8884 local filter = table.pack(...)
8885 if filter.n == 0 then
8886 return nil
8887 end
8888
8889 return function(...)
8890 local signal = table.pack(...)
8891 if type(signal[1]) ~= "string" then
8892 return false
8893 end
8894 for i = 1, filter.n do
8895 if filter[i] ~= nil and signal[1]:match(filter[i]) then
8896 return true
8897 end
8898 end
8899 return false
8900 end
8901end
8902
8903function event.pullMultiple(...)
8904 local seconds
8905 local args
8906 if type(...) == "number" then
8907 seconds = ...
8908 args = table.pack(select(2,...))
8909 for i=1,args.n do
8910 checkArg(i+1, args[i], "string", "nil")
8911 end
8912 else
8913 args = table.pack(...)
8914 for i=1,args.n do
8915 checkArg(i, args[i], "string", "nil")
8916 end
8917 end
8918 return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n)))
8919end
8920
8921function event.cancel(timerId)
8922 checkArg(1, timerId, "number")
8923 if event.handlers[timerId] then
8924 event.handlers[timerId] = nil
8925 return true
8926 end
8927 return false
8928end
8929
8930function event.ignore(name, callback)
8931 checkArg(1, name, "string")
8932 checkArg(2, callback, "function")
8933 for id, handler in pairs(event.handlers) do
8934 if handler.key == name and handler.callback == callback then
8935 event.handlers[id] = nil
8936 return true
8937 end
8938 end
8939 return false
8940end
8941
8942 mnt/6c1/lib/core/install_utils.lua 0000600 00000004555 13215223736 011610 0 local cmd, arg, options, devices = ...
8943
8944local function select_prompt(devs, prompt)
8945 table.sort(devs, function(a, b) return a.path<b.path end)
8946 local num_devs = #devs
8947
8948 if num_devs < 2 then
8949 return devs[1]
8950 end
8951
8952 io.write(prompt,'\n')
8953
8954 for i = 1, num_devs do
8955 local src = devs[i]
8956 local dev = src.dev
8957 local selection_label = (src.prop or {}).label or dev.getLabel()
8958 if selection_label then
8959 selection_label = string.format("%s (%s...)", selection_label, dev.address:sub(1, 8))
8960 else
8961 selection_label = dev.address
8962 end
8963 io.write(string.format("%d) %s at %s [r%s]\n", i, selection_label, src.path, dev.isReadOnly() and 'o' or 'w'))
8964 end
8965
8966 io.write("Please enter a number between 1 and " .. num_devs .. '\n')
8967 io.write("Enter 'q' to cancel the installation: ")
8968 for _=1,5 do
8969 local result = io.read() or "q"
8970 if result == "q" then
8971 os.exit()
8972 end
8973 local number = tonumber(result)
8974 if number and number > 0 and number <= num_devs then
8975 return devs[number]
8976 else
8977 io.write("Invalid input, please try again: ")
8978 os.sleep(0)
8979 end
8980 end
8981 print("\ntoo many bad inputs, aborting")
8982 os.exit(1)
8983end
8984
8985if cmd == "select" then
8986 if arg == "sources" then
8987 if #devices == 0 then
8988 if options.label then
8989 io.stderr:write("Nothing to install labeled: " .. options.label .. '\n')
8990 elseif options.from then
8991 io.stderr:write("Nothing to install from: " .. options.from .. '\n')
8992 else
8993 io.stderr:write("Nothing to install\n")
8994 end
8995 os.exit(1)
8996 end
8997 local index_of_rw_source
8998 for index,entry in ipairs(devices) do
8999 if not entry.dev.isReadOnly() then
9000 if index_of_rw_source then
9001 -- this means there was another rw source, no special action required
9002 index_of_rw_source = nil
9003 break
9004 end
9005 index_of_rw_source = index
9006 end
9007 end
9008 if index_of_rw_source then
9009 table.remove(devices, index_of_rw_source)
9010 end
9011 return select_prompt(devices, "What do you want to install?")
9012 elseif arg == "targets" then
9013 if #devices == 0 then
9014 if options.to then
9015 io.stderr:write("No such target to install to: " .. options.to .. '\n')
9016 else
9017 io.stderr:write("No writable disks found, aborting\n")
9018 end
9019 os.exit(1)
9020 end
9021
9022 return select_prompt(devices, "Where do you want to install to?")
9023 end
9024end mnt/6c1/lib/core/install_basics.lua 0000600 00000013717 13215223732 011710 0 local computer = require("computer")
9025local shell = require("shell")
9026local fs = require("filesystem")
9027
9028local args, options = shell.parse(...)
9029
9030if options.help then
9031 io.write([[Usage: install [OPTION]...
9032 --from=ADDR install filesystem at ADDR
9033 default: builds list of
9034 candidates and prompts user
9035 --to=ADDR same as --from but for target
9036 --fromDir=PATH install PATH from source
9037 --root=PATH same as --fromDir but target
9038 --toDir=PATH same as --root
9039 -u, --update update files interactively
9040 --label override label from .prop
9041 --nosetlabel do not label target
9042 --nosetboot do not use target for boot
9043 --noreboot do not reboot after install
9044]])
9045 return nil -- exit success
9046end
9047
9048local utils_path = "/lib/core/install_utils.lua"
9049local utils
9050
9051local rootfs = fs.get("/")
9052if not rootfs then
9053 io.stderr:write("no root filesystem, aborting\n");
9054 os.exit(1)
9055end
9056
9057local label = args[1]
9058options.label = label
9059
9060local source_filter = options.from
9061local source_filter_dev
9062if source_filter then
9063 local from_path = shell.resolve(source_filter)
9064 if fs.isDirectory(from_path) then
9065 source_filter_dev = fs.get(from_path)
9066 source_filter = source_filter_dev.address
9067 options.from = from_path
9068 end
9069end
9070
9071local target_filter = options.to
9072local target_filter_dev
9073if target_filter then
9074 local to_path = shell.resolve(target_filter)
9075 if fs.isDirectory(target_filter) then
9076 target_filter_dev = fs.get(to_path)
9077 target_filter = target_filter_dev.address
9078 options.to = to_path
9079 end
9080end
9081
9082local sources = {}
9083local targets = {}
9084
9085-- tmpfs is not a candidate unless it is specified
9086
9087local comps = require("component").list("filesystem")
9088local devices = {}
9089
9090-- not all mounts are components, only use components
9091for dev, path in fs.mounts() do
9092 if comps[dev.address] then
9093 local known = devices[dev]
9094 devices[dev] = known and #known < #path and known or path
9095 end
9096end
9097
9098local dev_dev = fs.get("/dev")
9099devices[dev_dev == rootfs or dev_dev] = nil
9100local tmpAddress = computer.tmpAddress()
9101
9102for dev, path in pairs(devices) do
9103 local address = dev.address
9104 local install_path = dev == target_filter_dev and options.to or path
9105 local specified = target_filter and address:find(target_filter, 1, true) == 1
9106
9107 if dev.isReadOnly() then
9108 if specified then
9109 io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
9110 os.exit(1)
9111 end
9112 elseif specified or
9113 not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source
9114 not target_filter and
9115 address ~= tmpAddress then
9116 table.insert(targets, {dev=dev, path=install_path, specified=specified})
9117 end
9118end
9119
9120local target = targets[1]
9121-- if there is only 1 target, the source selection cannot include it
9122if #targets == 1 then
9123 devices[targets[1].dev] = nil
9124end
9125
9126for dev, path in pairs(devices) do
9127 local address = dev.address
9128 local install_path = dev == source_filter_dev and options.from or path
9129 local specified = source_filter and address:find(source_filter, 1, true) == 1
9130
9131 if fs.list(install_path)()
9132 and (specified or
9133 not source_filter and
9134 address ~= tmpAddress and
9135 not (address == rootfs.address and not rootfs.isReadOnly())) then
9136 local prop = {}
9137 local prop_path = path .. "/.prop"
9138 local prop_file = fs.open(prop_path)
9139 if prop_file then
9140 local prop_data = prop_file:read(math.huge)
9141 prop_file:close()
9142 local prop_load = load("return " .. prop_data)
9143 prop = prop_load and prop_load()
9144 if not prop then
9145 io.stderr:write("Ignoring " .. path .. " due to malformed prop file\n")
9146 prop = {ignore = true}
9147 end
9148 end
9149 if not prop.ignore then
9150 if not label or label:lower() == (prop.label or dev.getLabel() or ""):lower() then
9151 table.insert(sources, {dev=dev, path=install_path, prop=prop, specified=specified})
9152 end
9153 end
9154 end
9155end
9156
9157-- Ask the user to select a source
9158local source = sources[1]
9159if #sources ~= 1 then
9160 utils = loadfile(utils_path, "bt", _G)
9161 source = utils("select", "sources", options, sources)
9162end
9163if not source then return end
9164
9165-- Remove the source from the target options
9166for index,entry in ipairs(targets) do
9167 if entry.dev == source.dev then
9168 table.remove(targets, index)
9169 target = targets[1]
9170 end
9171end
9172
9173-- Ask the user to select a target
9174if #targets ~= 1 then
9175 utils = utils or loadfile(utils_path, "bt", _G)
9176 target = utils("select", "targets", options, targets)
9177end
9178if not target then return end
9179
9180options =
9181{
9182 from = source.path .. '/',
9183 to = target.path .. '/',
9184 fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""),
9185 root = fs.canonical(options.root or options.toDir or source.prop.root or ""),
9186 update = options.update or options.u,
9187 label = source.prop.label or label,
9188 setlabel = not (options.nosetlabel or options.nolabelset) and source.prop.setlabel,
9189 setboot = not (options.nosetboot or options.noboot) and source.prop.setboot,
9190 reboot = not options.noreboot and source.prop.reboot,
9191}
9192
9193local cp_args =
9194{
9195 "-vrx" .. (options.update and "ui" or ""),
9196 "--skip=.prop",
9197 fs.concat(options.from, options.fromDir) .. "/.",
9198 fs.concat(options.to , options.root)
9199}
9200
9201local source_display = options.label or source.dev.getLabel() or source.path
9202local special_target = ""
9203if #targets > 1 or target_filter or source_filter then
9204 special_target = " to " .. cp_args[4]
9205end
9206
9207io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
9208if not ((io.read() or "n").."y"):match("^%s*[Yy]") then
9209 io.write("Installation cancelled\n")
9210 os.exit()
9211end
9212
9213local installer_path = options.from .. "/.install"
9214if fs.exists(installer_path) then
9215 local installer, reason = loadfile(installer_path, "bt", setmetatable({install=options}, {__index = _G}))
9216 if not installer then
9217 io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n')
9218 os.exit(1)
9219 end
9220 os.exit(installer())
9221end
9222
9223options.cp_args = cp_args
9224options.target = target
9225
9226return options
9227 mnt/6c1/lib/core/full_text.lua 0000600 00000017577 13215223727 010740 0 local text = require("text")
9228local tx = require("transforms")
9229local unicode = require("unicode")
9230
9231-- separate string value into an array of words delimited by whitespace
9232-- groups by quotes
9233-- options is a table used for internal undocumented purposes
9234function text.tokenize(value, options)
9235 checkArg(1, value, "string")
9236 checkArg(2, options, "table", "nil")
9237 options = options or {}
9238
9239 local tokens, reason = text.internal.tokenize(value, options)
9240
9241 if type(tokens) ~= "table" then
9242 return nil, reason
9243 end
9244
9245 if options.doNotNormalize then
9246 return tokens
9247 end
9248
9249 return text.internal.normalize(tokens)
9250end
9251
9252-------------------------------------------------------------------------------
9253-- like tokenize, but does not drop any text such as whitespace
9254-- splits input into an array for sub strings delimited by delimiters
9255-- delimiters are included in the result if not dropDelims
9256function text.split(input, delimiters, dropDelims, di)
9257 checkArg(1, input, "string")
9258 checkArg(2, delimiters, "table")
9259 checkArg(3, dropDelims, "boolean", "nil")
9260 checkArg(4, di, "number", "nil")
9261
9262 if #input == 0 then return {} end
9263 di = di or 1
9264 local result = {input}
9265 if di > #delimiters then return result end
9266
9267 local function add(part, index, r, s, e)
9268 local sub = part:sub(s,e)
9269 if #sub == 0 then return index end
9270 local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
9271 for i=1,#subs do
9272 table.insert(result, index+i-1, subs[i])
9273 end
9274 return index+#subs
9275 end
9276
9277 local i,d=1,delimiters[di]
9278 while true do
9279 local next = table.remove(result,i)
9280 if not next then break end
9281 local si,ei = next:find(d)
9282 if si and ei and ei~=0 then -- delim found
9283 i=add(next, i, di+1, 1, si-1)
9284 i=dropDelims and i or add(next, i, false, si, ei)
9285 i=add(next, i, di, ei+1)
9286 else
9287 i=add(next, i, di+1, 1, #next)
9288 end
9289 end
9290
9291 return result
9292end
9293
9294-----------------------------------------------------------------------------
9295
9296-- splits each word into words at delimiters
9297-- delimiters are kept as their own words
9298-- quoted word parts are not split
9299function text.internal.splitWords(words, delimiters)
9300 checkArg(1,words,"table")
9301 checkArg(2,delimiters,"table")
9302
9303 local split_words = {}
9304 local next_word
9305 local function add_part(part)
9306 if next_word then
9307 split_words[#split_words+1] = {}
9308 end
9309 table.insert(split_words[#split_words], part)
9310 next_word = false
9311 end
9312 for wi=1,#words do local word = words[wi]
9313 next_word = true
9314 for pi=1,#word do local part = word[pi]
9315 local qr = part.qr
9316 if qr then
9317 add_part(part)
9318 else
9319 local part_text_splits = text.split(part.txt, delimiters)
9320 tx.foreach(part_text_splits, function(sub_txt, spi)
9321 local delim = #text.split(sub_txt, delimiters, true) == 0
9322 next_word = next_word or delim
9323 add_part({txt=sub_txt,qr=qr})
9324 next_word = delim
9325 end)
9326 end
9327 end
9328 end
9329
9330 return split_words
9331end
9332
9333function text.internal.normalize(words, omitQuotes)
9334 checkArg(1, words, "table")
9335 checkArg(2, omitQuotes, "boolean", "nil")
9336 local norms = {}
9337 for _,word in ipairs(words) do
9338 local norm = {}
9339 for _,part in ipairs(word) do
9340 norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
9341 end
9342 norms[#norms+1]=table.concat(norm)
9343 end
9344 return norms
9345end
9346
9347function text.internal.stream_base(binary)
9348 return
9349 {
9350 binary = binary,
9351 plen = binary and string.len or unicode.len,
9352 psub = binary and string.sub or unicode.sub,
9353 seek = function (handle, whence, to)
9354 if not handle.txt then
9355 return nil, "bad file descriptor"
9356 end
9357 to = to or 0
9358 local offset = handle:indexbytes()
9359 if whence == "cur" then
9360 offset = offset + to
9361 elseif whence == "set" then
9362 offset = to
9363 elseif whence == "end" then
9364 offset = handle.len + to
9365 end
9366 offset = math.max(0, math.min(offset, handle.len))
9367 handle:byteindex(offset)
9368 return offset
9369 end,
9370 indexbytes = function (handle)
9371 return handle.psub(handle.txt, 1, handle.index):len()
9372 end,
9373 byteindex = function (handle, offset)
9374 local sub = string.sub(handle.txt, 1, offset)
9375 handle.index = handle.plen(sub)
9376 end,
9377 }
9378end
9379
9380function text.internal.reader(txt, mode)
9381 checkArg(1, txt, "string")
9382 local reader = setmetatable(
9383 {
9384 txt = txt,
9385 len = string.len(txt),
9386 index = 0,
9387 read = function(_, n)
9388 checkArg(1, n, "number")
9389 if not _.txt then
9390 return nil, "bad file descriptor"
9391 end
9392 if _.index >= _.plen(_.txt) then
9393 return nil
9394 end
9395 local next = _.psub(_.txt, _.index + 1, _.index + n)
9396 _.index = _.index + _.plen(next)
9397 return next
9398 end,
9399 close = function(_)
9400 if not _.txt then
9401 return nil, "bad file descriptor"
9402 end
9403 _.txt = nil
9404 return true
9405 end,
9406 }, {__index=text.internal.stream_base(mode:match("b"))})
9407
9408 return require("buffer").new("r", reader)
9409end
9410
9411function text.internal.writer(ostream, mode, append_txt)
9412 if type(ostream) == "table" then
9413 local mt = getmetatable(ostream) or {}
9414 checkArg(1, mt.__call, "function")
9415 end
9416 checkArg(1, ostream, "function", "table")
9417 checkArg(2, append_txt, "string", "nil")
9418 local writer = setmetatable(
9419 {
9420 txt = "",
9421 index = 0, -- last location of write
9422 len = 0,
9423 write = function(_, ...)
9424 if not _.txt then
9425 return nil, "bad file descriptor"
9426 end
9427 local pre = _.psub(_.txt, 1, _.index)
9428 local vs = {}
9429 local pos = _.psub(_.txt, _.index + 1)
9430 for i,v in ipairs({...}) do
9431 table.insert(vs, v)
9432 end
9433 vs = table.concat(vs)
9434 _.index = _.index + _.plen(vs)
9435 _.txt = pre .. vs .. pos
9436 _.len = string.len(_.txt)
9437 return true
9438 end,
9439 close = function(_)
9440 if not _.txt then
9441 return nil, "bad file descriptor"
9442 end
9443 ostream((append_txt or "") .. _.txt)
9444 _.txt = nil
9445 return true
9446 end,
9447 }, {__index=text.internal.stream_base(mode:match("b"))})
9448
9449 return require("buffer").new("w", writer)
9450end
9451
9452function text.detab(value, tabWidth)
9453 checkArg(1, value, "string")
9454 checkArg(2, tabWidth, "number", "nil")
9455 tabWidth = tabWidth or 8
9456 local function rep(match)
9457 local spaces = tabWidth - match:len() % tabWidth
9458 return match .. string.rep(" ", spaces)
9459 end
9460 local result = value:gsub("([^\n]-)\t", rep) -- truncate results
9461 return result
9462end
9463
9464function text.padLeft(value, length)
9465 checkArg(1, value, "string", "nil")
9466 checkArg(2, length, "number")
9467 if not value or unicode.wlen(value) == 0 then
9468 return string.rep(" ", length)
9469 else
9470 return string.rep(" ", length - unicode.wlen(value)) .. value
9471 end
9472end
9473
9474function text.padRight(value, length)
9475 checkArg(1, value, "string", "nil")
9476 checkArg(2, length, "number")
9477 if not value or unicode.wlen(value) == 0 then
9478 return string.rep(" ", length)
9479 else
9480 return value .. string.rep(" ", length - unicode.wlen(value))
9481 end
9482end
9483
9484function text.wrap(value, width, maxWidth)
9485 checkArg(1, value, "string")
9486 checkArg(2, width, "number")
9487 checkArg(3, maxWidth, "number")
9488 local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
9489 if unicode.wlen(line) > width then -- do we even need to wrap?
9490 local partial = unicode.wtrunc(line, width)
9491 local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
9492 if wrapped or unicode.wlen(line) > maxWidth then
9493 partial = wrapped or partial
9494 return partial, unicode.sub(value, unicode.len(partial) + 1), true
9495 else
9496 return "", value, true -- write in new line.
9497 end
9498 end
9499 local start = unicode.len(line) + unicode.len(nl) + 1
9500 return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
9501end
9502
9503function text.wrappedLines(value, width, maxWidth)
9504 local line
9505 return function()
9506 if value then
9507 line, value = text.wrap(value, width, maxWidth)
9508 return line
9509 end
9510 end
9511end
9512
9513 mnt/6c1/lib/core/boot.lua 0000600 00000010263 13215223740 007651 0 -- called from /init.lua
9514local raw_loadfile = ...
9515
9516_G._OSVERSION = "AxeOS 1.0(forked from 1.7.1)"
9517
9518local component = component
9519local computer = computer
9520local unicode = unicode
9521
9522-- Runlevel information.
9523local runlevel, shutdown = "S", computer.shutdown
9524computer.runlevel = function() return runlevel end
9525computer.shutdown = function(reboot)
9526 runlevel = reboot and 6 or 0
9527 if os.sleep then
9528 computer.pushSignal("shutdown")
9529 os.sleep(0.1) -- Allow shutdown processing.
9530 end
9531 shutdown(reboot)
9532end
9533
9534local screen = component.list('screen', true)()
9535for address in component.list('screen', true) do
9536 if #component.invoke(address, 'getKeyboards') > 0 then
9537 screen = address
9538 break
9539 end
9540end
9541
9542_G.boot_screen = screen
9543
9544-- Report boot progress if possible.
9545local gpu = component.list("gpu", true)()
9546local w, h
9547if gpu and screen then
9548 gpu = component.proxy(gpu)
9549 gpu.bind(screen)
9550 w, h = gpu.maxResolution()
9551 gpu.setResolution(w, h)
9552 gpu.setBackground(0x000000)
9553 gpu.setForeground(0xFFFFFF)
9554 gpu.fill(1, 1, w, h, " ")
9555end
9556local y = 1
9557local uptime = computer.uptime
9558-- we actually want to ref the original pullSignal here because /lib/event intercepts it later
9559-- because of that, we must re-pushSignal when we use this, else things break badly
9560local pull = computer.pullSignal
9561local last_sleep = uptime()
9562local function status(msg)
9563 if gpu and screen then
9564 gpu.set(1, y, msg)
9565 if y == h then
9566 gpu.copy(1, 2, w, h - 1, 0, -1)
9567 gpu.fill(1, h, w, 1, " ")
9568 else
9569 y = y + 1
9570 end
9571 end
9572 -- boot can be slow in some environments, protect from timeouts
9573 if uptime() - last_sleep > 1 then
9574 local signal = table.pack(pull(0))
9575 -- there might not be any signal
9576 if signal.n > 0 then
9577 -- push the signal back in queue for the system to use it
9578 computer.pushSignal(table.unpack(signal, 1, signal.n))
9579 end
9580 last_sleep = uptime()
9581 end
9582end
9583
9584status("Booting " .. _OSVERSION .. "...")
9585
9586-- Custom low-level dofile implementation reading from our ROM.
9587local loadfile = function(file)
9588 status("> " .. file)
9589 return raw_loadfile(file)
9590end
9591
9592local function dofile(file)
9593 local program, reason = loadfile(file)
9594 if program then
9595 local result = table.pack(pcall(program))
9596 if result[1] then
9597 return table.unpack(result, 2, result.n)
9598 else
9599 error(result[2])
9600 end
9601 else
9602 error(reason)
9603 end
9604end
9605
9606status("Initializing package management...")
9607
9608-- Load file system related libraries we need to load other stuff moree
9609-- comfortably. This is basically wrapper stuff for the file streams
9610-- provided by the filesystem components.
9611local package = dofile("/lib/package.lua")
9612
9613do
9614 -- Unclutter global namespace now that we have the package module and a filesystem
9615 _G.component = nil
9616 _G.computer = nil
9617 _G.process = nil
9618 _G.unicode = nil
9619 -- Inject the package modules into the global namespace, as in Lua.
9620 _G.package = package
9621
9622 -- Initialize the package module with some of our own APIs.
9623 package.loaded.component = component
9624 package.loaded.computer = computer
9625 package.loaded.unicode = unicode
9626 package.loaded.buffer = assert(loadfile("/lib/buffer.lua"))()
9627 package.loaded.filesystem = assert(loadfile("/lib/filesystem.lua"))()
9628
9629 -- Inject the io modules
9630 _G.io = assert(loadfile("/lib/io.lua"))()
9631end
9632
9633status("Initializing file system...")
9634
9635-- Mount the ROM and temporary file systems to allow working on the file
9636-- system module from this point on.
9637require("filesystem").mount(computer.getBootAddress(), "/")
9638
9639status("Running boot scripts...")
9640
9641-- Run library startup scripts. These mostly initialize event handlers.
9642local function rom_invoke(method, ...)
9643 return component.invoke(computer.getBootAddress(), method, ...)
9644end
9645
9646local scripts = {}
9647for _, file in ipairs(rom_invoke("list", "boot")) do
9648 local path = "boot/" .. file
9649 if not rom_invoke("isDirectory", path) then
9650 table.insert(scripts, path)
9651 end
9652end
9653table.sort(scripts)
9654for i = 1, #scripts do
9655 dofile(scripts[i])
9656end
9657
9658status("Initializing components...")
9659
9660for c, t in component.list() do
9661 computer.pushSignal("component_added", c, t)
9662end
9663
9664status("Initializing system...")
9665
9666computer.pushSignal("init") -- so libs know components are initialized.
9667require("event").pull(1, "init") -- Allow init processing.
9668_G.runlevel = 1 mnt/6c1/lib/core/full_vt.lua 0000600 00000005414 13215223727 010370 0 local vt100 = require("vt100")
9669
9670local rules = vt100.rules
9671
9672-- [?7[hl] wrap mode
9673rules[{"%[", "%?", "7", "[hl]"}] = function(window, _, _, _, nowrap)
9674 window.nowrap = nowrap == "l"
9675end
9676
9677-- helper scroll function
9678local function set_cursor(window, x, y)
9679 window.x = math.min(math.max(x, 1), window.width)
9680 window.y = math.min(math.max(y, 1), window.height)
9681end
9682
9683-- -- These DO NOT SCROLL
9684-- [(%d+)A move cursor up n lines
9685-- [(%d+)B move cursor down n lines
9686-- [(%d+)C move cursor right n lines
9687-- [(%d+)D move cursor left n lines
9688rules[{"%[", "%d+", "[ABCD]"}] = function(window, _, n, dir)
9689 local dx, dy = 0, 0
9690 n = tonumber(n)
9691 if dir == "A" then
9692 dy = -n
9693 elseif dir == "B" then
9694 dy = n
9695 elseif dir == "C" then
9696 dx = n
9697 else -- D
9698 dx = -n
9699 end
9700 set_cursor(window, window.x + dx, window.y + dy)
9701end
9702
9703-- [Line;ColumnH Move cursor to screen location v,h
9704-- [Line;Columnf ^ same
9705rules[{"%[", "%d+", ";", "%d+", "[Hf]"}] = function(window, _, y, _, x)
9706 set_cursor(window, tonumber(x), tonumber(y))
9707end
9708
9709-- [K clear line from cursor right
9710-- [0K ^ same
9711-- [1K clear line from cursor left
9712-- [2K clear entire line
9713local function clear_line(window, _, n)
9714 n = tonumber(n) or 0
9715 local x = n == 0 and window.x or 1
9716 local rep = n == 1 and window.x or window.width
9717 window.gpu.set(x, window.y, (" "):rep(rep))
9718end
9719rules[{"%[", "[012]?", "K"}] = clear_line
9720
9721-- [J clear screen from cursor down
9722-- [0J ^ same
9723-- [1J clear screen from cursor up
9724-- [2J clear entire screen
9725rules[{"%[", "[012]?", "J"}] = function(window, _, n)
9726 clear_line(window, _, n)
9727 n = tonumber(n) or 0
9728 local y = n == 0 and (window.y + 1) or 1
9729 local rep = n == 1 and (window.y - 1) or window.height
9730 window.gpu.fill(1, y, window.width, rep, " ")
9731end
9732
9733-- [H move cursor to upper left corner
9734-- [;H ^ same
9735-- [f ^ same
9736-- [;f ^ same
9737rules[{"%[;?", "[Hf]"}] = function(window)
9738 set_cursor(window, 1, 1)
9739end
9740
9741-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
9742rules[{"%[", "6", "n"}] = function(window)
9743 -- this solution puts the response on stdin, but it isn't echo'd
9744 -- I'm personally fine with the lack of echo
9745 io.stdin.bufferRead = string.format("%s%s%d;%dR", io.stdin.bufferRead, string.char(0x1b), window.y, window.x)
9746end
9747
9748-- D scroll up one line -- moves cursor down
9749-- E move to next line (acts the same ^, but x=1)
9750-- M scroll down one line -- moves cursor up
9751rules[{"[DEM]"}] = function(window, dir)
9752 if dir == "D" then
9753 window.y = window.y + 1
9754 elseif dir == "E" then
9755 window.y = window.y + 1
9756 window.x = 1
9757 else -- M
9758 window.y = window.y - 1
9759 end
9760end
9761 mnt/6c1/lib/core/full_ls.lua 0000600 00000026270 13215223737 010361 0 local fs = require("filesystem")
9762local shell = require("shell")
9763local tty = require("tty")
9764local unicode = require("unicode")
9765local tx = require("transforms")
9766local text = require("text")
9767
9768local dirsArg, ops = shell.parse(...)
9769
9770if ops.help then
9771 print([[Usage: ls [OPTION]... [FILE]...
9772 -a, --all do not ignore entries starting with .
9773 --full-time with -l, print time in full iso format
9774 -h, --human-readable with -l and/or -s, print human readable sizes
9775 --si likewise, but use powers of 1000 not 1024
9776 -l use a long listing format
9777 -r, --reverse reverse order while sorting
9778 -R, --recursive list subdirectories recursively
9779 -S sort by file size
9780 -t sort by modification time, newest first
9781 -X sort alphabetically by entry extension
9782 -1 list one file per line
9783 -p append / indicator to directories
9784 -M display Microsoft-style file and directory
9785 count after listing
9786 --no-color Do not colorize the output (default colorized)
9787 --help display this help and exit
9788For more info run: man ls]])
9789 return 0
9790end
9791
9792if #dirsArg == 0 then
9793 table.insert(dirsArg, ".")
9794end
9795
9796local ec = 0
9797local fOut = tty.isAvailable() and io.output().tty
9798local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
9799local function stat(names, index)
9800 local name = names[index]
9801 if type(name) == "table" then
9802 return name
9803 end
9804 local info = {}
9805 info.key = name
9806 info.path = name:sub(1, 1) == "/" and "" or names.path
9807 info.full_path = fs.concat(info.path, name)
9808 info.isDir = fs.isDirectory(info.full_path)
9809 info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "")
9810 info.sort_name = info.name:gsub("^%.","")
9811 info.isLink, info.link = fs.isLink(info.full_path)
9812 info.size = info.isLink and 0 or fs.size(info.full_path)
9813 info.time = fs.lastModified(info.full_path)
9814 info.fs = fs.get(info.full_path)
9815 info.ext = info.name:match("(%.[^.]+)$") or ""
9816 names[index] = info
9817 return info
9818end
9819local function toArray(i) local r={} for n in i do r[#r+1]=n end return r end
9820local set_color = function() end
9821local function colorize() return end
9822if fOut and not ops["no-color"] then
9823 local LSC = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
9824 local parts = text.split(e, {"="}, true)
9825 return parts[2], parts[1]
9826 end)
9827 colorize = function(info)
9828 return
9829 info.isLink and LSC.ln or
9830 info.isDir and LSC.di or
9831 LSC['*'..info.ext] or
9832 LSC.fi
9833 end
9834 set_color=function(c)
9835 io.write(string.char(0x1b), "[", c or "", "m")
9836 end
9837end
9838local msft={reports=0,proxies={}}
9839function msft.report(files, dirs, used, proxy)
9840 local free = proxy.spaceTotal() - proxy.spaceUsed()
9841 set_color()
9842 local pattern = "%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n"
9843 io.write(string.format(pattern, files, tostring(used), dirs, tostring(free)))
9844end
9845function msft.tail(names)
9846 local fsproxy = fs.get(names.path)
9847 if not fsproxy then
9848 return
9849 end
9850 local totalSize, totalFiles, totalDirs = 0, 0, 0
9851 for i=1,#names do
9852 local info = stat(names, i)
9853 if info.isDir then
9854 totalDirs = totalDirs + 1
9855 else
9856 totalFiles = totalFiles + 1
9857 end
9858 totalSize = totalSize + info.size
9859 end
9860 msft.report(totalFiles, totalDirs, totalSize, fsproxy)
9861 local ps = msft.proxies
9862 ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0}
9863 local p = ps[fsproxy]
9864 p.files = p.files + totalFiles
9865 p.dirs = p.dirs + totalDirs
9866 p.used = p.used + totalSize
9867 msft.reports = msft.reports + 1
9868end
9869function msft.final()
9870 if msft.reports < 2 then return end
9871 local groups = {}
9872 for proxy,report in pairs(msft.proxies) do
9873 table.insert(groups, {proxy=proxy,report=report})
9874 end
9875 set_color()
9876 print("Total Files Listed:")
9877 for _,pair in ipairs(groups) do
9878 local proxy, report = pair.proxy, pair.report
9879 if #groups>1 then
9880 print("As pertaining to: "..proxy.address)
9881 end
9882 msft.report(report.files, report.dirs, report.used, proxy)
9883 end
9884end
9885
9886if not ops.M then
9887 msft.tail=function()end
9888 msft.final=function()end
9889end
9890
9891local function nod(n)
9892 return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
9893end
9894
9895local function formatSize(size)
9896 if not ops.h and not ops['human-readable'] and not ops.si then
9897 return tostring(size)
9898 end
9899 local sizes = {"", "K", "M", "G"}
9900 local unit = 1
9901 local power = ops.si and 1000 or 1024
9902 while size > power and unit < #sizes do
9903 unit = unit + 1
9904 size = size / power
9905 end
9906 return nod(math.floor(size*10)/10)..sizes[unit]
9907end
9908
9909local function pad(txt)
9910 txt = tostring(txt)
9911 return #txt >= 2 and txt or "0"..txt
9912end
9913
9914local function formatDate(epochms)
9915 --local day_names={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
9916 local month_names={"January","February","March","April","May","June","July","August","September","October","November","December"}
9917 if epochms == 0 then return "" end
9918 local d = os.date("*t", epochms)
9919 local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
9920 if ops["full-time"] then
9921 return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
9922 else
9923 return string.format("%s %+2s %+2s:%+2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
9924 end
9925end
9926
9927local function filter(names)
9928 if ops.a then
9929 return names
9930 end
9931 local set = {}
9932 for key, value in pairs(names) do
9933 if type(key) == "number" then
9934 local info = stat(names, key)
9935 if fs.name(info.name):sub(1, 1) ~= "." then
9936 table.insert(set, names[key])
9937 end
9938 else
9939 set[key] = value
9940 end
9941 end
9942 return set
9943end
9944
9945local function sort(names)
9946 local once = false
9947 local function ni(v)
9948 local vname = type(v) == "string" and v or v.key
9949 for i=1,#names do
9950 local info = stat(names, i)
9951 if info.key == vname then
9952 return i
9953 end
9954 end
9955 end
9956 local function sorter(key)
9957 once = true
9958 table.sort(names, function(a, b)
9959 local ast = stat(names, ni(a))
9960 local bst = stat(names, ni(b))
9961 return ast[key] > bst[key]
9962 end)
9963 end
9964 if ops.t then sorter("time") end
9965 if ops.X then sorter("ext") end
9966 if ops.S then sorter("size") end
9967 local rev = ops.r or ops.reverse
9968 if not once then sorter("sort_name") rev=not rev end
9969 if rev then
9970 for i=1,#names/2 do
9971 names[i], names[#names - i + 1] = names[#names - i + 1], names[i]
9972 end
9973 end
9974 return names
9975end
9976
9977local function dig(names, dirs, dir)
9978 if ops.R then
9979 local di = 1
9980 for i=1,#names do
9981 local info = stat(names, i)
9982 if info.isDir then
9983 local path = dir..(dir:sub(-1) == "/" and "" or "/")
9984 table.insert(dirs, di, path..info.name)
9985 di = di + 1
9986 end
9987 end
9988 end
9989 return names
9990end
9991
9992local first_display = true
9993local function display(names)
9994 local mt={}
9995 local lines = setmetatable({}, mt)
9996 if ops.l then
9997 lines.n = #names
9998 local max_size_width = 1
9999 local max_date_width = 0
10000 for i=1,lines.n do
10001 local info = stat(names, i)
10002 max_size_width = math.max(max_size_width, formatSize(info.size):len())
10003 max_date_width = math.max(max_date_width, formatDate(info.time):len())
10004 end
10005 mt.__index = function(_, index)
10006 local info = stat(names, index)
10007 local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f'
10008 local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or ""
10009 local write_mode = info.fs.isReadOnly() and '-' or 'w'
10010 local size = formatSize(info.size)
10011 local modDate = formatDate(info.time)
10012 local format = "%s-r%s %+"..tostring(max_size_width).."s %"..tostring(max_date_width).."s"
10013 local meta = string.format(format, file_type, write_mode, size, modDate)
10014 local item = info.name..link_target
10015 return {{name = meta}, {color = colorize(info), name = item}}
10016 end
10017 elseif ops["1"] or not fOut then
10018 lines.n = #names
10019 mt.__index = function(_, index)
10020 local info = stat(names, index)
10021 return {{color = colorize(info), name = info.name}}
10022 end
10023 else -- columns
10024 local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1
10025 local function real(x, y)
10026 local index = y + ((x-1) * items_per_column)
10027 return index <= #names and index or nil
10028 end
10029 local function max_name(column_index)
10030 local max = 0 -- return the width of the max element in column_index
10031 for r=1,items_per_column do
10032 local ri = real(column_index, r)
10033 if not ri then break end
10034 local info = stat(names, ri)
10035 max = math.max(max, unicode.wlen(info.name))
10036 end
10037 return max
10038 end
10039 local function measure()
10040 local total = 0
10041 for column_index=1,num_columns do
10042 total = total + max_name(column_index) + (column_index > 1 and 2 or 0)
10043 end
10044 return total
10045 end
10046 while items_per_column<#names do
10047 items_per_column = items_per_column + 1
10048 num_columns = math.ceil(#names/items_per_column)
10049 if measure() < width then
10050 break
10051 end
10052 end
10053 lines.n = items_per_column
10054 mt.__index=function(_, line_index)
10055 return setmetatable({},{
10056 __len=function()return num_columns end,
10057 __index=function(_, column_index)
10058 local ri = real(column_index, line_index)
10059 if not ri then return end
10060 local info = stat(names, ri)
10061 local name = info.name
10062 return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))}
10063 end,
10064 })
10065 end
10066 end
10067 for line_index=1,lines.n do
10068 local line = lines[line_index]
10069 for element_index=1,#line do
10070 local e = line[element_index]
10071 if not e then break end
10072 first_display = false
10073 set_color(e.color)
10074 io.write(e.name)
10075 end
10076 print()
10077 end
10078 msft.tail(names)
10079end
10080local header = function() end
10081if #dirsArg > 1 or ops.R then
10082 header = function(path)
10083 if not first_display then print() end
10084 set_color()
10085 io.write(path,":\n")
10086 end
10087end
10088local function displayDirList(dirs)
10089 while #dirs > 0 do
10090 local dir = table.remove(dirs, 1)
10091 header(dir)
10092 local path = shell.resolve(dir)
10093 local list, reason = fs.list(path)
10094 if not list then
10095 perr(reason)
10096 else
10097 local names = toArray(list)
10098 names.path = path
10099 display(dig(sort(filter(names)), dirs, dir))
10100 end
10101 end
10102end
10103local dir_set, file_set = {}, {path=shell.getWorkingDirectory()}
10104for _,dir in ipairs(dirsArg) do
10105 local path = shell.resolve(dir)
10106 local real, why = fs.realPath(path)
10107 local access_msg = "cannot access " .. tostring(path) .. ": "
10108 if not real then
10109 perr(access_msg .. why)
10110 elseif not fs.exists(path) then
10111 perr(access_msg .. "No such file or directory")
10112 elseif fs.isDirectory(path) then
10113 table.insert(dir_set, dir)
10114 else -- file or link
10115 table.insert(file_set, dir)
10116 end
10117end
10118
10119io.output():setvbuf("line")
10120
10121local ok, msg = pcall(function()
10122 if #file_set > 0 then display(sort(file_set)) end
10123 displayDirList(dir_set)
10124 msft.final()
10125end)
10126
10127io.output():flush()
10128io.output():setvbuf("no")
10129set_color()
10130
10131assert(ok, msg)
10132
10133return ec
10134
10135 mnt/6c1/lib/core/full_transforms.lua 0000600 00000005260 13215223726 012133 0 local lib = require("transforms")
10136
10137local adjust=lib.internal.range_adjust
10138local view=lib.internal.table_view
10139
10140-- works like string.sub but on elements of an indexed table
10141function lib.sub(tbl,f,l)
10142 checkArg(1,tbl,'table')
10143 local r,s={},#tbl
10144 f,l=adjust(f,l,s)
10145 l=math.min(l,s)
10146 for i=math.max(f,1),l do
10147 r[#r+1]=tbl[i]
10148 end
10149 return r
10150end
10151
10152-- Returns a list of subsets of tbl where partitioner acts as a delimiter.
10153function lib.partition(tbl,partitioner,dropEnds,f,l)
10154 checkArg(1,tbl,'table')
10155 checkArg(2,partitioner,'function','table')
10156 checkArg(3,dropEnds,'boolean','nil')
10157 if type(partitioner)=='table'then
10158 return lib.partition(tbl,function(e,i,tbl)
10159 return lib.first(tbl,partitioner,i)
10160 end,dropEnds,f,l)
10161 end
10162 local s=#tbl
10163 f,l=adjust(f,l,s)
10164 local cut=view(tbl,f,l)
10165 local result={}
10166 local need=true
10167 local exp=function()if need then result[#result+1]={}need=false end end
10168 local i=f
10169 while i<=l do
10170 local e=cut[i]
10171 local ds,de=partitioner(e,i,cut)
10172 -- true==partition here
10173 if ds==true then ds,de=i,i
10174 elseif ds==false then ds,de=nil,nil end
10175 if ds~=nil then
10176 ds,de=adjust(ds,de,l)
10177 ds=ds>=i and ds--no more
10178 end
10179 if not ds then -- false or nil
10180 exp()
10181 table.insert(result[#result],e)
10182 else
10183 local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
10184 if #sub>0 then
10185 exp()
10186 result[#result+math.min(#result[#result],1)]=sub
10187 end
10188 -- ensure i moves forward
10189 local ensured=math.max(math.max(de or ds,ds),i)
10190 if de and ds and de<ds and ensured==i then
10191 if #result==0 then result[1]={} end
10192 table.insert(result[#result],e)
10193 end
10194 i=ensured
10195 need=true
10196 end
10197 i=i+1
10198 end
10199
10200 return result
10201end
10202
10203-- calls callback(e,i,tbl) for each ith element e in table tbl from first
10204function lib.foreach(tbl,c,f,l)
10205 checkArg(1,tbl,'table')
10206 checkArg(2,c,'function','string')
10207 local ck=c
10208 c=type(c)=="string" and function(e) return e[ck] end or c
10209 local s=#tbl
10210 f,l=adjust(f,l,s)
10211 tbl=view(tbl,f,l)
10212 local r={}
10213 for i=f,l do
10214 local n,k=c(tbl[i],i,tbl)
10215 if n~=nil then
10216 if k then r[k]=n
10217 else r[#r+1]=n end
10218 end
10219 end
10220 return r
10221end
10222
10223function lib.where(tbl,p,f,l)
10224 return lib.foreach(tbl,
10225 function(e,i,tbl)
10226 return p(e,i,tbl)and e or nil
10227 end,f,l)
10228end
10229
10230-- works with pairs on tables
10231-- returns the kv pair, or nil and the number of pairs iterated
10232function lib.at(tbl, index)
10233 checkArg(1, tbl, "table")
10234 checkArg(2, index, "number", "nil")
10235 local current_index = 1
10236 for k,v in pairs(tbl) do
10237 if current_index == index then
10238 return k,v
10239 end
10240 current_index = current_index + 1
10241 end
10242 return nil, current_index - 1 -- went one too far
10243end
10244 mnt/6c1/lib/core/full_sh.lua 0000600 00000037770 13215223734 010361 0 local fs = require("filesystem")
10245local process = require("process")
10246local shell = require("shell")
10247local text = require("text")
10248local tx = require("transforms")
10249local unicode = require("unicode")
10250
10251local sh = require("sh")
10252
10253local isWordOf = sh.internal.isWordOf
10254
10255-------------------------------------------------------------------------------
10256
10257function sh.internal.command_passed(ec)
10258 return sh.internal.command_result_as_code(ec) == 0
10259end
10260
10261-- takes ewords and searches for redirections (may not have any)
10262-- removes the redirects and their arguments from the ewords
10263-- returns a redirection table that is used during process load
10264-- returns false if no redirections are defined
10265-- to open the redirect handles, see openCommandRedirects
10266function sh.internal.buildCommandRedirects(ewords)
10267 local redirects = {}
10268 local index = 1 -- we move index manually to allow removals from ewords
10269 local from_io, to_io, mode
10270 local syn_err_msg = "syntax error near unexpected token "
10271
10272 -- hasValidPiping has been modified, it does not verify redirects now
10273 -- we could have bad redirects such as "echo hi > > foo"
10274 -- we must validate the input here
10275
10276 while true do
10277 local eword = ewords[index]
10278 if not eword then break end
10279
10280 -- redirections are
10281 -- 1. single part
10282 -- 2. not quoted
10283 local part = eword[1]
10284 local token = not eword[2] and not part.qr and eword.txt or ""
10285 local _, _, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)([<>]>?)%&?(.*)")
10286 if mode_txt then
10287 if mode then
10288 return nil, syn_err_msg .. token
10289 end
10290 mode = assert(({["<"]="r",[">"]="w",[">>"]="a",})[mode_txt], "redirect failed to detect mode")
10291 from_io = from_io_txt ~= "" and tonumber(from_io_txt) or mode == "r" and 0 or 1
10292 to_io = to_io_txt ~= "" and tonumber(to_io_txt)
10293 elseif mode then
10294 token = sh.internal.glob(eword)
10295 if #token > 1 then
10296 return nil, string.format("%s: ambiguous redirect", eword.txt)
10297 end
10298 to_io = token[1]
10299 else
10300 index = index + 1
10301 end
10302
10303 if mode then
10304 table.remove(ewords, index)
10305 end
10306
10307 if to_io then
10308 table.insert(redirects, {from_io, to_io, mode})
10309 mode = nil
10310 to_io = nil
10311 end
10312 end
10313
10314 if mode then
10315 return nil, syn_err_msg .. "newline"
10316 end
10317
10318 return redirects
10319end
10320
10321-- redirects as built by buildCommentRedirects
10322function sh.internal.openCommandRedirects(redirects)
10323 local data = process.info().data
10324 local ios, handles = data.io, data.handles
10325
10326 for _,rjob in ipairs(redirects) do
10327 local from_io, to_io, mode = table.unpack(rjob)
10328
10329 if type(to_io) == "number" then -- io to io
10330 -- from_io and to_io should be numbers
10331 ios[from_io] = ios[to_io]
10332 else
10333 -- to_io should be a string
10334 local file, reason = io.open(shell.resolve(to_io), mode)
10335 if not file then
10336 io.stderr:write("could not open '" .. to_io .. "': " .. reason .. "\n")
10337 os.exit(1)
10338 end
10339 table.insert(handles, file)
10340 ios[from_io] = file
10341 end
10342 end
10343end
10344
10345-- takes an eword, returns a list of glob hits or {word} if no globs exist
10346function sh.internal.glob(eword)
10347 -- words are parts, parts are txt and qr
10348 -- eword.txt is a convenience field of the parts
10349 -- turn word into regex based on globits
10350 local globbers = {{"*",".*"},{"?","."}}
10351 local glob_pattern = ""
10352 local has_globits
10353 for _,part in ipairs(eword) do
10354 local next = part.txt
10355 -- globs only exist outside quotes
10356 if not part.qr then
10357 local escaped = text.escapeMagic(next)
10358 next = escaped
10359
10360 for _,glob_rule in ipairs(globbers) do
10361 --remove duplicates
10362 while true do
10363 local prev = next
10364 next = next:gsub(text.escapeMagic(glob_rule[1]):rep(2), glob_rule[1])
10365 if prev == next then
10366 break
10367 end
10368 end
10369 --revert globit
10370 next = next:gsub("%%%"..glob_rule[1], glob_rule[2])
10371 end
10372
10373 -- if next is still equal to escaped that means no globits were detected in this word part
10374 -- this word may not contain a globit, the prior search did a cheap search for globits
10375 has_globits = has_globits or next ~= escaped
10376 end
10377 glob_pattern = glob_pattern .. next
10378 end
10379
10380 if not has_globits then
10381 return {eword.txt}
10382 end
10383
10384 local segments = text.split(glob_pattern, {"/"}, true)
10385 local hiddens = tx.foreach(segments,function(e)return e:match("^%%%.")==nil end)
10386 local function is_visible(s,i)
10387 return not hiddens[i] or s:match("^%.") == nil
10388 end
10389
10390 local function magical(s)
10391 for _,glob_rule in ipairs(globbers) do
10392 if (" "..s):match("[^%%]"..text.escapeMagic(glob_rule[2])) then
10393 return true
10394 end
10395 end
10396 end
10397
10398 local is_abs = glob_pattern:sub(1, 1) == "/"
10399 local root = is_abs and '' or shell.getWorkingDirectory():gsub("([^/])$","%1/")
10400 local paths = {is_abs and "/" or ''}
10401 local relative_separator = ''
10402 for i,segment in ipairs(segments) do
10403 local enclosed_pattern = string.format("^(%s)/?$", segment)
10404 local next_paths = {}
10405 for _,path in ipairs(paths) do
10406 if fs.isDirectory(root..path) then
10407 if magical(segment) then
10408 for file in fs.list(root..path) do
10409 if file:match(enclosed_pattern) and is_visible(file, i) then
10410 table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
10411 end
10412 end
10413 else -- not a globbing segment, just use it raw
10414 local plain = text.removeEscapes(segment)
10415 local fpath = root..path..relative_separator..plain
10416 local hit = path..relative_separator..plain:gsub("/+$",'')
10417 if fs.exists(fpath) then
10418 table.insert(next_paths, hit)
10419 end
10420 end
10421 end
10422 end
10423 paths = next_paths
10424 if not next(paths) then
10425 -- if no next_paths were hit here, the ENTIRE glob value is not a path
10426 return {eword.txt}
10427 end
10428 relative_separator = "/"
10429 end
10430 return paths
10431end
10432
10433function sh.getMatchingPrograms(baseName)
10434 local result = {}
10435 local result_keys = {} -- cache for fast value lookup
10436 -- TODO only matching files with .lua extension for now, might want to
10437 -- extend this to other extensions at some point? env var? file attrs?
10438 if not baseName or #baseName == 0 then
10439 baseName = "^(.*)%.lua$"
10440 else
10441 baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$"
10442 end
10443 for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
10444 for file in fs.list(shell.resolve(basePath)) do
10445 local match = file:match(baseName)
10446 if match and not result_keys[match] then
10447 table.insert(result, match)
10448 result_keys[match] = true
10449 end
10450 end
10451 end
10452 return result
10453end
10454
10455function sh.getMatchingFiles(partial_path)
10456 -- name: text of the partial file name being expanded
10457 local name = partial_path:gsub("^.*/", "")
10458 -- here we remove the name text from the partialPrefix
10459 local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1)
10460
10461 local resolvedPath = shell.resolve(basePath)
10462 local result, baseName = {}
10463
10464 -- note: we strip the trailing / to make it easier to navigate through
10465 -- directories using tab completion (since entering the / will then serve
10466 -- as the intention to go into the currently hinted one).
10467 -- if we have a directory but no trailing slash there may be alternatives
10468 -- on the same level, so don't look inside that directory... (cont.)
10469 if fs.isDirectory(resolvedPath) and name == "" then
10470 baseName = "^(.-)/?$"
10471 else
10472 baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
10473 end
10474
10475 for file in fs.list(resolvedPath) do
10476 local match = file:match(baseName)
10477 if match then
10478 table.insert(result, basePath .. match:gsub("(%s)", "\\%1"))
10479 end
10480 end
10481 -- (cont.) but if there's only one match and it's a directory, *then* we
10482 -- do want to add the trailing slash here.
10483 if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
10484 result[1] = result[1] .. "/"
10485 end
10486 return result
10487end
10488
10489function sh.internal.hintHandlerSplit(line)
10490 -- I do not plan on having text tokenizer parse error on
10491 -- trailiing \ in case of future support for multiple line
10492 -- input. But, there are also no hints for it
10493 if line:match("\\$") then return nil end
10494
10495 local splits, simple = text.internal.tokenize(line,{show_escapes=true})
10496 if not splits then -- parse error, e.g. unclosed quotes
10497 return nil -- no split, no hints
10498 end
10499
10500 local num_splits = #splits
10501
10502 -- search for last statement delimiters
10503 local last_close = 0
10504 for index = num_splits, 1, -1 do
10505 local word = splits[index]
10506 if isWordOf(word, {";","&&","||","|"}) then
10507 last_close = index
10508 break
10509 end
10510 end
10511
10512 -- if the very last word of the line is a delimiter
10513 -- consider this a fresh new, empty line
10514 -- this captures edge cases with empty input as well (i.e. no splits)
10515 if last_close == num_splits then
10516 return nil -- no hints on empty command
10517 end
10518
10519 local last_word = splits[num_splits]
10520 local normal = text.internal.normalize({last_word})[1]
10521
10522 -- if there is white space following the words
10523 -- and we have at least one word following the last delimiter
10524 -- then in all cases we are looking for ANY arg
10525 if unicode.sub(line, -unicode.len(normal)) ~= normal then
10526 return line, nil, ""
10527 end
10528
10529 local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1)
10530
10531 -- renormlizing the string will create 'printed' quality text
10532 normal = text.internal.normalize(text.internal.tokenize(normal), true)[1]
10533
10534 -- one word: cmd
10535 -- many: arg
10536 if last_close == num_splits - 1 then
10537 return prefix, normal, nil
10538 else
10539 return prefix, nil, normal
10540 end
10541end
10542
10543function sh.internal.hintHandlerImpl(full_line, cursor)
10544 -- line: text preceding the cursor: we want to hint this part (expand it)
10545 local line = unicode.sub(full_line, 1, cursor - 1)
10546 -- suffix: text following the cursor (if any, else empty string) to append to the hints
10547 local suffix = unicode.sub(full_line, cursor)
10548
10549 -- hintHandlerSplit helps make the hints work even after delimiters such as ;
10550 -- it also catches parse errors such as unclosed quotes
10551 -- prev: not needed for this hint
10552 -- cmd: the command needing hint
10553 -- arg: the argument needing hint
10554 local prev, cmd, arg = sh.internal.hintHandlerSplit(line)
10555
10556 -- also, if there is no text to hint, there are no hints
10557 if not prev then -- no hints e.g. unclosed quote, e.g. no text
10558 return {}
10559 end
10560 local result
10561
10562 local searchInPath = cmd and not cmd:find("/")
10563 if searchInPath then
10564 result = sh.getMatchingPrograms(cmd)
10565 else
10566 -- special arg issue, after equal sign
10567 if arg then
10568 local equal_index = arg:find("=[^=]*$")
10569 if equal_index then
10570 prev = prev .. unicode.sub(arg, 1, equal_index)
10571 arg = unicode.sub(arg, equal_index + 1)
10572 end
10573 end
10574 result = sh.getMatchingFiles(cmd or arg)
10575 end
10576
10577 -- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete
10578 local resultSuffix = suffix
10579 if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
10580 not suffix:sub(1,1):find('%s') and
10581 #result == 1 or searchInPath then
10582 resultSuffix = " " .. resultSuffix
10583 end
10584
10585 table.sort(result)
10586 for i = 1, #result do
10587 -- the hints define the whole line of text
10588 result[i] = prev .. result[i] .. resultSuffix
10589 end
10590 return result
10591end
10592
10593-- verifies that no pipes are doubled up nor at the start nor end of words
10594function sh.internal.hasValidPiping(words, pipes)
10595 checkArg(1, words, "table")
10596 checkArg(2, pipes, "table", "nil")
10597
10598 if #words == 0 then
10599 return true
10600 end
10601
10602 local semi_split = tx.first(text.syntax, {{";"}}) -- symbols before ; are redirects and follow slightly different rules, see buildCommandRedirects
10603 pipes = pipes or tx.sub(text.syntax, semi_split + 1)
10604
10605 local state = "" -- cannot start on a pipe
10606
10607 for w=1,#words do
10608 local word = words[w]
10609 for p=1,#word do
10610 local part = word[p]
10611 if part.qr then
10612 state = nil
10613 elseif part.txt == "" then
10614 state = nil -- not sure how this is possible (empty part without quotes?)
10615 elseif #text.split(part.txt, pipes, true) == 0 then
10616 local prev = state
10617 state = part.txt
10618 if prev then -- cannot have two pipes in a row
10619 word = nil
10620 break
10621 end
10622 else
10623 state = nil
10624 end
10625 end
10626 if not word then -- bad pipe
10627 break
10628 end
10629 end
10630
10631 if state then
10632 return false, "syntax error near unexpected token " .. state
10633 else
10634 return true
10635 end
10636end
10637
10638function sh.internal.boolean_executor(chains, predicator)
10639 local function not_gate(result, reason)
10640 return sh.internal.command_passed(result) and 1 or 0, reason
10641 end
10642
10643 local last = true
10644 local last_reason
10645 local boolean_stage = 1
10646 local negation_stage = 2
10647 local command_stage = 0
10648 local stage = negation_stage
10649 local skip = false
10650
10651 for ci=1,#chains do
10652 local next = chains[ci]
10653 local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
10654
10655 if single == "||" then
10656 if stage ~= command_stage or #chains == 0 then
10657 return nil, "syntax error near unexpected token '"..single.."'"
10658 end
10659 if sh.internal.command_passed(last) then
10660 skip = true
10661 end
10662 stage = boolean_stage
10663 elseif single == "&&" then
10664 if stage ~= command_stage or #chains == 0 then
10665 return nil, "syntax error near unexpected token '"..single.."'"
10666 end
10667 if not sh.internal.command_passed(last) then
10668 skip = true
10669 end
10670 stage = boolean_stage
10671 elseif not skip then
10672 local chomped = #next
10673 local negate = sh.internal.remove_negation(next)
10674 chomped = chomped ~= #next
10675 if negate then
10676 local prev = predicator
10677 predicator = function(n,i)
10678 local result, reason = not_gate(prev(n,i))
10679 predicator = prev
10680 return result, reason
10681 end
10682 end
10683 if chomped then
10684 stage = negation_stage
10685 end
10686 if #next > 0 then
10687 last, last_reason = predicator(next,ci)
10688 stage = command_stage
10689 end
10690 else
10691 skip = false
10692 stage = command_stage
10693 end
10694 end
10695
10696 if stage == negation_stage then
10697 last = not_gate(last)
10698 end
10699
10700 return last, last_reason
10701end
10702
10703function sh.internal.splitStatements(words, semicolon)
10704 checkArg(1, words, "table")
10705 checkArg(2, semicolon, "string", "nil")
10706 semicolon = semicolon or ";"
10707
10708 return tx.partition(words, function(g, i, t)
10709 if isWordOf(g, {semicolon}) then
10710 return i, i
10711 end
10712 end, true)
10713end
10714
10715function sh.internal.splitChains(s,pc)
10716 checkArg(1, s, "table")
10717 checkArg(2, pc, "string", "nil")
10718 pc = pc or "|"
10719 return tx.partition(s, function(w)
10720 -- each word has multiple parts due to quotes
10721 if isWordOf(w, {pc}) then
10722 return true
10723 end
10724 end, true) -- drop |s
10725end
10726
10727function sh.internal.groupChains(s)
10728 checkArg(1,s,"table")
10729 return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
10730end
10731
10732function sh.internal.remove_negation(chain)
10733 if isWordOf(chain[1], {"!"}) then
10734 table.remove(chain, 1)
10735 return true and not sh.internal.remove_negation(chain)
10736 end
10737 return false
10738end
10739
10740function sh.internal.execute_complex(statements, eargs, env)
10741 for si=1,#statements do local s = statements[si]
10742 local chains = sh.internal.groupChains(s)
10743 local last_code, reason = sh.internal.boolean_executor(chains, function(chain, chain_index)
10744 local pipe_parts = sh.internal.splitChains(chain)
10745 local next_args = chain_index == #chains and si == #statements and eargs or {}
10746 return sh.internal.executePipes(pipe_parts, next_args, env)
10747 end)
10748 sh.internal.ec.last = sh.internal.command_result_as_code(last_code, reason)
10749 end
10750 return true
10751end
10752
10753function sh.internal.parse_sub(input)
10754 -- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times
10755 local packed = {}
10756 -- not using for i... because i can skip ahead
10757 local i, len = 1, #input
10758
10759 while i < len do
10760
10761 local fi, si, capture = input:find("`([^`]*)`", i)
10762
10763 if not fi then
10764 table.insert(packed, input:sub(i))
10765 break
10766 end
10767
10768 local sub = io.popen(capture)
10769 local result = input:sub(i, fi - 1) .. sub:read("*a")
10770 sub:close()
10771
10772 -- command substitution cuts trailing newlines
10773 table.insert(packed, (result:gsub("\n+$","")))
10774 i = si+1
10775 end
10776
10777 return table.concat(packed)
10778end
10779
10780
10781 mnt/6c1/lib/core/full_filesystem.lua 0000600 00000014646 13215223735 012131 0 local filesystem = require("filesystem")
10782local component = require("component")
10783
10784function filesystem.makeDirectory(path)
10785 if filesystem.exists(path) then
10786 return nil, "file or directory with that name already exists"
10787 end
10788 local node, rest = filesystem.findNode(path)
10789 if node.fs and rest then
10790 local success, reason = node.fs.makeDirectory(rest)
10791 if not success and not reason and node.fs.isReadOnly() then
10792 reason = "filesystem is readonly"
10793 end
10794 return success, reason
10795 end
10796 if node.fs then
10797 return nil, "virtual directory with that name already exists"
10798 end
10799 return nil, "cannot create a directory in a virtual directory"
10800end
10801
10802function filesystem.lastModified(path)
10803 local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
10804 if not node or not vnode.fs and not vrest then
10805 return 0 -- virtual directory
10806 end
10807 if node.fs and rest then
10808 return node.fs.lastModified(rest)
10809 end
10810 return 0 -- no such file or directory
10811end
10812
10813function filesystem.mounts()
10814 local tmp = {}
10815 for path,node in pairs(filesystem.fstab) do
10816 table.insert(tmp, {node.fs,path})
10817 end
10818 return function()
10819 local next = table.remove(tmp)
10820 if next then return table.unpack(next) end
10821 end
10822end
10823
10824function filesystem.link(target, linkpath)
10825 checkArg(1, target, "string")
10826 checkArg(2, linkpath, "string")
10827
10828 if filesystem.exists(linkpath) then
10829 return nil, "file already exists"
10830 end
10831 local linkpath_parent = filesystem.path(linkpath)
10832 if not filesystem.exists(linkpath_parent) then
10833 return nil, "no such directory"
10834 end
10835 local linkpath_real, reason = filesystem.realPath(linkpath_parent)
10836 if not linkpath_real then
10837 return nil, reason
10838 end
10839 if not filesystem.isDirectory(linkpath_real) then
10840 return nil, "not a directory"
10841 end
10842
10843 local _, _, vnode, _ = filesystem.findNode(linkpath_real, true)
10844 vnode.links[filesystem.name(linkpath)] = target
10845 return true
10846end
10847
10848function filesystem.umount(fsOrPath)
10849 checkArg(1, fsOrPath, "string", "table")
10850 local real
10851 local fs
10852 local addr
10853 if type(fsOrPath) == "string" then
10854 real = filesystem.realPath(fsOrPath)
10855 addr = fsOrPath
10856 else -- table
10857 fs = fsOrPath
10858 end
10859
10860 local paths = {}
10861 for path,node in pairs(filesystem.fstab) do
10862 if real == path or addr == node.fs.address or fs == node.fs then
10863 table.insert(paths, path)
10864 end
10865 end
10866 for _,path in ipairs(paths) do
10867 local node = filesystem.fstab[path]
10868 filesystem.fstab[path] = nil
10869 node.fs = nil
10870 node.parent.children[node.name] = nil
10871 end
10872 return #paths > 0
10873end
10874
10875function filesystem.size(path)
10876 local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
10877 if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then
10878 return 0 -- virtual directory or symlink
10879 end
10880 if node.fs and rest then
10881 return node.fs.size(rest)
10882 end
10883 return 0 -- no such file or directory
10884end
10885
10886function filesystem.isLink(path)
10887 local name = filesystem.name(path)
10888 local node, rest, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
10889 if not node then return nil, rest end
10890 local target = vnode.links[name]
10891 -- having vrest here indicates we are not at the
10892 -- owning vnode due to a mount point above this point
10893 -- but we can have a target when there is a link at
10894 -- the mount point root, with the same name
10895 if not vrest and target ~= nil then
10896 return true, target
10897 end
10898 return false
10899end
10900
10901function filesystem.copy(fromPath, toPath)
10902 local data = false
10903 local input, reason = filesystem.open(fromPath, "rb")
10904 if input then
10905 local output = filesystem.open(toPath, "wb")
10906 if output then
10907 repeat
10908 data, reason = input:read(1024)
10909 if not data then break end
10910 data, reason = output:write(data)
10911 if not data then data, reason = false, "failed to write" end
10912 until not data
10913 output:close()
10914 end
10915 input:close()
10916 end
10917 return data == nil, reason
10918end
10919
10920local function readonly_wrap(proxy)
10921 checkArg(1, proxy, "table")
10922 if proxy.isReadOnly() then
10923 return proxy
10924 end
10925
10926 local function roerr() return nil, "filesystem is readonly" end
10927 return setmetatable({
10928 rename = roerr,
10929 open = function(path, mode)
10930 checkArg(1, path, "string")
10931 checkArg(2, mode, "string")
10932 if mode:match("[wa]") then
10933 return roerr()
10934 end
10935 return proxy.open(path, mode)
10936 end,
10937 isReadOnly = function()
10938 return true
10939 end,
10940 write = roerr,
10941 setLabel = roerr,
10942 makeDirectory = roerr,
10943 remove = roerr,
10944 }, {__index=proxy})
10945end
10946
10947local function bind_proxy(path)
10948 local real, reason = filesystem.realPath(path)
10949 if not real then
10950 return nil, reason
10951 end
10952 if not filesystem.isDirectory(real) then
10953 return nil, "must bind to a directory"
10954 end
10955 local real_fs, real_fs_path = filesystem.get(real)
10956 if real == real_fs_path then
10957 return real_fs
10958 end
10959 -- turn /tmp/foo into foo
10960 local rest = real:sub(#real_fs_path + 1)
10961 local function wrap_relative(fp)
10962 return function(path, ...)
10963 return fp(filesystem.concat(rest, path), ...)
10964 end
10965 end
10966 local bind = {
10967 type = "filesystem_bind",
10968 address = real,
10969 isReadOnly = real_fs.isReadOnly,
10970 list = wrap_relative(real_fs.list),
10971 isDirectory = wrap_relative(real_fs.isDirectory),
10972 size = wrap_relative(real_fs.size),
10973 lastModified = wrap_relative(real_fs.lastModified),
10974 exists = wrap_relative(real_fs.exists),
10975 open = wrap_relative(real_fs.open),
10976 remove = wrap_relative(real_fs.remove),
10977 read = real_fs.read,
10978 write = real_fs.write,
10979 close = real_fs.close,
10980 getLabel = function() return "" end,
10981 setLabel = function() return nil, "cannot set the label of a bind point" end,
10982 }
10983 return bind
10984end
10985
10986filesystem.internal = {}
10987function filesystem.internal.proxy(filter, options)
10988 checkArg(1, filter, "string")
10989 checkArg(2, options, "table", "nil")
10990 options = options or {}
10991 local address, proxy, reason
10992 if options.bind then
10993 proxy, reason = bind_proxy(filter)
10994 else
10995 -- no options: filter should be a label or partial address
10996 for c in component.list("filesystem", true) do
10997 if component.invoke(c, "getLabel") == filter then
10998 address = c
10999 break
11000 end
11001 if c:sub(1, filter:len()) == filter then
11002 address = c
11003 break
11004 end
11005 end
11006 if not address then
11007 return nil, "no such file system"
11008 end
11009 proxy, reason = component.proxy(address)
11010 end
11011 if not proxy then
11012 return proxy, reason
11013 end
11014 if options.readonly then
11015 proxy = readonly_wrap(proxy)
11016 end
11017 return proxy
11018end
11019 mnt/6c1/lib/core/full_keyboard.lua 0000600 00000011421 13215223737 011533 0 local keyboard = require("keyboard")
11020
11021keyboard.keys["1"] = 0x02
11022keyboard.keys["2"] = 0x03
11023keyboard.keys["3"] = 0x04
11024keyboard.keys["4"] = 0x05
11025keyboard.keys["5"] = 0x06
11026keyboard.keys["6"] = 0x07
11027keyboard.keys["7"] = 0x08
11028keyboard.keys["8"] = 0x09
11029keyboard.keys["9"] = 0x0A
11030keyboard.keys["0"] = 0x0B
11031keyboard.keys.a = 0x1E
11032keyboard.keys.b = 0x30
11033keyboard.keys.c = 0x2E
11034keyboard.keys.d = 0x20
11035keyboard.keys.e = 0x12
11036keyboard.keys.f = 0x21
11037keyboard.keys.g = 0x22
11038keyboard.keys.h = 0x23
11039keyboard.keys.i = 0x17
11040keyboard.keys.j = 0x24
11041keyboard.keys.k = 0x25
11042keyboard.keys.l = 0x26
11043keyboard.keys.m = 0x32
11044keyboard.keys.n = 0x31
11045keyboard.keys.o = 0x18
11046keyboard.keys.p = 0x19
11047keyboard.keys.q = 0x10
11048keyboard.keys.r = 0x13
11049keyboard.keys.s = 0x1F
11050keyboard.keys.t = 0x14
11051keyboard.keys.u = 0x16
11052keyboard.keys.v = 0x2F
11053keyboard.keys.w = 0x11
11054keyboard.keys.x = 0x2D
11055keyboard.keys.y = 0x15
11056keyboard.keys.z = 0x2C
11057
11058keyboard.keys.apostrophe = 0x28
11059keyboard.keys.at = 0x91
11060keyboard.keys.back = 0x0E -- backspace
11061keyboard.keys.backslash = 0x2B
11062keyboard.keys.capital = 0x3A -- capslock
11063keyboard.keys.colon = 0x92
11064keyboard.keys.comma = 0x33
11065keyboard.keys.enter = 0x1C
11066keyboard.keys.equals = 0x0D
11067keyboard.keys.grave = 0x29 -- accent grave
11068keyboard.keys.lbracket = 0x1A
11069keyboard.keys.lcontrol = 0x1D
11070keyboard.keys.lmenu = 0x38 -- left Alt
11071keyboard.keys.lshift = 0x2A
11072keyboard.keys.minus = 0x0C
11073keyboard.keys.numlock = 0x45
11074keyboard.keys.pause = 0xC5
11075keyboard.keys.period = 0x34
11076keyboard.keys.rbracket = 0x1B
11077keyboard.keys.rcontrol = 0x9D
11078keyboard.keys.rmenu = 0xB8 -- right Alt
11079keyboard.keys.rshift = 0x36
11080keyboard.keys.scroll = 0x46 -- Scroll Lock
11081keyboard.keys.semicolon = 0x27
11082keyboard.keys.slash = 0x35 -- / on main keyboard
11083keyboard.keys.space = 0x39
11084keyboard.keys.stop = 0x95
11085keyboard.keys.tab = 0x0F
11086keyboard.keys.underline = 0x93
11087
11088-- Keypad (and numpad with numlock off)
11089keyboard.keys.up = 0xC8
11090keyboard.keys.down = 0xD0
11091keyboard.keys.left = 0xCB
11092keyboard.keys.right = 0xCD
11093keyboard.keys.home = 0xC7
11094keyboard.keys["end"] = 0xCF
11095keyboard.keys.pageUp = 0xC9
11096keyboard.keys.pageDown = 0xD1
11097keyboard.keys.insert = 0xD2
11098keyboard.keys.delete = 0xD3
11099
11100-- Function keys
11101keyboard.keys.f1 = 0x3B
11102keyboard.keys.f2 = 0x3C
11103keyboard.keys.f3 = 0x3D
11104keyboard.keys.f4 = 0x3E
11105keyboard.keys.f5 = 0x3F
11106keyboard.keys.f6 = 0x40
11107keyboard.keys.f7 = 0x41
11108keyboard.keys.f8 = 0x42
11109keyboard.keys.f9 = 0x43
11110keyboard.keys.f10 = 0x44
11111keyboard.keys.f11 = 0x57
11112keyboard.keys.f12 = 0x58
11113keyboard.keys.f13 = 0x64
11114keyboard.keys.f14 = 0x65
11115keyboard.keys.f15 = 0x66
11116keyboard.keys.f16 = 0x67
11117keyboard.keys.f17 = 0x68
11118keyboard.keys.f18 = 0x69
11119keyboard.keys.f19 = 0x71
11120
11121-- Japanese keyboards
11122keyboard.keys.kana = 0x70
11123keyboard.keys.kanji = 0x94
11124keyboard.keys.convert = 0x79
11125keyboard.keys.noconvert = 0x7B
11126keyboard.keys.yen = 0x7D
11127keyboard.keys.circumflex = 0x90
11128keyboard.keys.ax = 0x96
11129
11130-- Numpad
11131keyboard.keys.numpad0 = 0x52
11132keyboard.keys.numpad1 = 0x4F
11133keyboard.keys.numpad2 = 0x50
11134keyboard.keys.numpad3 = 0x51
11135keyboard.keys.numpad4 = 0x4B
11136keyboard.keys.numpad5 = 0x4C
11137keyboard.keys.numpad6 = 0x4D
11138keyboard.keys.numpad7 = 0x47
11139keyboard.keys.numpad8 = 0x48
11140keyboard.keys.numpad9 = 0x49
11141keyboard.keys.numpadmul = 0x37
11142keyboard.keys.numpaddiv = 0xB5
11143keyboard.keys.numpadsub = 0x4A
11144keyboard.keys.numpadadd = 0x4E
11145keyboard.keys.numpaddecimal = 0x53
11146keyboard.keys.numpadcomma = 0xB3
11147keyboard.keys.numpadenter = 0x9C
11148keyboard.keys.numpadequals = 0x8D
11149
11150-- Create inverse mapping for name lookup.
11151setmetatable(keyboard.keys,
11152{
11153 __index = function(tbl, k)
11154 if type(k) ~= "number" then return end
11155 for name,value in pairs(tbl) do
11156 if value == k then
11157 return name
11158 end
11159 end
11160 end
11161})
11162 mnt/6c1/lib/core/full_buffer.lua 0000600 00000012400 13215223741 011175 0 local buffer = require("buffer")
11163local unicode = require("unicode")
11164
11165function buffer:getTimeout()
11166 return self.readTimeout
11167end
11168
11169function buffer:setTimeout(value)
11170 self.readTimeout = tonumber(value)
11171end
11172
11173function buffer:seek(whence, offset)
11174 whence = tostring(whence or "cur")
11175 assert(whence == "set" or whence == "cur" or whence == "end",
11176 "bad argument #1 (set, cur or end expected, got " .. whence .. ")")
11177 offset = offset or 0
11178 checkArg(2, offset, "number")
11179 assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
11180
11181 if self.mode.w or self.mode.a then
11182 self:flush()
11183 elseif whence == "cur" then
11184 offset = offset - #self.bufferRead
11185 end
11186 local result, reason = self.stream:seek(whence, offset)
11187 if result then
11188 self.bufferRead = ""
11189 return result
11190 else
11191 return nil, reason
11192 end
11193end
11194
11195function buffer:buffered_write(arg)
11196 local result, reason
11197 if self.bufferMode == "full" then
11198 if self.bufferSize - #self.bufferWrite < #arg then
11199 result, reason = self:flush()
11200 if not result then
11201 return nil, reason
11202 end
11203 end
11204 if #arg > self.bufferSize then
11205 result, reason = self.stream:write(arg)
11206 else
11207 self.bufferWrite = self.bufferWrite .. arg
11208 result = self
11209 end
11210 else--if self.bufferMode == "line" then
11211 local l
11212 repeat
11213 local idx = arg:find("\n", (l or 0) + 1, true)
11214 if idx then
11215 l = idx
11216 end
11217 until not idx
11218 if l or #arg > self.bufferSize then
11219 result, reason = self:flush()
11220 if not result then
11221 return nil, reason
11222 end
11223 end
11224 if l then
11225 result, reason = self.stream:write(arg:sub(1, l))
11226 if not result then
11227 return nil, reason
11228 end
11229 arg = arg:sub(l + 1)
11230 end
11231 if #arg > self.bufferSize then
11232 result, reason = self.stream:write(arg)
11233 else
11234 self.bufferWrite = self.bufferWrite .. arg
11235 result = self
11236 end
11237 end
11238 return result, reason
11239end
11240
11241----------------------------------------------------------------------------------------------
11242
11243function buffer:readNumber(readChunk)
11244 local len, sub
11245 if self.mode.b then
11246 len = rawlen
11247 sub = string.sub
11248 else
11249 len = unicode.len
11250 sub = unicode.sub
11251 end
11252
11253 local number_text = ""
11254 local white_done
11255
11256 local function peek()
11257 if len(self.bufferRead) == 0 then
11258 local result, reason = readChunk(self)
11259 if not result then
11260 return result, reason
11261 end
11262 end
11263 return sub(self.bufferRead, 1, 1)
11264 end
11265
11266 local function pop()
11267 local n = sub(self.bufferRead, 1, 1)
11268 self.bufferRead = sub(self.bufferRead, 2)
11269 return n
11270 end
11271
11272 while true do
11273 local peeked = peek()
11274 if not peeked then
11275 break
11276 end
11277
11278 if peeked:match("[%s]") then
11279 if white_done then
11280 break
11281 end
11282 pop()
11283 else
11284 white_done = true
11285 if not tonumber(number_text .. peeked .. "0") then
11286 break
11287 end
11288 number_text = number_text .. pop() -- add pop to number_text
11289 end
11290 end
11291
11292 return tonumber(number_text)
11293end
11294
11295function buffer:readBytesOrChars(readChunk, n)
11296 n = math.max(n, 0)
11297 local len, sub
11298 if self.mode.b then
11299 len = rawlen
11300 sub = string.sub
11301 else
11302 len = unicode.len
11303 sub = unicode.sub
11304 end
11305 local data = ""
11306 repeat
11307 if len(self.bufferRead) == 0 then
11308 local result, reason = readChunk(self)
11309 if not result then
11310 if reason then
11311 return result, reason
11312 else -- eof
11313 return #data > 0 and data or nil
11314 end
11315 end
11316 end
11317 local left = n - len(data)
11318 data = data .. sub(self.bufferRead, 1, left)
11319 self.bufferRead = sub(self.bufferRead, left + 1)
11320 until len(data) == n
11321 return data
11322end
11323
11324function buffer:readAll(readChunk)
11325 repeat
11326 local result, reason = readChunk(self)
11327 if not result and reason then
11328 return result, reason
11329 end
11330 until not result -- eof
11331 local result = self.bufferRead
11332 self.bufferRead = ""
11333 return result
11334end
11335
11336function buffer:formatted_read(readChunk, ...)
11337 self.timeout = require("computer").uptime() + self.readTimeout
11338 local function read(n, format)
11339 if type(format) == "number" then
11340 return self:readBytesOrChars(readChunk, format)
11341 else
11342 local first_char_index = 1
11343 if type(format) ~= "string" then
11344 error("bad argument #" .. n .. " (invalid option)")
11345 elseif unicode.sub(format, 1, 1) == "*" then
11346 first_char_index = 2
11347 end
11348 format = unicode.sub(format, first_char_index, first_char_index)
11349 if format == "n" then
11350 return self:readNumber(readChunk)
11351 elseif format == "l" then
11352 return self:readLine(true, self.timeout)
11353 elseif format == "L" then
11354 return self:readLine(false, self.timeout)
11355 elseif format == "a" then
11356 return self:readAll(readChunk)
11357 else
11358 error("bad argument #" .. n .. " (invalid format)")
11359 end
11360 end
11361 end
11362
11363 local results = {}
11364 local formats = table.pack(...)
11365 for i = 1, formats.n do
11366 local result, reason = read(i, formats[i])
11367 if result then
11368 results[i] = result
11369 elseif reason then
11370 return nil, reason
11371 end
11372 end
11373 return table.unpack(results, 1, formats.n)
11374end
11375
11376function buffer:size()
11377 local len = self.mode.b and rawlen or unicode.len
11378 local size = len(self.bufferRead)
11379 if self.stream.size then
11380 size = size + self.stream:size()
11381 end
11382 return size
11383end
11384 mnt/6c1/lib/core/full_shell.lua 0000600 00000001136 13215223727 011043 0 local shell = require("shell")
11385local process = require("process")
11386
11387function shell.aliases()
11388 return pairs(process.info().data.aliases)
11389end
11390
11391function shell.execute(command, env, ...)
11392 local sh, reason = shell.getShell()
11393 if not sh then
11394 return false, reason
11395 end
11396 local proc = process.load(sh, nil, nil, command)
11397 local result = table.pack(process.internal.continue(proc, env, command, ...))
11398 if result.n == 0 then return true end
11399 return table.unpack(result, 1, result.n)
11400end
11401
11402function shell.getPath()
11403 return os.getenv("PATH")
11404end
11405
11406function shell.setPath(value)
11407 os.setenv("PATH", value)
11408end
11409 mnt/6c1/lib/core/devfs/02_utils.lua 0000600 00000001676 13215223730 011465 0 return
11410{
11411 eeprom =
11412 {
11413 link = "components/by-type/eeprom/0/contents",
11414 isAvailable = function()
11415 local comp = require("component")
11416 return comp.list("eeprom")()
11417 end
11418 },
11419 ["eeprom-data"] =
11420 {
11421 link = "components/by-type/eeprom/0/data",
11422 isAvailable = function()
11423 local comp = require("component")
11424 return comp.list("eeprom")()
11425 end
11426 },
11427 null =
11428 {
11429 open = function(mode)
11430 return
11431 {
11432 read = function() end,
11433 write = function() end
11434 }
11435 end
11436 },
11437 random =
11438 {
11439 open = function(mode)
11440 if mode and not mode:match("r") then
11441 return nil, "read only"
11442 end
11443 return
11444 {
11445 read = function(self, n)
11446 local chars = {}
11447 for i=1,n do
11448 table.insert(chars,string.char(math.random(0,255)))
11449 end
11450 return table.concat(chars)
11451 end
11452 }
11453 end,
11454 size = function()
11455 return math.huge
11456 end
11457 },
11458}
11459 mnt/6c1/lib/core/devfs/adapters/filesystem.lua 0000600 00000001176 13215223732 014010 0 local fs = require("filesystem")
11460local text = require("text")
11461
11462return function(proxy)
11463 return
11464 {
11465 ["label"] =
11466 {
11467 read = function() return proxy.getLabel() or "" end,
11468 write= function(v) proxy.setLabel(text.trim(v)) end
11469 },
11470 ["isReadOnly"] = {proxy.isReadOnly()},
11471 ["spaceUsed"] = {proxy.spaceUsed()},
11472 ["spaceTotal"] = {proxy.spaceTotal()},
11473 ["mounts"] = {read = function()
11474 local mounts = {}
11475 for mproxy,mpath in fs.mounts() do
11476 if mproxy.address == proxy.address then
11477 table.insert(mounts, mpath)
11478 end
11479 end
11480 return table.concat(mounts, "\n")
11481 end}
11482 }
11483end
11484 mnt/6c1/lib/core/devfs/adapters/internet.lua 0000600 00000000200 13215223731 013436 0 return function(proxy)
11485 return
11486 {
11487 httpEnabled = {proxy.isHttpEnabled()},
11488 tcpEnabled = {proxy.isTcpEnabled()},
11489 }
11490end
11491 mnt/6c1/lib/core/devfs/adapters/screen.lua 0000600 00000001132 13215223731 013072 0 local adapter_api = ...
11492
11493return function(proxy)
11494 return
11495 {
11496 ["aspectRatio"] = {proxy.getAspectRatio()},
11497 ["keyboards"] = {read=function()
11498 local ks = {}
11499 for _,ka in ipairs(proxy.getKeyboards()) do
11500 table.insert(ks, ka)
11501 end
11502 return table.concat(ks, "\n")
11503 end},
11504 ["on"] = adapter_api.create_toggle(proxy.isOn, proxy.turnOn, proxy.turnOff), -- turnOn and turnOff
11505 ["precise"] = adapter_api.create_toggle(proxy.isPrecise, proxy.setPrecise),
11506 ["touchModeInverted"] = adapter_api.create_toggle(proxy.isTouchModeInverted, proxy.setTouchModeInverted),
11507 }
11508end
11509 mnt/6c1/lib/core/devfs/adapters/gpu.lua 0000600 00000001432 13215223731 012411 0 local adapter_api = ...
11510
11511return function(proxy)
11512 return
11513 {
11514 viewport = {write = adapter_api.createWriter(proxy.setViewport, 2, "number", "number"), proxy.getViewport()},
11515 resolution = {write = adapter_api.createWriter(proxy.setResolution, 2, "number", "number"), proxy.getResolution()},
11516 maxResolution = {proxy.maxResolution()},
11517 screen = {link="../"..proxy.getScreen(),isAvailable=proxy.getScreen},
11518 depth = {write = adapter_api.createWriter(proxy.setDepth, 1, "number"), proxy.getDepth()},
11519 maxDepth = {proxy.maxDepth()},
11520 background = {write = adapter_api.createWriter(proxy.setBackground, 1, "number", "boolean"), proxy.getBackground()},
11521 foreground = {write = adapter_api.createWriter(proxy.setForeground, 1, "number", "boolean"), proxy.getForeground()},
11522 }
11523end
11524 mnt/6c1/lib/core/devfs/adapters/computer.lua 0000600 00000000350 13215223731 013452 0 local adapter_api = ...
11525
11526return function(proxy)
11527 return
11528 {
11529 beep = {write=adapter_api.createWriter(proxy.beep, 0, "number", "number")},
11530 running = adapter_api.create_toggle(proxy.isRunning, proxy.start, proxy.stop),
11531 }
11532end
11533 mnt/6c1/lib/core/devfs/adapters/modem.lua 0000600 00000000437 13215223731 012723 0 return function(proxy)
11534 return
11535 {
11536 wakeMessage =
11537 {
11538 read = function() return proxy.getWakeMessage() or "" end,
11539 write= function(msg) return proxy.setWakeMessage(msg) end,
11540 },
11541 wireless = {proxy.isWireless()},
11542 maxPacketSize = {proxy.maxPacketSize()},
11543 }
11544end
11545 mnt/6c1/lib/core/devfs/adapters/eeprom.lua 0000600 00000001050 13215223731 013101 0 local cache = {}
11546local function cload(callback)
11547 local c = cache[callback]
11548 if not c then
11549 c = callback()
11550 cache[callback] = c
11551 end
11552 return c
11553end
11554
11555return function(proxy)
11556 return
11557 {
11558 contents = {read=proxy.get, write=proxy.set},
11559 data = {read=proxy.getData, write=proxy.setData},
11560 checksum = {read=proxy.getChecksum,size=function() return 8 end},
11561 size = {cload(proxy.getSize)},
11562 dataSize = {cload(proxy.getDataSize)},
11563 label = {write=proxy.setLabel,proxy.getLabel()},
11564 makeReadonly = {write=proxy.makeReadonly}
11565 }
11566end
11567 mnt/6c1/lib/core/devfs/adapters 0000700 13215223732 010013 5 mnt/6c1/lib/core/devfs/01_hw.lua 0000600 00000007346 13215223730 010742 0 local comp = require("component")
11568local text = require("text")
11569
11570local dcache = {}
11571local pcache = {}
11572local adapter_pwd = "/lib/core/devfs/adapters/"
11573
11574local adapter_api = {}
11575
11576function adapter_api.toArgsPack(input, pack)
11577 local split = text.split(input, {"%s"}, true)
11578 local req = pack[1]
11579 local num = #split
11580 if num < req then return nil, "insufficient args" end
11581 local result = {n=num}
11582 for index=1,num do
11583 local typename = pack[index+1]
11584 local token = split[index]
11585 if typename == "boolean" then
11586 if token ~= "true" and token ~= "false" then return nil, "bad boolean value" end
11587 token = token == "true"
11588 elseif typename == "number" then
11589 token = tonumber(token)
11590 if not token then return nil, "bad number value" end
11591 end
11592 result[index] = token
11593 end
11594 return result
11595end
11596
11597function adapter_api.createWriter(callback, ...)
11598 local types = table.pack(...)
11599 return function(input)
11600 local args, why = adapter_api.toArgsPack(input, types)
11601 if not args then return why end
11602 return callback(table.unpack(args, 1, args.n))
11603 end
11604end
11605
11606function adapter_api.create_toggle(read, write, switch)
11607 return
11608 {
11609 read = read and function() return tostring(read()) end,
11610 write = write and function(value)
11611 value = text.trim(tostring(value))
11612 local on = value == "1" or value == "true"
11613 local off = value == "0" or value == "false"
11614 if not on and not off then
11615 return nil, "bad value"
11616 end
11617 if switch then
11618 (off and switch or write)()
11619 else
11620 write(on)
11621 end
11622 end
11623 }
11624end
11625
11626function adapter_api.make_link(list, addr, prefix, bOmitZero)
11627 prefix = prefix or ""
11628 local zero = bOmitZero and "" or "0"
11629 local id = 0
11630 local name
11631 repeat
11632 name = string.format("%s%s", prefix, id == 0 and zero or tostring(id))
11633 id = id + 1
11634 until not list[name]
11635 list[name] = {link=addr}
11636end
11637
11638return
11639{
11640 components =
11641 {
11642 list = function()
11643 local dirs = {}
11644 local types = {}
11645 local labels = {}
11646 local ads = {}
11647
11648 dirs["by-type"] = {list=function()return types end}
11649 dirs["by-label"] = {list=function()return labels end}
11650 dirs["by-address"] = {list=function()return ads end}
11651
11652 -- first sort the addr, primaries first, then sorted by address lexigraphically
11653 local hw_addresses = {}
11654 for addr,type in comp.list() do
11655 local isPrim = comp.isPrimary(addr)
11656 table.insert(hw_addresses, select(isPrim and 1 or 2, 1, {type,addr}))
11657 end
11658
11659 for _,pair in ipairs(hw_addresses) do
11660 local type, addr = table.unpack(pair)
11661 if not dcache[type] then
11662 local adapter_file = adapter_pwd .. type .. ".lua"
11663 local loader = loadfile(adapter_file, "bt", _G)
11664 dcache[type] = loader and loader(adapter_api)
11665 end
11666 local adapter = dcache[type]
11667 if adapter then
11668 local proxy = pcache[addr] or comp.proxy(addr)
11669 pcache[addr] = proxy
11670 ads[addr] =
11671 {
11672 list = function()
11673 local devfs_proxy = adapter(proxy)
11674 devfs_proxy.address = {proxy.address}
11675 devfs_proxy.slot = {proxy.slot}
11676 devfs_proxy.type = {proxy.type}
11677 devfs_proxy.device = {device=proxy}
11678 return devfs_proxy
11679 end
11680 }
11681
11682 -- by type building
11683 local type_dir = types[type] or {list={}}
11684 adapter_api.make_link(type_dir.list, "../../by-address/"..addr)
11685 types[type] = type_dir
11686
11687 -- by label building (labels are only supported in filesystems
11688 local label = require("devfs").getDeviceLabel(proxy)
11689 if label then
11690 adapter_api.make_link(labels, "../by-address/"..addr, label, true)
11691 end
11692 end
11693 end
11694 return dirs
11695 end
11696 },
11697}
11698 mnt/6c1/lib/core/devfs 0000700 13215223731 006207 5 mnt/6c1/lib/core/lua_shell.lua 0000600 00000007272 13215223741 010665 0 local package = require("package")
11699local tty = require("tty")
11700
11701local function optrequire(...)
11702 local success, module = pcall(require, ...)
11703 if success then
11704 return module
11705 end
11706end
11707
11708local env -- forward declare for binding in metamethod
11709env = setmetatable({}, {
11710 __index = function(_, k)
11711 _ENV[k] = _ENV[k] or optrequire(k)
11712 return _ENV[k]
11713 end,
11714 __pairs = function(t)
11715 return function(_, key)
11716 local k, v = next(t, key)
11717 if not k and t == env then
11718 t = _ENV
11719 k, v = next(t)
11720 end
11721 if not k and t == _ENV then
11722 t = package.loaded
11723 k, v = next(t)
11724 end
11725 return k, v
11726 end
11727 end,
11728})
11729env._PROMPT = tostring(env._PROMPT or "\27[32mlua> \27[37m")
11730
11731local function findTable(t, path)
11732 if type(t) ~= "table" then return nil end
11733 if not path or #path == 0 then return t end
11734 local name = string.match(path, "[^.]+")
11735 for k, v in pairs(t) do
11736 if k == name then
11737 return findTable(v, string.sub(path, #name + 2))
11738 end
11739 end
11740 local mt = getmetatable(t)
11741 if t == env then mt = {__index=_ENV} end
11742 if mt then
11743 return findTable(mt.__index, path)
11744 end
11745 return nil
11746end
11747
11748local function findKeys(t, r, prefix, name)
11749 if type(t) ~= "table" then return end
11750 for k, v in pairs(t) do
11751 if type(k) == "string" and string.match(k, "^"..name) then
11752 local postfix = ""
11753 if type(v) == "function" then postfix = "()"
11754 elseif type(v) == "table" and getmetatable(v) and getmetatable(v).__call then postfix = "()"
11755 elseif type(v) == "table" then postfix = "."
11756 end
11757 r[prefix..k..postfix] = true
11758 end
11759 end
11760 local mt = getmetatable(t)
11761 if t == env then mt = {__index=_ENV} end
11762 if mt then
11763 return findKeys(mt.__index, r, prefix, name)
11764 end
11765end
11766
11767local read_handler = {hint = function(line, index)
11768 line = (line or "")
11769 local tail = line:sub(index)
11770 line = line:sub(1, index - 1)
11771 local path = string.match(line, "[a-zA-Z_][a-zA-Z0-9_.]*$")
11772 if not path then return nil end
11773 local suffix = string.match(path, "[^.]+$") or ""
11774 local prefix = string.sub(path, 1, #path - #suffix)
11775 local tbl = findTable(env, prefix)
11776 if not tbl then return nil end
11777 local keys = {}
11778 local hints = {}
11779 findKeys(tbl, keys, string.sub(line, 1, #line - #suffix), suffix)
11780 for key in pairs(keys) do
11781 table.insert(hints, key .. tail)
11782 end
11783 return hints
11784end}
11785
11786io.write("\27[37m".._VERSION .. " Copyright (C) 1994-2017 Lua.org, PUC-Rio\n")
11787io.write("\27[33mEnter a statement and hit enter to evaluate it.\n")
11788io.write("Prefix an expression with '=' to show its value.\n")
11789io.write("Press Ctrl+D to exit the interpreter.\n\27[37m")
11790
11791while tty.isAvailable() do
11792 io.write(env._PROMPT)
11793 local command = tty.read(read_handler)
11794 if not command then -- eof
11795 return
11796 end
11797 local code, reason
11798 if string.sub(command, 1, 1) == "=" then
11799 code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env)
11800 else
11801 code, reason = load("return " .. command, "=stdin", "t", env)
11802 if not code then
11803 code, reason = load(command, "=stdin", "t", env)
11804 end
11805 end
11806 if code then
11807 local result = table.pack(xpcall(code, debug.traceback))
11808 if not result[1] then
11809 if type(result[2]) == "table" and result[2].reason == "terminated" then
11810 os.exit(result[2].code)
11811 end
11812 io.stderr:write(tostring(result[2]) .. "\n")
11813 else
11814 local ok, why = pcall(function()
11815 for i = 2, result.n do
11816 io.write(require("serialization").serialize(result[i], true) .. "\t")
11817 end
11818 end)
11819 if not ok then
11820 io.stderr:write("crashed serializing result: ", tostring(why))
11821 end
11822 if tty.getCursor() > 1 then
11823 io.write("\n")
11824 end
11825 end
11826 else
11827 io.stderr:write(tostring(reason) .. "\n")
11828 end
11829end
11830 mnt/6c1/lib/core 0000700 13215223741 005101 5 mnt/6c1/lib/event.lua 0000600 00000011266 13215223742 007105 0 local computer = require("computer")
11831local keyboard = require("keyboard")
11832
11833local event = {}
11834local handlers = {}
11835local lastInterrupt = -math.huge
11836
11837event.handlers = handlers
11838
11839function event.register(key, callback, interval, times, opt_handlers)
11840 local handler =
11841 {
11842 key = key,
11843 times = times or 1,
11844 callback = callback,
11845 interval = interval or math.huge,
11846 }
11847
11848 handler.timeout = computer.uptime() + handler.interval
11849 opt_handlers = opt_handlers or handlers
11850
11851 local id = 0
11852 repeat
11853 id = id + 1
11854 until not opt_handlers[id]
11855
11856 opt_handlers[id] = handler
11857 return id
11858end
11859
11860local _pullSignal = computer.pullSignal
11861setmetatable(handlers, {__call=function(_,...)return _pullSignal(...)end})
11862computer.pullSignal = function(...) -- dispatch
11863 local current_time = computer.uptime()
11864 local interrupting = current_time - lastInterrupt > 1 and keyboard.isControlDown() and keyboard.isKeyDown(keyboard.keys.c)
11865 if interrupting then
11866 lastInterrupt = current_time
11867 if keyboard.isAltDown() then
11868 error("interrupted", 0)
11869 end
11870 event.push("interrupted", current_time)
11871 end
11872 local event_data = table.pack(handlers(...))
11873 local signal = event_data[1]
11874 local copy = {}
11875 for id,handler in pairs(handlers) do
11876 copy[id] = handler
11877 end
11878 for id,handler in pairs(copy) do
11879 -- timers have false keys
11880 -- nil keys match anything
11881 if (handler.key == nil or handler.key == signal) or current_time >= handler.timeout then
11882 handler.times = handler.times - 1
11883 handler.timeout = current_time + handler.interval
11884 -- we have to remove handlers before making the callback in case of timers that pull
11885 -- and we have to check handlers[id] == handler because callbacks may have unregistered things
11886 if handler.times <= 0 and handlers[id] == handler then
11887 handlers[id] = nil
11888 end
11889 -- call
11890 local result, message = pcall(handler.callback, table.unpack(event_data, 1, event_data.n))
11891 if not result then
11892 pcall(event.onError, message)
11893 elseif message == false and handlers[id] == handler then
11894 handlers[id] = nil
11895 end
11896 end
11897 end
11898 return table.unpack(event_data, 1, event_data.n)
11899end
11900
11901local function createPlainFilter(name, ...)
11902 local filter = table.pack(...)
11903 if name == nil and filter.n == 0 then
11904 return nil
11905 end
11906
11907 return function(...)
11908 local signal = table.pack(...)
11909 if name and not (type(signal[1]) == "string" and signal[1]:match(name)) then
11910 return false
11911 end
11912 for i = 1, filter.n do
11913 if filter[i] ~= nil and filter[i] ~= signal[i + 1] then
11914 return false
11915 end
11916 end
11917 return true
11918 end
11919end
11920
11921-------------------------------------------------------------------------------
11922
11923function event.listen(name, callback)
11924 checkArg(1, name, "string")
11925 checkArg(2, callback, "function")
11926 for _, handler in pairs(handlers) do
11927 if handler.key == name and handler.callback == callback then
11928 return false
11929 end
11930 end
11931 return event.register(name, callback, math.huge, math.huge)
11932end
11933
11934function event.onError(message)
11935 local log = io.open("/tmp/event.log", "a")
11936 if log then
11937 pcall(log.write, log, tostring(message), "\n")
11938 log:close()
11939 end
11940end
11941
11942function event.pull(...)
11943 local args = table.pack(...)
11944 if type(args[1]) == "string" then
11945 return event.pullFiltered(createPlainFilter(...))
11946 else
11947 checkArg(1, args[1], "number", "nil")
11948 checkArg(2, args[2], "string", "nil")
11949 return event.pullFiltered(args[1], createPlainFilter(select(2, ...)))
11950 end
11951end
11952
11953function event.pullFiltered(...)
11954 local args = table.pack(...)
11955 local seconds, filter
11956
11957 if type(args[1]) == "function" then
11958 filter = args[1]
11959 else
11960 checkArg(1, args[1], "number", "nil")
11961 checkArg(2, args[2], "function", "nil")
11962 seconds = args[1]
11963 filter = args[2]
11964 end
11965
11966 local deadline = seconds and (computer.uptime() + seconds) or math.huge
11967 repeat
11968 local closest = deadline
11969 for _,handler in pairs(handlers) do
11970 closest = math.min(handler.timeout, closest)
11971 end
11972 local signal = table.pack(computer.pullSignal(closest - computer.uptime()))
11973 if signal.n > 0 then
11974 if not (seconds or filter) or filter == nil or filter(table.unpack(signal, 1, signal.n)) then
11975 return table.unpack(signal, 1, signal.n)
11976 end
11977 end
11978 until computer.uptime() >= deadline
11979end
11980
11981function event.timer(interval, callback, times)
11982 checkArg(1, interval, "number")
11983 checkArg(2, callback, "function")
11984 checkArg(3, times, "number", "nil")
11985 return event.register(false, callback, interval, times)
11986end
11987
11988-- users may expect to find event.push to exist
11989event.push = computer.pushSignal
11990
11991require("package").delay(event, "/lib/core/full_event.lua")
11992
11993-------------------------------------------------------------------------------
11994
11995return event
11996 mnt/6c1/lib/internet.lua 0000600 00000005774 13215223743 007624 0 local buffer = require("buffer")
11997local component = require("component")
11998local event = require("event")
11999
12000local internet = {}
12001
12002-------------------------------------------------------------------------------
12003
12004function internet.request(url, data, headers)
12005 checkArg(1, url, "string")
12006 checkArg(2, data, "string", "table", "nil")
12007 checkArg(3, headers, "table", "nil")
12008
12009 if not component.isAvailable("internet") then
12010 error("no primary internet card found", 2)
12011 end
12012 local inet = component.internet
12013
12014 local post
12015 if type(data) == "string" then
12016 post = data
12017 elseif type(data) == "table" then
12018 for k, v in pairs(data) do
12019 post = post and (post .. "&") or ""
12020 post = post .. tostring(k) .. "=" .. tostring(v)
12021 end
12022 end
12023
12024 local request, reason = inet.request(url, post, headers)
12025 if not request then
12026 error(reason, 2)
12027 end
12028
12029 return setmetatable(
12030 {
12031 ["()"] = "function():string -- Tries to read data from the socket stream and return the read byte array.",
12032 close = setmetatable({},
12033 {
12034 __call = request.close,
12035 __tostring = function() return "function() -- closes the connection" end
12036 })
12037 },
12038 {
12039 __call = function()
12040 while true do
12041 local data, reason = request.read()
12042 if not data then
12043 request.close()
12044 if reason then
12045 error(reason, 2)
12046 else
12047 return nil -- eof
12048 end
12049 elseif #data > 0 then
12050 return data
12051 end
12052 -- else: no data, block
12053 os.sleep(0)
12054 end
12055 end,
12056 __index = request,
12057 })
12058end
12059
12060-------------------------------------------------------------------------------
12061
12062local socketStream = {}
12063
12064function socketStream:close()
12065 if self.socket then
12066 self.socket.close()
12067 self.socket = nil
12068 end
12069end
12070
12071function socketStream:seek()
12072 return nil, "bad file descriptor"
12073end
12074
12075function socketStream:read(n)
12076 if not self.socket then
12077 return nil, "connection is closed"
12078 end
12079 return self.socket.read(n)
12080end
12081
12082function socketStream:write(value)
12083 if not self.socket then
12084 return nil, "connection is closed"
12085 end
12086 while #value > 0 do
12087 local written, reason = self.socket.write(value)
12088 if not written then
12089 return nil, reason
12090 end
12091 value = string.sub(value, written + 1)
12092 end
12093 return true
12094end
12095
12096function internet.socket(address, port)
12097 checkArg(1, address, "string")
12098 checkArg(2, port, "number", "nil")
12099 if port then
12100 address = address .. ":" .. port
12101 end
12102
12103 local inet = component.internet
12104 local socket, reason = inet.connect(address)
12105 if not socket then
12106 return nil, reason
12107 end
12108
12109 local stream = {inet = inet, socket = socket}
12110 local metatable = {__index = socketStream,
12111 __metatable = "socketstream"}
12112 return setmetatable(stream, metatable)
12113end
12114
12115function internet.open(address, port)
12116 local stream, reason = internet.socket(address, port)
12117 if not stream then
12118 return nil, reason
12119 end
12120 return buffer.new("rwb", stream)
12121end
12122
12123-------------------------------------------------------------------------------
12124
12125return internet mnt/6c1/lib/sh.lua 0000600 00000017376 13215223746 006412 0 local process = require("process")
12126local shell = require("shell")
12127local text = require("text")
12128local tx = require("transforms")
12129
12130local sh = {}
12131sh.internal = {}
12132
12133function sh.internal.isWordOf(w, vs)
12134 return w and #w == 1 and not w[1].qr and tx.first(vs,{{w[1].txt}}) ~= nil
12135end
12136
12137local isWordOf = sh.internal.isWordOf
12138
12139-------------------------------------------------------------------------------
12140
12141--SH API
12142
12143sh.internal.ec = {}
12144sh.internal.ec.parseCommand = 127
12145sh.internal.ec.last = 0
12146
12147function sh.getLastExitCode()
12148 return sh.internal.ec.last
12149end
12150
12151function sh.internal.command_result_as_code(ec, reason)
12152 -- convert lua result to bash ec
12153 local code
12154 if ec == false then
12155 code = 1
12156 elseif ec == nil or ec == true then
12157 code = 0
12158 elseif type(ec) ~= "number" then
12159 code = 2 -- illegal number
12160 else
12161 code = ec
12162 end
12163
12164 if reason and code ~= 0 then io.stderr:write(reason, "\n") end
12165 return code
12166end
12167
12168function sh.internal.resolveActions(input, resolver, resolved)
12169 checkArg(1, input, "string")
12170 checkArg(2, resolver, "function", "nil")
12171 checkArg(3, resolved, "table", "nil")
12172 resolver = resolver or shell.getAlias
12173 resolved = resolved or {}
12174
12175 local processed = {}
12176
12177 local prev_was_delim, simple = true, true
12178 local words, reason = text.internal.tokenize(input)
12179
12180 if not words then
12181 return nil, reason
12182 end
12183
12184 while #words > 0 do
12185 local next = table.remove(words,1)
12186 if isWordOf(next, {";","&&","||","|"}) then
12187 prev_was_delim,simple = true,false
12188 resolved = {}
12189 elseif prev_was_delim then
12190 prev_was_delim = false
12191 -- if current is actionable, resolve, else pop until delim
12192 if next and #next == 1 and not next[1].qr then
12193 local key = next[1].txt
12194 if key == "!" then
12195 prev_was_delim,simple = true,false -- special redo
12196 elseif not resolved[key] then
12197 resolved[key] = resolver(key)
12198 local value = resolved[key]
12199 if value and key ~= value then
12200 local replacement_tokens, reason = sh.internal.resolveActions(value, resolver, resolved)
12201 if not replacement_tokens then
12202 return replacement_tokens, reason
12203 end
12204 simple = simple and reason
12205 words = tx.concat(replacement_tokens, words)
12206 next = table.remove(words,1)
12207 end
12208 end
12209 end
12210 end
12211
12212 table.insert(processed, next)
12213 end
12214
12215 return processed, simple
12216end
12217
12218function sh.internal.statements(input)
12219 checkArg(1, input, "string")
12220
12221 local words, reason = sh.internal.resolveActions(input)
12222 if type(words) ~= "table" then
12223 return words, reason
12224 elseif #words == 0 then
12225 return true
12226 elseif reason and not input:find("[<>]") then
12227 return {words}, reason
12228 end
12229
12230 -- we shall validate pipes before any statement execution
12231 local statements = sh.internal.splitStatements(words)
12232 for i=1,#statements do
12233 local ok, why = sh.internal.hasValidPiping(statements[i])
12234 if not ok then return nil,why end
12235 end
12236 return statements
12237end
12238
12239-- returns true if key is a string that represents a valid command line identifier
12240function sh.internal.isIdentifier(key)
12241 if type(key) ~= "string" then
12242 return false
12243 end
12244
12245 return key:match("^[%a_][%w_]*$") == key
12246end
12247
12248-- expand (interpret) a single quoted area
12249-- examples: $foo, "$foo", or `cmd` in back ticks
12250function sh.expand(value)
12251 local expanded = value
12252 :gsub("%$([_%w%?]+)", function(key)
12253 if key == "?" then
12254 return tostring(sh.getLastExitCode())
12255 end
12256 return os.getenv(key) or ''
12257 end)
12258 :gsub("%${(.*)}", function(key)
12259 if sh.internal.isIdentifier(key) then
12260 return os.getenv(key) or ''
12261 end
12262 io.stderr:write("${" .. key .. "}: bad substitution\n")
12263 os.exit(1)
12264 end)
12265 return expanded
12266end
12267
12268function sh.internal.createThreads(commands, env, start_args)
12269 -- Piping data between programs works like so:
12270 -- program1 gets its output replaced with our custom stream.
12271 -- program2 gets its input replaced with our custom stream.
12272 -- repeat for all programs
12273 -- custom stream triggers execution of "next" program after write.
12274 -- custom stream triggers yield before read if buffer is empty.
12275 -- custom stream may have "redirect" entries for fallback/duplication.
12276 local threads = {}
12277 for i = 1, #commands do
12278 local command = commands[i]
12279 local program, args, redirects = table.unpack(command)
12280 local name = tostring(program)
12281 local thread_env = type(program) == "string" and env or nil
12282 local thread, reason = process.load(program or "/dev/null", thread_env, function(...)
12283 if redirects then
12284 sh.internal.openCommandRedirects(redirects)
12285 end
12286
12287 args = tx.concat(args, start_args[i] or {}, table.pack(...))
12288
12289 -- popen expects each process to first write an empty string
12290 -- this is required for proper thread order
12291 io.write("")
12292 return table.unpack(args, 1, args.n or #args)
12293 end, name)
12294
12295 if not thread then
12296 for _,t in ipairs(threads) do
12297 process.internal.close(t)
12298 end
12299 return nil, reason
12300 end
12301
12302 threads[i] = thread
12303
12304 end
12305
12306 if #threads > 1 then
12307 require("pipe").buildPipeChain(threads)
12308 end
12309
12310 return threads
12311end
12312
12313function sh.internal.executePipes(pipe_parts, eargs, env)
12314 local commands = {}
12315 for _,words in ipairs(pipe_parts) do
12316 -- evaluated words
12317 local ewords = {}
12318 local has_globits
12319 local has_redirects
12320 for _,word in ipairs(words) do
12321 local eword = {txt=""}
12322 for _,part in ipairs(word) do
12323 -- expand all parts if interpreted (not literal)
12324 -- i.e '' is literal, "" and `` are interpreted
12325 local next = part.txt
12326 local quoted = part.qr
12327 local literal, keep_whitespace, sub
12328 if quoted then
12329 literal = quoted[3]
12330 keep_whitespace = quoted[1] == '"'
12331 sub = quoted[1]:match('`') or next:find('`') and ''
12332 else
12333 if next:match("[%*%?]") then has_globits = true end
12334 if next:match("[<>]") then has_redirects = true end
12335 end
12336 if not literal then
12337 next = sh.expand(next)
12338 if sub then
12339 next = sh.internal.parse_sub(sub .. next .. sub)
12340 end
12341 if not keep_whitespace then
12342 next = text.trim((next:gsub("%s+", " ")))
12343 end
12344 end
12345 eword[#eword + 1] = { txt = next, qr = quoted }
12346 eword.txt = eword.txt .. next
12347 end
12348 ewords[#ewords + 1] = eword
12349 end
12350 local redirects, reason
12351 if has_redirects then
12352 redirects, reason = sh.internal.buildCommandRedirects(ewords)
12353 if reason then return false, reason end
12354 end
12355 local args = {}
12356 for _,eword in ipairs(ewords) do
12357 if has_globits then
12358 for _,arg in ipairs(sh.internal.glob(eword)) do
12359 args[#args + 1] = arg
12360 end
12361 else
12362 args[#args + 1] = eword.txt
12363 end
12364 end
12365 commands[#commands + 1] = table.pack(table.remove(args, 1), args, redirects)
12366 end
12367
12368 local threads, reason = sh.internal.createThreads(commands, env, {[#commands]=eargs})
12369 if not threads then return false, reason end
12370 return process.internal.continue(threads[1])
12371end
12372
12373function sh.execute(env, command, ...)
12374 checkArg(2, command, "string")
12375 if command:find("^%s*#") then return true, 0 end
12376 local statements, reason = sh.internal.statements(command)
12377 if not statements or statements == true then
12378 return statements, reason
12379 elseif #statements == 0 then
12380 return true, 0
12381 end
12382
12383 -- MUST be table.pack for non contiguous ...
12384 local eargs = table.pack(...)
12385
12386 -- simple
12387 if reason then
12388 sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements, eargs, env))
12389 return true
12390 end
12391
12392 return sh.internal.execute_complex(statements, eargs, env)
12393end
12394
12395function sh.hintHandler(full_line, cursor)
12396 return sh.internal.hintHandlerImpl(full_line, cursor)
12397end
12398
12399require("package").delay(sh, "/lib/core/full_sh.lua")
12400
12401return sh
12402 mnt/6c1/lib/bit32.lua 0000600 00000004050 13215223712 006675 0 --[[ Backwards compat for Lua 5.3; only loaded in 5.3 because package.loaded is
12403 prepopulated with the existing global bit32 in 5.2. ]]
12404
12405local bit32 = {}
12406
12407-------------------------------------------------------------------------------
12408
12409local function fold(init, op, ...)
12410 local result = init
12411 local args = table.pack(...)
12412 for i = 1, args.n do
12413 result = op(result, args[i])
12414 end
12415 return result
12416end
12417
12418local function trim(n)
12419 return n & 0xFFFFFFFF
12420end
12421
12422local function mask(w)
12423 return ~(0xFFFFFFFF << w)
12424end
12425
12426function bit32.arshift(x, disp)
12427 return x // (2 ^ disp)
12428end
12429
12430function bit32.band(...)
12431 return fold(0xFFFFFFFF, function(a, b) return a & b end, ...)
12432end
12433
12434function bit32.bnot(x)
12435 return ~x
12436end
12437
12438function bit32.bor(...)
12439 return fold(0, function(a, b) return a | b end, ...)
12440end
12441
12442function bit32.btest(...)
12443 return bit32.band(...) ~= 0
12444end
12445
12446function bit32.bxor(...)
12447 return fold(0, function(a, b) return a ~ b end, ...)
12448end
12449
12450local function fieldargs(f, w)
12451 w = w or 1
12452 assert(f >= 0, "field cannot be negative")
12453 assert(w > 0, "width must be positive")
12454 assert(f + w <= 32, "trying to access non-existent bits")
12455 return f, w
12456end
12457
12458function bit32.extract(n, field, width)
12459 local f, w = fieldargs(field, width)
12460 return (n >> f) & mask(w)
12461end
12462
12463function bit32.replace(n, v, field, width)
12464 local f, w = fieldargs(field, width)
12465 local m = mask(w)
12466 return (n & ~(m << f)) | ((v & m) << f)
12467end
12468
12469function bit32.lrotate(x, disp)
12470 if disp == 0 then
12471 return x
12472 elseif disp < 0 then
12473 return bit32.rrotate(x, -disp)
12474 else
12475 disp = disp & 31
12476 x = trim(x)
12477 return trim((x << disp) | (x >> (32 - disp)))
12478 end
12479end
12480
12481function bit32.lshift(x, disp)
12482 return trim(x << disp)
12483end
12484
12485function bit32.rrotate(x, disp)
12486 if disp == 0 then
12487 return x
12488 elseif disp < 0 then
12489 return bit32.lrotate(x, -disp)
12490 else
12491 disp = disp & 31
12492 x = trim(x)
12493 return trim((x >> disp) | (x << (32 - disp)))
12494 end
12495end
12496
12497function bit32.rshift(x, disp)
12498 return trim(x >> disp)
12499end
12500
12501-------------------------------------------------------------------------------
12502
12503return bit32
12504 mnt/6c1/lib/pipe.lua 0000600 00000017244 13215223724 006723 0 local process = require("process")
12505local shell = require("shell")
12506local buffer = require("buffer")
12507local command_result_as_code = require("sh").internal.command_result_as_code
12508
12509local pipe = {}
12510local _root_co = assert(process.info(), "process metadata failed to load").data.coroutine_handler
12511
12512-- root can be a coroutine or a function
12513function pipe.createCoroutineStack(root, env, name)
12514 checkArg(1, root, "thread", "function")
12515
12516 if type(root) == "function" then
12517 root = assert(process.load(root, env, nil, name or "pipe"), "failed to load proc data for given function")
12518 end
12519
12520 local proc = assert(process.list[root], "coroutine must be a process thread else the parent process is corrupted")
12521
12522 local pco = setmetatable({root=root}, {__index=_root_co})
12523 proc.data.coroutine_handler = pco
12524
12525 function pco.yield(...)
12526 return _root_co.yield(nil, ...)
12527 end
12528 function pco.yield_past(co, ...)
12529 return _root_co.yield(co, ...)
12530 end
12531 function pco.resume(co, ...)
12532 checkArg(1, co, "thread")
12533 local args = table.pack(...)
12534 while true do -- for consecutive sysyields
12535 local result = table.pack(_root_co.resume(co, table.unpack(args, 1, args.n)))
12536 local target = result[2] == true and pco.root or result[2]
12537 if not result[1] or _root_co.status(co) == "dead" then
12538 return table.unpack(result, 1, result.n)
12539 elseif target and target ~= co then
12540 args = table.pack(_root_co.yield(table.unpack(result, 2, result.n)))
12541 else
12542 return true, table.unpack(result, 3, result.n)
12543 end
12544 end
12545 end
12546 return pco
12547end
12548
12549local pipe_stream =
12550{
12551 continue = function(self, exit)
12552 local result = table.pack(coroutine.resume(self.next))
12553 while true do -- repeat resumes if B (A|B) makes a natural yield
12554 -- if B crashed or closed in the last resume
12555 -- then we can close the stream
12556 if coroutine.status(self.next) == "dead" then
12557 self:close()
12558 -- always cause os.exit when the pipe closes
12559 -- this is very important
12560 -- e.g. cat very_large_file | head
12561 -- when head is done, cat should stop
12562 result[1] = nil
12563 end
12564 -- the pipe closed or crashed
12565 if not result[1] then
12566 if exit then
12567 os.exit(command_result_as_code(result[2]))
12568 end
12569 return self
12570 end
12571 -- next is suspended, read_mode indicates why
12572 if self.read_mode then
12573 -- B wants A to write again, resume A
12574 return self
12575 end
12576 -- not reading, it is requesting a yield
12577 -- yield_past(true) will exit this coroutine stack
12578 result = table.pack(coroutine.yield_past(true, table.unpack(result, 2, result.n)))
12579 result = table.pack(coroutine.resume(self.next, table.unpack(result, 1, result.n))) -- the request was for an event
12580 end
12581 end,
12582 close = function(self)
12583 self.closed = true
12584 if coroutine.status(self.next) == "suspended" then
12585 self:continue()
12586 end
12587 self.redirect = {}
12588 end,
12589 seek = function()
12590 return nil, "bad file descriptor"
12591 end,
12592 write = function(self, value)
12593 if not self.redirect[1] and self.closed then
12594 -- if next is dead, ignore all writes
12595 if coroutine.status(self.next) ~= "dead" then
12596 io.stderr:write("attempt to use a closed stream\n")
12597 os.exit(1)
12598 end
12599 elseif self.redirect[1] then
12600 return self.redirect[1]:write(value)
12601 elseif not self.closed then
12602 self.buffer = self.buffer .. value
12603 return self:continue(true)
12604 end
12605 os.exit(0) -- abort the current process: SIGPIPE
12606 end,
12607 read = function(self, n)
12608 if self.closed then
12609 return nil -- eof
12610 end
12611 if self.redirect[0] then
12612 -- popen could be using this code path
12613 -- if that is the case, it is important to leave stream.buffer alone
12614 return self.redirect[0]:read(n)
12615 elseif self.buffer == "" then
12616 -- the pipe_stream write resume is waiting on this process B (A|B) to yield
12617 -- yield here requests A to output again. However, B may elsewhere want a
12618 -- natural yield (i.e. for events). To differentiate this yield from natural
12619 -- yields we set read_mode here, which the pipe_stream write detects
12620 self.read_mode = true
12621 coroutine.yield_past(self.next) -- next is the first croutine in this stack
12622 self.read_mode = false
12623 end
12624 local result = string.sub(self.buffer, 1, n)
12625 self.buffer = string.sub(self.buffer, n + 1)
12626 return result
12627 end
12628}
12629
12630-- prog1 | prog2 | ... | progn
12631function pipe.buildPipeChain(progs)
12632 local chain = {}
12633 local prev_piped_stream
12634 for i=1,#progs do
12635 local thread = progs[i]
12636 -- A needs to be a stack in case any thread in A call write and then B natural yields
12637 -- B needs to be a stack in case any thread in B calls read
12638 pipe.createCoroutineStack(thread)
12639 chain[i] = thread
12640 local data = process.info(thread).data
12641 local pio = data.io
12642
12643 local piped_stream
12644 if i < #progs then
12645 local handle = setmetatable({redirect = {rawget(pio, 1)},buffer = ""}, {__index = pipe_stream})
12646 piped_stream = buffer.new("rw", handle)
12647 piped_stream:setvbuf("no", 1024)
12648 pio[1] = piped_stream
12649 table.insert(data.handles, piped_stream)
12650 end
12651
12652 if prev_piped_stream then
12653 prev_piped_stream.stream.redirect[0] = rawget(pio, 0)
12654 prev_piped_stream.stream.next = thread
12655 pio[0] = prev_piped_stream
12656 end
12657
12658 prev_piped_stream = piped_stream
12659 end
12660
12661 return chain
12662end
12663
12664local chain_stream =
12665{
12666 read = function(self, value, ...)
12667 if self.io_stream.closed then return nil end
12668 -- wake up prog
12669 self.ready = false -- the pipe proc sets this true when ios completes
12670 local ret = table.pack(coroutine.resume(self.pco.root, value, ...))
12671 if coroutine.status(self.pco.root) == "dead" then
12672 return nil
12673 elseif not ret[1] then
12674 return table.unpack(ret, 1, ret.n)
12675 end
12676 if not self.ready then
12677 -- prog yielded back without writing/reading
12678 return self:read(coroutine.yield())
12679 end
12680 return ret[2]
12681 end,
12682 write = function(self, ...)
12683 return self:read(...)
12684 end,
12685 close = function(self)
12686 self.io_stream:close()
12687 end,
12688}
12689
12690function pipe.popen(prog, mode, env)
12691 mode = mode or "r"
12692 if mode ~= "r" and mode ~= "w" then
12693 return nil, "bad argument #2: invalid mode " .. tostring(mode) .. " must be r or w"
12694 end
12695
12696 local r = mode == "r"
12697
12698 local chain = {}
12699 -- to simplify the code - shell.execute is run within a function to pass (prog, env)
12700 -- if cmd_proc where to come second (mode=="w") then the pipe_proc would have to pass
12701 -- the starting args. which is possible, just more complicated
12702 local cmd_proc = process.load(function() return shell.execute(prog, env) end, nil, nil, prog)
12703
12704 -- the chain stream is the popen controller
12705 local stream = setmetatable({}, { __index = chain_stream })
12706
12707 -- the stream needs its own process for io
12708 local pipe_proc = process.load(function()
12709 local n = r and 0 or ""
12710 local key = r and "read" or "write"
12711 local ios = stream.io_stream
12712 while not ios.closed do
12713 -- read from pipe
12714 local ret = table.pack(ios[key](ios, n))
12715 stream.ready = true
12716 -- yield outside the chain now
12717 n = coroutine.yield_past(chain[1], table.unpack(ret, 1, ret.n))
12718 end
12719 end, nil, nil, "pipe_handler")
12720
12721 chain[r and 1 or 2] = cmd_proc
12722 chain[r and 2 or 1] = pipe_proc
12723
12724 -- link the cmd and pipe proc io
12725 pipe.buildPipeChain(chain)
12726 local cmd_data = process.info(chain[1]).data
12727 local cmd_stack = cmd_data.coroutine_handler
12728
12729 -- store handle to io_stream from easy access later
12730 stream.io_stream = cmd_data.io[1].stream
12731 stream.pco = cmd_stack
12732
12733 -- popen commands start out running, like threads
12734 cmd_stack.resume(cmd_stack.root)
12735
12736 local buffered_stream = buffer.new(mode, stream)
12737 buffered_stream:setvbuf("no", 1024)
12738 return buffered_stream
12739end
12740
12741return pipe
12742 mnt/6c1/lib/process.lua 0000600 00000010667 13215223747 007453 0 local process = {}
12743
12744-------------------------------------------------------------------------------
12745
12746--Initialize coroutine library--
12747process.list = setmetatable({}, {__mode="k"})
12748
12749function process.findProcess(co)
12750 co = co or coroutine.running()
12751 for main, p in pairs(process.list) do
12752 if main == co then
12753 return p
12754 end
12755 for _, instance in pairs(p.instances) do
12756 if instance == co then
12757 return p
12758 end
12759 end
12760 end
12761end
12762
12763-------------------------------------------------------------------------------
12764function process.load(path, env, init, name)
12765 checkArg(1, path, "string", "function")
12766 checkArg(2, env, "table", "nil")
12767 checkArg(3, init, "function", "nil")
12768 checkArg(4, name, "string", "nil")
12769
12770 assert(type(path) == "string" or env == nil, "process cannot load function environemnts")
12771
12772 local p = process.findProcess()
12773 env = env or p.env
12774 local code
12775 if type(path) == "string" then
12776 code = function(...)
12777 local fs, shell = require("filesystem"), require("shell")
12778 local program, reason = shell.resolve(path, "lua")
12779 if not program then
12780 return require("tools/programLocations").reportNotFound(path, reason)
12781 end
12782 os.setenv("_", program)
12783 local f = fs.open(program)
12784 if f then
12785 local shebang = (f:read(1024) or ""):match("^#!([^\n]+)")
12786 f:close()
12787 if shebang then
12788 path = shebang:gsub("%s","")
12789 return code(program, ...)
12790 end
12791 end
12792 -- local command
12793 return assert(loadfile(program, "bt", env))(...)
12794 end
12795 else -- path is code
12796 code = path
12797 end
12798
12799 local thread = nil
12800 thread = coroutine.create(function(...)
12801 -- pcall code so that we can remove it from the process list on exit
12802 local result =
12803 {
12804 xpcall(function(...)
12805 init = init or function(...) return ... end
12806 return code(init(...))
12807 end,
12808 function(msg)
12809 if type(msg) == "table" and msg.reason == "terminated" then
12810 return msg.code or 0
12811 end
12812 local stack = debug.traceback():gsub("^([^\n]*\n)[^\n]*\n[^\n]*\n","%1")
12813 io.stderr:write(string.format("%s:\n%s", msg or "", stack))
12814 return 128 -- syserr
12815 end, ...)
12816 }
12817 process.internal.close(thread, result)
12818 --result[1] is false if the exception handler also crashed
12819 if not result[1] and type(result[2]) ~= "number" then
12820 require("event").onError(string.format("process library exception handler crashed: %s", tostring(result[2])))
12821 end
12822 return select(2, table.unpack(result))
12823 end, true)
12824 local new_proc =
12825 {
12826 path = path,
12827 command = name or tostring(path),
12828 env = env,
12829 data =
12830 {
12831 handles = {},
12832 io = {},
12833 },
12834 parent = p,
12835 instances = setmetatable({}, {__mode="v"}),
12836 }
12837 setmetatable(new_proc.data.io, {__index=p.data.io})
12838 setmetatable(new_proc.data, {__index=p.data})
12839 process.list[thread] = new_proc
12840
12841 return thread
12842end
12843
12844function process.info(levelOrThread)
12845 checkArg(1, levelOrThread, "thread", "number", "nil")
12846 local p
12847 if type(levelOrThread) == "thread" then
12848 p = process.findProcess(levelOrThread)
12849 else
12850 local level = levelOrThread or 1
12851 p = process.findProcess()
12852 while level > 1 and p do
12853 p = p.parent
12854 level = level - 1
12855 end
12856 end
12857 if p then
12858 return {path=p.path, env=p.env, command=p.command, data=p.data}
12859 end
12860end
12861
12862--table of undocumented api subject to change and intended for internal use
12863process.internal = {}
12864--this is a future stub for a more complete method to kill a process
12865function process.internal.close(thread, result)
12866 checkArg(1,thread,"thread")
12867 local pdata = process.info(thread).data
12868 pdata.result = result
12869 for _,v in pairs(pdata.handles) do
12870 pcall(v.close, v)
12871 end
12872 process.list[thread] = nil
12873end
12874
12875function process.internal.continue(co, ...)
12876 local result = {}
12877 -- Emulate CC behavior by making yields a filtered event.pull()
12878 local args = table.pack(...)
12879 while coroutine.status(co) ~= "dead" do
12880 result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
12881 if coroutine.status(co) ~= "dead" then
12882 args = table.pack(coroutine.yield(table.unpack(result, 2, result.n)))
12883 elseif not result[1] then
12884 io.stderr:write(result[2])
12885 end
12886 end
12887 return table.unpack(result, 2, result.n)
12888end
12889
12890function process.running(level) -- kept for backwards compat, prefer process.info
12891 local info = process.info(level)
12892 if info then
12893 return info.path, info.env, info.command
12894 end
12895end
12896
12897return process
12898 mnt/6c1/lib/package.lua 0000600 00000004350 13215223744 007355 0 local package = {}
12899
12900package.path = "/lib/?.lua;/usr/lib/?.lua;/home/lib/?.lua;./?.lua;/lib/?/init.lua;/usr/lib/?/init.lua;/home/lib/?/init.lua;./?/init.lua"
12901
12902local loading = {}
12903
12904local loaded = {
12905 ["_G"] = _G,
12906 ["bit32"] = bit32,
12907 ["coroutine"] = coroutine,
12908 ["math"] = math,
12909 ["os"] = os,
12910 ["package"] = package,
12911 ["string"] = string,
12912 ["table"] = table
12913}
12914package.loaded = loaded
12915
12916function package.searchpath(name, path, sep, rep)
12917 checkArg(1, name, "string")
12918 checkArg(2, path, "string")
12919 sep = sep or '.'
12920 rep = rep or '/'
12921 sep, rep = '%' .. sep, rep
12922 name = string.gsub(name, sep, rep)
12923 local fs = require("filesystem")
12924 local errorFiles = {}
12925 for subPath in string.gmatch(path, "([^;]+)") do
12926 subPath = string.gsub(subPath, "?", name)
12927 if subPath:sub(1, 1) ~= "/" and os.getenv then
12928 subPath = fs.concat(os.getenv("PWD") or "/", subPath)
12929 end
12930 if fs.exists(subPath) then
12931 local file = fs.open(subPath, "r")
12932 if file then
12933 file:close()
12934 return subPath
12935 end
12936 end
12937 table.insert(errorFiles, "\tno file '" .. subPath .. "'")
12938 end
12939 return nil, table.concat(errorFiles, "\n")
12940end
12941
12942function require(module)
12943 checkArg(1, module, "string")
12944 if loaded[module] ~= nil then
12945 return loaded[module]
12946 elseif not loading[module] then
12947 local library, status, step
12948
12949 step, library, status = "not found", package.searchpath(module, package.path)
12950
12951 if library then
12952 step, library, status = "loadfile failed", loadfile(library)
12953 end
12954
12955 if library then
12956 loading[module] = true
12957 step, library, status = "load failed", pcall(library, module)
12958 loading[module] = false
12959 end
12960
12961 assert(library, string.format("module '%s' %s:\n%s", module, step, status))
12962 loaded[module] = status
12963 return status
12964 else
12965 error("already loading: " .. module .. "\n" .. debug.traceback(), 2)
12966 end
12967end
12968
12969function package.delay(lib, file)
12970 local mt = {
12971 __index = function(tbl, key)
12972 setmetatable(lib, nil)
12973 setmetatable(lib.internal or {}, nil)
12974 dofile(file)
12975 return tbl[key]
12976 end
12977 }
12978 if lib.internal then
12979 setmetatable(lib.internal, mt)
12980 end
12981 setmetatable(lib, mt)
12982end
12983
12984-------------------------------------------------------------------------------
12985
12986return package
12987 mnt/6c1/lib 0000700 13215223746 004156 5