# pmsh
Philip Miesbauer SHell (pmsh) — a modern, minimal shell written in Rust.
[](https://codecov.io/gh/philipmiesbauer/pmsh)
## 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:
```bash
cargo build --release
./target/release/pmsh
```
### Example session
```bash
philip:~$ echo hello
hello
philip:~$ cd /tmp
philip:/tmp$ cd /var
philip:/var$ cd -
/tmp
philip:/tmp$ history
1: echo hello
2: cd /tmp
3: cd /var
4: cd -
5: echo hello | wc -c
6: history
philip:/tmp$ exit
Exiting.
```
## Pipelines
Pipelines allow you to chain commands together, sending the output of one command as input to the next:
```bash
**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 `~` and `cd -` for previous dir)
- `history` — print command history
- `exit` — 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:
```bash
cargo test
```
Run integration test (PTY-based):
```bash
cargo test --test integration_repl
```
Check formatting and lints:
```bash
cargo fmt -- --check
cargo clippy -- -D warnings
```
Generate coverage (requires cargo-tarpaulin):
```bash
cargo tarpaulin --out Xml --out Lcov --run-types Tests
```
## 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:
```rust
use crate::parser::Command;
if let Some(cmd) = Command::parse("echo hello world") {
assert_eq!(cmd.name, "echo");
assert_eq!(cmd.args, vec!["hello".into(), "world".into()]);
}
```
- Executor (`src/executor.rs`)
- Purpose: Execute external programs with arguments.
- API: `Executor::execute(cmd: &Command) -> Result<(), String>`
- Example:
```rust
use crate::executor::Executor;
use crate::parser::Command;
let cmd = Command { name: "echo".into(), args: vec!["hi".into()] };
Executor::execute(&cmd)?;
```
- 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:
```rust
use crate::history::HistoryManager;
let mgr = HistoryManager::new()?;
let mut hist = mgr.load()?;
mgr.add_entry("echo hi", &mut hist)?;
mgr.save(&hist)?;
```
- 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:
```rust
use crate::path_utils::{expand_home, collapse_tilde};
let shown = expand_home("/home/user/projects");
let abs = collapse_tilde("~/projects");
```
- UI (`src/ui.rs`)
- Purpose: Prompt formatting.
- APIs:
- `format_prompt() -> String`
- `format_prompt_with(cwd: &str, user: &str) -> String` (pure, test helper)
- Example:
```rust
use crate::ui::format_prompt_with;
let p = format_prompt_with("/home/user", "alice");
assert!(p.starts_with("alice:"));
```
- 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}`
- Example:
```rust
use crate::builtins::{handle_builtin, BuiltinResult};
use crate::history::HistoryManager;
use crate::parser::Command;
let mgr = HistoryManager::new()?;
let mut hist = vec![];
let mut oldpwd = None;
let cmd = Command { name: "cd".into(), args: vec!["/tmp".into()] };
match handle_builtin(&cmd, &mgr, &mut hist, &mut oldpwd)? {
BuiltinResult::HandledContinue => {}
_ => {}
}
```
- 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: `RealExecutor` bridges to `Executor`
- Example (testing with mocks):
```rust
use crate::repl::{run_repl, ReadlineEvent, LineEditor, ExecutorTrait};
# struct MockEditor; # struct MockExec; #
# impl LineEditor for MockEditor { }
# impl ExecutorTrait for MockExec { }
# let mut editor = MockEditor; let exec = MockExec; let mgr = Default::default();
let mut history = vec![];
run_repl(&mut editor, &mgr, &mut history, &exec);
```
- Integration Tests (`tests/integration_repl.rs`)
- Purpose: End-to-end validation via a PTY using `expectrl`.
- How to run:
```bash
cargo test --test integration_repl
```
## 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)
- `break`
- `continue`
- `eval`
- `exec`
- `export`
- `readonly`
- `return`
- `set`
- `shift`
- `times`
- `trap`
- `unset`
- **Regular Builtins**:
- `alias`
- `bg`
- `command`
- `false`
- `fc`
- `fg`
- `getopts`
- `jobs`
- `kill`
- `newgrp`
- `pwd`
- `read`
- `true`
- `umask`
- `unalias`
- `wait`
- **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/fi`
- `case/esac`
- `for` loops
- `while` loops
- `until` loops
- Functions
## Contributing
PRs welcome! Please ensure all tests pass and code is formatted.
## License
MIT