difftreelog
feat (de)serialize nix imports
in: trunk
5 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2208,6 +2208,7 @@
name = "nixlike"
version = "0.1.0"
dependencies = [
+ "itertools 0.14.0",
"linked-hash-map",
"peg",
"ron",
crates/nixlike/Cargo.tomldiffbeforeafterboth--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -10,6 +10,7 @@
linked-hash-map = "0.5.6"
peg = "0.8.5"
ron = "0.11.0"
-serde = "1.0.219"
+serde = { version = "1.0.219", features = ["derive"] }
serde-transcode = "1.1.1"
serde_json = "1.0.140"
+itertools = "0.14.0"
crates/nixlike/src/de_impl.rsdiffbeforeafterboth--- a/crates/nixlike/src/de_impl.rs
+++ b/crates/nixlike/src/de_impl.rs
@@ -2,7 +2,7 @@
use linked_hash_map::LinkedHashMap;
use serde::{
- Deserializer,
+ Deserializer, Serialize,
de::{self, MapAccess, SeqAccess},
};
@@ -138,6 +138,10 @@
Value::Object(o) => visitor.visit_map(ObjectAccess::new(o)),
Value::Array(a) => visitor.visit_seq(ArrayAccess::new(a)),
Value::Null => visitor.visit_none(),
+ Value::Import(d) => {
+ let value = d.serialize(crate::se_impl::MySerialize)?;
+ value.deserialize_any(visitor)
+ }
}
}
@@ -323,7 +327,13 @@
where
V: serde::de::Visitor<'de>,
{
- visitor.visit_map(self.parse_object().map(ObjectAccess::new)?)
+ match self {
+ Value::Import(d) => {
+ let value = d.serialize(crate::se_impl::MySerialize)?;
+ value.deserialize_map(visitor)
+ }
+ v => visitor.visit_map(v.parse_object().map(ObjectAccess::new)?),
+ }
}
fn deserialize_struct<V>(
crates/nixlike/src/lib.rsdiffbeforeafterboth--- a/crates/nixlike/src/lib.rs
+++ b/crates/nixlike/src/lib.rs
@@ -5,6 +5,8 @@
//! expressions and expect it to work, only basic primitives are supported, and there is no
//! variables/recursive records, interpolation, e.t.c.
+use std::marker::PhantomData;
+
use linked_hash_map::LinkedHashMap;
use peg::str::LineCol;
use se_impl::MySerialize;
@@ -39,9 +41,28 @@
Boolean(bool),
Object(LinkedHashMap<String, Value>),
Array(Vec<Value>),
+ Import(NixImport),
Null,
}
+#[derive(Debug, Serialize, Deserialize)]
+pub struct NixImport {
+ #[serde(rename = "__magic_import")]
+ import: String,
+ // Magic values should have exactly two values to avoid pretty-printing
+ // as nix inline object value
+ __magic_marker: PhantomData<()>,
+}
+
+impl NixImport {
+ pub fn new(import: impl AsRef<str>) -> Self {
+ Self {
+ import: import.as_ref().to_string(),
+ __magic_marker: PhantomData,
+ }
+ }
+}
+
fn count_spaces(l: &str) -> usize {
l.chars().take_while(|&c| c == ' ').count()
}
@@ -150,8 +171,12 @@
rule array() -> Vec<Value>
= "[" _ v:value()**_ _ "]" {v}
+ rule import() -> NixImport
+ = "import" _ s:string() {NixImport::new(s)}
+
rule value() -> Value
- = o:object() { Value::Object(o) }
+ = i:import() { Value::Import(i) }
+ / o:object() { Value::Object(o) }
/ a:array() { Value::Array(a) }
/ s:string() { Value::String(s) }
/ "null" { Value::Null }
@@ -191,26 +216,43 @@
out
}
-#[test]
-fn test() {
- assert_eq!(serialize("Hello\nworld").unwrap(), "\"Hello\\nworld\"\n");
-}
pub fn format_nix(value: &String) -> String {
// TODO
value.to_owned()
}
-#[test]
-fn parse_multiline() {
- // First line is ignored, unless there is a significant characters.
- assert_eq!(nixlike::multiline_string("''\n''").expect("parse"), "");
- // Rest of the lines are processed normally.
- assert_eq!(nixlike::multiline_string("''\n\n''").expect("parse"), "\n");
- // Example with significant character on first line.
- assert_eq!(nixlike::multiline_string("''t\n''").expect("parse"), "t\n");
- // There might be nothing in multiline string block.
- assert_eq!(nixlike::multiline_string("''''").expect("parse"), "");
- // And there also might just be spaces, they are removed due to dedent, and output is empty because
- // first line was also ignored due to missing significant characters.
- assert_eq!(nixlike::multiline_string("'' ''").expect("parse"), "");
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test() {
+ assert_eq!(serialize("Hello\nworld").unwrap(), "\"Hello\\nworld\"");
+ }
+
+ #[test]
+ fn parse_multiline() {
+ // First line is ignored, unless there is a significant characters.
+ assert_eq!(nixlike::multiline_string("''\n''").expect("parse"), "");
+ // Rest of the lines are processed normally.
+ assert_eq!(nixlike::multiline_string("''\n\n''").expect("parse"), "\n");
+ // Example with significant character on first line.
+ assert_eq!(nixlike::multiline_string("''t\n''").expect("parse"), "t\n");
+ // There might be nothing in multiline string block.
+ assert_eq!(nixlike::multiline_string("''''").expect("parse"), "");
+ // And there also might just be spaces, they are removed due to dedent, and output is empty because
+ // first line was also ignored due to missing significant characters.
+ assert_eq!(nixlike::multiline_string("'' ''").expect("parse"), "");
+ }
+
+ #[test]
+ fn test_nix_import_roundtrip() {
+ let import = NixImport::new("./some/path.nix");
+
+ let serialized = serialize(&import).expect("serialize");
+ assert_eq!(serialized, "import \"./some/path.nix\"");
+
+ let deserialized: NixImport = parse_str(&serialized).expect("deserialize");
+ assert_eq!(deserialized.import, "./some/path.nix");
+ }
}
crates/nixlike/src/to_string.rsdiffbeforeafterboth1use crate::Value;23pub fn write_identifier(k: &str, out: &mut String) {4 if k.contains(['.', '\'', '\"', '\\', '\n', '\t', '\r', '$']) {5 write_nix_str_singleline(k, out);6 } else {7 out.push_str(k);8 }9}1011fn write_nix_obj_key_buf(k: &str, v: &Value, out: &mut String, padding: &mut usize) {12 write_identifier(k, out);13 match v {14 Value::Object(o) if o.len() == 1 => {15 let (k, v) = o.iter().next().unwrap();1617 out.push('.');18 write_nix_obj_key_buf(k, v, out, padding);19 }20 v => {21 out.push_str(" = ");22 write_nix_buf(v, out, padding);23 out.push(';');24 }25 }26}2728pub fn escape_string(str: &str) -> String {29 format!(30 "\"{}\"",31 str.replace('\\', "\\\\")32 .replace('"', "\\\"")33 .replace('\n', "\\n")34 .replace('\t', "\\t")35 .replace('\r', "\\r")36 .replace('$', "\\$")37 )38}3940fn write_padding(out: &mut String, padding: &usize) {41 for _ in 0..*padding {42 out.push_str(" ");43 }44}4546pub fn write_nix_str_singleline(str: &str, out: &mut String) {47 out.push_str(&escape_string(str))48}49pub fn write_nix_str(str: &str, out: &mut String, padding: &mut usize) {50 if str.ends_with('\n') {51 out.push_str("''");52 *padding += 1;53 for ele in str[0..str.len() - 1].split('\n') {54 out.push('\n');55 write_padding(out, padding);56 out.push_str(57 &ele58 // '' is escaped with '59 .replace("''", "'''")60 // ${ is escaped wth ''61 .replace("${", "''${")62 // \t is not counted as whitespace for dedent63 // to avoid confusion, it is printed literally.64 //65 // ...Escaped \t literal should be prefixed with '' for... Idk, this logic is complicated.66 .replace('\t', "''\\t"),67 );68 }69 out.push('\n');70 *padding -= 1;71 write_padding(out, padding);72 // Final newline is assumed due to str.ends_with condition73 out.push_str("''");74 } else {75 write_nix_str_singleline(str, out);76 }77}7879fn write_nix_buf(value: &Value, out: &mut String, padding: &mut usize) {80 match value {81 Value::Null => out.push_str("null"),82 Value::Boolean(v) => out.push_str(if *v { "true" } else { "false" }),83 Value::Number(n) => out.push_str(&format!("{n}")),84 Value::String(s) => write_nix_str(s, out, padding),85 Value::Array(a) => {86 if a.is_empty() {87 out.push_str("[ ]");88 } else {89 out.push_str("[\n");90 *padding += 1;91 for item in a {92 write_padding(out, padding);93 write_nix_buf(item, out, padding);94 out.push('\n');95 }96 *padding -= 1;97 write_padding(out, padding);98 out.push(']');99 }100 }101 Value::Object(obj) => {102 if obj.is_empty() {103 out.push_str("{ }")104 } else {105 out.push_str("{\n");106 *padding += 1;107 for (k, v) in obj {108 write_padding(out, padding);109 write_nix_obj_key_buf(k, v, out, padding);110 out.push('\n');111 }112 *padding -= 1;113 write_padding(out, padding);114 out.push('}');115 }116 }117 };118}119120pub fn write_nix(value: &Value) -> String {121 let mut out = String::new();122 write_nix_buf(value, &mut out, &mut 0);123 out124}1use itertools::Itertools;23use crate::Value;45pub fn write_identifier(k: &str, out: &mut String) {6 if k.contains(['.', '\'', '\"', '\\', '\n', '\t', '\r', '$']) {7 write_nix_str_singleline(k, out);8 } else {9 out.push_str(k);10 }11}1213fn write_nix_obj_key_buf(k: &str, v: &Value, out: &mut String, padding: &mut usize) {14 write_identifier(k, out);15 match v {16 Value::Object(o) if o.len() == 1 => {17 let (k, v) = o.iter().next().unwrap();1819 out.push('.');20 write_nix_obj_key_buf(k, v, out, padding);21 }22 v => {23 out.push_str(" = ");24 write_nix_buf(v, out, padding);25 out.push(';');26 }27 }28}2930pub fn escape_string(str: &str) -> String {31 format!(32 "\"{}\"",33 str.replace('\\', "\\\\")34 .replace('"', "\\\"")35 .replace('\n', "\\n")36 .replace('\t', "\\t")37 .replace('\r', "\\r")38 .replace('$', "\\$")39 )40}4142fn write_padding(out: &mut String, padding: &usize) {43 for _ in 0..*padding {44 out.push_str(" ");45 }46}4748pub fn write_nix_str_singleline(str: &str, out: &mut String) {49 out.push_str(&escape_string(str))50}51pub fn write_nix_str(str: &str, out: &mut String, padding: &mut usize) {52 if str.ends_with('\n') {53 out.push_str("''");54 *padding += 1;55 for ele in str[0..str.len() - 1].split('\n') {56 out.push('\n');57 write_padding(out, padding);58 out.push_str(59 &ele60 // '' is escaped with '61 .replace("''", "'''")62 // ${ is escaped wth ''63 .replace("${", "''${")64 // \t is not counted as whitespace for dedent65 // to avoid confusion, it is printed literally.66 //67 // ...Escaped \t literal should be prefixed with '' for... Idk, this logic is complicated.68 .replace('\t', "''\\t"),69 );70 }71 out.push('\n');72 *padding -= 1;73 write_padding(out, padding);74 // Final newline is assumed due to str.ends_with condition75 out.push_str("''");76 } else {77 write_nix_str_singleline(str, out);78 }79}8081fn write_nix_import(import: &str, out: &mut String, padding: &mut usize) {82 out.push_str("import ");83 write_nix_str(import, out, padding)84}85fn write_nix_buf(value: &Value, out: &mut String, padding: &mut usize) {86 match value {87 Value::Null => out.push_str("null"),88 Value::Boolean(v) => out.push_str(if *v { "true" } else { "false" }),89 Value::Number(n) => out.push_str(&format!("{n}")),90 Value::String(s) => write_nix_str(s, out, padding),91 Value::Array(a) => {92 if a.is_empty() {93 out.push_str("[ ]");94 } else {95 out.push_str("[\n");96 *padding += 1;97 for item in a {98 write_padding(out, padding);99 write_nix_buf(item, out, padding);100 out.push('\n');101 }102 *padding -= 1;103 write_padding(out, padding);104 out.push(']');105 }106 }107 Value::Import(i) => write_nix_import(&i.import, out, padding),108 Value::Object(obj) => {109 if obj.is_empty() {110 out.push_str("{ }");111 } else if obj.len() == 2112 && let Some([(importk, Value::String(importv)), (markerk, Value::Null)]) =113 obj.iter().next_array::<2>()114 && markerk == "__magic_marker"115 && importk == "__magic_import"116 {117 write_nix_import(importv, out, padding)118 } else {119 out.push_str("{\n");120 *padding += 1;121 for (k, v) in obj {122 write_padding(out, padding);123 write_nix_obj_key_buf(k, v, out, padding);124 out.push('\n');125 }126 *padding -= 1;127 write_padding(out, padding);128 out.push('}');129 }130 }131 };132}133134pub fn write_nix(value: &Value) -> String {135 let mut out = String::new();136 write_nix_buf(value, &mut out, &mut 0);137 out138}