Asti's Notes
FP, Rx, EDA
A DOS-like shell in .Net
And a simple tutorial for NetShell

Setup

Add a reference to NetShell, create a new DOS.cs file. The main file just calls NetShell's RpcShell with an instance of DOS.

static int Main(string[] args)
{
    var shell = new RpcShell(new DOS()) { 
        Prompt = Environment.CurrentDirectory, FlagPrefix = "/" 
    };
    return shell.Run();
}

With that out of the way, let's implement the first command.

echo

This is trivial. Add a Command attribute with the (optional) name and help-text.

[Command("echo")]
public void Echo(string text) => Console.WriteLine(text);

Let's see how that looks like:

Maybe add a bit of colour.

[Command("echo")]
public void Echo(string text, ConsoleColor color = ConsoleColor.White)
{
	Console.ForegroundColor = color;	
	Console.WriteLine(text);
	Console.ResetColor();
}

cls

cls is also trivial to implement.

[Command("cls")]
public void Clear() => Console.Clear();

exit

Of course we need an exit command - this just calls Shell.Exit

[Command("exit")]
public void Exit(Shell shell) => shell.Exit(0);

cd

This is as you'd expect, but you also need to change the current shell prompt. We're using the built-in dependency injection to get the instance of the shell, and set the prompt.

[Command("cd")]
public void ChangeDir([Suggest(nameof(SuggestDirs))] string directory, Shell shell)
{
    var path = Path.GetFullPath(directory);
    if (!Directory.Exists(path))
        throw new DirectoryNotFoundException($"{path} does not exist");

    shell.Prompt = Environment.CurrentDirectory = path;
}

type

Just return the file contents and it'll be printed out automatically.

[Command("type")]
public string Type([Suggest(nameof(SuggestFiles))] string filename) => 
	File.ReadAllText(Path.Combine(Dir, filename));

And to auto-complete files in the current directory:

public IEnumerable<string> SuggestFiles() => 
	Directory.EnumerateFiles(Dir).Select(Path.GetFileName);

dir

Here we're showing a simple dir command, but here's how to add help-text to the command, and for each pattern.

[Command("dir", "Lists directories and files with an optional pattern")]
public IEnumerable<string> List(
	[Description("Accepts glob patterns")] string pattern = "*",
	[Description("Only show files if true")] bool files = false
)
{
	var dirs = files ? Enumerable.Empty<string>() : Directory.EnumerateDirectories(Dir, pattern);
	var allfiles = Directory.EnumerateFiles(Dir, pattern);
	return Enumerable.Concat(dirs, allfiles).Select(Path.GetFileName);
}

Now if we do a help dir we get:

D:\>help dir
Command dir Lists directories and files with an optional pattern
Syntax: dir (String [pattern] = *) (Boolean [files] = False)
/pattern    Accepts glob patterns
/files	    Only show files if true
D:\>

start

Running applications: Anything that doesn't match our commands, we can attempt to run with the given command-line - this is DOS’ famous “Bad command or file name”. We can do that with the DefaultCommand attribute which marks a catch-all method.

The following implementation redirects the stdin/stdout of our console to the program, till it's terminated.


[DefaultCommand]
public void Execute(string name, string[] args)
{
    using (var process = Process.Start(new ProcessStartInfo(name, String.Join(" ", args)) { RedirectStandardOutput = true, UseShellExecute = false }))
    {
        while (!process.StandardOutput.EndOfStream)
        {
            if (Console.KeyAvailable)
                process.StandardInput.Write(Console.ReadKey().KeyChar);

            if (process.StandardOutput.Peek() != -1)
                Console.Write((char)process.StandardOutput.Read());
        }
    }
}

Our demo isn't complete until we run the Windows Command Prompt inside our command prompt!


Last modified on 2020-02-18

Comments Disabled.