Mini Micro takes a simplistic and unified approach to the command line: the command line is just a MiniScript REPL. There are global functions available for your typical shell commands, such cd
(change directory), pwd
(print working directory), and run
. But, because this is a MiniScript REPL, you're typing in MiniScript syntax. That means that all string literals (such as file or path names) have to be quoted. And you certainly can't type the name of a file to run it.
All this is fine once you get used to it, but often causes a stumbling block for new users. And even experienced users may miss some of the niceties of their favorite command shell.
Fortunately, Mini Micro is also made to be highly customizable! And that even gives you the tools to create your own command shell, exactly as you like it.
Introducing mash
Here we are going to build a little command shell called mash
(a portmanteau of "Mini Bash"). Begin by launching Mini Micro, and using the edit
command to start editing a new program. Then type or paste the following:
// Mini Bash, or "mash"
import "stringUtil"
mashPath = "/usr/mash"
mash = function
env.shell = mashPath
exit
end function
_savedGlobals.mash = @mash
splitQuoted = function(s, delim=" ", quoteChar="""")
a = s.split(quoteChar)
b = []
for n in a.indexes
if n % 2 == 0 then
for m in a[n].split(delim)
if m then b.push m
end for
else
b.push a[n]
end if
end for
return b
end function
This gets the ball rolling by defining where this mash
program is going to live, making a function to re-invoke it as the shell (by assigning the right path to env.shell
, and then exit
ing MiniScript to return to that shell), and making sure this mash
command survives a reset
by stuffing it into _savedGlobals
. Then, because the current version of /sys/lib/stringUtil does not include a splitQuoted
function, we define a simple one here.
Save this program as /usr/mash.ms, then continue editing. We're going to add a commands
map, with one entry (or sometimes two) for each command we want to support.
commands = {}
commands.cd = function(args)
if args.len < 2 then
cd
else if args.len > 2 then
print "cd: Too many arguments."
_printMark "Usage: `cd path`"
else
cd args[1]
end if
commands.pwd
end function
commands.clear = function(args)
clear
text.color = color.orange
end function
commands.cls = @commands.clear // make "cls" an alias for "clear"
commands.exit = function(args)
_printMark "Enter `mash` to return to the mash shell."
env.shell = ""
exit
end function
commands.ls = function(args)
if args.len == 2 then
olddir = file.curdir
cd args[1]
files = file.children
cd olddir
else
files = file.children
end if
files.sort
for name in files
if text.column > 51 then
print
else
text.column = ceil(text.column/17)*17
end if
print name, ""
end for
if text.column > 0 then print
end function
commands.dir = @commands.ls // make "dir" an alias for "ls"
commands.pwd = function(args)
print file.curdir
end function
The commands our miniature shell supports are cd
, clear
, exit
, ls
, and pwd
, with a couple of aliases (cls
for clear
, and dir
for ls
). I hacked out these methods rather quickly; feel free to build on them!
Next, a nice thing about most shells is that you can just type the name of a program (sometimes preceded by "./") at the shell prompt to run it. So let's make a program that does this โ and goes a little further; if the filename you enter is not a MiniScript program, but something like a text file, image, or sound, then we'll view it with the view
command.
runProgram = function(args)
info = file.info(args[0])
if info == null then info = file.info(args[0] + ".ms")
if info == null then return false
if info.isDirectory then
print args[0] + ": is directory"
else if info.path.endsWith(".ms") then
env.shell = mashPath
load info.path
run
else
view info.path
end if
return true
end function
Note that one thing mash does not do in the above runProgram
function is search a standard search path. That would be a nice feature โ feel free to add it!
Finally, we need a main program to get input from the user, and figure out what to do with it.
// main loop
while true
if text.column > 0 then print
cmdLine = input("mash>")
args = splitQuoted(cmdLine)
if not args or not args[0] then continue
if not commands.hasIndex(args[0]) then
if not runProgram(args) then
print "Unknown command: " + args[0]
end if
continue
end if
f = commands[args[0]]
f args
end while
Boiled down to its essence, this main loop just does this: get input from the user, split it on spaces, and see if the first word is some function in our commands
map. If so, run that function; otherwise try to run a program with a matching name.
Benefits
While simple, this program already provides many of the benefits of a standard bash-like command shell:
- No need to put quotation marks around file names and paths!
- No need to type
run
to run a program. - Fully customizable directory listings and other behavior (for example,
mash
'sclear
command also resets the text color).
Of course there's always a trade-off; you lose the ability to type MiniScript code at any command prompt you see. If you're at the "mash>" prompt, you'll need to exit
to get to the MiniScript REPL, and then enter mash
to return to your shell.
Taking it Further
Our miniature shell is only about 100 lines at this point. There's plenty of room for expansion! Here are some ideas to get you started:
- Add an
edit
command that just calls through to the standard one. - Make the
ls
command accept arguments, like "-l" for long form. - Also make the
ls
command color the file names according to their file type. - Add a search path, so when you write a little MiniScript program that's generally useful, you can invoke it by name no matter what directory you're in.
- Add support for glob patterns in any file name or path.
- Add more file commands such as mkdir, rm, find, etc.
If you decide to live with a shell rather than just the MiniScript REPL, I'm sure you'll find things you'd like to add or change. And now you can do that. The power is yours!
Top comments (1)
This was a nice addition to Mini Micro.