Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 53 additions & 16 deletions argv/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ fn help_structure(
chain: &[&CommandMeta<'_>],
long: bool,
inherit_version_actions: bool,
suppress_global: bool,
) -> HelpStructure {
let meta = *chain.last().expect("a page is always about some command");
let mut headings = Vec::new();
Expand Down Expand Up @@ -716,6 +717,11 @@ fn help_structure(
}

let (own, inherited) = own_and_global(chain, inherit_version_actions);
let inherited = if suppress_global {
Vec::new()
} else {
inherited
};
let visible_arg = |arg: &&ArgMeta<'_>| {
!arg.hide
&& if long {
Expand Down Expand Up @@ -933,15 +939,23 @@ fn rendered_page(
long: bool,
style: Style,
inherit_version_actions: bool,
suppress_global: bool,
) -> String {
let sections = if long {
long_sections(spec, path, chain, inherit_version_actions)
long_sections(spec, path, chain, inherit_version_actions, suppress_global)
} else {
short_sections(spec, path, chain, inherit_version_actions)
short_sections(spec, path, chain, inherit_version_actions, suppress_global)
};
// Plain output never reads the spellings used to recognize colored spans.
let structure = if style.coloured {
help_structure(spec, path, chain, long, inherit_version_actions)
help_structure(
spec,
path,
chain,
long,
inherit_version_actions,
suppress_global,
)
} else {
HelpStructure::default()
};
Expand Down Expand Up @@ -984,7 +998,15 @@ fn assembled_help(
inherit_version_actions: bool,
include_default_help: bool,
) -> String {
let page = rendered_page(spec, path, chain, long, style, inherit_version_actions);
let page = rendered_page(
spec,
path,
chain,
long,
style,
inherit_version_actions,
false,
);
if include_default_help {
with_default_command_help(
spec,
Expand Down Expand Up @@ -1472,7 +1494,7 @@ fn short_help_with(
) -> String {
assemble(
spec,
&short_sections(spec, path, chain, inherit_version_actions),
&short_sections(spec, path, chain, inherit_version_actions, false),
Style::PLAIN,
)
}
Expand All @@ -1482,17 +1504,22 @@ fn short_sections(
path: &[&str],
chain: &[&CommandMeta<'_>],
inherit_version_actions: bool,
suppress_global: bool,
) -> Sections {
let meta = *chain.last().expect("a page is always about some command");
let (own, inherited) = own_and_global(chain, inherit_version_actions);
let own: Vec<_> = own
.into_iter()
.filter(|flag| !flag.hide_short_help)
.collect();
let inherited: Vec<_> = inherited
.into_iter()
.filter(|(flag, _)| !flag.hide_short_help)
.collect();
let inherited: Vec<_> = if suppress_global {
Vec::new()
} else {
inherited
.into_iter()
.filter(|(flag, _)| !flag.hide_short_help)
.collect()
};
let mut sections = Sections::default();
// The narrow page wraps too. Its descriptions used to run off the end of the terminal,
// which the wide page has never done — and `-h` is the form most people type.
Expand Down Expand Up @@ -1786,13 +1813,18 @@ fn with_default_command_help(
let mut child_path = path.to_vec();
child_path.push(child.cmd.name);
let child_chain = [root, child];
// The root's own page, printed directly above, already lists every one of its visible
// flags under `Flags:` — global-marked or not. Since the child's only ancestor here is
// that same root, its `Global flags:` section would repeat exactly that list, so it is
// dropped from the appended page rather than shown twice.
let child_page = rendered_page(
spec,
&child_path,
&child_chain,
long,
style,
inherit_version_actions,
true,
);
let mut out = parent.trim_end().to_string();
out.push_str("\n\nDefault command: ");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -2528,7 +2560,7 @@ fn long_help_with(
) -> String {
assemble(
spec,
&long_sections(spec, path, chain, inherit_version_actions),
&long_sections(spec, path, chain, inherit_version_actions, false),
Style::PLAIN,
)
}
Expand All @@ -2538,17 +2570,22 @@ fn long_sections(
path: &[&str],
chain: &[&CommandMeta<'_>],
inherit_version_actions: bool,
suppress_global: bool,
) -> Sections {
let meta = *chain.last().expect("a page is always about some command");
let (own, inherited) = own_and_global(chain, inherit_version_actions);
let own: Vec<_> = own
.into_iter()
.filter(|flag| !flag.hide_long_help)
.collect();
let inherited: Vec<_> = inherited
.into_iter()
.filter(|(flag, _)| !flag.hide_long_help)
.collect();
let inherited: Vec<_> = if suppress_global {
Vec::new()
} else {
inherited
.into_iter()
.filter(|(flag, _)| !flag.hide_long_help)
.collect()
};
let width = terminal_width(meta);
let mut sections = Sections::default();
let out = &mut sections.about;
Expand Down Expand Up @@ -3717,9 +3754,9 @@ fn topics_with_blocks(
) -> Option<Vec<(Topic, String)>> {
let (path, chain) = find(spec, cmd)?;
let sections = if long {
long_sections(spec, &path, &chain, false)
long_sections(spec, &path, &chain, false, false)
} else {
short_sections(spec, &path, &chain, false)
short_sections(spec, &path, &chain, false, false)
};
let mut used = Vec::<String>::new();
Some(
Expand Down
23 changes: 23 additions & 0 deletions conformance/tests/default_subcommand_help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use usage_derive::Cli;
unknown_flags = "error"
)]
struct Ex {
#[usage(long, global = true)]
#[allow(dead_code)]
color: bool,
#[usage(subcommand)]
#[allow(dead_code)]
command: Option<Commands>,
Expand Down Expand Up @@ -42,6 +45,26 @@ fn parent_help_names_the_default_and_appends_its_page() {
assert!(page.contains("Usage: ex install"), "{page}");
}

#[test]
fn the_appended_default_page_does_not_repeat_the_root_global_flags() {
// The root's own `Flags:` section already lists `--color`; the appended `install` page
// must not show it again under its own `Global flags:` section — on both the short and
// the long page, since each has its own render path down to `with_default_command_help`.
for page in [
usage_argv::help::short_help(Ex::spec(), &["ex"], &[Ex::spec().root]),
usage_argv::help::long_help(Ex::spec(), &["ex"], &[Ex::spec().root]),
] {
let (root, appended) = page
.split_once("Default command: ")
.expect("the default command's page is appended");
assert!(root.contains("--color"), "{page}");
assert!(
!appended.contains("--color") && !appended.contains("Global flags:"),
"the appended page should not repeat the root's --color under its own Global flags section:\n{page}"
);
}
}

#[test]
fn the_appended_default_page_is_styled_like_the_parent() {
let page = usage_argv::help::render_styled(
Expand Down
9 changes: 5 additions & 4 deletions docs/spec/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,11 @@ cmd "query" {}
```

`ex --help` then lists `install (default)` and `query`, says `Default command:
install`, and prints the same page as `ex install --help`. Parent-only flags
and an empty invocation stay on the parent. A hidden default is neither marked
nor appended. `flatten_help` already inlines every child, so the append is
skipped there.
install`, and prints the same page as `ex install --help` — minus its `Global
flags:` section, which the parent page just printed above it. Parent-only
flags and an empty invocation stay on the parent. A hidden default is neither
marked nor appended. `flatten_help` already inlines every child, so the append
is skipped there.

Rust derives use `#[usage(default_subcommand = "install", default_subcommand_help)]`.

Expand Down
19 changes: 14 additions & 5 deletions go/argv/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ const shortCol = 4
// the root down to this one, which is what a page needs to work out which
// inherited globals are still this command's to offer.
func ShortHelp(spec HelpSpec, path []string, chain []*Command, help HelpTable) string {
return withDefaultCommandHelp(spec, path, chain, help, false, shortHelpPage(spec, path, chain, help))
return withDefaultCommandHelp(spec, path, chain, help, false, shortHelpPage(spec, path, chain, help, false))
}

func shortHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTable) string {
func shortHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTable, suppressGlobal bool) string {
if len(chain) == 0 {
return ""
}
Expand Down Expand Up @@ -174,7 +174,11 @@ func shortHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTabl

own, inherited := ownAndGlobal(chain, help)
own = filterHelpMode(own, help, false)
inherited = filterHelpMode(inherited, help, false)
if suppressGlobal {
inherited = nil
} else {
inherited = filterHelpMode(inherited, help, false)
}

// One column over *both* lists, so the two sections read as one table with a
// rule through it rather than two tables that happen to be adjacent.
Expand Down Expand Up @@ -413,11 +417,16 @@ func withDefaultCommandHelp(spec HelpSpec, path []string, chain []*Command, help
}
childPath := append(append([]string{}, path...), child.Name)
childChain := []*Command{root, child}
// The root's own page, printed directly above, already lists every one of its
// visible flags under "Flags:" — global-marked or not. Since the child's only
// ancestor here is that same root, its "Global flags:" section would repeat
// exactly that list, so it is dropped from the appended page rather than shown
// twice.
var childPage string
if long {
childPage = longHelpPage(spec, childPath, childChain, help)
childPage = longHelpPage(spec, childPath, childChain, help, true)
} else {
childPage = shortHelpPage(spec, childPath, childChain, help)
childPage = shortHelpPage(spec, childPath, childChain, help, true)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
var b strings.Builder
b.WriteString(strings.TrimRight(parent, "\n"))
Expand Down
12 changes: 8 additions & 4 deletions go/argv/page_long.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ const (

// LongHelp renders what `--help` prints for the command at the end of `chain`.
func LongHelp(spec HelpSpec, path []string, chain []*Command, help HelpTable) string {
return withDefaultCommandHelp(spec, path, chain, help, true, longHelpPage(spec, path, chain, help))
return withDefaultCommandHelp(spec, path, chain, help, true, longHelpPage(spec, path, chain, help, false))
}

func longHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTable) string {
func longHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTable, suppressGlobal bool) string {
if len(chain) == 0 {
return ""
}
Expand Down Expand Up @@ -116,7 +116,11 @@ func longHelpPage(spec HelpSpec, path []string, chain []*Command, help HelpTable

own, inherited := ownAndGlobal(chain, help)
own = filterHelpMode(own, help, true)
inherited = filterHelpMode(inherited, help, true)
if suppressGlobal {
inherited = nil
} else {
inherited = filterHelpMode(inherited, help, true)
}

// One column over *both* lists, so the two sections read as one table with a
// rule through it rather than two tables that happen to be adjacent.
Expand Down Expand Up @@ -204,7 +208,7 @@ func AllHelp(spec HelpSpec, path []string, chain []*Command, help HelpTable) str
if out.Len() > 0 {
out.WriteByte('\n')
}
out.WriteString(longHelpPage(spec, currentPath, currentChain, help))
out.WriteString(longHelpPage(spec, currentPath, currentChain, help, false))
current := currentChain[len(currentChain)-1]
children := append([]*Command(nil), current.Subcommands...)
sort.SliceStable(children, func(i, j int) bool {
Expand Down
25 changes: 25 additions & 0 deletions go/argv/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,31 @@ func TestDefaultSubcommandHelpAppendsTheChildPage(t *testing.T) {
}
}

func TestDefaultSubcommandHelpDoesNotRepeatRootGlobalFlags(t *testing.T) {
spec, root, _, _, help := defaultInstallFixture()
root.DefaultSubcommandHelp = true
color := &Flag{Name: "color", Key: 6, Longs: []string{"color"}, Global: true}
root.Flags = append(root.Flags, color)
help = append(help, Help{Key: 6})
// Short and long help each have their own render path down to
// withDefaultCommandHelp, so both need to suppress the repeat.
for _, page := range []string{
ShortHelp(spec, []string{"ex"}, []*Command{root}, help),
LongHelp(spec, []string{"ex"}, []*Command{root}, help),
} {
rootPage, appended, ok := strings.Cut(page, "Default command: ")
if !ok {
t.Fatalf("the default command's page should be appended:\n%s", page)
}
if !strings.Contains(rootPage, "--color") {
t.Fatalf("the root's own page should list --color:\n%s", page)
}
if strings.Contains(appended, "--color") || strings.Contains(appended, "Global flags:") {
t.Fatalf("the appended page should not repeat the root's --color under its own Global flags section:\n%s", page)
}
}
}

func TestHiddenDefaultIsNotMarkedOrAppended(t *testing.T) {
spec, root, _, _, help := defaultInstallFixture()
root.DefaultSubcommandHelp = true
Expand Down