just playing with tangled
1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Write as _;
16use std::io::Write as _;
17
18use clap::builder::PossibleValue;
19use clap::builder::StyledStr;
20use crossterm::style::Stylize as _;
21use itertools::Itertools as _;
22use tracing::instrument;
23
24use crate::cli_util::CommandHelper;
25use crate::command_error;
26use crate::command_error::CommandError;
27use crate::ui::Ui;
28
29/// Print this message or the help of the given subcommand(s)
30#[derive(clap::Args, Clone, Debug)]
31pub(crate) struct HelpArgs {
32 /// Print help for the subcommand(s)
33 pub(crate) command: Vec<String>,
34 /// Show help for keywords instead of commands
35 #[arg(
36 long,
37 short = 'k',
38 conflicts_with = "command",
39 value_parser = KEYWORDS
40 .iter()
41 .map(|k| PossibleValue::new(k.name).help(k.description))
42 .collect_vec()
43 )]
44 pub(crate) keyword: Option<String>,
45}
46
47#[instrument(skip_all)]
48pub(crate) fn cmd_help(
49 ui: &mut Ui,
50 command: &CommandHelper,
51 args: &HelpArgs,
52) -> Result<(), CommandError> {
53 if let Some(name) = &args.keyword {
54 let keyword = find_keyword(name).expect("clap should check this with `value_parser`");
55 ui.request_pager();
56 write!(ui.stdout(), "{}", keyword.content)?;
57
58 return Ok(());
59 }
60
61 let bin_name = command
62 .string_args()
63 .first()
64 .map_or(command.app().get_name(), |name| name.as_ref());
65 let mut args_to_show_help = vec![bin_name];
66 args_to_show_help.extend(args.command.iter().map(|s| s.as_str()));
67 args_to_show_help.push("--help");
68
69 // TODO: `help log -- -r` will give a cryptic error, ideally, it should state
70 // that the subcommand `log -r` doesn't exist.
71 let help_err = command
72 .app()
73 .clone()
74 .subcommand_required(true)
75 .try_get_matches_from(args_to_show_help)
76 .expect_err("Clap library should return a DisplayHelp error in this context");
77
78 Err(command_error::cli_error(help_err))
79}
80
81#[derive(Clone)]
82struct Keyword {
83 name: &'static str,
84 description: &'static str,
85 content: &'static str,
86}
87
88// TODO: Add all documentation to keywords
89//
90// Maybe adding some code to build.rs to find all the docs files and build the
91// `KEYWORDS` at compile time.
92//
93// It would be cool to follow the docs hierarchy somehow.
94//
95// One of the problems would be `config.md`, as it has the same name as a
96// subcommand.
97//
98// TODO: Find a way to render markdown using ANSI escape codes.
99//
100// Maybe we can steal some ideas from https://github.com/jj-vcs/jj/pull/3130
101const KEYWORDS: &[Keyword] = &[
102 Keyword {
103 name: "bookmarks",
104 description: "Named pointers to revisions (similar to Git's branches)",
105 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "bookmarks.md")),
106 },
107 Keyword {
108 name: "config",
109 description: "How and where to set configuration options",
110 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "config.md")),
111 },
112 Keyword {
113 name: "filesets",
114 description: "A functional language for selecting a set of files",
115 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "filesets.md")),
116 },
117 Keyword {
118 name: "glossary",
119 description: "Definitions of various terms",
120 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "glossary.md")),
121 },
122 Keyword {
123 name: "revsets",
124 description: "A functional language for selecting a set of revision",
125 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "revsets.md")),
126 },
127 Keyword {
128 name: "templates",
129 description: "A functional language to customize command output",
130 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "templates.md")),
131 },
132 Keyword {
133 name: "tutorial",
134 description: "Show a tutorial to get started with jj",
135 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "tutorial.md")),
136 },
137];
138
139fn find_keyword(name: &str) -> Option<&Keyword> {
140 KEYWORDS.iter().find(|keyword| keyword.name == name)
141}
142
143pub fn show_keyword_hint_after_help() -> StyledStr {
144 let mut ret = StyledStr::new();
145 writeln!(
146 ret,
147 "{} lists available keywords. Use {} to show help for one of these keywords.",
148 "'jj help --help'".bold(),
149 "'jj help -k'".bold(),
150 )
151 .unwrap();
152 ret
153}