8001f6686656

rewrite the manual short and plain-spoken, correct its stale claims, and make koh man <command> return the whole entry with examples (3.0.71)

time
2026-09-26 14:04 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
koh man
parent
6554688b6256
4 files VERSION+1 −1src/cli/cmd_man.v+23 −15tests/cli_test.v+3 −0v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.701+3.0.71

view file

src/cli/cmd_man.v+23 −15
@@ -27,13 +27,17 @@2727 	return 02828 }2929 30-// section_of returns the part of the COMMANDS section that documents one31-// command: from its synopsis to the next command's.30+// section_of returns one command's entry from the COMMANDS section: its31+// synopsis lines, which may name several commands, down to the next entry.3232 fn section_of(name string) ?string {3333 	lines := manual.split_into_lines()3434 	mut start := -135+	mut in_commands := false3536 	for i, line in lines {36-		if line.starts_with('    koh ') && line.contains(' ${name}') {37+		if line == 'COMMANDS' {38+			in_commands = true39+		} else if in_commands && is_synopsis(line)40+			&& (line.starts_with('    koh ${name} ') || line == '    koh ${name}') {3741 			start = i3842 			break3943 		}@@ -41,19 +45,23 @@4145 	if start < 0 {4246 		return none4347 	}44-	mut end := lines.len45-	for i := start + 1; i < lines.len; i++ {46-		trimmed := lines[i].trim_space()47-		if trimmed.starts_with('koh ') && lines[i].starts_with('    ') {48-			end = i49-			break50-		}51-		if lines[i].starts_with('OPTIONS') || lines[i].starts_with('ENVIRONMENT') {52-			end = i53-			break54-		}48+	for start > 0 && is_synopsis(lines[start - 1]) {49+		start--5550 	}56-	return lines[start..end].join('\n')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+}62+63+fn is_synopsis(line string) bool {64+	return line.starts_with('    koh ')5765 }5866 5967 // page shows the manual in Koh 2's colours, through the built-in pager when it

view file

tests/cli_test.v+3 −0
@@ -897,6 +897,9 @@897897 	assert strip.code == 0898898 	assert strip.out.contains('strip')899899 	assert !strip.out.contains('koh save ')900+	steal := run_home(dir, home, 'man', 'steal').out901+	assert steal.starts_with('    koh steal') && steal.contains('koh steal my-project')902+	assert run_home(dir, home, 'man', 'init').out.trim_space() == "koh init\n        Create `.koh` here, and a starter `.kohignore` if there isn't one."900903 	assert run_home(dir, home, 'man', 'nonsense').code == 2901904 	legacy := temp_repo('legacy')902905 	defer { os.rmdir_all(legacy) or {} }

view file

v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 	name: 'koh3'3-	version: '3.0.70'3+	version: '3.0.71'44 	description: 'One SQLite file per repo, one line of history.'55 	license: 'MIT'66 }

view file