formats.libconfig: init

Co-authored-by: ckie <25263210+ckiee@users.noreply.github.com>
Signed-off-by: h7x4 <h7x4@nani.wtf>
This commit is contained in:
h7x4 2023-07-30 01:38:28 +02:00
parent 5e4c2ada4f
commit 3530342dcc
No known key found for this signature in database
GPG Key ID: 9F2F7D8250F35146
7 changed files with 469 additions and 0 deletions

View File

@ -34,6 +34,8 @@ rec {
inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; }) inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
javaProperties; javaProperties;
libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format;
json = {}: { json = {}: {
type = with lib.types; let type = with lib.types; let

View File

@ -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;
};
};
}

View File

@ -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",
]

View File

@ -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"

View File

@ -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");
}

View File

@ -0,0 +1,4 @@
#!/usr/bin/env nix-shell
#!nix-shell -p cargo -i bash
cd "$(dirname "$0")"
cargo update

View File

@ -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");
}