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 13, 2024
1 parent 93a4fcf commit 7024fe6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* `jj squash` now supports `-f/-t` shorthands for `--from/--[in]to`.

* `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
83 changes: 83 additions & 0 deletions cli/src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::fmt::Write;

use clap::builder::StyledStr;
use itertools::Itertools;
use tracing::instrument;

use crate::cli_util::CommandHelper;
Expand All @@ -26,21 +31,99 @@ pub(crate) struct HelpArgs {
pub(crate) command: Vec<String>,
}

struct Category {
description: &'static str,
content: &'static str,
}

#[instrument(skip_all)]
pub(crate) fn cmd_help(
_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
// HashMap 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 = HashMap::from([
(
"revsets",
Category {
description: "Documentation about revsets",
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 Some(category) = categories.get(args.command.join(" ").as_str()) {
let mut help_str = StyledStr::new();
let _ = help_str.write_str(category.content);

let help_err = clap::Command::new("stub")
.override_help(help_str)
.try_get_matches_from(vec!["stub", "--help"])
.unwrap_err();

return Err(command_error::cli_error(help_err));
}

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[4mCategories:\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")
);

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.
#[allow(clippy::uninlined_format_args)]
let help_err = command
.app()
.clone()
.help_template(format!(
"{}\n\n{}\n\n{}",
help_template_before_categories,
help_template_categories,
help_template_after_categories
))
.subcommand_required(true)
.try_get_matches_from(args_to_show_help)
.expect_err("Clap library should return a DisplayHelp error in this context");
Expand Down
14 changes: 9 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,12 @@ fn test_help() {
For more information, try '--help'.
"#);
}

#[test]
fn test_help_category() {
let test_env = TestEnvironment::default();
// Help command should work 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 7024fe6

Please sign in to comment.