//! `koh man` — the manual, built into the binary. //! //! The page lives in `docs/koh3-man.txt` and is embedded at build time, so a //! `koh` copied to another machine still carries its own documentation. With a //! command name it prints just that command's part; otherwise the whole page, //! through the built-in pager on a terminal. module cli import os import src.fmt import src.view const manual = $embed_file('../../docs/koh3-man.txt').to_string() pub fn cmd_man(raw []string) int { args := parse('man', raw, Options{}) or { return usage_error(err.msg()) } mut text := manual if args.positional.len > 0 { name := args.positional_at(0) text = section_of(name) or { eprintln(' ${fmt.red(fmt.glyph_fail)} the manual has no "${name}" — try "koh man" for the whole page') return 2 } } page(text) return 0 } // section_of returns one command's entry from the COMMANDS section: its // synopsis lines, which may name several commands, down to the next entry. fn section_of(name string) ?string { lines := manual.split_into_lines() mut start := -1 mut in_commands := false for i, line in lines { if line == 'COMMANDS' { in_commands = true } else if in_commands && is_synopsis(line) && (line.starts_with(' koh ${name} ') || line == ' koh ${name}') { start = i break } } if start < 0 { return none } for start > 0 && is_synopsis(lines[start - 1]) { start-- } mut end := start for end < lines.len && is_synopsis(lines[end]) { end++ } // The body is indented eight columns; anything shallower starts the next // entry, group or section. for end < lines.len && (lines[end] == '' || lines[end].starts_with(' ')) { end++ } return lines[start..end].join('\n').trim_right('\n ') } fn is_synopsis(line string) bool { return line.starts_with(' koh ') } // page shows the manual in Koh 2's colours, through the built-in pager when it // is longer than the terminal, and plainly when the output is going somewhere // else. fn page(text string) { lines := text.trim_right('\n').split_into_lines().map(manual_line(it)) rows, _ := view.terminal_size() if os.is_atty(1) != 0 && os.is_atty(0) != 0 && lines.len >= rows { view.page_manual(lines) return } for line in lines { println(line.text) } } // manual_line colours one line as Koh 2's `colorManLine` does: headings blue // behind a ◆, command lines amber, lead-ins ending in a colon orange, and body // text light with its `--flags` picked out. fn manual_line(line string) view.ManualLine { trimmed := line.trim_left(' ') if line != '' && !line.starts_with(' ') && line == line.to_upper() { return view.ManualLine{ text: if fmt.colour_enabled() { fmt.blue('◆ ${line}') } else { line } section: line } } if trimmed.starts_with('koh ') { return view.ManualLine{ text: fmt.gold(line) } } if trimmed != line && trimmed.ends_with(':') { return view.ManualLine{ text: fmt.warning(line) } } return view.ManualLine{ text: line[..line.len - trimmed.len] + body_text(trimmed) } } // body_text paints body text light, with each `--flag` in it amber. fn body_text(text string) string { mut out := '' mut rest := text for { at := rest.index('--') or { break } mut end := at + 2 for end < rest.len && rest[end] !in [` `, `,`, `)`, `.`, `\``] { end++ } if end == at + 2 { out += fmt.light(rest[..end]) } else { out += fmt.light(rest[..at]) + fmt.gold(rest[at..end]) } rest = rest[end..] } return out + fmt.light(rest) }