Architecture of Command-Line Interface prototype

Class diagram (created with Visual Paradigm):

The main class is
It prompts user for an input, then passes it to a cli.preprocessor.Preprocessor.
Then, the preprocessed string goes to cli.lexer.Lexer.
After that, lexems are parsed by cli.parser.Parser.
These three steps are clearly separated and are testable by themselves.

Parser makes a cli.commands.RunnableCommand out of stream of lexems.
cli.commands.RunnableCommand represents a command (in broad sense), so
that echo, wc, assignment and others, as well as pipes have
their respective class counterparts. The former must inherit from cli.commands.SingleCommand,
the latter - from cli.commands.CommandChain.

Commands run in an environment. It is represented by cli.environment.Environment.
Commands read from cli.streams.InputStream-s and write to cli.streams.OutputStream.
When a pipe is processed, the first command’s OutputStream is converted to the second command’s InputStream.

RunnableCommand provides a simple interface to run a command, given
value is cli.commands.RunnableCommandResult, which is an abstraction on return value.

Each command (like echo and exit) is implemented as a separate class
in cli.commands or cli.single_command. This allows easy extension.
On the other hand, commands do not know about each other, so they need to implement
their argument parsing by themselves.

Commands interact with each other using return codes. One exception from this rule
is exit command - it throws cli.exceptions.ExitException on run,
so that shell can exit immediately.