Advertisement
briannovius

[SQUIDDEV] Mildly better shell

Jul 25th, 2018
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.65 KB | None | 0 0
  1. local arg = table.pack(...)
  2. local root_dir = ".mbs"
  3. local rom_dir = "rom/.mbs"
  4. local install_dir = fs.exists(root_dir) and root_dir or rom_dir
  5. local repo_url = "https://raw.githubusercontent.com/SquidDev-CC/mbs/master/"
  6.  
  7. --- Write a string with the given colour to the terminal
  8. local function write_coloured(colour, text)
  9. local old = term.getTextColour()
  10. term.setTextColour(colour)
  11. write(text)
  12. term.setTextColour(old)
  13. end
  14.  
  15. --- Print usage for this program
  16. local commands = { "install", "modules", "module", "download" }
  17. local function print_usage()
  18. local name = fs.getName(shell.getRunningProgram()):gsub("%.lua$", "")
  19. write_coloured(colours.cyan, name .. " modules ") print("Print the status of all modules")
  20. write_coloured(colours.cyan, name .. " module ") print("Print information about a given module")
  21. write_coloured(colours.cyan, name .. " install ") print("Download all modules and create a startup file")
  22. write_coloured(colours.cyan, name .. " download ") print("Download all modules WITHOUT creating a startup file")
  23. end
  24.  
  25. --- Attempt to load a module from the given path, returning the module or false
  26. -- and an error message.
  27. local function load_module(path)
  28. if fs.isDir(path) then return false, "Invalid module (is directory)" end
  29.  
  30. local fn, err = loadfile(path, _ENV)
  31. if not fn then return false, "Invalid module (" .. err .. ")" end
  32.  
  33. local ok, res = pcall(fn)
  34. if not ok then return false, "Invalid module (" .. res .. ")" end
  35.  
  36. if type(res) ~= "table" or type(res.description) ~= "string" or type(res.enabled) ~= "function" then
  37. return false, "Malformed module"
  38. end
  39.  
  40. return res
  41. end
  42.  
  43. --- Setup all modules
  44. local function setup_module(module)
  45. for _, setting in ipairs(module.settings) do
  46. if settings.get(setting.name) == nil then
  47. settings.set(setting.name, setting.default)
  48. end
  49. end
  50. end
  51.  
  52. --- Download a set of files
  53. local function download_files(files)
  54. if #files == 0 then return end
  55.  
  56. local urls = {}
  57. for _, file in ipairs(files) do
  58. local url = repo_url .. file
  59. http.request(url)
  60. urls[url] = file
  61. end
  62.  
  63. while true do
  64. local event, url, arg1 = os.pullEvent()
  65. if event == "http_success" and urls[url] then
  66. local handle = fs.open(fs.combine(root_dir, urls[url]), "w")
  67. handle.write(arg1.readAll())
  68. handle.close()
  69. arg1.close()
  70.  
  71. urls[url] = nil
  72. if next(urls) == nil then return end
  73. elseif event == "http_failure" and urls[url] then
  74. error("Could not download " .. urls[url], 0)
  75. end
  76. end
  77. end
  78.  
  79. --- read completion helper, completes text using the given options
  80. local function complete_multi(text, options, add_spaces)
  81. local results = {}
  82. for n = 1, #options do
  83. local option = options[n]
  84. if #option + (add_spaces and 1 or 0) > #text and option:sub(1, #text) == text then
  85. local result = option:sub(#text + 1)
  86. if add_spaces then
  87. results[#results + 1] = result .. " "
  88. else
  89. results[#results + 1] = result
  90. end
  91. end
  92. end
  93. return results
  94. end
  95.  
  96. --- Append an object to a list if it is not already contained within
  97. local function add_unique(list, x)
  98. for i = 1, #list do if list[i] == x then return end end
  99. list[#list + 1] = x
  100. end
  101.  
  102. local function load_all_modules()
  103. -- Load all modules and update them.
  104. local module_dir = fs.combine(root_dir, "modules")
  105. local modules = fs.isDir(module_dir) and fs.list(module_dir) or {}
  106.  
  107. -- Add the default modules if not already there.
  108. for _, module in ipairs { "lua.lua", "pager.lua", "readline.lua", "shell.lua" } do
  109. add_unique(modules, module)
  110. end
  111.  
  112. local files = {}
  113. for i = 1, #modules do files[i] = "modules/" .. modules[i] end
  114. download_files(files)
  115.  
  116. -- Scan for dependencies in enabled modules, downloading them as well
  117. local deps = {}
  118. for i = 1, #files do
  119. local module = load_module(fs.combine(root_dir, files[i]))
  120. if module then
  121. setup_module(module)
  122. if module.enabled() then
  123. for _, dep in ipairs(module.dependencies) do deps[#deps + 1] = dep end
  124. end
  125. end
  126. end
  127. download_files(deps)
  128. end
  129.  
  130. if arg.n == 0 then
  131. printError("Expected some command")
  132. print_usage()
  133. error()
  134. elseif arg[1] == "download" then
  135. load_all_modules()
  136. elseif arg[1] == "install" then
  137. load_all_modules()
  138.  
  139. -- If we're on CC 1.80 then we'll create a startup directory and use that.
  140. if fs.exists("rom/startup.lua") then
  141. -- Move the existing startup file if required
  142. if fs.exists("startup") and not fs.isDir("startup")then
  143. local handle = fs.open("startup", "r")
  144. local contents = handle.readAll()
  145. handle.close()
  146. fs.delete("startup")
  147.  
  148. handle = fs.open("startup/00_init.lua", "w")
  149. handle.write(contents)
  150. handle.close()
  151. end
  152.  
  153. -- We'll write at the last posible position
  154. local handle = fs.open("startup/99_mbs.lua", "w")
  155. handle.writeLine(("shell.run(%q)"):format(shell.getRunningProgram() .. " startup"))
  156. handle.close()
  157. else
  158. -- Otherwise just append to the startup file
  159.  
  160. -- A rather ugly hack to determine if we have an uncommented "mbs startup" somewhere
  161. -- in the file.
  162. -- Note this doesn't handle block comments, but it's good enough.
  163. local contains = false
  164. local body = ("shell.run(%q)"):format(shell.getRunningProgram() .. " startup")
  165. if fs.exists("startup") then
  166. local handle = fs.open("startup", "r")
  167. contains = ("\n" .. handle.readAll() .. "\n"):find("\n" .. body .. "\n", 1, true)
  168. handle.close()
  169. end
  170.  
  171. -- If we've no existing "mbs startup" then append it to the end.
  172. if not contains then
  173. local handle = fs.open("startup", "a")
  174. handle.writeLine(body)
  175. handle.close()
  176. end
  177. end
  178.  
  179. print("Please reboot to apply changes.")
  180. elseif arg[1] == "startup" then
  181. -- Gather a list of all modules
  182. local module_dir = fs.combine(install_dir, "modules")
  183. local files = fs.isDir(module_dir) and fs.list(module_dir) or {}
  184.  
  185. -- Load those modules and determine which are enabled.
  186. local enabled = {}
  187. local module_names = {}
  188. for _, file in ipairs(files) do
  189. local module = load_module(fs.combine(module_dir, file))
  190. if module then
  191. setup_module(module)
  192. module_names[#module_names + 1] = file:gsub("%.lua$", "")
  193. if module.enabled() then enabled[#enabled + 1] = module end
  194. end
  195. end
  196.  
  197. shell.setCompletionFunction(shell.getRunningProgram(), function(shell, index, text, previous)
  198. if index == 1 then
  199. return complete_multi(text, commands, true)
  200. elseif index == 2 and previous[#previous] == "module" then
  201. return complete_multi(text, module_names, false)
  202. end
  203. end)
  204.  
  205. -- Setup those modules
  206. for _, module in ipairs(enabled) do
  207. if type(module.setup) == "function" then module.setup(install_dir) end
  208. end
  209.  
  210. -- And run the startup hook if needed
  211. for _, module in ipairs(enabled) do
  212. if type(module.startup) == "function" then module.startup(install_dir) end
  213. end
  214.  
  215. elseif arg[1] == "modules" then
  216. local module_dir = fs.combine(install_dir, "modules")
  217. local files = fs.isDir(module_dir) and fs.list(module_dir) or {}
  218. local found_any = false
  219.  
  220. for _, file in ipairs(files) do
  221. local res, err = load_module(fs.combine(module_dir, file))
  222. write_coloured(colours.cyan, file:gsub("%.lua$", "") .. " ")
  223. if res then
  224. write(res.description)
  225. if res.enabled() then
  226. write_coloured(colours.green, " (enabled)")
  227. else
  228. write_coloured(colours.red, " (disabled)")
  229. end
  230. found_any = true
  231. else
  232. write_coloured(colours.red, err)
  233. end
  234.  
  235. print()
  236. end
  237.  
  238. if not found_any then error("No modules found. Maybe try running the `install` command?", 0) end
  239. elseif arg[1] == "module" then
  240. if not arg[2] then error("Expected module name", 0) end
  241. local module, err = load_module(fs.combine(install_dir, fs.combine("modules", arg[2] .. ".lua")))
  242. if not module then error(err, 0) end
  243.  
  244. write(module.description)
  245. if module.enabled() then
  246. write_coloured(colours.green, " (enabled)")
  247. else
  248. write_coloured(colours.red, " (disabled)")
  249. end
  250. print()
  251.  
  252. print()
  253.  
  254. for _, setting in ipairs(module.settings) do
  255. local value = settings.get(setting.name)
  256. write_coloured(colours.cyan, setting.name)
  257. write(" " .. setting.description .. " (")
  258. write_coloured(colours.yellow, textutils.serialise(value))
  259. if value ~= setting.default then
  260. write(", default is ")
  261. write_coloured(colours.yellow, textutils.serialise(setting.default))
  262. end
  263.  
  264. write(")")
  265. print()
  266. end
  267. else
  268. printError("Unknown command")
  269. print_usage()
  270. error()
  271. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement