Yet Another Markdown Editor! TUI for READMEs and friends.
editor markdown readme development-tool
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246/// CLI argument parsing, shell integration, and config-write subcommand.////// Extracted from `main.rs` so the entry-point file stays as a thin/// orchestration layer (`setup_panic_hook` → `run` → `main`).use std::io;use std::path::PathBuf;
// ---------------------------------------------------------------------------// Subcommand type// ---------------------------------------------------------------------------
pub(super) enum Command { /// Open a file for editing, or start with an untitled buffer (`path = None`). Edit { path: Option<PathBuf>, read_only: bool, }, Init { shell: Option<String>, }, WriteConfig, /// Render a file to stdout with ANSI colour codes (for file-manager previewers). Preview { path: PathBuf, },}
// ---------------------------------------------------------------------------// Shell integration// ---------------------------------------------------------------------------
/// Returns the shell wrapper function string for `eval "$(yame init)"`.////// The output is identical for bash and zsh — the function body uses constructs/// common to both. The `shell` argument is accepted for forward-compatibility/// and explicit dotfile documentation; it does not change the output today.pub(super) fn shell_init_str(_shell: &str) -> String { r#"yame() { if (( $# != 1 )) || [[ "$1" =~ ^- ]]; then command yame "$@" return fi
# If the argument is an exact path, open it directly. if [[ -f "$1" ]]; then command yame "$1" return fi
# Require fd and fzf; fall back to plain invocation if either is missing. if ! command -v fd &>/dev/null || ! command -v fzf &>/dev/null; then command yame "$@" return fi
local target
# Tier 1: fuzzy-find a Markdown file. target=$(fd --type f --extension md "$1" 2>/dev/null | fzf --select-1 --exit-0 --preview 'head -20 {}')
# Tier 2: fuzzy-find any file (including hidden), skipping heavy directories. if [[ -z "$target" ]]; then target=$(fd --type f --hidden -E "node_modules" -E ".git" -E "target" "$1" 2>/dev/null | fzf --select-1 --exit-0 --preview 'head -20 {}') fi
if [[ -n "$target" ]]; then command yame "$target" else printf "yame: no file matching '%s' found. Open new file? [y/N] " "$1" >&2 read -r ans && [[ "$ans" =~ ^[Yy]$ ]] && command yame "$1" fi}"# .to_string()}
#[mutants::skip] // Reads $SHELL env var and calls process::exit — not unit-testable.pub(super) fn detect_shell() -> String { match std::env::var("SHELL") { Ok(path) if path.contains("zsh") => "zsh".to_string(), Ok(path) if path.contains("bash") => "bash".to_string(), Ok(path) => { eprintln!("================================================================"); eprintln!("yame init: unsupported shell."); eprintln!("================================================================"); eprintln!("'yame init' currently only supports Bash and Zsh."); eprintln!(); eprintln!("Detected shell: {path}"); eprintln!("To set up yame's shell integration manually, see:"); eprintln!(" https://github.com/cyrusae/yame"); eprintln!("================================================================"); std::process::exit(1); } Err(_) => { eprintln!("================================================================"); eprintln!("yame init: $SHELL is unset."); eprintln!("================================================================"); eprintln!("Pass the shell name explicitly:"); eprintln!(" eval \"$(yame init bash)\""); eprintln!(" eval \"$(yame init zsh)\""); eprintln!("================================================================"); std::process::exit(1); } }}
// ---------------------------------------------------------------------------// Help text// ---------------------------------------------------------------------------
#[mutants::skip] // Prints to stdout and calls process::exit — not unit-testable.pub(super) fn print_help() { println!("yame — yet another markdown editor"); println!(); println!("USAGE"); println!(" yame <file> Open <file> for editing (created if it doesn't exist)"); println!(" yame Open an untitled buffer (save-as on first Ctrl+S)"); println!(" yame -r <file> Open <file> in read-only mode (no edits, no save)"); println!(" yame --preview <file> Render <file> to stdout with ANSI colours (for lf/etc)"); println!(" yame init Print shell integration function (eval in .bashrc/.zshrc)"); println!(" yame write-config Write default config to ~/.config/yame/config.toml"); println!( " yame --version Print version yame --help Show this help" ); println!(); println!("KEYBINDINGS"); println!(" Ctrl+S Save Ctrl+Z Undo Ctrl+C Copy selection"); println!(" Ctrl+Q Quit Ctrl+Y Redo Ctrl+V Paste"); println!(" Ctrl+X Cut Ctrl+R Reload config"); println!(" Arrow keys · Home/End · PgUp/PgDn · mouse click / drag / scroll"); println!(); #[cfg(not(windows))] println!("CONFIG ~/.config/yame/config.toml (respects $XDG_CONFIG_HOME)"); #[cfg(windows)] println!(r"CONFIG %APPDATA%\yame\config.toml"); println!(); println!(" https://github.com/cyrusae/yame");}
// ---------------------------------------------------------------------------// Argument parsing// ---------------------------------------------------------------------------
/// Returns the version string printed by `--version` / `-V`.////// Separate from `parse_args` so it can be unit-tested without spawning a process.pub(super) fn version_string() -> String { format!("yame {}", env!("CARGO_PKG_VERSION"))}
#[mutants::skip] // Reads std::env::args() — side-effectful, not unit-testable.pub(super) fn parse_args() -> Result<Command, ()> { let args: Vec<String> = std::env::args().skip(1).collect();
if args.iter().any(|a| a == "-h" || a == "--help") { print_help(); std::process::exit(0); }
if args.iter().any(|a| a == "-V" || a == "--version") { println!("{}", version_string()); std::process::exit(0); }
match args.as_slice() { // No arguments → open an untitled buffer. [] => Ok(Command::Edit { path: None, read_only: false, }), [a] if a == "init" => Ok(Command::Init { shell: None }), [a, s] if a == "init" => Ok(Command::Init { shell: Some(s.clone()), }), [a] if a == "write-config" => Ok(Command::WriteConfig), [flag, path] if flag == "--preview" && !path.starts_with('-') => Ok(Command::Preview { path: PathBuf::from(path), }), [path] if !path.starts_with('-') => Ok(Command::Edit { path: Some(PathBuf::from(path)), read_only: false, }), [flag, path] if (flag == "-r" || flag == "--read-only") && !path.starts_with('-') => { Ok(Command::Edit { path: Some(PathBuf::from(path)), read_only: true, }) } [path, flag] if (flag == "-r" || flag == "--read-only") && !path.starts_with('-') => { Ok(Command::Edit { path: Some(PathBuf::from(path)), read_only: true, }) } _ => { eprintln!("error: unexpected arguments"); eprintln!("Run 'yame --help' for usage."); Err(()) } }}
// ---------------------------------------------------------------------------// write-config subcommand// ---------------------------------------------------------------------------
#[mutants::skip] // Filesystem + stdin I/O — not unit-testable.pub(super) fn run_write_config() { use std::io::Write; use yame::config::{DEFAULT_CONFIG_TEMPLATE, config_path};
let path = config_path();
if path.exists() { print!( "Config already exists at {}.\nOverwrite? [y/N] ", path.display() ); let _ = io::stdout().flush(); let mut input = String::new(); if io::stdin().read_line(&mut input).is_err() { eprintln!("error: could not read input"); std::process::exit(1); } if !input.trim().eq_ignore_ascii_case("y") { println!("Aborted — config unchanged."); return; } }
if let Some(dir) = path.parent() && let Err(e) = std::fs::create_dir_all(dir) { eprintln!("error: could not create config directory: {e}"); std::process::exit(1); }
match std::fs::write(&path, DEFAULT_CONFIG_TEMPLATE) { Ok(()) => println!("Config written to {}", path.display()), Err(e) => { eprintln!("error: could not write config: {e}"); std::process::exit(1); } }}