summary refs log tree commit diff
path: root/pkgs/pkgs-lib/formats/libconfig/src/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/pkgs-lib/formats/libconfig/src/src/main.rs')
-rw-r--r--pkgs/pkgs-lib/formats/libconfig/src/src/main.rs271
1 files changed, 271 insertions, 0 deletions
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 00000000000..4da45f647d4
--- /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");
+}