Skip to content

Commit

Permalink
help: Add a category feature
Browse files Browse the repository at this point in the history
It would be nice to not need to go the documentation website. This aims
to solve that by introducing the concept of categories to the help
command.

Basically, categories are things that we want to add help messages to,
but they don't necessarily have an associated subcommand.

For now we only have two categories:
	- `revsets`: Shows the docs for revsets
	- `tutorial`: Shows the tutorial that is on the documentation

You get the category content by tipping `jj help revsets`.

It would be nice to have all the documentation on the categories, maybe
a next commit could do it.
  • Loading branch information
Grillo-0 committed Oct 17, 2024
1 parent 33f9b89 commit 01830bb
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* Timestamp objects in templates now have `after(date) -> Boolean` and
`before(date) -> Boolean` methods for comparing timestamps to other dates.

* `jj help` now can give help for some categories, like `jj help revsets`.

### Fixed bugs

* Error on `trunk()` revset resolution is now handled gracefully.
Expand Down
82 changes: 81 additions & 1 deletion cli/src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::BTreeMap;
use std::io::Write;

use itertools::Itertools;
use tracing::instrument;

use crate::cli_util::CommandHelper;
Expand All @@ -28,22 +32,98 @@ pub(crate) struct HelpArgs {

#[instrument(skip_all)]
pub(crate) fn cmd_help(
_ui: &mut Ui,
ui: &mut Ui,
command: &CommandHelper,
args: &HelpArgs,
) -> Result<(), CommandError> {
// TODO: Add all documentation to categories
//
// Maybe adding some code to build.rs to find all the docs files and build the
// BTreeMap at compile time.
//
// It would be cool to follow the docs hierarchy somehow.
//
// One of the problems would be `config.md`, as it has the same name as a
// subcommand.
let categories = BTreeMap::from([
(
"revsets",
Category {
description: "A functional language for selecting a set of revision",
content: include_str!("../../../docs/revsets.md"),
},
),
(
"tutorial",
Category {
description: "Show a tutorial to get started with jj",
content: include_str!("../../../docs/tutorial.md"),
},
),
]);

if let [name] = &*args.command {
if let Some(category) = categories.get(name.as_str()) {
ui.request_pager();
write!(ui.stdout(), "{}", category.content)?;

return Ok(());
}
}

let mut args_to_show_help = vec![command.app().get_name()];
args_to_show_help.extend(args.command.iter().map(|s| s.as_str()));
args_to_show_help.push("--help");

let help_template_before_categories = "\
{about-with-newline}\n{usage-heading} {usage}\n\n\x1b[1m\x1b[4mCommands:\x1b[0m\n{subcommands}
";

let subcommand_max_len = command
.app()
.get_subcommands()
.map(|cmd| cmd.get_name().len())
.max()
.unwrap();

let help_template_categories = format!(
"\x1b[1m\x1b[4mHelp Categories:\x1b[0m\n{}",
&categories
.iter()
.map(|(&name, category)| format!(
"{{tab}}\x1b[1m{:<width$}\x1b[0m{{tab}}{}",
name,
category.description,
width = subcommand_max_len
))
.join("\n")
) + "\n";

let help_template_after_categories = "\
\x1b[1m\x1b[4mOptions:\x1b[0m\n{options}
";

// TODO: `help log -- -r` will gives an cryptic error, ideally, it should state
// that the subcommand `log -r` doesn't exist.
let help_err = command
.app()
.clone()
.help_template(
[
help_template_before_categories,
help_template_categories.as_str(),
help_template_after_categories,
]
.join("\n"),
)
.subcommand_required(true)
.try_get_matches_from(args_to_show_help)
.expect_err("Clap library should return a DisplayHelp error in this context");

Err(command_error::cli_error(help_err))
}

struct Category {
description: &'static str,
content: &'static str,
}
29 changes: 24 additions & 5 deletions cli/tests/test_help_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ use crate::common::TestEnvironment;
fn test_help() {
let test_env = TestEnvironment::default();

let help_cmd_stdout = test_env.jj_cmd_success(test_env.env_root(), &["help"]);
// The help command output should be equal to the long --help flag
let help_flag_stdout = test_env.jj_cmd_success(test_env.env_root(), &["--help"]);
assert_eq!(help_cmd_stdout, help_flag_stdout);

// Help command should work with commands
let help_cmd_stdout = test_env.jj_cmd_success(test_env.env_root(), &["help", "log"]);
let help_flag_stdout = test_env.jj_cmd_success(test_env.env_root(), &["log", "--help"]);
Expand Down Expand Up @@ -84,3 +79,27 @@ fn test_help() {
For more information, try '--help'.
"#);
}

#[test]
fn test_help_category() {
let test_env = TestEnvironment::default();
// Now that we have a custom help output, capture it
let help_cmd_stdout = test_env.jj_cmd_success(test_env.env_root(), &["help"]);

insta::with_settings!({filters => vec![
(r"(?s:.)+Commands:\n(.+\n)+\s+\n", ""),
(r"Options:\n((.+\n)+\n?)+", ""),
]}, {
insta::assert_snapshot!(help_cmd_stdout, @r#"
Help Categories:
revsets A functional language for selecting a set of revision
tutorial Show a tutorial to get started with jj
"#);
});

// Help command should work with category commands
let help_cmd_stdout = test_env.jj_cmd_success(test_env.env_root(), &["help", "revsets"]);
// It should be equal to the docs
assert_eq!(help_cmd_stdout, include_str!("../../docs/revsets.md"));
}

0 comments on commit 01830bb

Please sign in to comment.