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 19, 2024
1 parent 33f9b89 commit abe4799
Show file tree
Hide file tree
Showing 3 changed files with 96 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
73 changes: 72 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::fmt::Write as _;
use std::io::Write;

use crossterm::style::Stylize;
use tracing::instrument;

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

#[instrument(skip_all)]
pub(crate) fn cmd_help(
_ui: &mut Ui,
ui: &mut Ui,
command: &CommandHelper,
args: &HelpArgs,
) -> Result<(), CommandError> {
if let [name] = &*args.command {
if let Some(category) = find_category(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");
Expand All @@ -42,8 +55,66 @@ pub(crate) fn cmd_help(
.app()
.clone()
.subcommand_required(true)
.after_help(format_categories(command.app()))
.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))
}

#[derive(Clone)]
struct Category {
description: &'static str,
content: &'static str,
}

// TODO: Add all documentation to categories
//
// Maybe adding some code to build.rs to find all the docs files and build the
// `CATEGORIES` 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.
const CATEGORIES: &[(&str, Category)] = &[
(
"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"),
},
),
];

fn find_category(name: &str) -> Option<&Category> {
CATEGORIES
.iter()
.find(|(cat_name, _)| *cat_name == name)
.map(|tuple| &tuple.1)
}

fn format_categories(command: &clap::Command) -> String {
let subcommand_max_len = command
.get_subcommands()
.map(|cmd| cmd.get_name().len())
.max()
.unwrap();

let mut ret = String::new();

writeln!(ret, "{}", "Help Categories:".bold().underlined()).unwrap();
for (name, category) in CATEGORIES {
write!(ret, " {} ", format!("{name:<subcommand_max_len$}").bold()).unwrap();
writeln!(ret, "{}", category.description,).unwrap();
}

ret
}
27 changes: 22 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,25 @@ 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).+(?<h>Help Categories:.+)", "$h"),
]}, {
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 abe4799

Please sign in to comment.