formats.hocon: init

h7x4 2554eba2 06570e54

+481
+2
pkgs/pkgs-lib/formats.nix
··· 41 41 42 42 libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format; 43 43 44 + hocon = (import ./formats/hocon/default.nix { inherit lib pkgs; }).format; 45 + 44 46 json = {}: { 45 47 46 48 type = with lib.types; let
+149
pkgs/pkgs-lib/formats/hocon/default.nix
··· 1 + { lib 2 + , pkgs 3 + }: 4 + let 5 + inherit (pkgs) buildPackages callPackage; 6 + 7 + hocon-generator = buildPackages.rustPlatform.buildRustPackage { 8 + name = "hocon-generator"; 9 + version = "0.1.0"; 10 + src = ./src; 11 + 12 + passthru.updateScript = ./update.sh; 13 + 14 + cargoLock.lockFile = ./src/Cargo.lock; 15 + }; 16 + 17 + hocon-validator = pkgs.writers.writePython3Bin "hocon-validator" { 18 + libraries = [ pkgs.python3Packages.pyhocon ]; 19 + } '' 20 + from sys import argv 21 + from pyhocon import ConfigFactory 22 + 23 + if not len(argv) == 2: 24 + print("USAGE: hocon-validator <file>") 25 + 26 + ConfigFactory.parse_file(argv[1]) 27 + ''; 28 + in 29 + { 30 + # https://github.com/lightbend/config/blob/main/HOCON.md 31 + format = { 32 + generator ? hocon-generator 33 + , validator ? hocon-validator 34 + # `include classpath("")` is not implemented in pyhocon. 35 + # In the case that you need this functionality, 36 + # you will have to disable pyhocon validation. 37 + , doCheck ? true 38 + }: { 39 + type = let 40 + type' = with lib.types; let 41 + atomType = nullOr (oneOf [ 42 + bool 43 + float 44 + int 45 + path 46 + str 47 + ]); 48 + in (oneOf [ 49 + atomType 50 + (listOf atomType) 51 + (attrsOf type') 52 + ]) // { 53 + description = "HOCON value"; 54 + }; 55 + in type'; 56 + 57 + lib = { 58 + mkInclude = value: let 59 + includeStatement = if lib.isAttrs value && !(lib.isDerivation value) then { 60 + required = false; 61 + type = null; 62 + _type = "include"; 63 + } // value else { 64 + value = toString value; 65 + required = false; 66 + type = null; 67 + _type = "include"; 68 + }; 69 + in 70 + assert lib.assertMsg (lib.elem includeStatement.type [ "file" "url" "classpath" null ]) '' 71 + Type of HOCON mkInclude is not of type 'file', 'url' or 'classpath': 72 + ${(lib.generators.toPretty {}) includeStatement} 73 + ''; 74 + includeStatement; 75 + 76 + mkAppend = value: { 77 + inherit value; 78 + _type = "append"; 79 + }; 80 + 81 + mkSubstitution = value: 82 + if lib.isString value 83 + then 84 + { 85 + inherit value; 86 + optional = false; 87 + _type = "substitution"; 88 + } 89 + else 90 + assert lib.assertMsg (lib.isAttrs value) '' 91 + Value of invalid type provided to `hocon.lib.mkSubstition`: ${lib.typeOf value} 92 + ''; 93 + assert lib.assertMsg (value ? "value") '' 94 + Argument to `hocon.lib.mkSubstition` is missing a `value`: 95 + ${builtins.toJSON value} 96 + ''; 97 + { 98 + value = value.value; 99 + optional = value.optional or false; 100 + _type = "substitution"; 101 + }; 102 + }; 103 + 104 + generate = name: value: 105 + callPackage 106 + ({ 107 + stdenvNoCC 108 + , hocon-generator 109 + , hocon-validator 110 + , writeText 111 + }: 112 + stdenvNoCC.mkDerivation rec { 113 + inherit name; 114 + 115 + dontUnpack = true; 116 + 117 + json = builtins.toJSON value; 118 + passAsFile = [ "json" ]; 119 + 120 + strictDeps = true; 121 + nativeBuildInputs = [ hocon-generator ]; 122 + buildPhase = '' 123 + runHook preBuild 124 + hocon-generator < $jsonPath > output.conf 125 + runHook postBuild 126 + ''; 127 + 128 + inherit doCheck; 129 + nativeCheckInputs = [ hocon-validator ]; 130 + checkPhase = '' 131 + runHook preCheck 132 + hocon-validator output.conf 133 + runHook postCheck 134 + ''; 135 + 136 + installPhase = '' 137 + runHook preInstall 138 + mv output.conf $out 139 + runHook postInstall 140 + ''; 141 + 142 + passthru.json = writeText "${name}.json" json; 143 + }) 144 + { 145 + hocon-generator = generator; 146 + hocon-validator = validator; 147 + }; 148 + }; 149 + }
+1
pkgs/pkgs-lib/formats/hocon/src/.gitignore
··· 1 + target
+89
pkgs/pkgs-lib/formats/hocon/src/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "hocon-generator" 7 + version = "0.1.0" 8 + dependencies = [ 9 + "serde", 10 + "serde_json", 11 + ] 12 + 13 + [[package]] 14 + name = "itoa" 15 + version = "1.0.9" 16 + source = "registry+https://github.com/rust-lang/crates.io-index" 17 + checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 18 + 19 + [[package]] 20 + name = "proc-macro2" 21 + version = "1.0.69" 22 + source = "registry+https://github.com/rust-lang/crates.io-index" 23 + checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 24 + dependencies = [ 25 + "unicode-ident", 26 + ] 27 + 28 + [[package]] 29 + name = "quote" 30 + version = "1.0.33" 31 + source = "registry+https://github.com/rust-lang/crates.io-index" 32 + checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 33 + dependencies = [ 34 + "proc-macro2", 35 + ] 36 + 37 + [[package]] 38 + name = "ryu" 39 + version = "1.0.15" 40 + source = "registry+https://github.com/rust-lang/crates.io-index" 41 + checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 42 + 43 + [[package]] 44 + name = "serde" 45 + version = "1.0.190" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" 48 + dependencies = [ 49 + "serde_derive", 50 + ] 51 + 52 + [[package]] 53 + name = "serde_derive" 54 + version = "1.0.190" 55 + source = "registry+https://github.com/rust-lang/crates.io-index" 56 + checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" 57 + dependencies = [ 58 + "proc-macro2", 59 + "quote", 60 + "syn", 61 + ] 62 + 63 + [[package]] 64 + name = "serde_json" 65 + version = "1.0.107" 66 + source = "registry+https://github.com/rust-lang/crates.io-index" 67 + checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 68 + dependencies = [ 69 + "itoa", 70 + "ryu", 71 + "serde", 72 + ] 73 + 74 + [[package]] 75 + name = "syn" 76 + version = "2.0.38" 77 + source = "registry+https://github.com/rust-lang/crates.io-index" 78 + checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 79 + dependencies = [ 80 + "proc-macro2", 81 + "quote", 82 + "unicode-ident", 83 + ] 84 + 85 + [[package]] 86 + name = "unicode-ident" 87 + version = "1.0.12" 88 + source = "registry+https://github.com/rust-lang/crates.io-index" 89 + checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+10
pkgs/pkgs-lib/formats/hocon/src/Cargo.toml
··· 1 + [package] 2 + name = "hocon-generator" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 + 8 + [dependencies] 9 + serde = "1.0.178" 10 + serde_json = "1.0.104"
+226
pkgs/pkgs-lib/formats/hocon/src/src/main.rs
··· 1 + use serde_json::{value, Map, Value}; 2 + 3 + #[derive(Debug)] 4 + enum HOCONValue { 5 + Null, 6 + Append(Box<HOCONValue>), 7 + Bool(bool), 8 + Number(value::Number), 9 + String(String), 10 + List(Vec<HOCONValue>), 11 + Substitution(String, bool), 12 + Object(Vec<HOCONInclude>, Vec<(String, HOCONValue)>), 13 + } 14 + 15 + #[derive(Debug)] 16 + enum HOCONInclude { 17 + Heuristic(String, bool), 18 + Url(String, bool), 19 + File(String, bool), 20 + ClassPath(String, bool), 21 + } 22 + 23 + impl HOCONInclude { 24 + fn map_fst(&self, f: &dyn Fn(&String) -> String) -> HOCONInclude { 25 + match self { 26 + HOCONInclude::Heuristic(s, r) => HOCONInclude::Heuristic(f(s), *r), 27 + HOCONInclude::Url(s, r) => HOCONInclude::Url(f(s), *r), 28 + HOCONInclude::File(s, r) => HOCONInclude::File(f(s), *r), 29 + HOCONInclude::ClassPath(s, r) => HOCONInclude::ClassPath(f(s), *r), 30 + } 31 + } 32 + } 33 + 34 + fn parse_include(o: &Map<String, Value>) -> HOCONInclude { 35 + let value = o 36 + .get("value") 37 + .expect("Missing field 'value' for include statement") 38 + .as_str() 39 + .expect("Field 'value' is not a string in include statement") 40 + .to_string(); 41 + let required = o 42 + .get("required") 43 + .expect("Missing field 'required' for include statement") 44 + .as_bool() 45 + .expect("Field 'required'is not a bool in include statement"); 46 + let include_type = match o 47 + .get("type") 48 + .expect("Missing field 'type' for include statement") 49 + { 50 + Value::Null => None, 51 + Value::String(s) => Some(s.as_str()), 52 + t => panic!("Field 'type' is not a string in include statement: {:?}", t), 53 + }; 54 + 55 + // Assert that this was an intentional include 56 + debug_assert!(o.get("_type").and_then(|t| t.as_str()) == Some("include")); 57 + 58 + match include_type { 59 + None => HOCONInclude::Heuristic(value, required), 60 + Some("url") => HOCONInclude::Url(value, required), 61 + Some("file") => HOCONInclude::File(value, required), 62 + Some("classpath") => HOCONInclude::ClassPath(value, required), 63 + _ => panic!( 64 + "Could not recognize type for include statement: {}", 65 + include_type.unwrap() 66 + ), 67 + } 68 + } 69 + 70 + fn parse_special_types(o: &Map<String, Value>) -> Option<HOCONValue> { 71 + o.get("_type") 72 + .and_then(|r#type| r#type.as_str()) 73 + .map(|r#type| match r#type { 74 + "substitution" => { 75 + let value = o 76 + .get("value") 77 + .expect("Missing value for substitution") 78 + .as_str() 79 + .unwrap_or_else(|| panic!("Substition value is not a string: {:?}", o)); 80 + let required = o 81 + .get("required") 82 + .unwrap_or(&Value::Bool(false)) 83 + .as_bool() 84 + .unwrap_or_else(|| panic!("Substition value is not a string: {:?}", o)); 85 + 86 + debug_assert!(!value.contains('}')); 87 + 88 + HOCONValue::Substitution(value.to_string(), required) 89 + } 90 + "append" => { 91 + let value = o.get("value").expect("Missing value for append"); 92 + 93 + HOCONValue::Append(Box::new(json_to_hocon(value))) 94 + } 95 + _ => panic!( 96 + "\ 97 + Attribute set contained special element '_type',\ 98 + but its value is not recognized:\n{}", 99 + r#type 100 + ), 101 + }) 102 + } 103 + 104 + fn json_to_hocon(v: &Value) -> HOCONValue { 105 + match v { 106 + Value::Null => HOCONValue::Null, 107 + Value::Bool(b) => HOCONValue::Bool(*b), 108 + Value::Number(n) => HOCONValue::Number(n.clone()), 109 + Value::String(s) => HOCONValue::String(s.clone()), 110 + Value::Array(a) => { 111 + let items = a.iter().map(json_to_hocon).collect::<Vec<HOCONValue>>(); 112 + HOCONValue::List(items) 113 + } 114 + Value::Object(o) => { 115 + if let Some(result) = parse_special_types(o) { 116 + return result; 117 + } 118 + 119 + let mut items = o 120 + .iter() 121 + .filter(|(key, _)| key.as_str() != "_includes") 122 + .map(|(key, value)| (key.clone(), json_to_hocon(value))) 123 + .collect::<Vec<(String, HOCONValue)>>(); 124 + 125 + items.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap()); 126 + 127 + let includes = o 128 + .get("_includes") 129 + .map(|x| { 130 + x.as_array() 131 + .expect("_includes is not an array") 132 + .iter() 133 + .map(|x| { 134 + x.as_object() 135 + .unwrap_or_else(|| panic!("Include is not an object: {}", x)) 136 + }) 137 + .map(parse_include) 138 + .collect::<Vec<HOCONInclude>>() 139 + }) 140 + .unwrap_or(vec![]); 141 + 142 + HOCONValue::Object(includes, items) 143 + } 144 + } 145 + } 146 + 147 + impl ToString for HOCONValue { 148 + fn to_string(&self) -> String { 149 + match self { 150 + HOCONValue::Null => "null".to_string(), 151 + HOCONValue::Bool(b) => b.to_string(), 152 + HOCONValue::Number(n) => n.to_string(), 153 + HOCONValue::String(s) => serde_json::to_string(&Value::String(s.clone())).unwrap(), 154 + HOCONValue::Substitution(v, required) => { 155 + format!("${{{}{}}}", if *required { "" } else { "?" }, v) 156 + } 157 + HOCONValue::List(l) => { 158 + let items = l 159 + .iter() 160 + .map(|item| item.to_string()) 161 + .collect::<Vec<String>>() 162 + .join(",\n") 163 + .split('\n') 164 + .map(|s| " ".to_owned() + s) 165 + .collect::<Vec<String>>() 166 + .join("\n"); 167 + format!("[\n{}\n]", items) 168 + } 169 + HOCONValue::Object(i, o) => { 170 + let includes = i 171 + .iter() 172 + .map(|x| { 173 + x.map_fst(&|s| serde_json::to_string(&Value::String(s.clone())).unwrap()) 174 + }) 175 + .map(|x| match x { 176 + HOCONInclude::Heuristic(s, r) => (s.to_string(), r), 177 + HOCONInclude::Url(s, r) => (format!("url({})", s), r), 178 + HOCONInclude::File(s, r) => (format!("file({})", s), r), 179 + HOCONInclude::ClassPath(s, r) => (format!("classpath({})", s), r), 180 + }) 181 + .map(|(i, r)| if r { format!("required({})", i) } else { i }) 182 + .map(|s| format!("include {}", s)) 183 + .collect::<Vec<String>>() 184 + .join("\n"); 185 + let items = o 186 + .iter() 187 + .map(|(key, value)| { 188 + ( 189 + serde_json::to_string(&Value::String(key.clone())).unwrap(), 190 + value, 191 + ) 192 + }) 193 + .map(|(key, value)| match value { 194 + HOCONValue::Append(v) => format!("{} += {}", key, v.to_string()), 195 + v => format!("{} = {}", key, v.to_string()), 196 + }) 197 + .collect::<Vec<String>>() 198 + .join("\n"); 199 + 200 + let content = (if includes.is_empty() { 201 + items 202 + } else { 203 + format!("{}{}", includes, items) 204 + }) 205 + .split('\n') 206 + .map(|s| format!(" {}", s)) 207 + .collect::<Vec<String>>() 208 + .join("\n"); 209 + 210 + format!("{{\n{}\n}}", content) 211 + } 212 + HOCONValue::Append(_) => panic!("Append should not be present at this point"), 213 + } 214 + } 215 + } 216 + 217 + fn main() { 218 + let stdin = std::io::stdin().lock(); 219 + let json = serde_json::Deserializer::from_reader(stdin) 220 + .into_iter::<Value>() 221 + .next() 222 + .expect("Could not read content from stdin") 223 + .expect("Could not parse JSON from stdin"); 224 + 225 + print!("{}\n\n", json_to_hocon(&json).to_string()); 226 + }
+4
pkgs/pkgs-lib/formats/hocon/update.sh
··· 1 + #!/usr/bin/env nix-shell 2 + #!nix-shell -p cargo -i bash 3 + cd "$(dirname "$0")" 4 + cargo update