Add documentation for shell and multishell

Heh, one place we're more complete than cc.cc :p.
This commit is contained in:
Jonathan Coates 2020-04-22 14:39:39 +01:00
parent f3de97d67f
commit 2f42a4e85b
No known key found for this signature in database
GPG Key ID: D6D4CB5BFBBB5CB8
8 changed files with 339 additions and 84 deletions

View File

@ -23,7 +23,7 @@ body {
"Droid Sans", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
code, pre, .parameter, .type, .definition-name, .reference {
code, pre, .parameter, .type, .definition-name, .reference-code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}

View File

@ -2,8 +2,8 @@
(sources
/doc/stub/
/src/main/resources/assets/computercraft/lua/bios.lua
/src/main/resources/assets/computercraft/lua/rom/
/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/
/src/test/resources/test-rom)
@ -15,13 +15,13 @@
(library-path
/doc/stub/
/src/main/resources/assets/computercraft/lua/rom/apis
/src/main/resources/assets/computercraft/lua/rom/apis/command
/src/main/resources/assets/computercraft/lua/rom/apis/turtle
/src/main/resources/*/computercraft/lua/rom/apis
/src/main/resources/*/computercraft/lua/rom/apis/command
/src/main/resources/*/computercraft/lua/rom/apis/turtle
/src/main/resources/assets/computercraft/lua/rom/modules/main
/src/main/resources/assets/computercraft/lua/rom/modules/command
/src/main/resources/assets/computercraft/lua/rom/modules/turtle))
/src/main/resources/*/computercraft/lua/rom/modules/main
/src/main/resources/*/computercraft/lua/rom/modules/command
/src/main/resources/*/computercraft/lua/rom/modules/turtle))
(at /
(linters
@ -35,9 +35,7 @@
;; be good to find a compromise in the future, but this works for now.
-var:unused-arg
;; Suppress a couple of documentation comments warnings for now. We'll
;; hopefully be able to remove them in the future.
-doc:undocumented -doc:undocumented-arg -doc:unresolved-reference
;; Some APIS (keys, colour and os mainly) are incomplete right now.
-var:unresolved-member)
(lint
(bracket-spaces
@ -50,8 +48,8 @@
;; We disable the unused global linter in bios.lua and the APIs. In the future
;; hopefully we'll get illuaminate to handle this.
(at
(/src/main/resources/assets/computercraft/lua/bios.lua
/src/main/resources/assets/computercraft/lua/rom/apis/)
(/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/apis/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
@ -60,19 +58,27 @@
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
;; Ensure any fully documented modules stay fully documented.
;; Suppress warnings for currently undocumented modules.
(at
(/src/main/resources/assets/computercraft/lua/rom/apis/colors.lua
/src/main/resources/assets/computercraft/lua/rom/apis/colours.lua
/src/main/resources/assets/computercraft/lua/rom/apis/disk.lua
/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua
/src/main/resources/assets/computercraft/lua/rom/apis/help.lua
/src/main/resources/assets/computercraft/lua/rom/apis/keys.lua
/src/main/resources/assets/computercraft/lua/rom/apis/paintutils.lua
/src/main/resources/assets/computercraft/lua/rom/apis/parallel.lua
/src/main/resources/assets/computercraft/lua/rom/apis/peripheral.lua
/src/main/resources/assets/computercraft/lua/rom/apis/rednet.lua
/src/main/resources/assets/computercraft/lua/rom/apis/settings.lua
/src/main/resources/assets/computercraft/lua/rom/apis/texutils.lua
/src/main/resources/assets/computercraft/lua/rom/apis/vector.lua)
(linters doc:undocumented doc:undocumented-arg))
(/doc/stub/commands.lua
/doc/stub/fs.lua
/doc/stub/http.lua
/doc/stub/os.lua
/doc/stub/redstone.lua
/doc/stub/term.lua
/doc/stub/turtle.lua
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
(linters -doc:undocumented -doc:undocumented-arg))
;; These currently rely on unknown references.
(at
(/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua
/src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua
/src/main/resources/*/computercraft/lua/rom/programs/shell.lua)
(linters -doc:unresolved-reference))

View File

@ -521,14 +521,11 @@ function os.run(_tEnv, _sPath, ...)
expect(1, _tEnv, "table")
expect(2, _sPath, "string")
local tArgs = table.pack(...)
local tEnv = _tEnv
setmetatable(tEnv, { __index = _G })
local fnFile, err = loadfile(_sPath, nil, tEnv)
if fnFile then
local ok, err = pcall(function()
fnFile(table.unpack(tArgs, 1, tArgs.n))
end)
local ok, err = pcall(fnFile, ...)
if not ok then
if err and err ~= "" then
printError(err)

View File

@ -106,7 +106,7 @@ end
-- This generally returns the same as @{disk.getLabel} for records.
--
-- @tparam string name The name of the disk drive.
-- @treturn string|false|nil The track title, `false` if there is not a music
-- @treturn string|false|nil The track title, @{false} if there is not a music
-- record in the drive or `nil` if no drive is present.
function getAudioTitle(name)
if isDrive(name) then

View File

@ -32,7 +32,7 @@ for _, v in ipairs(valid_types) do valid_types[v] = true end
-- you to provide defaults and additional metadata.
--
-- @tparam string name The name of this option
-- @tparam[opt] { description? = string, default? = value, type? = string } options
-- @tparam[opt] { description? = string, default? = any, type? = string } options
-- Options for this setting. This table accepts the following fields:
--
-- - `description`: A description which may be printed when running the `set` program.
@ -127,7 +127,7 @@ end
--- Get details about a specific setting.
--
-- @tparam string name The name of the setting to get.
-- @treturn { description? = string, default? = value, type? = string, value? = value }
-- @treturn { description? = string, default? = any, type? = string, value? = any }
-- Information about this setting. This includes all information from @{settings.define},
-- as well as this setting's value.
function getDetails(name)

View File

@ -16,7 +16,7 @@ local completion = require "cc.completion"
--- Complete the name of a file relative to the current working directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam table shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching files.
local function file(shell, text)
@ -25,7 +25,7 @@ end
--- Complete the name of a directory relative to the current working directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam table shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching directories.
local function dir(shell, text)
@ -35,7 +35,7 @@ end
--- Complete the name of a file or directory relative to the current working
-- directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam table shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @tparam { string... } previous The shell arguments before this one.
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
@ -61,9 +61,10 @@ end
--- Complete the name of a program.
--
-- @tparam shell shell The shell we're completing in
-- @tparam table shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching programs.
-- @see shell.completeProgram
local function program(shell, text)
return shell.completeProgram(text)
end

View File

@ -1,3 +1,21 @@
--- Multishell allows multiple programs to be run at the same time.
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
--
-- @module[module] multishell
local expect = dofile("rom/modules/main/cc/expect.lua").expect
-- Setup process switching
@ -190,12 +208,26 @@ local function setMenuVisible(bVis)
end
end
local multishell = {}
local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
-- @see setFocus
function multishell.getFocus()
return nCurrentProcess
end
--- Change the currently visible process.
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
@ -206,6 +238,13 @@ function multishell.setFocus(n)
return false
end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")
if n >= 1 and n <= #tProcesses then
@ -214,19 +253,45 @@ function multishell.getTitle(n)
return nil
end
function multishell.setTitle(n, sTitle)
--- Set the title of the given process.
--
-- @tparam number n The process index.
-- @tparam string title The new process title.
-- @see getTitle
-- @usage Change the title of the current process
--
-- multishell.setTitle(multishell.getCurrent(), "Hello")
function multishell.setTitle(n, title)
expect(1, n, "number")
expect(2, sTitle, "string")
expect(2, title, "string")
if n >= 1 and n <= #tProcesses then
setProcessTitle(n, sTitle)
setProcessTitle(n, title)
redrawMenu()
end
end
--- Get the index of the currently running process.
--
-- @treturn number The currently running process.
function multishell.getCurrent()
return nRunningProcess
end
--- Start a new process, with the given environment, program and arguments.
--
-- The returned process index is not constant over the program's run. It can be
-- safely used immediately after launching (for instance, to update the title or
-- switch to that tab). However, after your program has yielded, it may no
-- longer be correct.
--
-- @tparam table tProgramEnv The environment to load the path under.
-- @tparam string sProgramPath The path to the program to run.
-- @param ... Additional arguments to pass to the program.
-- @treturn number The index of the created process.
-- @see os.run
-- @usage Run the "hello" program, and set its title to "Hello!"
-- local id = multishell.launch({}, "/rom/programs/fun/hello.lua")
-- multishell.setTitle(id, "Hello!")
function multishell.launch(tProgramEnv, sProgramPath, ...)
expect(1, tProgramEnv, "table")
expect(2, sProgramPath, "string")
@ -238,6 +303,9 @@ function multishell.launch(tProgramEnv, sProgramPath, ...)
return nResult
end
--- Get the number of processes within this multishell.
--
-- @treturn number The number of processes.
function multishell.getCount()
return #tProcesses
end

View File

@ -1,3 +1,15 @@
--- The shell API provides access to CraftOS's command line interface.
--
-- It allows you to @{run|start programs}, @{setCompletionFunction|add
-- completion for a program}, and much more.
--
-- @{shell} is not a "true" API. Instead, it is a standard program, which its
-- API into the programs that it launches. This allows for multiple shells to
-- run at the same time, but means that the API is not available in the global
-- environment, and so is unavailable to other @{os.loadAPI|APIs}.
--
-- @module[module] shell
local expect = dofile("rom/modules/main/cc/expect.lua").expect
local multishell = multishell
@ -15,7 +27,7 @@ local tAliases = parentShell and parentShell.aliases() or {}
local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
local tProgramStack = {}
local shell = {}
local shell = {} --- @export
local function createShellEnv(sDir)
local tEnv = {}
tEnv.shell = shell
@ -172,6 +184,18 @@ local function tokenise(...)
end
-- Install shell API
--- Run a program with the supplied arguments.
--
-- All arguments are concatenated together and then parsed as a command line. As
-- a result, `shell.run("program a b")` is the same as `shell.run("program",
-- "a", "b")`.
--
-- @tparam string ... The program to run and its arguments.
-- @treturn boolean Whether the program exited successfully.
-- @usage Run `paint my-image` from within your program:
--
-- shell.run("paint", "my-image")
function shell.run(...)
local tWords = tokenise(...)
local sCommand = tWords[1]
@ -181,38 +205,83 @@ function shell.run(...)
return false
end
--- Exit the current shell.
--
-- This does _not_ terminate your program, it simply makes the shell terminate
-- after your program has finished. If this is the toplevel shell, then the
-- computer will be shutdown.
function shell.exit()
bExit = true
end
--- Return the current working directory. This is what is displayed before the
-- `> ` of the shell prompt, and is used by @{shell.resolve} to handle relative
-- paths.
--
-- @treturn string The current working directory.
-- @see setDir To change the working directory.
function shell.dir()
return sDir
end
function shell.setDir(_sDir)
expect(1, _sDir, "string")
if not fs.isDir(_sDir) then
--- Set the current working directory.
--
-- @tparam string dir The new working directory.
-- @throws If the path does not exist or is not a directory.
-- @usage Set the working directory to "rom"
--
-- shell.setDir("rom")
function shell.setDir(dir)
expect(1, dir, "string")
if not fs.isDir(dir) then
error("Not a directory", 2)
end
sDir = fs.combine(_sDir, "")
sDir = fs.combine(dir, "")
end
--- Set the path where programs are located.
--
-- The path is composed of a list of directory names in a string, each separated
-- by a colon (`:`). On normal turtles will look in the current directory (`.`),
-- `/rom/programs` and `/rom/programs/turtle` folder, making the path
-- `.:/rom/programs:/rom/programs/turtle`.
--
-- @treturn string The current shell's path.
-- @see setPath To change the current path.
function shell.path()
return sPath
end
function shell.setPath(_sPath)
expect(1, _sPath, "string")
sPath = _sPath
--- Set the @{path|current program path}.
--
-- Be careful to prefix directories with a `/`. Otherwise they will be searched
-- for from the @{shell.dir|current directory}, rather than the computer's root.
--
-- @tparam string path The new program path.
function shell.setPath(path)
expect(1, path, "string")
sPath = path
end
function shell.resolve(_sPath)
expect(1, _sPath, "string")
local sStartChar = string.sub(_sPath, 1, 1)
--- Resolve a relative path to an absolute path.
--
-- The @{fs} and @{io} APIs work using absolute paths, and so we must convert
-- any paths relative to the @{dir|current directory} to absolute ones. This
-- does nothing when the path starts with `/`.
--
-- @tparam string path The path to resolve.
-- @usage Resolve `startup.lua` when in the `rom` folder.
--
-- shell.setDir("rom")
-- print(shell.resolve("startup.lua"))
-- -- => rom/startup.lua
function shell.resolve(path)
expect(1, path, "string")
local sStartChar = string.sub(path, 1, 1)
if sStartChar == "/" or sStartChar == "\\" then
return fs.combine("", _sPath)
return fs.combine("", path)
else
return fs.combine(sDir, _sPath)
return fs.combine(sDir, path)
end
end
@ -226,16 +295,25 @@ local function pathWithExtension(_sPath, _sExt)
return _sPath .. "." .. _sExt
end
function shell.resolveProgram(_sCommand)
expect(1, _sCommand, "string")
--- Resolve a program, using the @{path|program path} and list of @{aliases|aliases}.
--
-- @tparam string command The name of the program
-- @treturn string|nil The absolute path to the program, or @{nil} if it could
-- not be found.
-- @usage Locate the `hello` program.
--
-- shell.resolveProgram("hello")
-- -- => rom/programs/fun/hello.lua
function shell.resolveProgram(command)
expect(1, command, "string")
-- Substitute aliases firsts
if tAliases[_sCommand] ~= nil then
_sCommand = tAliases[_sCommand]
if tAliases[command] ~= nil then
command = tAliases[command]
end
-- If the path is a global path, use it directly
if _sCommand:find("/") or _sCommand:find("\\") then
local sPath = shell.resolve(_sCommand)
if command:find("/") or command:find("\\") then
local sPath = shell.resolve(command)
if fs.exists(sPath) and not fs.isDir(sPath) then
return sPath
else
@ -249,7 +327,7 @@ function shell.resolveProgram(_sCommand)
-- Otherwise, look on the path variable
for sPath in string.gmatch(sPath, "[^:]+") do
sPath = fs.combine(shell.resolve(sPath), _sCommand)
sPath = fs.combine(shell.resolve(sPath), command)
if fs.exists(sPath) and not fs.isDir(sPath) then
return sPath
else
@ -264,7 +342,15 @@ function shell.resolveProgram(_sCommand)
return nil
end
function shell.programs(_bIncludeHidden)
--- Return a list of all programs on the @{shell.path|path}.
--
-- @tparam[opt] boolean include_hidden Include hidden files. Namely, any which
-- start with `.`.
-- @treturn { string } A list of available programs.
-- @usage textutils.tabulate(shell.programs())
function shell.programs(include_hidden)
expect(1, include_hidden, "boolean", "nil")
local tItems = {}
-- Add programs from the path
@ -275,7 +361,7 @@ function shell.programs(_bIncludeHidden)
for n = 1, #tList do
local sFile = tList[n]
if not fs.isDir(fs.combine(sPath, sFile)) and
(_bIncludeHidden or string.sub(sFile, 1, 1) ~= ".") then
(include_hidden or string.sub(sFile, 1, 1) ~= ".") then
if #sFile > 4 and sFile:sub(-4) == ".lua" then
sFile = sFile:sub(1, -5)
end
@ -351,6 +437,21 @@ local function completeProgramArgument(sProgram, nArgument, sPart, tPreviousPart
return nil
end
--- Complete a shell command line.
--
-- This accepts an incomplete command, and completes the program name or
-- arguments. For instance, `l` will be completed to `ls`, and `ls ro` will be
-- completed to `ls rom/`.
--
-- Completion handlers for your program may be registered with
-- @{shell.setCompletionFunction}.
--
-- @tparam string sLine The input to complete.
-- @treturn { string }|nil The list of possible completions.
-- @see read For more information about completion.
-- @see shell.completeProgram
-- @see shell.setCompletionFunction
-- @see shell.getCompletionInfo
function shell.complete(sLine)
expect(1, sLine, "string")
if #sLine > 0 then
@ -388,23 +489,66 @@ function shell.complete(sLine)
return nil
end
function shell.completeProgram(sProgram)
expect(1, sProgram, "string")
return completeProgram(sProgram)
--- Complete the name of a program.
--
-- @tparam string program The name of a program to complete.
-- @treturn { string } A list of possible completions.
-- @see cc.shell.completion.program
function shell.completeProgram(program)
expect(1, program, "string")
return completeProgram(program)
end
function shell.setCompletionFunction(sProgram, fnComplete)
expect(1, sProgram, "string")
expect(2, fnComplete, "function")
tCompletionInfo[sProgram] = {
fnComplete = fnComplete,
--- Set the completion function for a program. When the program is entered on
-- the command line, this program will be called to provide auto-complete
-- information.
--
-- The completion function accepts four arguments:
--
-- 1. The current shell. As completion functions are inherited, this is not
-- guaranteed to be the shell you registered this function in.
-- 2. The index of the argument currently being completed.
-- 3. The current argument. This may be the empty string.
-- 4. A list of the previous arguments.
--
-- For instance, when completing `pastebin put rom/st` our pastebin completion
-- function will receive the shell API, an index of 2, `rom/st` as the current
-- argument, and a "previous" table of `{ "put" }`. This function may then wish
-- to return a table containing `artup.lua`, indicating the entire command
-- should be completed to `pastebin put rom/startup.lua`.
--
-- You completion entries may also be followed by a space, if you wish to
-- indicate another argument is expected.
--
-- @tparam string program The path to the program. This should be an absolute path
-- _without_ the leading `/`.
-- @tparam function(shell: table, index: number, argument: string, previous: { string }):({ string }|nil) complete
-- The completion function.
-- @see cc.shell.completion Various utilities to help with writing completion functions.
-- @see shell.complete
-- @see read For more information about completion.
function shell.setCompletionFunction(program, complete)
expect(1, program, "string")
expect(2, complete, "function")
tCompletionInfo[program] = {
fnComplete = complete,
}
end
--- Get a table containing all completion functions.
--
-- This should only be needed when building custom shells. Use
-- @{setCompletionFunction} to add a completion function.
--
-- @treturn { [string] = { fnComplete = function } } A table mapping the
-- absolute path of programs, to their completion functions.
function shell.getCompletionInfo()
return tCompletionInfo
end
--- Returns the path to the currently running program.
--
-- @treturn string The absolute path to the running program.
function shell.getRunningProgram()
if #tProgramStack > 0 then
return tProgramStack[#tProgramStack]
@ -412,17 +556,38 @@ function shell.getRunningProgram()
return nil
end
function shell.setAlias(_sCommand, _sProgram)
expect(1, _sCommand, "string")
expect(2, _sProgram, "string")
tAliases[_sCommand] = _sProgram
--- Add an alias for a program.
--
-- @tparam string command The name of the alias to add.
-- @tparam string program The name or path to the program.
-- @usage Alias `vim` to the `edit` program
--
-- shell.setAlias("vim", "edit")
function shell.setAlias(command, program)
expect(1, command, "string")
expect(2, program, "string")
tAliases[command] = program
end
function shell.clearAlias(_sCommand)
expect(1, _sCommand, "string")
tAliases[_sCommand] = nil
--- Remove an alias.
--
-- @tparam string command The alias name to remove.
function shell.clearAlias(command)
expect(1, command, "string")
tAliases[command] = nil
end
--- Get the current aliases for this shell.
--
-- Aliases are used to allow multiple commands to refer to a single program. For
-- instance, the `list` program is aliased `dir` or `ls`. Running `ls`, `dir` or
-- `list` in the shell will all run the `list` program.
--
-- @treturn { [string] = string } A table, where the keys are the names of
-- aliases, and the values are the path to the program.
-- @see shell.setAlias
-- @see shell.resolveProgram This uses aliases when resolving a program name to
-- an absolute path.
function shell.aliases()
-- Copy aliases
local tCopy = {}
@ -433,6 +598,20 @@ function shell.aliases()
end
if multishell then
--- Open a new @{multishell} tab running a command.
--
-- This behaves similarly to @{shell.run}, but instead returns the process
-- index.
--
-- This function is only available if the @{multishell} API is.
--
-- @tparam string ... The command line to run.
-- @see shell.run
-- @see multishell.launch
-- @usage Launch the Lua interpreter and switch to it.
--
-- local id = shell.openTab("lua")
-- shell.switchTab(id)
function shell.openTab(...)
local tWords = tokenise(...)
local sCommand = tWords[1]
@ -448,9 +627,13 @@ if multishell then
end
end
function shell.switchTab(nID)
expect(1, nID, "number")
multishell.setFocus(nID)
--- Switch to the @{multishell} tab with the given index.
--
-- @tparam number id The tab to switch to.
-- @see multishell.setFocus
function shell.switchTab(id)
expect(1, id, "number")
multishell.setFocus(id)
end
end