commands: move config code to config.rs

This commit is contained in:
Antoine Cezar 2023-10-27 15:21:21 +02:00 committed by Antoine Cezar
parent 4d63e0bfa7
commit 8624d39a6b
2 changed files with 250 additions and 226 deletions

243
cli/src/commands/config.rs Normal file
View file

@ -0,0 +1,243 @@
// Copyright 2020 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::io::Write;
use clap::builder::NonEmptyStringValueParser;
use itertools::Itertools;
use tracing::instrument;
use crate::cli_util::{
get_new_config_file_path, run_ui_editor, serialize_config_value, user_error,
write_config_value_to_file, CommandError, CommandHelper,
};
use crate::config::{AnnotatedValue, ConfigSource};
use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)]
#[command(group = clap::ArgGroup::new("config_level").multiple(false).required(true))]
pub(crate) struct ConfigArgs {
/// Target the user-level config
#[arg(long, group = "config_level")]
user: bool,
/// Target the repo-level config
#[arg(long, group = "config_level")]
repo: bool,
}
impl ConfigArgs {
fn get_source_kind(&self) -> ConfigSource {
if self.user {
ConfigSource::User
} else if self.repo {
ConfigSource::Repo
} else {
// Shouldn't be reachable unless clap ArgGroup is broken.
panic!("No config_level provided");
}
}
}
/// Manage config options
///
/// Operates on jj configuration, which comes from the config file and
/// environment variables.
///
/// For file locations, supported config options, and other details about jj
/// config, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
#[derive(clap::Subcommand, Clone, Debug)]
pub(crate) enum ConfigSubcommand {
#[command(visible_alias("l"))]
List(ConfigListArgs),
#[command(visible_alias("g"))]
Get(ConfigGetArgs),
#[command(visible_alias("s"))]
Set(ConfigSetArgs),
#[command(visible_alias("e"))]
Edit(ConfigEditArgs),
}
/// List variables set in config file, along with their values.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ConfigListArgs {
/// An optional name of a specific config option to look up.
#[arg(value_parser = NonEmptyStringValueParser::new())]
pub name: Option<String>,
/// Whether to explicitly include built-in default values in the list.
#[arg(long)]
pub include_defaults: bool,
// TODO(#1047): Support --show-origin using LayeredConfigs.
// TODO(#1047): Support ConfigArgs (--user or --repo).
}
/// Get the value of a given config option.
///
/// Unlike `jj config list`, the result of `jj config get` is printed without
/// extra formatting and therefore is usable in scripting. For example:
///
/// $ jj config list user.name
/// user.name="Martin von Zweigbergk"
/// $ jj config get user.name
/// Martin von Zweigbergk
#[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)]
pub(crate) struct ConfigGetArgs {
#[arg(required = true)]
name: String,
}
/// Update config file to set the given option to a given value.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ConfigSetArgs {
#[arg(required = true)]
name: String,
#[arg(required = true)]
value: String,
#[clap(flatten)]
config_args: ConfigArgs,
}
/// Start an editor on a jj config file.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ConfigEditArgs {
#[clap(flatten)]
pub config_args: ConfigArgs,
}
#[instrument(skip_all)]
pub(crate) fn cmd_config(
ui: &mut Ui,
command: &CommandHelper,
subcommand: &ConfigSubcommand,
) -> Result<(), CommandError> {
match subcommand {
ConfigSubcommand::List(sub_args) => cmd_config_list(ui, command, sub_args),
ConfigSubcommand::Get(sub_args) => cmd_config_get(ui, command, sub_args),
ConfigSubcommand::Set(sub_args) => cmd_config_set(ui, command, sub_args),
ConfigSubcommand::Edit(sub_args) => cmd_config_edit(ui, command, sub_args),
}
}
#[instrument(skip_all)]
pub(crate) fn cmd_config_list(
ui: &mut Ui,
command: &CommandHelper,
args: &ConfigListArgs,
) -> Result<(), CommandError> {
ui.request_pager();
let name_path = args
.name
.as_ref()
.map_or(vec![], |name| name.split('.').collect_vec());
let values = command.resolved_config_values(&name_path)?;
let mut wrote_values = false;
for AnnotatedValue {
path,
value,
source,
is_overridden,
} in &values
{
// Remove overridden values.
// TODO(#1047): Allow printing overridden values via `--include-overridden`.
if *is_overridden {
continue;
}
// Skip built-ins if not included.
if !args.include_defaults && *source == ConfigSource::Default {
continue;
}
writeln!(
ui.stdout(),
"{}={}",
path.join("."),
serialize_config_value(value)
)?;
wrote_values = true;
}
if !wrote_values {
// Note to stderr explaining why output is empty.
if let Some(name) = &args.name {
writeln!(ui.warning(), "No matching config key for {name}")?;
} else {
writeln!(ui.warning(), "No config to list")?;
}
}
Ok(())
}
#[instrument(skip_all)]
pub(crate) fn cmd_config_get(
ui: &mut Ui,
command: &CommandHelper,
args: &ConfigGetArgs,
) -> Result<(), CommandError> {
let value = command
.settings()
.config()
.get_string(&args.name)
.map_err(|err| match err {
config::ConfigError::Type {
origin,
unexpected,
expected,
key,
} => {
let expected = format!("a value convertible to {expected}");
// Copied from `impl fmt::Display for ConfigError`. We can't use
// the `Display` impl directly because `expected` is required to
// be a `'static str`.
let mut buf = String::new();
use std::fmt::Write;
write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap();
if let Some(key) = key {
write!(buf, " for key `{key}`").unwrap();
}
if let Some(origin) = origin {
write!(buf, " in {origin}").unwrap();
}
CommandError::ConfigError(buf.to_string())
}
err => err.into(),
})?;
writeln!(ui.stdout(), "{value}")?;
Ok(())
}
#[instrument(skip_all)]
pub(crate) fn cmd_config_set(
_ui: &mut Ui,
command: &CommandHelper,
args: &ConfigSetArgs,
) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
if config_path.is_dir() {
return Err(user_error(format!(
"Can't set config in path {path} (dirs not supported)",
path = config_path.display()
)));
}
write_config_value_to_file(&args.name, &args.value, &config_path)
}
#[instrument(skip_all)]
pub(crate) fn cmd_config_edit(
_ui: &mut Ui,
command: &CommandHelper,
args: &ConfigEditArgs,
) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
run_ui_editor(command.settings(), &config_path)
}

