cli: config: leverage toml_edit::Value to serialize values

I use ValueKind::Ty(ref v) here because (*v).into() looked rather noisy.

Fixes #3374
This commit is contained in:
Yuya Nishihara 2024-05-21 20:10:22 +09:00
parent d38c366a98
commit ef8038f60f
4 changed files with 53 additions and 20 deletions

View file

@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj split` will now refuse to split an empty commit. * `jj split` will now refuse to split an empty commit.
* `jj config list` now uses multi-line strings and single-quoted strings in the
output when appropriate.
### Deprecations ### Deprecations
- Attempting to alias a built-in command now gives a warning, rather than being silently ignored. - Attempting to alias a built-in command now gives a warning, rather than being silently ignored.

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::fmt;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -172,23 +173,29 @@ fn toml_escape_key(key: String) -> String {
toml_edit::Key::from(key).to_string() toml_edit::Key::from(key).to_string()
} }
// TODO: Use a proper TOML library to serialize instead. fn to_toml_value(value: &config::Value) -> Result<toml_edit::Value, config::ConfigError> {
fn serialize_config_value(value: &config::Value) -> String { fn type_error<T: fmt::Display>(message: T) -> config::ConfigError {
match &value.kind { config::ConfigError::Message(message.to_string())
config::ValueKind::Table(table) => format!( }
"{{{}}}", // It's unlikely that the config object contained unsupported values, but
// TODO: Remove sorting when config crate maintains deterministic ordering. // there's no guarantee. For example, values coming from environment
table // variables might be big int.
.iter() match value.kind {
.sorted_by_key(|(k, _)| *k) config::ValueKind::Nil => Err(type_error(format!("Unexpected value: {value}"))),
.map(|(k, v)| format!("{k}={}", serialize_config_value(v))) config::ValueKind::Boolean(v) => Ok(v.into()),
.join(", ") config::ValueKind::I64(v) => Ok(v.into()),
), config::ValueKind::I128(v) => Ok(i64::try_from(v).map_err(type_error)?.into()),
config::ValueKind::Array(vals) => { config::ValueKind::U64(v) => Ok(i64::try_from(v).map_err(type_error)?.into()),
format!("[{}]", vals.iter().map(serialize_config_value).join(", ")) config::ValueKind::U128(v) => Ok(i64::try_from(v).map_err(type_error)?.into()),
} config::ValueKind::Float(v) => Ok(v.into()),
config::ValueKind::String(val) => format!("{val:?}"), config::ValueKind::String(ref v) => Ok(v.into()),
_ => value.to_string(), // TODO: Remove sorting when config crate maintains deterministic ordering.
config::ValueKind::Table(ref table) => table
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| Ok((k, to_toml_value(v)?)))
.collect(),
config::ValueKind::Array(ref array) => array.iter().map(to_toml_value).collect(),
} }
} }
@ -286,7 +293,8 @@ fn config_template_language() -> GenericTemplateLanguage<'static, AnnotatedValue
}); });
language.add_keyword("value", |self_property| { language.add_keyword("value", |self_property| {
// TODO: would be nice if we can provide raw dynamically-typed value // TODO: would be nice if we can provide raw dynamically-typed value
let out_property = self_property.map(|annotated| serialize_config_value(&annotated.value)); let out_property =
self_property.and_then(|annotated| Ok(to_toml_value(&annotated.value)?.to_string()));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property))
}); });
language.add_keyword("overridden", |self_property| { language.add_keyword("overridden", |self_property| {

View file

@ -334,6 +334,6 @@ fn test_alias_in_repo_config() {
], ],
); );
insta::assert_snapshot!(stdout, @r###" insta::assert_snapshot!(stdout, @r###"
aliases.l=["log", "-r@", "--no-graph", "-T\"user alias\\n\""] aliases.l=["log", "-r@", "--no-graph", '-T"user alias\n"']
"###); "###);
} }

View file

@ -104,11 +104,12 @@ fn test_config_list_inline_table() {
x = 1 x = 1
[[test-table]] [[test-table]]
y = ["z"] y = ["z"]
z."key=with whitespace" = []
"#, "#,
); );
let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "list", "test-table"]); let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "list", "test-table"]);
insta::assert_snapshot!(stdout, @r###" insta::assert_snapshot!(stdout, @r###"
test-table=[{x=1}, {y=["z"]}] test-table=[{ x = 1 }, { y = ["z"], z = { "key=with whitespace" = [] } }]
"###); "###);
} }
@ -136,6 +137,27 @@ fn test_config_list_all() {
"###); "###);
} }
#[test]
fn test_config_list_multiline_string() {
let test_env = TestEnvironment::default();
test_env.add_config(
r#"
multiline = '''
foo
bar
'''
"#,
);
let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "list", "multiline"]);
insta::assert_snapshot!(stdout, @r###"
multiline="""
foo
bar
"""
"###);
}
#[test] #[test]
fn test_config_list_layer() { fn test_config_list_layer() {
let mut test_env = TestEnvironment::default(); let mut test_env = TestEnvironment::default();