pmsh
Philip Miesbauer SHell (pmsh) — a modern, minimal shell written in Rust.
Features
- Interactive REPL with line editing (rustyline)
- Command parsing and execution (external commands)
- Pipelines (e.g.,
echo hello | wc -c) - Builtins:
cd,cd -,history,exit,source - Persistent command history (
~/.pmsh_history, up to 1000 entries) - Prompt shows user and current directory, with
~for HOME - Tilde expansion and collapse for paths
- Deterministic, robust unit and integration tests
- CI with code quality and coverage reporting
Usage
Build and run:
Example session
|
|
Pipelines
Pipelines allow you to chain commands together, sending the output of one command as input to the next:
|
| |
Note: Currently, builtins cannot be used inside pipelines (e.g., cd /tmp | echo ok is not supported). This is a planned feature for future releases.
cd [dir]— change directory (supports~andcd -for previous dir)history— print command historyexit— save history and exit
History
- Commands are saved to
~/.pmsh_history(up to 1000 entries) - History is loaded on startup and saved on exit and after each command
Prompt
- Format:
<user>:<cwd>$ - HOME is shown as
~(e.g.,philip:~$)
Development
Run tests:
Run integration test (PTY-based):
Check formatting and lints:
Generate coverage (requires cargo-tarpaulin):
Internal Tools Help
This section documents pmsh’s internal modules and how to use them in code and tests.
-
Parser (
src/parser.rs)- Purpose: Parse a command line into a
Command { name, args }. - API:
Command::parse(line: &str) -> Option<Command> - Example:
use crateCommand; if let Some = parse
- Purpose: Parse a command line into a
-
Executor (
src/executor.rs)- Purpose: Execute external programs with arguments.
- API:
Executor::execute(cmd: &Command) -> Result<(), String> - Example:
use crateExecutor; use crateCommand; let cmd = Command ; execute?;
-
History Manager (
src/history.rs)- Purpose: Persist and manage command history (
~/.pmsh_history). - APIs:
HistoryManager::new() -> Result<Self, String>load(&self) -> Result<Vec<String>, String>save(&self, history: &[String]) -> Result<(), String>add_entry(&self, entry: &str, history: &mut Vec<String>) -> Result<(), String>
- Example:
use crateHistoryManager; let mgr = new?; let mut hist = mgr.load?; mgr.add_entry?; mgr.save?;
- Purpose: Persist and manage command history (
-
Path Utilities (
src/path_utils.rs)- Purpose: Home expansion/compaction.
- APIs:
expand_home(path: &str) -> String(turn$HOME/...into~/...for display)collapse_tilde(path: &str) -> std::path::PathBuf(turn~/...into absolute path)
- Example:
use crate; let shown = expand_home; let abs = collapse_tilde;
-
UI (
src/ui.rs)- Purpose: Prompt formatting.
- APIs:
format_prompt() -> Stringformat_prompt_with(cwd: &str, user: &str) -> String(pure, test helper)
- Example:
use crateformat_prompt_with; let p = format_prompt_with; assert!;
-
Builtins (
src/builtins.rs)- Purpose: Handle internal shell commands.
- API:
handle_builtin(cmd, history_mgr, command_history, oldpwd) -> Result<BuiltinResult, String>- Supports
cd [dir],cd -,history,exit BuiltinResult::{HandledContinue, HandledExit, NotHandled}
- Supports
- Example:
use crate; use crateHistoryManager; use crateCommand; let mgr = new?; let mut hist = vec!; let mut oldpwd = None; let cmd = Command ; match handle_builtin?
-
REPL (
src/repl.rs)- Purpose: Event loop driving input, builtins, execution, and history.
- APIs:
run_repl(editor, history_mgr, command_history, executor)- Traits:
LineEditor(readline, add_history_entry),ExecutorTrait(execute) - Adapter:
RealExecutorbridges toExecutor
- Example (testing with mocks):
use crate; # ; # ; # /* see tests for full mocks */ # # # let mut editor = MockEditor; let exec = MockExec; let mgr = Defaultdefault; let mut history = vec!; run_repl;
-
Integration Tests (
tests/integration_repl.rs)- Purpose: End-to-end validation via a PTY using
expectrl. - How to run:
- Purpose: End-to-end validation via a PTY using
Roadmap to POSIX Compliance
This shell is not yet POSIX compliant. Here is a high-level overview of features required to move towards compliance:
-
Pipelines and Redirection:
|(pipe)>(redirect stdout)<(redirect stdin)>>(append stdout)2>(redirect stderr)
-
Special Builtins:
.(dot):(colon)breakcontinueevalexecexportreadonlyreturnsetshifttimestrapunset
-
Regular Builtins:
aliasbgcommandfalsefcfggetoptsjobskillnewgrppwdreadtrueumaskunaliaswait
-
Quoting:
'...'(single quotes)"..."(double quotes)\(escape character)
-
Variable Expansion:
${parameter}$parameter$@$*$#$?$$$!
-
Command Substitution:
$(command)`command`
-
Conditional Execution:
&&(AND)||(OR)
-
Background Jobs:
&(run in background)
-
Shell Grammar:
if/then/elif/else/ficase/esacforloopswhileloopsuntilloops- Functions
Contributing
PRs welcome! Please ensure all tests pass and code is formatted.
License
MIT