-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcmd
340 lines (296 loc) · 7.8 KB
/
cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
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,
}
-- Colors
local promptColor, textColor, bgColor = colors.yellow, colors.white, colors.black
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
local lastColor = term.getTextColor()
term.setTextColor(colors.yellow)
term.writeColored(os.getName(), colors.yellow)
term.writeColored(" [Version " .. os.getVersion() .. "]\n", colors.cyan)
term.writeColored("Copyright (c) EtK2000\n", colors.lime)
term.setTextColor(lastColor)
-- 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)
lastColor = term.getTextColor()
term.setTextColor(colors.yellow)
write(shell.dir() .. "> ")
term.setTextColor(lastColor)
local sLine = read(nil, tCommandHistory, shell.complete)
table.insert(tCommandHistory, sLine)
shell.run(sLine)
-- restore color when programs break it, TODO: maybe only let "color" change the color...
if sLine == "ls" or string.starts(sLine, "ls ") then
term.setTextColor(lastColor)
end
end
end