mdlite 0.2.1

A super-lightweight terminal Markdown reader
Documentation
use std::env;
use std::fs;
use std::io::{self, IsTerminal, Read};
use std::process;

use mdlite::{render_markdown, run_pager};

const HELP: &str = "\
mdlite - lightweight terminal markdown reader

Usage:
  mdlite [FILE]
  mdlite --pager [FILE]
  cat README.md | mdlite
  cat README.md | mdlite --pager

Options:
  -p, --pager   View output through the built-in pager

Features:
  - ATX and Setext headings
  - fenced code blocks
  - block quotes
  - unordered and ordered lists
  - task list items
  - thematic breaks
  - basic GFM-style tables
  - inline code, emphasis, strong, strikethrough, and links

Notes:
  - This is a lightweight reader, not a full CommonMark/GFM implementation
  - ANSI styling is used only when stdout is a terminal
";

fn main() {
    if let Err(error) = run() {
        eprintln!("error: {error}");
        process::exit(1);
    }
}

fn run() -> Result<(), String> {
    let mut path = None;
    let mut pager = false;

    for arg in env::args().skip(1) {
        match arg.as_str() {
            "-h" | "--help" => {
                print!("{HELP}");
                return Ok(());
            }
            "-p" | "--pager" => {
                pager = true;
            }
            _ if arg.starts_with('-') => {
                return Err(format!("unknown option: {arg}\n\n{HELP}"));
            }
            _ => {
                if path.is_some() {
                    return Err(format!("unexpected extra argument: {arg}\n\n{HELP}"));
                }
                path = Some(arg);
            }
        }
    }

    let markdown = match path {
        Some(path) => {
            fs::read_to_string(&path).map_err(|error| format!("failed to read {path}: {error}"))?
        }
        None => read_stdin()?,
    };

    let rendered = render_markdown(&markdown, io::stdout().is_terminal() || pager);
    if pager {
        run_pager(&rendered)?;
    } else {
        print!("{rendered}");
    }
    Ok(())
}

fn read_stdin() -> Result<String, String> {
    let stdin = io::stdin();
    if stdin.is_terminal() {
        return Err(format!("missing input\n\n{HELP}"));
    }

    let mut markdown = String::new();
    stdin
        .lock()
        .read_to_string(&mut markdown)
        .map_err(|error| format!("failed to read stdin: {error}"))?;
    Ok(markdown)
}