cmd_man.v

3.4 KB · raw

1//! `koh man` — the manual, built into the binary.2//!3//! The page lives in `docs/koh3-man.txt` and is embedded at build time, so a4//! `koh` copied to another machine still carries its own documentation. With a5//! command name it prints just that command's part; otherwise the whole page,6//! through the built-in pager on a terminal.78module cli910import os11import src.fmt12import src.view1314const manual = $embed_file('../../docs/koh3-man.txt').to_string()1516pub fn cmd_man(raw []string) int {17	args := parse('man', raw, Options{}) or { return usage_error(err.msg()) }18	mut text := manual19	if args.positional.len > 0 {20		name := args.positional_at(0)21		text = section_of(name) or {22			eprintln('  ${fmt.red(fmt.glyph_fail)} the manual has no "${name}" — try "koh man" for the whole page')23			return 224		}25	}26	page(text)27	return 028}2930// section_of returns one command's entry from the COMMANDS section: its31// synopsis lines, which may name several commands, down to the next entry.32fn section_of(name string) ?string {33	lines := manual.split_into_lines()34	mut start := -135	mut in_commands := false36	for i, line in lines {37		if line == 'COMMANDS' {38			in_commands = true39		} else if in_commands && is_synopsis(line)40			&& (line.starts_with('    koh ${name} ') || line == '    koh ${name}') {41			start = i42			break43		}44	}45	if start < 0 {46		return none47	}48	for start > 0 && is_synopsis(lines[start - 1]) {49		start--50	}51	mut end := start52	for end < lines.len && is_synopsis(lines[end]) {53		end++54	}55	// The body is indented eight columns; anything shallower starts the next56	// entry, group or section.57	for end < lines.len && (lines[end] == '' || lines[end].starts_with('        ')) {58		end++59	}60	return lines[start..end].join('\n').trim_right('\n ')61}6263fn is_synopsis(line string) bool {64	return line.starts_with('    koh ')65}6667// page shows the manual in Koh 2's colours, through the built-in pager when it68// is longer than the terminal, and plainly when the output is going somewhere69// else.70fn page(text string) {71	lines := text.trim_right('\n').split_into_lines().map(manual_line(it))72	rows, _ := view.terminal_size()73	if os.is_atty(1) != 0 && os.is_atty(0) != 0 && lines.len >= rows {74		view.page_manual(lines)75		return76	}77	for line in lines {78		println(line.text)79	}80}8182// manual_line colours one line as Koh 2's `colorManLine` does: headings blue83// behind a ◆, command lines amber, lead-ins ending in a colon orange, and body84// text light with its `--flags` picked out.85fn manual_line(line string) view.ManualLine {86	trimmed := line.trim_left(' ')87	if line != '' && !line.starts_with(' ') && line == line.to_upper() {88		return view.ManualLine{89			text:    if fmt.colour_enabled() { fmt.blue('◆ ${line}') } else { line }90			section: line91		}92	}93	if trimmed.starts_with('koh ') {94		return view.ManualLine{95			text: fmt.gold(line)96		}97	}98	if trimmed != line && trimmed.ends_with(':') {99		return view.ManualLine{100			text: fmt.warning(line)101		}102	}103	return view.ManualLine{104		text: line[..line.len - trimmed.len] + body_text(trimmed)105	}106}107108// body_text paints body text light, with each `--flag` in it amber.109fn body_text(text string) string {110	mut out := ''111	mut rest := text112	for {113		at := rest.index('--') or { break }114		mut end := at + 2115		for end < rest.len && rest[end] !in [` `, `,`, `)`, `.`, `\``] {116			end++117		}118		if end == at + 2 {119			out += fmt.light(rest[..end])120		} else {121			out += fmt.light(rest[..at]) + fmt.gold(rest[at..end])122		}123		rest = rest[end..]124	}125	return out + fmt.light(rest)126}