just playing with tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at diffedit3 347 lines 11 kB view raw
1// Copyright 2020 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::io::Write; 16 17use clap::builder::NonEmptyStringValueParser; 18use itertools::Itertools; 19use tracing::instrument; 20 21use crate::cli_util::{ 22 get_new_config_file_path, run_ui_editor, serialize_config_value, write_config_value_to_file, 23 CommandHelper, 24}; 25use crate::command_error::{config_error, user_error, CommandError}; 26use crate::config::{AnnotatedValue, ConfigSource}; 27use crate::generic_templater::GenericTemplateLanguage; 28use crate::template_builder::TemplateLanguage as _; 29use crate::templater::TemplatePropertyExt as _; 30use crate::ui::Ui; 31 32#[derive(clap::Args, Clone, Debug)] 33#[command(group = clap::ArgGroup::new("config_level").multiple(false).required(true))] 34pub(crate) struct ConfigArgs { 35 /// Target the user-level config 36 #[arg(long, group = "config_level")] 37 user: bool, 38 39 /// Target the repo-level config 40 #[arg(long, group = "config_level")] 41 repo: bool, 42} 43 44impl ConfigArgs { 45 fn get_source_kind(&self) -> ConfigSource { 46 if self.user { 47 ConfigSource::User 48 } else if self.repo { 49 ConfigSource::Repo 50 } else { 51 // Shouldn't be reachable unless clap ArgGroup is broken. 52 panic!("No config_level provided"); 53 } 54 } 55} 56 57/// Manage config options 58/// 59/// Operates on jj configuration, which comes from the config file and 60/// environment variables. 61/// 62/// For file locations, supported config options, and other details about jj 63/// config, see https://github.com/martinvonz/jj/blob/main/docs/config.md. 64#[derive(clap::Subcommand, Clone, Debug)] 65pub(crate) enum ConfigCommand { 66 #[command(visible_alias("l"))] 67 List(ConfigListArgs), 68 #[command(visible_alias("g"))] 69 Get(ConfigGetArgs), 70 #[command(visible_alias("s"))] 71 Set(ConfigSetArgs), 72 #[command(visible_alias("e"))] 73 Edit(ConfigEditArgs), 74 #[command(visible_alias("p"))] 75 Path(ConfigPathArgs), 76} 77 78/// List variables set in config file, along with their values. 79#[derive(clap::Args, Clone, Debug)] 80#[command(group(clap::ArgGroup::new("specific").args(&["repo", "user"])))] 81pub(crate) struct ConfigListArgs { 82 /// An optional name of a specific config option to look up. 83 #[arg(value_parser = NonEmptyStringValueParser::new())] 84 pub name: Option<String>, 85 /// Whether to explicitly include built-in default values in the list. 86 #[arg(long, conflicts_with = "specific")] 87 pub include_defaults: bool, 88 /// Allow printing overridden values. 89 #[arg(long)] 90 pub include_overridden: bool, 91 /// Target the user-level config 92 #[arg(long)] 93 user: bool, 94 /// Target the repo-level config 95 #[arg(long)] 96 repo: bool, 97 // TODO(#1047): Support --show-origin using LayeredConfigs. 98 /// Render each variable using the given template 99 /// 100 /// The following keywords are defined: 101 /// 102 /// * `name: String`: Config name. 103 /// * `value: String`: Serialized value in TOML syntax. 104 /// * `overridden: Boolean`: True if the value is shadowed by other. 105 /// 106 /// For the syntax, see https://github.com/martinvonz/jj/blob/main/docs/templates.md 107 #[arg(long, short = 'T', verbatim_doc_comment)] 108 template: Option<String>, 109} 110 111impl ConfigListArgs { 112 fn get_source_kind(&self) -> Option<ConfigSource> { 113 if self.user { 114 Some(ConfigSource::User) 115 } else if self.repo { 116 Some(ConfigSource::Repo) 117 } else { 118 //List all variables 119 None 120 } 121 } 122} 123 124/// Get the value of a given config option. 125/// 126/// Unlike `jj config list`, the result of `jj config get` is printed without 127/// extra formatting and therefore is usable in scripting. For example: 128/// 129/// $ jj config list user.name 130/// user.name="Martin von Zweigbergk" 131/// $ jj config get user.name 132/// Martin von Zweigbergk 133#[derive(clap::Args, Clone, Debug)] 134#[command(verbatim_doc_comment)] 135pub(crate) struct ConfigGetArgs { 136 #[arg(required = true)] 137 name: String, 138} 139 140/// Update config file to set the given option to a given value. 141#[derive(clap::Args, Clone, Debug)] 142pub(crate) struct ConfigSetArgs { 143 #[arg(required = true)] 144 name: String, 145 #[arg(required = true)] 146 value: String, 147 #[clap(flatten)] 148 config_args: ConfigArgs, 149} 150 151/// Start an editor on a jj config file. 152/// 153/// Creates the file if it doesn't already exist regardless of what the editor 154/// does. 155#[derive(clap::Args, Clone, Debug)] 156pub(crate) struct ConfigEditArgs { 157 #[clap(flatten)] 158 pub config_args: ConfigArgs, 159} 160 161/// Print the path to the config file 162/// 163/// A config file at that path may or may not exist. 164/// 165/// See `jj config edit` if you'd like to immediately edit the file. 166#[derive(clap::Args, Clone, Debug)] 167pub(crate) struct ConfigPathArgs { 168 #[clap(flatten)] 169 pub config_args: ConfigArgs, 170} 171 172#[instrument(skip_all)] 173pub(crate) fn cmd_config( 174 ui: &mut Ui, 175 command: &CommandHelper, 176 subcommand: &ConfigCommand, 177) -> Result<(), CommandError> { 178 match subcommand { 179 ConfigCommand::List(sub_args) => cmd_config_list(ui, command, sub_args), 180 ConfigCommand::Get(sub_args) => cmd_config_get(ui, command, sub_args), 181 ConfigCommand::Set(sub_args) => cmd_config_set(ui, command, sub_args), 182 ConfigCommand::Edit(sub_args) => cmd_config_edit(ui, command, sub_args), 183 ConfigCommand::Path(sub_args) => cmd_config_path(ui, command, sub_args), 184 } 185} 186 187// AnnotatedValue will be cloned internally in the templater. If the cloning 188// cost matters, wrap it with Rc. 189fn config_template_language() -> GenericTemplateLanguage<'static, AnnotatedValue> { 190 type L = GenericTemplateLanguage<'static, AnnotatedValue>; 191 let mut language = L::new(); 192 // "name" instead of "path" to avoid confusion with the source file path 193 language.add_keyword("name", |self_property| { 194 let out_property = self_property.map(|annotated| annotated.path.join(".")); 195 Ok(L::wrap_string(out_property)) 196 }); 197 language.add_keyword("value", |self_property| { 198 // TODO: would be nice if we can provide raw dynamically-typed value 199 let out_property = self_property.map(|annotated| serialize_config_value(&annotated.value)); 200 Ok(L::wrap_string(out_property)) 201 }); 202 language.add_keyword("overridden", |self_property| { 203 let out_property = self_property.map(|annotated| annotated.is_overridden); 204 Ok(L::wrap_boolean(out_property)) 205 }); 206 language 207} 208 209#[instrument(skip_all)] 210pub(crate) fn cmd_config_list( 211 ui: &mut Ui, 212 command: &CommandHelper, 213 args: &ConfigListArgs, 214) -> Result<(), CommandError> { 215 let template = { 216 let language = config_template_language(); 217 let text = match &args.template { 218 Some(value) => value.to_owned(), 219 None => command 220 .settings() 221 .config() 222 .get_string("templates.config_list")?, 223 }; 224 command 225 .parse_template(ui, &language, &text, GenericTemplateLanguage::wrap_self)? 226 .labeled("config_list") 227 }; 228 229 ui.request_pager(); 230 let mut formatter = ui.stdout_formatter(); 231 let name_path = args 232 .name 233 .as_ref() 234 .map_or(vec![], |name| name.split('.').collect_vec()); 235 let mut wrote_values = false; 236 for annotated in command.resolved_config_values(&name_path)? { 237 // Remove overridden values. 238 if annotated.is_overridden && !args.include_overridden { 239 continue; 240 } 241 242 if let Some(target_source) = args.get_source_kind() { 243 if target_source != annotated.source { 244 continue; 245 } 246 } 247 248 // Skip built-ins if not included. 249 if !args.include_defaults && annotated.source == ConfigSource::Default { 250 continue; 251 } 252 253 template.format(&annotated, formatter.as_mut())?; 254 wrote_values = true; 255 } 256 drop(formatter); 257 if !wrote_values { 258 // Note to stderr explaining why output is empty. 259 if let Some(name) = &args.name { 260 writeln!(ui.warning_default(), "No matching config key for {name}")?; 261 } else { 262 writeln!(ui.warning_default(), "No config to list")?; 263 } 264 } 265 Ok(()) 266} 267 268#[instrument(skip_all)] 269pub(crate) fn cmd_config_get( 270 ui: &mut Ui, 271 command: &CommandHelper, 272 args: &ConfigGetArgs, 273) -> Result<(), CommandError> { 274 let value = command 275 .settings() 276 .config() 277 .get_string(&args.name) 278 .map_err(|err| match err { 279 config::ConfigError::Type { 280 origin, 281 unexpected, 282 expected, 283 key, 284 } => { 285 let expected = format!("a value convertible to {expected}"); 286 // Copied from `impl fmt::Display for ConfigError`. We can't use 287 // the `Display` impl directly because `expected` is required to 288 // be a `'static str`. 289 let mut buf = String::new(); 290 use std::fmt::Write; 291 write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap(); 292 if let Some(key) = key { 293 write!(buf, " for key `{key}`").unwrap(); 294 } 295 if let Some(origin) = origin { 296 write!(buf, " in {origin}").unwrap(); 297 } 298 config_error(buf) 299 } 300 err => err.into(), 301 })?; 302 writeln!(ui.stdout(), "{value}")?; 303 Ok(()) 304} 305 306#[instrument(skip_all)] 307pub(crate) fn cmd_config_set( 308 _ui: &mut Ui, 309 command: &CommandHelper, 310 args: &ConfigSetArgs, 311) -> Result<(), CommandError> { 312 let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?; 313 if config_path.is_dir() { 314 return Err(user_error(format!( 315 "Can't set config in path {path} (dirs not supported)", 316 path = config_path.display() 317 ))); 318 } 319 write_config_value_to_file(&args.name, &args.value, &config_path) 320} 321 322#[instrument(skip_all)] 323pub(crate) fn cmd_config_edit( 324 _ui: &mut Ui, 325 command: &CommandHelper, 326 args: &ConfigEditArgs, 327) -> Result<(), CommandError> { 328 let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?; 329 run_ui_editor(command.settings(), &config_path) 330} 331 332#[instrument(skip_all)] 333pub(crate) fn cmd_config_path( 334 ui: &mut Ui, 335 command: &CommandHelper, 336 args: &ConfigPathArgs, 337) -> Result<(), CommandError> { 338 let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?; 339 writeln!( 340 ui.stdout(), 341 "{}", 342 config_path 343 .to_str() 344 .ok_or_else(|| user_error("The config path is not valid UTF-8"))? 345 )?; 346 Ok(()) 347}