summaryrefslogtreecommitdiffstats
path: root/pkgs/pkgs-lib
diff options
context:
space:
mode:
authorh7x4 <h7x4@nani.wtf>2023-07-30 01:38:28 +0200
committerh7x4 <h7x4@nani.wtf>2023-10-27 18:32:22 +0200
commit3530342dccf37c70dbf8b3b20b8ac0676033f6b5 (patch)
tree75ed178ac8e6f74905ae007ed743e8ad93360f87 /pkgs/pkgs-lib
parent5e4c2ada4fcd54b99d56d7bd62f384511a7e2593 (diff)
formats.libconfig: init
Co-authored-by: ckie <25263210+ckiee@users.noreply.github.com> Signed-off-by: h7x4 <h7x4@nani.wtf>
Diffstat (limited to 'pkgs/pkgs-lib')
-rw-r--r--pkgs/pkgs-lib/formats.nix2
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/default.nix121
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock40
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml10
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/src/src/main.rs271
-rwxr-xr-xpkgs/pkgs-lib/formats/libconfig/update.sh4
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/validator.c21
7 files changed, 469 insertions, 0 deletions
diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix
index 3a47d3dc849c..44d4cc733078 100644
--- a/pkgs/pkgs-lib/formats.nix
+++ b/pkgs/pkgs-lib/formats.nix
@@ -34,6 +34,8 @@ rec {
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
diff --git a/pkgs/pkgs-lib/formats/libconfig/default.nix b/pkgs/pkgs-lib/formats/libconfig/default.nix
new file mode 100644
index 000000000000..7433a7285353
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/default.nix
@@ -0,0 +1,121 @@
+{ 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;
+ };
+ };
+}
diff --git a/pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock b/pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock
new file mode 100644
index 000000000000..f8f921f996f9
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/src/Cargo.lock
@@ -0,0 +1,40 @@
+# 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",
+]
diff --git a/pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml b/pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml
new file mode 100644
index 000000000000..20ad44d22194
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/src/Cargo.toml
@@ -0,0 +1,10 @@
+[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"
diff --git a/pkgs/pkgs-lib/formats/libconfig/src/src/main.rs b/pkgs/pkgs-lib/formats/libconfig/src/src/main.rs
new file mode 100644
index 000000000000..4da45f647d46
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/src/src/main.rs
@@ -0,0 +1,271 @@
+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");
+}
diff --git a/pkgs/pkgs-lib/formats/libconfig/update.sh b/pkgs/pkgs-lib/formats/libconfig/update.sh
new file mode 100755
index 000000000000..ffc5ad3917f7
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/update.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env nix-shell
+#!nix-shell -p cargo -i bash
+cd "$(dirname "$0")"
+cargo update
diff --git a/pkgs/pkgs-lib/formats/libconfig/validator.c b/pkgs/pkgs-lib/formats/libconfig/validator.c
new file mode 100644
index 000000000000..738be0b774b5
--- /dev/null
+++ b/pkgs/pkgs-lib/formats/libconfig/validator.c
@@ -0,0 +1,21 @@
+// Copyright (C) 2005-2023 Mark A Lindner, ckie
+// SPDX-License-Identifier: LGPL-2.1-or-later
+#include <stdio.h>
+#include <libconfig.h>
+int main(int argc, char **argv)
+{
+ config_t cfg;
+ config_init(&cfg);
+ if (argc != 2)
+ {
+ fprintf(stderr, "USAGE: validator <path-to-validate>");
+ }
+ if(! config_read_file(&cfg, argv[1]))
+ {
+ fprintf(stderr, "[libconfig] %s:%d - %s\n", config_error_file(&cfg),
+ config_error_line(&cfg), config_error_text(&cfg));
+ config_destroy(&cfg);
+ return 1;
+ }
+ printf("[libconfig] validation ok\n");
+} \ No newline at end of file