Skip to content

Commit 4ac0ee7

Browse files
ScottArbeitScott Arbeit
andauthored
Add sections to longer help screens. (#56)
* CLI: group root help commands into sections * CLI.Tests: add coverage for grouped root help output * Docs: document root help grouping * CLI: group subcommand help sections * CLI: ignore help tokens after terminator --------- Co-authored-by: Scott Arbeit <scottarbeit@github.com>
1 parent 46fd43b commit 4ac0ee7

File tree

5 files changed

+673
-95
lines changed

5 files changed

+673
-95
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ More context:
3030

3131
This project uses **bd (beads)** for issue tracking.
3232
Always use `bd` commands to manage your work.
33+
When creating beads, include a description.
3334
Run `bd prime` for workflow context, or install hooks (`bd hooks install`) for auto-injection.
3435

3536
**Quick reference:**

src/Grace.CLI.Tests/Grace.CLI.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="Connect.Tests.fs" />
1818
<Compile Include="CommandParsing.Tests.fs" />
1919
<Compile Include="HelpDoesNotReadConfig.Tests.fs" />
20+
<Compile Include="RootHelpGrouping.Tests.fs" />
2021
<Compile Include="HistoryStorage.Tests.fs" />
2122
<Compile Include="WorkItem.Commands.Tests.fs" />
2223
<Compile Include="Review.Commands.Tests.fs" />
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
namespace Grace.CLI.Tests
2+
3+
open FsUnit
4+
open Grace.CLI
5+
open Grace.Shared.Client
6+
open NUnit.Framework
7+
open Spectre.Console
8+
open System
9+
open System.IO
10+
11+
[<NonParallelizable>]
12+
module RootHelpGroupingTests =
13+
type private GroupedHelpExpectation = { Args: string array; Headings: string list }
14+
15+
let private groupedHelpExpectations =
16+
[
17+
{
18+
Args = [| "repo"; "-h" |]
19+
Headings =
20+
[
21+
"Create and initialize:"
22+
"Inspect:"
23+
"Configuration:"
24+
"Lifecycle:"
25+
]
26+
}
27+
{
28+
Args = [| "branch"; "-h" |]
29+
Headings =
30+
[
31+
"Create and contribute:"
32+
"Promotion workflow:"
33+
"Inspect:"
34+
"Settings:"
35+
"Lifecycle:"
36+
]
37+
}
38+
{
39+
Args = [| "owner"; "-h" |]
40+
Headings =
41+
[
42+
"Create and inspect:"
43+
"Settings:"
44+
"Lifecycle:"
45+
]
46+
}
47+
{
48+
Args = [| "org"; "-h" |]
49+
Headings =
50+
[
51+
"Create and inspect:"
52+
"Settings:"
53+
"Lifecycle:"
54+
]
55+
}
56+
{
57+
Args = [| "pg"; "-h" |]
58+
Headings =
59+
[
60+
"Create and inspect:"
61+
"Manage promotions:"
62+
"Workflow:"
63+
"Lifecycle:"
64+
]
65+
}
66+
]
67+
68+
let private setAnsiConsoleOutput (writer: TextWriter) =
69+
let settings = AnsiConsoleSettings()
70+
settings.Out <- AnsiConsoleOutput(writer)
71+
AnsiConsole.Console <- AnsiConsole.Create(settings)
72+
73+
let private runWithCapturedOutput (args: string array) =
74+
use writer = new StringWriter()
75+
let originalOut = Console.Out
76+
77+
try
78+
Console.SetOut(writer)
79+
setAnsiConsoleOutput writer
80+
let exitCode = GraceCommand.main args
81+
exitCode, writer.ToString()
82+
finally
83+
Console.SetOut(originalOut)
84+
setAnsiConsoleOutput originalOut
85+
86+
let private withFileBackup (path: string) (action: unit -> unit) =
87+
let backupPath = path + ".testbackup"
88+
let hadExisting = File.Exists(path)
89+
90+
if hadExisting then File.Copy(path, backupPath, true)
91+
92+
try
93+
action ()
94+
finally
95+
if hadExisting then
96+
File.Copy(backupPath, path, true)
97+
File.Delete(backupPath)
98+
elif File.Exists(path) then
99+
File.Delete(path)
100+
101+
let private withGraceUserFileBackups (action: unit -> unit) =
102+
let configPath = UserConfiguration.getUserConfigurationPath ()
103+
let historyPath = HistoryStorage.getHistoryFilePath ()
104+
let lockPath = HistoryStorage.getHistoryLockPath ()
105+
106+
withFileBackup configPath (fun () -> withFileBackup historyPath (fun () -> withFileBackup lockPath action))
107+
108+
let private sliceBetween (text: string) (startText: string) (endText: string) =
109+
let startIndex = text.IndexOf(startText, StringComparison.Ordinal)
110+
let endIndex = text.IndexOf(endText, StringComparison.Ordinal)
111+
112+
if startIndex >= 0 && endIndex > startIndex then
113+
text.Substring(startIndex, endIndex - startIndex)
114+
else
115+
text
116+
117+
[<Test>]
118+
let ``root help groups commands`` () =
119+
withGraceUserFileBackups (fun () ->
120+
let exitCode, output = runWithCapturedOutput [||]
121+
exitCode |> should equal 0
122+
123+
output |> should contain "Getting started:"
124+
output |> should contain "Day-to-day development:"
125+
output |> should contain "Review and promotion:"
126+
127+
output
128+
|> should contain "Administration and access:"
129+
130+
output |> should contain "Local utilities:"
131+
132+
let gettingStarted = sliceBetween output "Getting started:" "Day-to-day development:"
133+
134+
gettingStarted |> should contain "auth"
135+
gettingStarted |> should contain "connect"
136+
gettingStarted |> should contain "config"
137+
gettingStarted |> should not' (contain "branch")
138+
139+
let dayToDay = sliceBetween output "Day-to-day development:" "Review and promotion:"
140+
141+
dayToDay |> should contain "branch"
142+
dayToDay |> should contain "diff"
143+
dayToDay |> should contain "directory-version"
144+
dayToDay |> should contain "watch"
145+
dayToDay |> should not' (contain "work"))
146+
147+
[<Test>]
148+
let ``subcommand help is not grouped`` () =
149+
withGraceUserFileBackups (fun () ->
150+
let exitCode, output =
151+
runWithCapturedOutput [| "branch"
152+
"-h" |]
153+
154+
exitCode |> should equal 0
155+
output |> should not' (contain "Getting started:")
156+
157+
output
158+
|> should not' (contain "Day-to-day development:")
159+
160+
output
161+
|> should not' (contain "Review and promotion:")
162+
163+
output
164+
|> should not' (contain "Administration and access:")
165+
166+
output |> should not' (contain "Local utilities:"))
167+
168+
[<Test>]
169+
let ``selected command helps are grouped`` () =
170+
withGraceUserFileBackups (fun () ->
171+
for expectation in groupedHelpExpectations do
172+
let exitCode, output = runWithCapturedOutput expectation.Args
173+
exitCode |> should equal 0
174+
175+
for heading in expectation.Headings do
176+
output |> should contain heading)

src/Grace.CLI/AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Read `../AGENTS.md` for global expectations before updating CLI code.
3030
expanding behavior instead of breaking existing scripts.
3131
3. Capture new command patterns or usage tips in this document to guide future
3232
agents.
33+
4. Root and selected subcommand help grouping lives in
34+
`src/Grace.CLI/Program.CLI.fs` under `rootHelpSections` and the related
35+
`*HelpSections` lists; update those lists when adding or renaming commands
36+
so new entries do not silently drift into "Other".
3337

3438
## Recent Patterns
3539

0 commit comments

Comments
 (0)