View file

@ -21,6 +21,7 @@ mod cat;
mod checkout;
mod chmod;
mod commit;
mod config;
mod debug;
mod git;
mod operation;
@ -32,7 +33,6 @@ use std::path::Path;
use std::sync::Arc;
use std::{fmt, fs, io};
use clap::builder::NonEmptyStringValueParser;
use clap::parser::ValueSource;
use clap::{ArgGroup, Command, CommandFactory, FromArgMatches, Subcommand};
use indexmap::{IndexMap, IndexSet};
@ -60,13 +60,11 @@ use maplit::{hashmap, hashset};
use tracing::instrument;
use crate::cli_util::{
self, check_stale_working_copy, get_new_config_file_path, print_checkout_stats,
print_git_import_stats, resolve_multiple_nonempty_revsets,
resolve_multiple_nonempty_revsets_default_single, run_ui_editor, serialize_config_value,
short_commit_hash, user_error, user_error_with_hint, write_config_value_to_file, Args,
CommandError, CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper,
self, check_stale_working_copy, print_checkout_stats, print_git_import_stats,
resolve_multiple_nonempty_revsets, resolve_multiple_nonempty_revsets_default_single,
run_ui_editor, short_commit_hash, user_error, user_error_with_hint, Args, CommandError,
CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper,
};
use crate::config::{AnnotatedValue, ConfigSource};
use crate::diff_util::{self, DiffFormat, DiffFormatArgs};
use crate::formatter::{Formatter, PlainTextFormatter};
use crate::graphlog::{get_graphlog, Edge};
@ -88,7 +86,7 @@ enum Commands {
Chmod(chmod::ChmodArgs),
Commit(commit::CommitArgs),
#[command(subcommand)]
Config(ConfigSubcommand),
Config(config::ConfigSubcommand),
#[command(subcommand)]
Debug(debug::DebugCommands),
Describe(DescribeArgs),
@ -165,97 +163,6 @@ struct InitArgs {
git_repo: Option<String>,
}
#[derive(clap::Args, Clone, Debug)]
#[command(group = clap::ArgGroup::new("config_level").multiple(false).required(true))]
struct ConfigArgs {
/// Target the user-level config
#[arg(long, group = "config_level")]
user: bool,
/// Target the repo-level config
#[arg(long, group = "config_level")]
repo: bool,
}
impl ConfigArgs {
fn get_source_kind(&self) -> ConfigSource {
if self.user {
ConfigSource::User
} else if self.repo {
ConfigSource::Repo
} else {
// Shouldn't be reachable unless clap ArgGroup is broken.
panic!("No config_level provided");
}
}
}
/// Manage config options
///
/// Operates on jj configuration, which comes from the config file and
/// environment variables.
///
/// For file locations, supported config options, and other details about jj
/// config, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
#[derive(clap::Subcommand, Clone, Debug)]
enum ConfigSubcommand {
#[command(visible_alias("l"))]
List(ConfigListArgs),
#[command(visible_alias("g"))]
Get(ConfigGetArgs),
#[command(visible_alias("s"))]
Set(ConfigSetArgs),
#[command(visible_alias("e"))]
Edit(ConfigEditArgs),
}
/// List variables set in config file, along with their values.
#[derive(clap::Args, Clone, Debug)]
struct ConfigListArgs {
/// An optional name of a specific config option to look up.
#[arg(value_parser = NonEmptyStringValueParser::new())]
pub name: Option<String>,
/// Whether to explicitly include built-in default values in the list.
#[arg(long)]
pub include_defaults: bool,
// TODO(#1047): Support --show-origin using LayeredConfigs.
// TODO(#1047): Support ConfigArgs (--user or --repo).
}
/// Get the value of a given config option.
///
/// Unlike `jj config list`, the result of `jj config get` is printed without
/// extra formatting and therefore is usable in scripting. For example:
///
/// $ jj config list user.name
/// user.name="Martin von Zweigbergk"
/// $ jj config get user.name
/// Martin von Zweigbergk
#[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)]
struct ConfigGetArgs {
#[arg(required = true)]
name: String,
}
/// Update config file to set the given option to a given value.
#[derive(clap::Args, Clone, Debug)]
struct ConfigSetArgs {
#[arg(required = true)]
name: String,
#[arg(required = true)]
value: String,
#[clap(flatten)]
config_args: ConfigArgs,
}
/// Start an editor on a jj config file.
#[derive(clap::Args, Clone, Debug)]
struct ConfigEditArgs {
#[clap(flatten)]
pub config_args: ConfigArgs,
}
/// Stop tracking specified paths in the working copy
#[derive(clap::Args, Clone, Debug)]
struct UntrackArgs {
@ -1169,132 +1076,6 @@ Set `ui.allow-init-native` to allow initializing a repo with the native backend.
Ok(())
}
#[instrument(skip_all)]
fn cmd_config(
ui: &mut Ui,
command: &CommandHelper,
subcommand: &ConfigSubcommand,
) -> Result<(), CommandError> {
match subcommand {
ConfigSubcommand::List(sub_args) => cmd_config_list(ui, command, sub_args),
ConfigSubcommand::Get(sub_args) => cmd_config_get(ui, command, sub_args),
ConfigSubcommand::Set(sub_args) => cmd_config_set(ui, command, sub_args),
ConfigSubcommand::Edit(sub_args) => cmd_config_edit(ui, command, sub_args),
}
}
#[instrument(skip_all)]
fn cmd_config_list(
ui: &mut Ui,
command: &CommandHelper,
args: &ConfigListArgs,
) -> Result<(), CommandError> {
ui.request_pager();
let name_path = args
.name
.as_ref()
.map_or(vec![], |name| name.split('.').collect_vec());
let values = command.resolved_config_values(&name_path)?;
let mut wrote_values = false;
for AnnotatedValue {
path,
value,
source,
is_overridden,
} in &values
{
// Remove overridden values.
// TODO(#1047): Allow printing overridden values via `--include-overridden`.
if *is_overridden {
continue;
}
// Skip built-ins if not included.
if !args.include_defaults && *source == ConfigSource::Default {
continue;
}
writeln!(
ui.stdout(),
"{}={}",
path.join("."),
serialize_config_value(value)
)?;
wrote_values = true;
}
if !wrote_values {
// Note to stderr explaining why output is empty.
if let Some(name) = &args.name {
writeln!(ui.warning(), "No matching config key for {name}")?;
} else {
writeln!(ui.warning(), "No config to list")?;
}
}
Ok(())
}
#[instrument(skip_all)]
fn cmd_config_get(
ui: &mut Ui,
command: &CommandHelper,
args: &ConfigGetArgs,
) -> Result<(), CommandError> {
let value = command
.settings()
.config()
.get_string(&args.name)
.map_err(|err| match err {
config::ConfigError::Type {
origin,
unexpected,
expected,
key,
} => {
let expected = format!("a value convertible to {expected}");
// Copied from `impl fmt::Display for ConfigError`. We can't use
// the `Display` impl directly because `expected` is required to
// be a `'static str`.
let mut buf = String::new();
use std::fmt::Write;
write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap();
if let Some(key) = key {
write!(buf, " for key `{key}`").unwrap();
}
if let Some(origin) = origin {
write!(buf, " in {origin}").unwrap();
}
CommandError::ConfigError(buf.to_string())
}
err => err.into(),
})?;
writeln!(ui.stdout(), "{value}")?;
Ok(())
}
#[instrument(skip_all)]
fn cmd_config_set(
_ui: &mut Ui,
command: &CommandHelper,
args: &ConfigSetArgs,
) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
if config_path.is_dir() {
return Err(user_error(format!(
"Can't set config in path {path} (dirs not supported)",
path = config_path.display()
)));
}
write_config_value_to_file(&args.name, &args.value, &config_path)
}
#[instrument(skip_all)]
fn cmd_config_edit(
_ui: &mut Ui,
command: &CommandHelper,
args: &ConfigEditArgs,
) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
run_ui_editor(command.settings(), &config_path)
}
#[instrument(skip_all)]
fn cmd_untrack(
ui: &mut Ui,
@ -3673,7 +3454,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
match &derived_subcommands {
Commands::Version(sub_args) => cmd_version(ui, command_helper, sub_args),
Commands::Init(sub_args) => cmd_init(ui, command_helper, sub_args),
Commands::Config(sub_args) => cmd_config(ui, command_helper, sub_args),
Commands::Config(sub_args) => config::cmd_config(ui, command_helper, sub_args),
Commands::Checkout(sub_args) => checkout::cmd_checkout(ui, command_helper, sub_args),
Commands::Untrack(sub_args) => cmd_untrack(ui, command_helper, sub_args),
Commands::Files(sub_args) => cmd_files(ui, command_helper, sub_args),