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