Advertisement
Guest User

shell.lua

a guest
Jun 27th, 2019
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.98 KB | None | 0 0
  1. local expect = _G["~expect"]
  2.  
  3. local multishell = multishell
  4. local parentShell = shell
  5. local parentTerm = term.current()
  6.  
  7. if multishell then
  8.     multishell.setTitle( multishell.getCurrent(), "shell" )
  9. end
  10.  
  11. local bExit = false
  12. local sDir = (parentShell and parentShell.dir()) or ""
  13. local sPath = (parentShell and parentShell.path()) or ".:/rom/programs"
  14. local tAliases = (parentShell and parentShell.aliases()) or {}
  15. local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
  16. local tProgramStack = {}
  17.  
  18. local shell = {}
  19. local function createShellEnv( sDir )
  20.     local tEnv = {}
  21.     tEnv[ "shell" ] = shell
  22.     tEnv[ "multishell" ] = multishell
  23.  
  24.     local package = {}
  25.     package.loaded = {
  26.         _G = _G,
  27.         bit32 = bit32,
  28.         coroutine = coroutine,
  29.         math = math,
  30.         package = package,
  31.         string = string,
  32.         table = table,
  33.     }
  34.     package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
  35.     if turtle then
  36.         package.path = package.path..";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
  37.     elseif command then
  38.         package.path = package.path..";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
  39.     end
  40.     package.config = "/\n;\n?\n!\n-"
  41.     package.preload = {}
  42.     package.loaders = {
  43.         function( name )
  44.             if package.preload[name] then
  45.                 return package.preload[name]
  46.             else
  47.                 return nil, "no field package.preload['" .. name .. "']"
  48.             end
  49.         end,
  50.         function( name )
  51.             local fname = string.gsub(name, "%.", "/")
  52.             local sError = ""
  53.             for pattern in string.gmatch(package.path, "[^;]+") do
  54.                 local sPath = string.gsub(pattern, "%?", fname)
  55.                 if sPath:sub(1,1) ~= "/" then
  56.                     sPath = fs.combine(sDir, sPath)
  57.                 end
  58.                 if fs.exists(sPath) and not fs.isDir(sPath) then
  59.                     local fnFile, sError = loadfile( sPath, tEnv )
  60.                     if fnFile then
  61.                         return fnFile, sPath
  62.                     else
  63.                         return nil, sError
  64.                     end
  65.                 else
  66.                     if #sError > 0 then
  67.                         sError = sError .. "\n  "
  68.                     end
  69.                     sError = sError .. "no file '" .. sPath .. "'"
  70.                 end
  71.             end
  72.             return nil, sError
  73.         end
  74.     }
  75.  
  76.     local sentinel = {}
  77.     local function require( name )
  78.         expect(1, name, "string")
  79.         if package.loaded[name] == sentinel then
  80.             error("loop or previous error loading module '" .. name .. "'", 0)
  81.         end
  82.         if package.loaded[name] then
  83.             return package.loaded[name]
  84.         end
  85.  
  86.         local sError = "module '" .. name .. "' not found:"
  87.         for _, searcher in ipairs(package.loaders) do
  88.             local loader = table.pack(searcher(name))
  89.             if loader[1] then
  90.                 package.loaded[name] = sentinel
  91.                 local result = loader[1](name, table.unpack(loader, 2, loader.n))
  92.                 if result == nil then result = true end
  93.  
  94.                 package.loaded[name] = result
  95.                 return result
  96.             else
  97.                 sError = sError .. "\n  " .. loader[2]
  98.             end
  99.         end
  100.         error(sError, 2)
  101.     end
  102.  
  103.     tEnv["package"] = package
  104.     tEnv["require"] = require
  105.  
  106.     return tEnv
  107. end
  108.  
  109. -- Colours
  110. local promptColour, textColour, bgColour
  111. if term.isColour() then
  112.     promptColour = colours.yellow
  113.     textColour = colours.white
  114.     bgColour = colours.black
  115. else
  116.     promptColour = colours.white
  117.     textColour = colours.white
  118.     bgColour = colours.black
  119. end
  120.  
  121. local function run( _sCommand, ... )
  122.     local sPath = shell.resolveProgram( _sCommand )
  123.     if sPath ~= nil then
  124.         tProgramStack[#tProgramStack + 1] = sPath
  125.         if multishell then
  126.             local sTitle = fs.getName( sPath )
  127.             if sTitle:sub(-4) == ".lua" then
  128.                 sTitle = sTitle:sub(1,-5)
  129.             end
  130.             multishell.setTitle( multishell.getCurrent(), sTitle )
  131.         end
  132.  
  133.         local sDir = fs.getDir( sPath )
  134.         local env = createShellEnv( sDir )
  135.         env[ "arg" ] = { [0] = _sCommand, ... }
  136.         local result = os.run( env, sPath, ... )
  137.  
  138.         tProgramStack[#tProgramStack] = nil
  139.         if multishell then
  140.             if #tProgramStack > 0 then
  141.                 local sTitle = fs.getName( tProgramStack[#tProgramStack] )
  142.                 if sTitle:sub(-4) == ".lua" then
  143.                     sTitle = sTitle:sub(1,-5)
  144.                 end
  145.                 multishell.setTitle( multishell.getCurrent(), sTitle )
  146.             else
  147.                 multishell.setTitle( multishell.getCurrent(), "shell" )
  148.             end
  149.         end
  150.         return result
  151.        else
  152.         printError( "No such program" )
  153.         return false
  154.     end
  155. end
  156.  
  157. local function tokenise( ... )
  158.     local sLine = table.concat( { ... }, " " )
  159.     local tWords = {}
  160.     local bQuoted = false
  161.     for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
  162.         if bQuoted then
  163.             table.insert( tWords, match )
  164.         else
  165.             for m in string.gmatch( match, "[^ \t]+" ) do
  166.                 table.insert( tWords, m )
  167.             end
  168.         end
  169.         bQuoted = not bQuoted
  170.     end
  171.     return tWords
  172. end
  173.  
  174. -- Install shell API
  175. function shell.run( ... )
  176.     local tWords = tokenise( ... )
  177.     local sCommand = tWords[1]
  178.     if sCommand then
  179.         return run( sCommand, table.unpack( tWords, 2 ) )
  180.     end
  181.     return false
  182. end
  183.  
  184. function shell.exit()
  185.     bExit = true
  186. end
  187.  
  188. function shell.dir()
  189.     return sDir
  190. end
  191.  
  192. function shell.setDir( _sDir )
  193.     expect(1, _sDir, "string")
  194.     if not fs.isDir( _sDir ) then
  195.         error( "Not a directory", 2 )
  196.     end
  197.     sDir = _sDir
  198. end
  199.  
  200. function shell.path()
  201.     return sPath
  202. end
  203.  
  204. function shell.setPath( _sPath )
  205.     expect(1, _sPath, "string")
  206.     sPath = _sPath
  207. end
  208.  
  209. function shell.resolve( _sPath )
  210.     expect(1, _sPath, "string")
  211.     local sStartChar = string.sub( _sPath, 1, 1 )
  212.     if sStartChar == "/" or sStartChar == "\\" then
  213.         return fs.combine( "", _sPath )
  214.     else
  215.         return fs.combine( sDir, _sPath )
  216.     end
  217. end
  218.  
  219. local function pathWithExtension( _sPath, _sExt )
  220.     local nLen = #sPath
  221.     local sEndChar = string.sub( _sPath, nLen, nLen )
  222.     -- Remove any trailing slashes so we can add an extension to the path safely
  223.     if sEndChar == "/" or sEndChar == "\\" then
  224.         _sPath = string.sub( _sPath, 1, nLen - 1 )
  225.     end
  226.     return _sPath .. "." .. _sExt
  227. end
  228.  
  229. function shell.resolveProgram( _sCommand )
  230.     expect(1, _sCommand, "string")
  231.     -- Substitute aliases firsts
  232.     if tAliases[ _sCommand ] ~= nil then
  233.         _sCommand = tAliases[ _sCommand ]
  234.     end
  235.  
  236.     -- If the path is a global path, use it directly
  237.     if _sCommand:find("/") or _sCommand:find("\\") then
  238.         local sPath = shell.resolve( _sCommand )
  239.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  240.             return sPath
  241.         else
  242.             local sPathLua = pathWithExtension( sPath, "lua" )
  243.             if fs.exists( sPathLua ) and not fs.isDir( sPathLua ) then
  244.                 return sPathLua
  245.             end
  246.         end
  247.         return nil
  248.     end
  249.  
  250.      -- Otherwise, look on the path variable
  251.     for sPath in string.gmatch(sPath, "[^:]+") do
  252.         sPath = fs.combine( shell.resolve( sPath ), _sCommand )
  253.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  254.             return sPath
  255.         else
  256.             local sPathLua = pathWithExtension( sPath, "lua" )
  257.             if fs.exists( sPathLua ) and not fs.isDir( sPathLua ) then
  258.                 return sPathLua
  259.             end
  260.         end
  261.     end
  262.  
  263.     -- Not found
  264.     return nil
  265. end
  266.  
  267. function shell.programs( _bIncludeHidden )
  268.     local tItems = {}
  269.  
  270.     -- Add programs from the path
  271.     for sPath in string.gmatch(sPath, "[^:]+") do
  272.         sPath = shell.resolve( sPath )
  273.         if fs.isDir( sPath ) then
  274.             local tList = fs.list( sPath )
  275.             for n=1,#tList do
  276.                 local sFile = tList[n]
  277.                 if not fs.isDir( fs.combine( sPath, sFile ) ) and
  278.                    (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
  279.                     if #sFile > 4 and sFile:sub(-4) == ".lua" then
  280.                         sFile = sFile:sub(1,-5)
  281.                     end
  282.                     tItems[ sFile ] = true
  283.                 end
  284.             end
  285.         end
  286.     end
  287.  
  288.     -- Sort and return
  289.     local tItemList = {}
  290.     for sItem, b in pairs( tItems ) do
  291.         table.insert( tItemList, sItem )
  292.     end
  293.     table.sort( tItemList )
  294.     return tItemList
  295. end
  296.  
  297. local function completeProgram( sLine )
  298.     if #sLine > 0 and (sLine:find("/") or sLine:find("\\")) then
  299.         -- Add programs from the root
  300.         return fs.complete( sLine, sDir, true, false )
  301.  
  302.     else
  303.         local tResults = {}
  304.         local tSeen = {}
  305.  
  306.         -- Add aliases
  307.         for sAlias, sCommand in pairs( tAliases ) do
  308.             if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
  309.                 local sResult = string.sub( sAlias, #sLine + 1 )
  310.                 if not tSeen[ sResult ] then
  311.                     table.insert( tResults, sResult )
  312.                     tSeen[ sResult ] = true
  313.                 end
  314.             end
  315.         end
  316.  
  317.         -- Add all subdirectories. We don't include files as they will be added in the block below
  318.         local tDirs = fs.complete( sLine, sDir, false, false )
  319.         for i = 1, #tDirs do
  320.             local sResult = tDirs[i]
  321.             if not tSeen[ sResult ] then
  322.                 table.insert ( tResults, sResult )
  323.                 tSeen [ sResult ] = true
  324.             end
  325.         end
  326.  
  327.         -- Add programs from the path
  328.         local tPrograms = shell.programs()
  329.         for n=1,#tPrograms do
  330.             local sProgram = tPrograms[n]
  331.             if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
  332.                 local sResult = string.sub( sProgram, #sLine + 1 )
  333.                 if not tSeen[ sResult ] then
  334.                     table.insert( tResults, sResult )
  335.                     tSeen[ sResult ] = true
  336.                 end
  337.             end
  338.         end
  339.  
  340.         -- Sort and return
  341.         table.sort( tResults )
  342.         return tResults
  343.     end
  344. end
  345.  
  346. local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
  347.     local tInfo = tCompletionInfo[ sProgram ]
  348.     if tInfo then
  349.         return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
  350.     end
  351.     return nil
  352. end
  353.  
  354. function shell.complete( sLine )
  355.     expect(1, sLine, "string")
  356.     if #sLine > 0 then
  357.         local tWords = tokenise( sLine )
  358.         local nIndex = #tWords
  359.         if string.sub( sLine, #sLine, #sLine ) == " " then
  360.             nIndex = nIndex + 1
  361.         end
  362.         if nIndex == 1 then
  363.             local sBit = tWords[1] or ""
  364.             local sPath = shell.resolveProgram( sBit )
  365.             if tCompletionInfo[ sPath ] then
  366.                 return { " " }
  367.             else
  368.                 local tResults = completeProgram( sBit )
  369.                 for n=1,#tResults do
  370.                     local sResult = tResults[n]
  371.                     local sPath = shell.resolveProgram( sBit .. sResult )
  372.                     if tCompletionInfo[ sPath ] then
  373.                         tResults[n] = sResult .. " "
  374.                     end
  375.                 end
  376.                 return tResults
  377.             end
  378.  
  379.         elseif nIndex > 1 then
  380.             local sPath = shell.resolveProgram( tWords[1] )
  381.             local sPart = tWords[nIndex] or ""
  382.             local tPreviousParts = tWords
  383.             tPreviousParts[nIndex] = nil
  384.             return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts )
  385.  
  386.         end
  387.     end
  388.     return nil
  389. end
  390.  
  391. function shell.completeProgram( sProgram )
  392.     expect(1, sProgram, "string")
  393.     return completeProgram( sProgram )
  394. end
  395.  
  396. function shell.setCompletionFunction( sProgram, fnComplete )
  397.     expect(1, sProgram, "string")
  398.     expect(2, fnComplete, "function")
  399.     tCompletionInfo[ sProgram ] = {
  400.         fnComplete = fnComplete
  401.     }
  402. end
  403.  
  404. function shell.getCompletionInfo()
  405.     return tCompletionInfo
  406. end
  407.  
  408. function shell.getRunningProgram()
  409.     if #tProgramStack > 0 then
  410.         return tProgramStack[#tProgramStack]
  411.     end
  412.     return nil
  413. end
  414.  
  415. function shell.setAlias( _sCommand, _sProgram )
  416.     expect(1, _sCommand, "string")
  417.     expect(2, _sProgram, "string")
  418.     tAliases[ _sCommand ] = _sProgram
  419. end
  420.  
  421. function shell.clearAlias( _sCommand )
  422.     expect(1, _sCommand, "string")
  423.     tAliases[ _sCommand ] = nil
  424. end
  425.  
  426. function shell.aliases()
  427.     -- Copy aliases
  428.     local tCopy = {}
  429.     for sAlias, sCommand in pairs( tAliases ) do
  430.         tCopy[sAlias] = sCommand
  431.     end
  432.     return tCopy
  433. end
  434.  
  435. if multishell then
  436.     function shell.openTab( ... )
  437.         local tWords = tokenise( ... )
  438.         local sCommand = tWords[1]
  439.         if sCommand then
  440.             local sPath = shell.resolveProgram( sCommand )
  441.             if sPath == "rom/programs/shell.lua" then
  442.                 return multishell.launch( createShellEnv( "rom/programs" ), sPath, table.unpack( tWords, 2 ) )
  443.             elseif sPath ~= nil then
  444.                 return multishell.launch( createShellEnv( "rom/programs" ), "rom/programs/shell.lua", sCommand, table.unpack( tWords, 2 ) )
  445.             else
  446.                 printError( "No such program" )
  447.             end
  448.         end
  449.     end
  450.  
  451.     function shell.switchTab( nID )
  452.         expect(1, nID, "number")
  453.         multishell.setFocus( nID )
  454.     end
  455. end
  456.  
  457. local tArgs = { ... }
  458. if #tArgs > 0 then
  459.     -- "shell x y z"
  460.     -- Run the program specified on the commandline
  461.     shell.run( ... )
  462.  
  463. else
  464.     -- "shell"
  465.     -- Print the header
  466.     term.setBackgroundColor( bgColour )
  467.     term.setTextColour( promptColour )
  468.     print( os.version() )
  469.     term.setTextColour( textColour )
  470.  
  471.     -- Run the startup program
  472.     if parentShell == nil then
  473.         shell.run( "/rom/startup.lua" )
  474.     end
  475.  
  476.     -- Read commands and execute them
  477.     local tCommandHistory = {}
  478.     while not bExit do
  479.         term.redirect( parentTerm )
  480.         term.setBackgroundColor( bgColour )
  481.         term.setTextColour( promptColour )
  482.         write( shell.dir() .. "> " )
  483.         term.setTextColour( textColour )
  484.  
  485.  
  486.         local sLine
  487.         if settings.get( "shell.autocomplete" ) then
  488.             sLine = read( nil, tCommandHistory, shell.complete )
  489.         else
  490.             sLine = read( nil, tCommandHistory )
  491.         end
  492.         if sLine:match("%S") and tCommandHistory[#tCommandHistory] ~= sLine then
  493.             table.insert( tCommandHistory, sLine )
  494.         end
  495.         shell.run( sLine )
  496.     end
  497. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement