TruShell is an interactive shell environment designed to integrate task tracking and time management tools seamlessly with traditional terminal commands. Built in Rust with a custom expression parser, TruShell extends the Unix philosophy by providing a unified interface where productivity features and system commands coexist naturally.
TruShell is not a replacement shell but rather a productivity layer that bridges the gap between system administration and personal task management. It runs as an interactive REPL (Read-Eval-Print Loop) that:
ls, cd, grep, etc.)|)>, >>, <, and &> for both standalone commands and pipeline stagesTruShell follows a modular, layered architecture typical of interpreted languages:
βββββββββββββββββββββββββββββββββββββββββββ
β Interactive REPL (main.rs) β
β β’ User Input Loop β
β β’ Command/Expression Routing β
β β’ Fallback Execution β
ββββββββββββββββ¬βββββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ
β β
ββββββββΌβββββββ ββββββββΌβββββββββββ
β Lexer β β Parser β
β (Tokenize) β β (AST Build) β
βββββββββββββββ βββββββββββββββββββ
β β
βββββββββ¬βββββββββ
β
ββββββββΌβββββββββββ
β AST Nodes β
β (Expressions) β
βββββββββββββββββββ
β
ββββββββΌβββββββββββββββββββ
β Command Execution β
β β’ System Calls β
β β’ Process Management β
βββββββββββββββββββββββββββ
crossterm for terminal handlinggit clone https://github.com/TruFoundation/TruShell.git
cd TruShell
cargo build --release
The compiled binary will be located at target/release/trushell.
./target/release/trushell
Or directly via Cargo:
cargo run
Upon startup, TruShell displays a welcome message and enters a prompt loop:
Welcome to TruShell Native Engine
trushell β―
exit: Gracefully shut down the shellCtrl+D (EOF): Safely terminate the shellExecute any command available in your PATH:
trushell β― ls -la
trushell β― pwd
trushell β― echo "Hello, World!"
trushell β― cd /tmp
trushell β― cd ~
The cd command is handled specially to modify TruShellβs working directory (not spawned as a subprocess).
Define variables using let:
trushell β― let x = 42
trushell β― let name = "Alice"
trushell β― let flag = true
Perform arithmetic and logical operations:
trushell β― let result = 10 + 5
trushell β― let product = 3 * 7
trushell β― let ratio = 100 / 4
trushell β― let is_big = 42 > 10
trushell β― let is_equal = 5 == 5
trushell β― let not_empty = "text" != ""
Chain operations together using |:
trushell β― ls() | filter { $it.size > 1mb }
Redirect command input/output and combine streams:
trushell β― echo hello > out.txt
trushell β― echo append >> out.txt
trushell β― cat < in.txt
trushell β― echo hello | cat > out.txt
trushell β― echo error &> error.log
TruShellβs lexer recognizes the following token categories:
| Token Class | Examples | Purpose |
|---|---|---|
| Keywords | let, true, false |
Language control structures |
| Identifiers | x, $var, _private |
Variable and function names |
| Numbers | 42, 3mb, 100kb |
Numeric literals with optional units |
| Strings | "hello" |
Quoted string literals |
| Flags | -la, --verbose, --help |
Command-line flags (preserved) |
| Operators | +, -, *, /, >, <, ==, != |
Binary operations |
| Delimiters | (), {}, [], ., ,, ; |
Structure and grouping |
| Pipes | \| |
Pipeline sequencing |
TruShell supports the following literal types:
pub enum Literal {
Number { value: i64, unit: Option<String> }, // 42, 1mb, 500ms
String(String), // "text"
Boolean(bool), // true, false
}
| Operator | Type | Precedence | Example |
|---|---|---|---|
+ |
Addition | Term | 5 + 3 |
- |
Subtraction | Term | 10 - 4 |
* |
Multiplication | Factor | 3 * 4 |
/ |
Division | Factor | 12 / 3 |
> |
Greater Than | Comparison | 5 > 3 |
< |
Less Than | Comparison | 2 < 5 |
>= |
Greater or Equal | Comparison | 5 >= 5 |
<= |
Less or Equal | Comparison | 3 <= 5 |
== |
Equals | Comparison | 5 == 5 |
!= |
Not Equals | Comparison | 3 != 5 |
TruShell follows standard mathematical precedence, evaluated in this order (lowest to highest):
>, <, >=, <=, ==, !=)+, -)*, /)Variables are declared with let and referenced with $:
trushell β― let count = 10
trushell β― let doubled = $count * 2
Variables starting with $ are treated as Variable tokens and can be accessed in expressions.
Code blocks are enclosed in {} and contain semicolon-separated statements:
trushell β― let data = { let x = 5; let y = 10; $x + $y }
main.rs β Interactive REPL & Command ExecutionPurpose: Orchestrates the shellβs interactive loop and manages command execution.
fn main()Key Invariants:
Ctrl+D) or explicit exit commandfn execute_system_command(cmd: &str, args: &[&str])Execution Flow:
Command::new(cmd)
ββ .args(args)
ββ .stdin(Stdio::inherit())
ββ .stdout(Stdio::inherit())
ββ .stderr(Stdio::inherit())
ββ .spawn()
ββ .wait()
fn probable_cli_from_ast(ast: &parser::ASTNode) -> Option<(String, Vec<String>)>ls -la is parsed as ls - la (subtraction)Some((cmd, args)) if the pattern matches, otherwise NoneExample Transformation:
Input: "ls -la"
Parse: BinaryOp { left: Identifier("ls"), op: Subtract, right: Literal(String("-la")) }
Extract: ("ls", ["-la"])
Execute: ls -la
parser.rs β Lexing & Parsing EnginePurpose: Converts raw user input into an Abstract Syntax Tree (AST) for interpretation.
pub enum Token {
Let, // Variable declaration
Flag(String), // CLI flags (-la, --help)
Identifier(String), // Variable/function names
Number(String), // Numeric literals
StringLiteral(String), // Quoted strings
Boolean(bool), // true/false
Equals, // = assignment
Pipe, // | pipeline
LParen, RParen, // ( )
LBrace, RBrace, // { }
Dot, // . property access
Comma, // , separator
Semicolon, // ; statement terminator
GreaterThan, // >
LessThan, // <
GreaterThanOrEqual, // >=
LessThanOrEqual, // <=
EqualsEquals, // ==
BangEquals, // !=
Plus, // +
Minus, // -
Star, // *
Slash, // /
}
pub enum ASTNode {
Let { name: String, value: Box<ASTNode> },
Pipeline { stages: Vec<Box<ASTNode>> },
Command { name: String, args: Vec<ASTNode> },
Redirect { source: Box<ASTNode>, fd: u8, mode: RedirectMode, target: RedirectTarget, merge_stderr: bool },
Block { body: Vec<ASTNode> },
BinaryOp { left: Box<ASTNode>, op: BinaryOperator, right: Box<ASTNode> },
Variable(String),
Literal(Literal),
PropertyAccess { target: Box<ASTNode>, property: String },
Identifier(String),
}
struct Lexer)Responsibility: Converts a string into a sequence of tokens.
a-z, A-Z, _, or $; continue with alphanumerics or _let, true, false β special tokens5, 100ms, 1mb)- followed by letters/hyphens (e.g., -la, --help)==, !=, >=, <=)The lexer recognizes CLI flags intelligently:
if let Some(second) = peek_two_chars_ahead {
if second.is_alphabetic() || second == '-' {
// Lex as a flag (e.g., -la, --verbose)
self.lex_flag()
} else {
// Lex as minus operator (e.g., 5 - 3)
Token::Minus
}
}
Effect: ls-la is lexed as a command followed by a flag, not as subtraction.
struct Parser)Responsibility: Converts tokens into an AST using recursive descent parsing.
parse_statement
ββ if let: parse_let_statement
ββ else: parse_pipeline
parse_pipeline
ββ parse_expression (with | separator)
parse_expression
ββ parse_comparison
parse_comparison (>/</>=/<=,==,!=)
ββ parse_term
parse_term (+,-)
ββ parse_factor
parse_factor (*,/)
ββ parse_primary
parse_primary (literals, identifiers, parens, blocks)
ββ parse_identifier_expression (handles ., (, {})
let x = 5
ββ Token::Let
ββ expect identifier β "x"
ββ expect =
ββ parse_expression β Literal(Number(5))
Result: ASTNode::Let {
name: "x",
value: Box::new(Literal(Number(5)))
}
ls() | filter { $it.size > 1mb }
ββ parse_expression β Command { name: "ls", args: [] }
ββ Token::Pipe
ββ parse_expression β Command { name: "filter", args: [Block {...}] }
ββ Token::Pipe (none)
Result: ASTNode::Pipeline {
stages: [Command{...}, Command{...}]
}
Numbers can include units (e.g., 5mb, 100ms):
pub fn parse_number_literal(raw: &str) -> Result<Literal, ParseError> {
let digits: String = raw.chars().take_while(|ch| ch.is_ascii_digit()).collect();
let unit: String = raw.chars().skip_while(|ch| ch.is_ascii_digit()).collect();
Ok(Literal::Number {
value: digits.parse::<i64>()?,
unit: if unit.is_empty() { None } else { Some(unit) },
})
}
Examples:
5 β Number { value: 5, unit: None }1mb β Number { value: 1, unit: Some("mb") }100ms β Number { value: 100, unit: Some("ms") }User Input String
β
[Lexer::tokenize()]
β
Token Vector
β
[Parser::parse_statement()]
β
ASTNode (Abstract Syntax Tree)
β
[main.rs execution logic]
β
Output/Side Effects
let x = 5 + 3Input: "let x = 5 + 3"
Step 1: Tokenization
[Let, Identifier("x"), Equals, Number("5"), Plus, Number("3")]
Step 2: Parsing (Recursive Descent)
parse_statement
ββ parse_let_statement
ββ expect Let β β
ββ expect_identifier β "x"
ββ expect Equals β β
ββ parse_expression (for "5 + 3")
ββ parse_comparison
ββ parse_term
ββ parse_factor β Literal(5)
ββ detect Plus
ββ parse_factor β Literal(3)
ββ combine: BinaryOp { left: 5, op: Add, right: 3 }
Step 3: AST Result
ASTNode::Let {
name: "x",
value: Box::new(
ASTNode::BinaryOp {
left: Box::new(Literal(Number { value: 5, unit: None })),
op: Add,
right: Box::new(Literal(Number { value: 3, unit: None }))
}
)
}
The parser provides descriptive error messages:
pub struct ParseError {
pub message: String,
}
Common Errors:
"Expected identifier, found ...""Unexpected token in expression""Unterminated string literal""Unexpected character: '...'" (from lexer)main()loop {
1. Read user input
2. Trim whitespace
3. Check for special commands:
ββ "exit" β break loop
ββ "cd ..." β change directory
4. Attempt to parse:
ββ Success β check if it's a probable CLI command
β ββ Yes β execute as system command
β ββ No β print AST (debug mode)
ββ Failure β fallback to system command execution
5. Display output
}
exitTerminates the shell gracefully:
if trimmed_input == "exit" {
println!("Goodbye!");
break;
}
cd (Change Directory)Handled specially without spawning a subprocess:
if trimmed_input.starts_with("cd") {
let parts: Vec<&str> = trimmed_input.split_whitespace().collect();
let new_dir = parts.get(1).copied().unwrap_or(".");
if let Err(e) = std::env::set_current_dir(new_dir) {
eprintln!("trushell: cd: {}: {}", new_dir, e);
}
continue;
}
When parsing fails or the AST doesnβt match a known pattern, TruShell falls back to executing the input as a system command:
Err(err) => {
eprintln!("Parse error: {}", err);
let parts: Vec<&str> = trimmed_input.split_whitespace().collect();
let command = parts[0];
let args = &parts[1..];
execute_system_command(command, args);
}
Result: Most Unix commands work transparently even if parsing fails.
TruShell now supports shell-style redirection for parsed commands, including standalone redirects and redirects on pipeline stages. The execution engine resolves redirect AST nodes before spawning processes and correctly wires command stdin/stdout for pipes:
cmd > file writes stdout to a filecmd >> file appends stdout to a filecmd < file reads stdin from a filecmd &> file redirects stderr to the same target filecmd | other > file pipes data into a redirected final stageThis integration keeps parsed AST behavior aligned with traditional shell semantics while preserving the existing fallback execution model.
Commands are executed with inherited I/O streams:
Command::new(cmd)
.args(args)
.stdin(Stdio::inherit()) // User input reaches subprocess
.stdout(Stdio::inherit()) // Subprocess output visible
.stderr(Stdio::inherit()) // Errors displayed directly
.spawn()
.wait()
trushell β― let x = 10
Parsed AST: Let { name: "x", value: ... }
trushell β― let y = $x * 2
Parsed AST: Let { name: "y", value: ... }
trushell β― let z = 100 / 5
Parsed AST: Let { name: "z", value: ... }
trushell β― let name = "Alice"
trushell β― let greeting = "Hello, " + $name
Parsed AST: Let { name: "greeting", value: ... }
trushell β― let is_adult = 25 > 18
Parsed AST: Let { name: "is_adult", value:
BinaryOp {
left: 25,
op: GreaterThan,
right: 18
}
}
trushell β― let in_range = 50 >= 10
trushell β― ls -la
total 48
drwxr-xr-x 5 user group 160 Jul 5 12:34 .
drwxr-xr-x 10 user group 320 Jul 4 18:22 ..
-rw-r--r-- 1 user group 1234 Jul 05 12:30 README.md
...
trushell β― pwd
/home/user/projects/TruShell
trushell β― echo "Building..."
Building...
trushell β― cd /tmp
trushell β― pwd
/tmp
trushell β― cd -
trushell β― pwd
/home/user/projects/TruShell
trushell β― ls() | filter { $it.size > 1mb }
Parsed AST: Pipeline {
stages: [
Command { name: "ls", args: [] },
Command { name: "filter", args: [Block { ... }] }
]
}
TruShell/
βββ Cargo.toml # Rust project manifest
βββ Cargo.lock # Dependency lock file
βββ src/
β βββ main.rs # REPL and command execution (4.3 KB)
β βββ parser.rs # Lexer and parser (19.0 KB)
βββ target/ # Compiled binaries (excluded from repo)
βββ README.md # This file
βββ LICENSE.md # Project license
std# Build in debug mode
cargo build
# Build optimized release binary
cargo build --release
# Run tests
cargo test
# Run with output
cargo run -- --verbose
The parser module includes unit tests:
#[test]
fn tokenize_basic_lets_and_pipeline() { ... }
#[test]
fn parse_let_statement() { ... }
#[test]
fn parse_pipeline_with_function_block() { ... }
Run tests with:
cargo test
git checkout -b feature/my-feature)time start, time stop, time log commands.trushellrc configuration file support.bashrc/.zshrc integration for seamless useman bash, man shTruShell is released under the terms specified in LICENSE.md. See that file for full details.
TruFoundation β Empowering productivity through open-source tooling.
Last Updated: July 5, 2026