@ -98,8 +98,24 @@ pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::p
directories
directories
}
}
// right overrides left
/// Merge two TOML documents, merging values from `right` onto `left`
pub fn merge_toml_values ( left : toml ::Value , right : toml ::Value ) -> toml ::Value {
///
/// When an array exists in both `left` and `right`, `right`'s array is
/// used. When a table exists in both `left` and `right`, the merged table
/// consists of all keys in `left`'s table unioned with all keys in `right`
/// with the values of `right` being merged recursively onto values of
/// `left`.
///
/// `merge_toplevel_arrays` controls whether a top-level array in the TOML
/// document is merged instead of overridden. This is useful for TOML
/// documents that use a top-level array of values like the `languages.toml`,
/// where one usually wants to override or add to the array instead of
/// replacing it altogether.
pub fn merge_toml_values (
left : toml ::Value ,
right : toml ::Value ,
merge_toplevel_arrays : bool ,
) -> toml ::Value {
use toml ::Value ;
use toml ::Value ;
fn get_name ( v : & Value ) -> Option < & str > {
fn get_name ( v : & Value ) -> Option < & str > {
@ -108,24 +124,35 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
match ( left , right ) {
match ( left , right ) {
( Value ::Array ( mut left_items ) , Value ::Array ( right_items ) ) = > {
( Value ::Array ( mut left_items ) , Value ::Array ( right_items ) ) = > {
// The top-level arrays should be merged but nested arrays should
// act as overrides. For the `languages.toml` config, this means
// that you can specify a sub-set of languages in an overriding
// `languages.toml` but that nested arrays like Language Server
// arguments are replaced instead of merged.
if merge_toplevel_arrays {
left_items . reserve ( right_items . len ( ) ) ;
left_items . reserve ( right_items . len ( ) ) ;
for rvalue in right_items {
for rvalue in right_items {
let lvalue = get_name ( & rvalue )
let lvalue = get_name ( & rvalue )
. and_then ( | rname | left_items . iter ( ) . position ( | v | get_name ( v ) = = Some ( rname ) ) )
. and_then ( | rname | {
left_items . iter ( ) . position ( | v | get_name ( v ) = = Some ( rname ) )
} )
. map ( | lpos | left_items . remove ( lpos ) ) ;
. map ( | lpos | left_items . remove ( lpos ) ) ;
let mvalue = match lvalue {
let mvalue = match lvalue {
Some ( lvalue ) = > merge_toml_values ( lvalue , rvalu e) ,
Some ( lvalue ) = > merge_toml_values ( lvalue , rvalu e, fals e) ,
None = > rvalue ,
None = > rvalue ,
} ;
} ;
left_items . push ( mvalue ) ;
left_items . push ( mvalue ) ;
}
}
Value ::Array ( left_items )
Value ::Array ( left_items )
} else {
Value ::Array ( right_items )
}
}
}
( Value ::Table ( mut left_map ) , Value ::Table ( right_map ) ) = > {
( Value ::Table ( mut left_map ) , Value ::Table ( right_map ) ) = > {
for ( rname , rvalue ) in right_map {
for ( rname , rvalue ) in right_map {
match left_map . remove ( & rname ) {
match left_map . remove ( & rname ) {
Some ( lvalue ) = > {
Some ( lvalue ) = > {
let merged_value = merge_toml_values ( lvalue , rvalue );
let merged_value = merge_toml_values ( lvalue , rvalue , merge_toplevel_arrays );
left_map . insert ( rname , merged_value ) ;
left_map . insert ( rname , merged_value ) ;
}
}
None = > {
None = > {
@ -143,23 +170,22 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
#[ cfg(test) ]
#[ cfg(test) ]
mod merge_toml_tests {
mod merge_toml_tests {
use super ::merge_toml_values ;
use super ::merge_toml_values ;
#[ test ]
fn language_tomls ( ) {
use toml ::Value ;
use toml ::Value ;
const USER : & str = "
#[ test ]
fn language_toml_map_merges ( ) {
const USER : & str = r #"
[ [ language ] ]
[ [ language ] ]
name = \ " nix \ "
name = "nix "
test = \ " bbb \ "
test = "bbb "
indent = { tab - width = 4 , unit = \ " \ " , test = \ " aaa \ " }
indent = { tab - width = 4 , unit = " " , test = "aaa " }
" ;
" # ;
let base : Value = toml ::from_slice ( include_bytes! ( "../../languages.toml" ) )
let base : Value = toml ::from_slice ( include_bytes! ( "../../languages.toml" ) )
. expect ( "Couldn't parse built-in languages config" ) ;
. expect ( "Couldn't parse built-in languages config" ) ;
let user : Value = toml ::from_str ( USER ) . unwrap ( ) ;
let user : Value = toml ::from_str ( USER ) . unwrap ( ) ;
let merged = merge_toml_values ( base , user );
let merged = merge_toml_values ( base , user , true );
let languages = merged . get ( "language" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
let languages = merged . get ( "language" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
let nix = languages
let nix = languages
. iter ( )
. iter ( )
@ -179,4 +205,33 @@ mod merge_toml_tests {
// We didn't change comment-token so it should be same
// We didn't change comment-token so it should be same
assert_eq! ( nix . get ( "comment-token" ) . unwrap ( ) . as_str ( ) . unwrap ( ) , "#" ) ;
assert_eq! ( nix . get ( "comment-token" ) . unwrap ( ) . as_str ( ) . unwrap ( ) , "#" ) ;
}
}
#[ test ]
fn language_toml_nested_array_merges ( ) {
const USER : & str = r #"
[ [ language ] ]
name = "typescript"
language - server = { command = "deno" , args = [ "lsp" ] }
" #;
let base : Value = toml ::from_slice ( include_bytes! ( "../../languages.toml" ) )
. expect ( "Couldn't parse built-in languages config" ) ;
let user : Value = toml ::from_str ( USER ) . unwrap ( ) ;
let merged = merge_toml_values ( base , user , true ) ;
let languages = merged . get ( "language" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
let ts = languages
. iter ( )
. find ( | v | v . get ( "name" ) . unwrap ( ) . as_str ( ) . unwrap ( ) = = "typescript" )
. unwrap ( ) ;
assert_eq! (
ts . get ( "language-server" )
. unwrap ( )
. get ( "args" )
. unwrap ( )
. as_array ( )
. unwrap ( ) ,
& vec! [ Value ::String ( "lsp" . into ( ) ) ]
)
}
}
}