Loading pkgs/pkgs-lib/formats.nix +7 −0 Original line number Diff line number Diff line Loading @@ -28,12 +28,19 @@ rec { generate = ...; }); Please note that `pkgs` may not always be available for use due to the split options doc build introduced in fc614c37c653, so lazy evaluation of only the 'type' field is required. */ inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; }) javaProperties; libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format; json = {}: { type = with lib.types; let Loading pkgs/pkgs-lib/formats/libconfig/default.nix 0 → 100644 +121 −0 Original line number Diff line number Diff line { lib , pkgs }: let inherit (pkgs) buildPackages callPackage; # Implementation notes: # Libconfig spec: https://hyperrealm.github.io/libconfig/libconfig_manual.html # # Since libconfig does not allow setting names to start with an underscore, # this is used as a prefix for both special types and include directives. # # The difference between 32bit and 64bit values became optional in libconfig # 1.5, so we assume 64bit values for all numbers. libconfig-generator = buildPackages.rustPlatform.buildRustPackage { name = "libconfig-generator"; version = "0.1.0"; src = ./src; passthru.updateScript = ./update.sh; cargoLock.lockFile = ./src/Cargo.lock; }; libconfig-validator = buildPackages.runCommandCC "libconfig-validator" { buildInputs = with buildPackages; [ libconfig ]; } '' mkdir -p "$out/bin" $CC -lconfig -x c - -o "$out/bin/libconfig-validator" ${./validator.c} ''; in { format = { generator ? libconfig-generator, validator ? libconfig-validator }: { inherit generator; type = with lib.types; let valueType = (oneOf [ bool int float str path (attrsOf valueType) (listOf valueType) ]) // { description = "libconfig value"; }; in attrsOf valueType; lib = { mkHex = value: { _type = "hex"; inherit value; }; mkOctal = value: { _type = "octal"; inherit value; }; mkFloat = value: { _type = "float"; inherit value; }; mkArray = value: { _type = "array"; inherit value; }; mkList = value: { _type = "list"; inherit value; }; }; generate = name: value: callPackage ({ stdenvNoCC , libconfig-generator , libconfig-validator , writeText }: stdenvNoCC.mkDerivation rec { inherit name; dontUnpack = true; json = builtins.toJSON value; passAsFile = [ "json" ]; strictDeps = true; nativeBuildInputs = [ libconfig-generator ]; buildPhase = '' runHook preBuild libconfig-generator < $jsonPath > output.cfg runHook postBuild ''; doCheck = true; nativeCheckInputs = [ libconfig-validator ]; checkPhase = '' runHook preCheck libconfig-validator output.cfg runHook postCheck ''; installPhase = '' runHook preInstall mv output.cfg $out runHook postInstall ''; passthru.json = writeText "${name}.json" json; }) { libconfig-generator = generator; libconfig-validator = validator; }; }; } pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock 0 → 100644 +40 −0 Original line number Diff line number Diff line # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libconfig-generator" version = "0.1.0" dependencies = [ "serde", "serde_json", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" [[package]] name = "serde_json" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", "serde", ] pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml 0 → 100644 +10 −0 Original line number Diff line number Diff line [package] name = "libconfig-generator" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] serde = "1.0.178" serde_json = "1.0.104" pkgs/pkgs-lib/formats/libconfig/src/src/main.rs 0 → 100644 +271 −0 Original line number Diff line number Diff line use serde_json::Value; use std::mem::discriminant; #[derive(Debug)] enum LibConfigIntNumber { Oct(i64), Hex(i64), Int(i64), } #[derive(Debug)] enum LibConfigValue { Bool(bool), Int(LibConfigIntNumber), Float(f64), String(String), Array(Vec<LibConfigValue>), List(Vec<LibConfigValue>), Group(Vec<String>, Vec<(String, LibConfigValue)>), } fn validate_setting_name(key: &str) -> bool { let first_char = key.chars().next().expect("Empty setting name"); (first_char.is_alphabetic() || first_char == '*') && key[1..] .chars() .all(|c| c.is_alphanumeric() || c == '_' || c == '*') } const SPECIAL_TYPES: [&str; 5] = ["octal", "hex", "float", "list", "array"]; fn object_is_special_type(o: &serde_json::Map<String, Value>) -> Option<&str> { o.get("_type").and_then(|x| x.as_str()).and_then(|x| { if SPECIAL_TYPES.contains(&x) { Some(x) } else { None } }) } fn vec_is_array(v: &Vec<LibConfigValue>) -> bool { if v.is_empty() { return true; } let first_item = v.first().unwrap(); if match first_item { LibConfigValue::Array(_) => true, LibConfigValue::List(_) => true, LibConfigValue::Group(_, _) => true, _ => false, } { return false; }; v[1..] .iter() .all(|item| discriminant(first_item) == discriminant(item)) } fn json_to_libconfig(v: &Value) -> LibConfigValue { match v { Value::Null => panic!("Null value not allowed in libconfig"), Value::Bool(b) => LibConfigValue::Bool(b.clone()), Value::Number(n) => { if n.is_i64() { LibConfigValue::Int(LibConfigIntNumber::Int(n.as_i64().unwrap())) } else if n.is_f64() { LibConfigValue::Float(n.as_f64().unwrap()) } else { panic!("{} is not i64 or f64, cannot be represented as number in libconfig", n); } } Value::String(s) => LibConfigValue::String(s.to_string()), Value::Array(a) => { let items = a .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); LibConfigValue::List(items) } Value::Object(o) => { if let Some(_type) = object_is_special_type(o) { let value = o .get("value") .expect(format!("Missing value for special type: {}", &_type).as_str()); return match _type { "octal" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Int(LibConfigIntNumber::Oct( i64::from_str_radix(&str_value, 8) .expect(format!("Invalid octal value: {}", value).as_str()), )) } "hex" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Int(LibConfigIntNumber::Hex( i64::from_str_radix(&str_value[2..], 16) .expect(format!("Invalid hex value: {}", value).as_str()), )) } "float" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Float( str_value .parse::<f64>() .expect(format!("Invalid float value: {}", value).as_str()), ) } "list" => { let items = value .as_array() .expect( format!("Value is not an array for special type: {}", &_type) .as_str(), ) .to_owned() .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); LibConfigValue::List(items) } "array" => { let items = value .as_array() .expect( format!("Value is not an array for special type: {}", &_type) .as_str(), ) .to_owned() .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); if !vec_is_array(&items) { panic!( "This can not be an array because of its contents: {:#?}", items ); } LibConfigValue::Array(items) } _ => panic!("Invalid type: {}", _type), }; } let mut items = o .iter() .filter(|(key, _)| key.as_str() != "_includes") .map(|(key, value)| (key.clone(), json_to_libconfig(value))) .collect::<Vec<(String, LibConfigValue)>>(); items.sort_by(|(a,_),(b,_)| a.partial_cmp(b).unwrap()); let includes = o .get("_includes") .map(|x| { x.as_array() .expect("_includes is not an array") .iter() .map(|x| { x.as_str() .expect("_includes item is not a string") .to_owned() }) .collect::<Vec<String>>() }) .unwrap_or(vec![]); for (key,_) in items.iter() { if !validate_setting_name(key) { panic!("Invalid setting name: {}", key); } } LibConfigValue::Group(includes, items) } } } impl ToString for LibConfigValue { fn to_string(&self) -> String { match self { LibConfigValue::Bool(b) => b.to_string(), LibConfigValue::Int(i) => match i { LibConfigIntNumber::Oct(n) => format!("0{:o}", n), LibConfigIntNumber::Hex(n) => format!("0x{:x}", n), LibConfigIntNumber::Int(n) => n.to_string(), }, LibConfigValue::Float(n) => format!("{:?}", n), LibConfigValue::String(s) => { format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")) } LibConfigValue::Array(a) => { let items = a .iter() .map(|item| item.to_string()) .collect::<Vec<String>>() .join(", "); format!("[{}]", items) } LibConfigValue::List(a) => { let items = a .iter() .map(|item| item.to_string()) .collect::<Vec<String>>() .join(", "); format!("({})", items) } LibConfigValue::Group(i, o) => { let includes = i .iter() .map(|x| x.replace("\\", "\\\\").replace("\"", "\\\"")) .map(|x| format!("@include \"{}\"", x)) .collect::<Vec<String>>() .join("\n"); let items = o .iter() .map(|(key, value)| format!("{}={};", key, value.to_string())) .collect::<Vec<String>>() .join(""); if includes.is_empty() { format!("{{{}}}", items) } else { format!("{{\n{}\n{}}}", includes, items) } } } } } fn main() { let stdin = std::io::stdin().lock(); let json = serde_json::Deserializer::from_reader(stdin) .into_iter::<Value>() .next() .expect("Could not read content from stdin") .expect("Could not parse JSON from stdin"); for (key, value) in json .as_object() .expect("Top level of JSON file is not an object") { print!("{}={};", key, json_to_libconfig(value).to_string()); } print!("\n\n"); } Loading
pkgs/pkgs-lib/formats.nix +7 −0 Original line number Diff line number Diff line Loading @@ -28,12 +28,19 @@ rec { generate = ...; }); Please note that `pkgs` may not always be available for use due to the split options doc build introduced in fc614c37c653, so lazy evaluation of only the 'type' field is required. */ inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; }) javaProperties; libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format; json = {}: { type = with lib.types; let Loading
pkgs/pkgs-lib/formats/libconfig/default.nix 0 → 100644 +121 −0 Original line number Diff line number Diff line { lib , pkgs }: let inherit (pkgs) buildPackages callPackage; # Implementation notes: # Libconfig spec: https://hyperrealm.github.io/libconfig/libconfig_manual.html # # Since libconfig does not allow setting names to start with an underscore, # this is used as a prefix for both special types and include directives. # # The difference between 32bit and 64bit values became optional in libconfig # 1.5, so we assume 64bit values for all numbers. libconfig-generator = buildPackages.rustPlatform.buildRustPackage { name = "libconfig-generator"; version = "0.1.0"; src = ./src; passthru.updateScript = ./update.sh; cargoLock.lockFile = ./src/Cargo.lock; }; libconfig-validator = buildPackages.runCommandCC "libconfig-validator" { buildInputs = with buildPackages; [ libconfig ]; } '' mkdir -p "$out/bin" $CC -lconfig -x c - -o "$out/bin/libconfig-validator" ${./validator.c} ''; in { format = { generator ? libconfig-generator, validator ? libconfig-validator }: { inherit generator; type = with lib.types; let valueType = (oneOf [ bool int float str path (attrsOf valueType) (listOf valueType) ]) // { description = "libconfig value"; }; in attrsOf valueType; lib = { mkHex = value: { _type = "hex"; inherit value; }; mkOctal = value: { _type = "octal"; inherit value; }; mkFloat = value: { _type = "float"; inherit value; }; mkArray = value: { _type = "array"; inherit value; }; mkList = value: { _type = "list"; inherit value; }; }; generate = name: value: callPackage ({ stdenvNoCC , libconfig-generator , libconfig-validator , writeText }: stdenvNoCC.mkDerivation rec { inherit name; dontUnpack = true; json = builtins.toJSON value; passAsFile = [ "json" ]; strictDeps = true; nativeBuildInputs = [ libconfig-generator ]; buildPhase = '' runHook preBuild libconfig-generator < $jsonPath > output.cfg runHook postBuild ''; doCheck = true; nativeCheckInputs = [ libconfig-validator ]; checkPhase = '' runHook preCheck libconfig-validator output.cfg runHook postCheck ''; installPhase = '' runHook preInstall mv output.cfg $out runHook postInstall ''; passthru.json = writeText "${name}.json" json; }) { libconfig-generator = generator; libconfig-validator = validator; }; }; }
pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock 0 → 100644 +40 −0 Original line number Diff line number Diff line # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libconfig-generator" version = "0.1.0" dependencies = [ "serde", "serde_json", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" [[package]] name = "serde_json" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", "serde", ]
pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml 0 → 100644 +10 −0 Original line number Diff line number Diff line [package] name = "libconfig-generator" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] serde = "1.0.178" serde_json = "1.0.104"
pkgs/pkgs-lib/formats/libconfig/src/src/main.rs 0 → 100644 +271 −0 Original line number Diff line number Diff line use serde_json::Value; use std::mem::discriminant; #[derive(Debug)] enum LibConfigIntNumber { Oct(i64), Hex(i64), Int(i64), } #[derive(Debug)] enum LibConfigValue { Bool(bool), Int(LibConfigIntNumber), Float(f64), String(String), Array(Vec<LibConfigValue>), List(Vec<LibConfigValue>), Group(Vec<String>, Vec<(String, LibConfigValue)>), } fn validate_setting_name(key: &str) -> bool { let first_char = key.chars().next().expect("Empty setting name"); (first_char.is_alphabetic() || first_char == '*') && key[1..] .chars() .all(|c| c.is_alphanumeric() || c == '_' || c == '*') } const SPECIAL_TYPES: [&str; 5] = ["octal", "hex", "float", "list", "array"]; fn object_is_special_type(o: &serde_json::Map<String, Value>) -> Option<&str> { o.get("_type").and_then(|x| x.as_str()).and_then(|x| { if SPECIAL_TYPES.contains(&x) { Some(x) } else { None } }) } fn vec_is_array(v: &Vec<LibConfigValue>) -> bool { if v.is_empty() { return true; } let first_item = v.first().unwrap(); if match first_item { LibConfigValue::Array(_) => true, LibConfigValue::List(_) => true, LibConfigValue::Group(_, _) => true, _ => false, } { return false; }; v[1..] .iter() .all(|item| discriminant(first_item) == discriminant(item)) } fn json_to_libconfig(v: &Value) -> LibConfigValue { match v { Value::Null => panic!("Null value not allowed in libconfig"), Value::Bool(b) => LibConfigValue::Bool(b.clone()), Value::Number(n) => { if n.is_i64() { LibConfigValue::Int(LibConfigIntNumber::Int(n.as_i64().unwrap())) } else if n.is_f64() { LibConfigValue::Float(n.as_f64().unwrap()) } else { panic!("{} is not i64 or f64, cannot be represented as number in libconfig", n); } } Value::String(s) => LibConfigValue::String(s.to_string()), Value::Array(a) => { let items = a .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); LibConfigValue::List(items) } Value::Object(o) => { if let Some(_type) = object_is_special_type(o) { let value = o .get("value") .expect(format!("Missing value for special type: {}", &_type).as_str()); return match _type { "octal" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Int(LibConfigIntNumber::Oct( i64::from_str_radix(&str_value, 8) .expect(format!("Invalid octal value: {}", value).as_str()), )) } "hex" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Int(LibConfigIntNumber::Hex( i64::from_str_radix(&str_value[2..], 16) .expect(format!("Invalid hex value: {}", value).as_str()), )) } "float" => { let str_value = value .as_str() .expect( format!("Value is not a string for special type: {}", &_type) .as_str(), ) .to_owned(); LibConfigValue::Float( str_value .parse::<f64>() .expect(format!("Invalid float value: {}", value).as_str()), ) } "list" => { let items = value .as_array() .expect( format!("Value is not an array for special type: {}", &_type) .as_str(), ) .to_owned() .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); LibConfigValue::List(items) } "array" => { let items = value .as_array() .expect( format!("Value is not an array for special type: {}", &_type) .as_str(), ) .to_owned() .iter() .map(|item| json_to_libconfig(item)) .collect::<Vec<LibConfigValue>>(); if !vec_is_array(&items) { panic!( "This can not be an array because of its contents: {:#?}", items ); } LibConfigValue::Array(items) } _ => panic!("Invalid type: {}", _type), }; } let mut items = o .iter() .filter(|(key, _)| key.as_str() != "_includes") .map(|(key, value)| (key.clone(), json_to_libconfig(value))) .collect::<Vec<(String, LibConfigValue)>>(); items.sort_by(|(a,_),(b,_)| a.partial_cmp(b).unwrap()); let includes = o .get("_includes") .map(|x| { x.as_array() .expect("_includes is not an array") .iter() .map(|x| { x.as_str() .expect("_includes item is not a string") .to_owned() }) .collect::<Vec<String>>() }) .unwrap_or(vec![]); for (key,_) in items.iter() { if !validate_setting_name(key) { panic!("Invalid setting name: {}", key); } } LibConfigValue::Group(includes, items) } } } impl ToString for LibConfigValue { fn to_string(&self) -> String { match self { LibConfigValue::Bool(b) => b.to_string(), LibConfigValue::Int(i) => match i { LibConfigIntNumber::Oct(n) => format!("0{:o}", n), LibConfigIntNumber::Hex(n) => format!("0x{:x}", n), LibConfigIntNumber::Int(n) => n.to_string(), }, LibConfigValue::Float(n) => format!("{:?}", n), LibConfigValue::String(s) => { format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")) } LibConfigValue::Array(a) => { let items = a .iter() .map(|item| item.to_string()) .collect::<Vec<String>>() .join(", "); format!("[{}]", items) } LibConfigValue::List(a) => { let items = a .iter() .map(|item| item.to_string()) .collect::<Vec<String>>() .join(", "); format!("({})", items) } LibConfigValue::Group(i, o) => { let includes = i .iter() .map(|x| x.replace("\\", "\\\\").replace("\"", "\\\"")) .map(|x| format!("@include \"{}\"", x)) .collect::<Vec<String>>() .join("\n"); let items = o .iter() .map(|(key, value)| format!("{}={};", key, value.to_string())) .collect::<Vec<String>>() .join(""); if includes.is_empty() { format!("{{{}}}", items) } else { format!("{{\n{}\n{}}}", includes, items) } } } } } fn main() { let stdin = std::io::stdin().lock(); let json = serde_json::Deserializer::from_reader(stdin) .into_iter::<Value>() .next() .expect("Could not read content from stdin") .expect("Could not parse JSON from stdin"); for (key, value) in json .as_object() .expect("Top level of JSON file is not an object") { print!("{}={};", key, json_to_libconfig(value).to_string()); } print!("\n\n"); }