Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
ComputercraftLua/rom/programs/shell
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
349 lines (304 sloc)
8.94 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local multishell = multishell | |
| local parentShell = shell | |
| local parentTerm = term.current() | |
| if multishell then | |
| multishell.setTitle( multishell.getCurrent(), "shell" ) | |
| end | |
| local bExit = false | |
| local sDir = (parentShell and parentShell.dir()) or "" | |
| local sPath = (parentShell and parentShell.path()) or ".:/rom/programs" | |
| local tAliases = (parentShell and parentShell.aliases()) or {} | |
| local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {} | |
| local tProgramStack = {} | |
| local shell = {} | |
| local tEnv = { | |
| [ "shell" ] = shell, | |
| [ "multishell" ] = multishell, | |
| } | |
| -- Colours | |
| local promptColour, textColour, bgColour | |
| if term.isColour() then | |
| promptColour = colours.yellow | |
| textColour = colours.white | |
| bgColour = colours.black | |
| else | |
| promptColour = colours.white | |
| textColour = colours.white | |
| bgColour = colours.black | |
| end | |
| local function run( _sCommand, ... ) | |
| local sPath = shell.resolveProgram( _sCommand ) | |
| if sPath ~= nil then | |
| tProgramStack[#tProgramStack + 1] = sPath | |
| if multishell then | |
| multishell.setTitle( multishell.getCurrent(), fs.getName( sPath ) ) | |
| end | |
| local result = os.run( tEnv, sPath, ... ) | |
| tProgramStack[#tProgramStack] = nil | |
| if multishell then | |
| if #tProgramStack > 0 then | |
| multishell.setTitle( multishell.getCurrent(), fs.getName( tProgramStack[#tProgramStack] ) ) | |
| else | |
| multishell.setTitle( multishell.getCurrent(), "shell" ) | |
| end | |
| end | |
| return result | |
| else | |
| printError( "No such program" ) | |
| return false | |
| end | |
| end | |
| local function tokenise( ... ) | |
| local sLine = table.concat( { ... }, " " ) | |
| local tWords = {} | |
| local bQuoted = false | |
| for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do | |
| if bQuoted then | |
| table.insert( tWords, match ) | |
| else | |
| for m in string.gmatch( match, "[^ \t]+" ) do | |
| table.insert( tWords, m ) | |
| end | |
| end | |
| bQuoted = not bQuoted | |
| end | |
| return tWords | |
| end | |
| -- Install shell API | |
| function shell.run( ... ) | |
| local tWords = tokenise( ... ) | |
| local sCommand = tWords[1] | |
| if sCommand then | |
| return run( sCommand, table.unpack( tWords, 2 ) ) | |
| end | |
| return false | |
| end | |
| function shell.exit() | |
| bExit = true | |
| end | |
| function shell.dir() | |
| return sDir | |
| end | |
| function shell.setDir( _sDir ) | |
| sDir = _sDir | |
| end | |
| function shell.path() | |
| return sPath | |
| end | |
| function shell.setPath( _sPath ) | |
| sPath = _sPath | |
| end | |
| function shell.resolve( _sPath ) | |
| local sStartChar = string.sub( _sPath, 1, 1 ) | |
| if sStartChar == "/" or sStartChar == "\\" then | |
| return fs.combine( "", _sPath ) | |
| else | |
| return fs.combine( sDir, _sPath ) | |
| end | |
| end | |
| function shell.resolveProgram( _sCommand ) | |
| -- Substitute aliases firsts | |
| if tAliases[ _sCommand ] ~= nil then | |
| _sCommand = tAliases[ _sCommand ] | |
| end | |
| -- If the path is a global path, use it directly | |
| local sStartChar = string.sub( _sCommand, 1, 1 ) | |
| if sStartChar == "/" or sStartChar == "\\" then | |
| local sPath = fs.combine( "", _sCommand ) | |
| if fs.exists( sPath ) and not fs.isDir( sPath ) then | |
| return sPath | |
| end | |
| return nil | |
| end | |
| -- Otherwise, look on the path variable | |
| for sPath in string.gmatch(sPath, "[^:]+") do | |
| sPath = fs.combine( shell.resolve( sPath ), _sCommand ) | |
| if fs.exists( sPath ) and not fs.isDir( sPath ) then | |
| return sPath | |
| end | |
| end | |
| -- Not found | |
| return nil | |
| end | |
| function shell.programs( _bIncludeHidden ) | |
| local tItems = {} | |
| -- Add programs from the path | |
| for sPath in string.gmatch(sPath, "[^:]+") do | |
| sPath = shell.resolve( sPath ) | |
| if fs.isDir( sPath ) then | |
| local tList = fs.list( sPath ) | |
| 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 | |
| tItems[ sFile ] = true | |
| end | |
| end | |
| end | |
| end | |
| -- Sort and return | |
| local tItemList = {} | |
| for sItem, b in pairs( tItems ) do | |
| table.insert( tItemList, sItem ) | |
| end | |
| table.sort( tItemList ) | |
| return tItemList | |
| end | |
| local function completeProgram( sLine ) | |
| if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then | |
| -- Add programs from the root | |
| return fs.complete( sLine, "", true, false ) | |
| else | |
| local tResults = {} | |
| local tSeen = {} | |
| -- Add aliases | |
| for sAlias, sCommand in pairs( tAliases ) do | |
| if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then | |
| local sResult = string.sub( sAlias, #sLine + 1 ) | |
| if not tSeen[ sResult ] then | |
| table.insert( tResults, sResult ) | |
| tSeen[ sResult ] = true | |
| end | |
| end | |
| end | |
| -- Add programs from the path | |
| local tPrograms = shell.programs() | |
| for n=1,#tPrograms do | |
| local sProgram = tPrograms[n] | |
| if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then | |
| local sResult = string.sub( sProgram, #sLine + 1 ) | |
| if not tSeen[ sResult ] then | |
| table.insert( tResults, sResult ) | |
| tSeen[ sResult ] = true | |
| end | |
| end | |
| end | |
| -- Sort and return | |
| table.sort( tResults ) | |
| return tResults | |
| end | |
| end | |
| local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts ) | |
| local tInfo = tCompletionInfo[ sProgram ] | |
| if tInfo then | |
| return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts ) | |
| end | |
| return nil | |
| end | |
| function shell.complete( sLine ) | |
| if #sLine > 0 then | |
| local tWords = tokenise( sLine ) | |
| local nIndex = #tWords | |
| if string.sub( sLine, #sLine, #sLine ) == " " then | |
| nIndex = nIndex + 1 | |
| end | |
| if nIndex == 1 then | |
| local sBit = tWords[1] or "" | |
| local sPath = shell.resolveProgram( sBit ) | |
| if tCompletionInfo[ sPath ] then | |
| return { " " } | |
| else | |
| local tResults = completeProgram( sBit ) | |
| for n=1,#tResults do | |
| local sResult = tResults[n] | |
| local sPath = shell.resolveProgram( sBit .. sResult ) | |
| if tCompletionInfo[ sPath ] then | |
| tResults[n] = sResult .. " " | |
| end | |
| end | |
| return tResults | |
| end | |
| elseif nIndex > 1 then | |
| local sPath = shell.resolveProgram( tWords[1] ) | |
| local sPart = tWords[nIndex] or "" | |
| local tPreviousParts = tWords | |
| tPreviousParts[nIndex] = nil | |
| return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts ) | |
| end | |
| end | |
| return nil | |
| end | |
| function shell.completeProgram( sProgram ) | |
| return completeProgram( sProgram ) | |
| end | |
| function shell.setCompletionFunction( sProgram, fnComplete ) | |
| tCompletionInfo[ sProgram ] = { | |
| fnComplete = fnComplete | |
| } | |
| end | |
| function shell.getCompletionInfo() | |
| return tCompletionInfo | |
| end | |
| function shell.getRunningProgram() | |
| if #tProgramStack > 0 then | |
| return tProgramStack[#tProgramStack] | |
| end | |
| return nil | |
| end | |
| function shell.setAlias( _sCommand, _sProgram ) | |
| tAliases[ _sCommand ] = _sProgram | |
| end | |
| function shell.clearAlias( _sCommand ) | |
| tAliases[ _sCommand ] = nil | |
| end | |
| function shell.aliases() | |
| -- Copy aliases | |
| local tCopy = {} | |
| for sAlias, sCommand in pairs( tAliases ) do | |
| tCopy[sAlias] = sCommand | |
| end | |
| return tCopy | |
| end | |
| if multishell then | |
| function shell.openTab( ... ) | |
| local tWords = tokenise( ... ) | |
| local sCommand = tWords[1] | |
| if sCommand then | |
| local sPath = shell.resolveProgram( sCommand ) | |
| if sPath == "rom/programs/shell" then | |
| return multishell.launch( tEnv, sPath, table.unpack( tWords, 2 ) ) | |
| elseif sPath ~= nil then | |
| return multishell.launch( tEnv, "rom/programs/shell", sCommand, table.unpack( tWords, 2 ) ) | |
| else | |
| printError( "No such program" ) | |
| end | |
| end | |
| end | |
| function shell.switchTab( nID ) | |
| multishell.setFocus( nID ) | |
| end | |
| end | |
| local tArgs = { ... } | |
| if #tArgs > 0 then | |
| -- "shell x y z" | |
| -- Run the program specified on the commandline | |
| shell.run( ... ) | |
| else | |
| -- "shell" | |
| -- Print the header | |
| term.setBackgroundColor( bgColour ) | |
| term.setTextColour( promptColour ) | |
| print( os.version() ) | |
| term.setTextColour( textColour ) | |
| -- Run the startup program | |
| if parentShell == nil then | |
| shell.run( "/rom/startup" ) | |
| end | |
| -- Read commands and execute them | |
| local tCommandHistory = {} | |
| while not bExit do | |
| term.redirect( parentTerm ) | |
| term.setBackgroundColor( bgColour ) | |
| term.setTextColour( promptColour ) | |
| write( shell.dir() .. "> " ) | |
| term.setTextColour( textColour ) | |
| local sLine | |
| if settings.get( "shell.autocomplete" ) then | |
| sLine = read( nil, tCommandHistory, shell.complete ) | |
| else | |
| sLine = read( nil, tCommandHistory ) | |
| end | |
| table.insert( tCommandHistory, sLine ) | |
| shell.run( sLine ) | |
| end | |
| end |