git.delta.rocks / jrsonnet / refs/commits / 9b70922a5a83

difftreelog

feat (de)serialize nix imports

upkxyxktYaroslav Bolyukin2026-01-22parent: #3bdc221.patch.diff
in: trunk

5 files changed

modifiedCargo.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",
modifiedcrates/nixlike/Cargo.tomldiffbeforeafterboth
before · crates/nixlike/Cargo.toml
1[package]2name = "nixlike"3version = "0.1.0"4edition.workspace = true5rust-version.workspace = true67[dependencies]8thiserror.workspace = true910linked-hash-map = "0.5.6"11peg = "0.8.5"12ron = "0.11.0"13serde = "1.0.219"14serde-transcode = "1.1.1"15serde_json = "1.0.140"
after · crates/nixlike/Cargo.toml
1[package]2name = "nixlike"3version = "0.1.0"4edition.workspace = true5rust-version.workspace = true67[dependencies]8thiserror.workspace = true910linked-hash-map = "0.5.6"11peg = "0.8.5"12ron = "0.11.0"13serde = { version = "1.0.219", features = ["derive"] }14serde-transcode = "1.1.1"15serde_json = "1.0.140"16itertools = "0.14.0"
modifiedcrates/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>(
modifiedcrates/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");
+	}
 }
modifiedcrates/nixlike/src/to_string.rsdiffbeforeafterboth
--- a/crates/nixlike/src/to_string.rs
+++ b/crates/nixlike/src/to_string.rs
@@ -1,3 +1,5 @@
+use itertools::Itertools;
+
 use crate::Value;
 
 pub fn write_identifier(k: &str, out: &mut String) {
@@ -76,6 +78,10 @@
 	}
 }
 
+fn write_nix_import(import: &str, out: &mut String, padding: &mut usize) {
+	out.push_str("import ");
+	write_nix_str(import, out, padding)
+}
 fn write_nix_buf(value: &Value, out: &mut String, padding: &mut usize) {
 	match value {
 		Value::Null => out.push_str("null"),
@@ -98,9 +104,17 @@
 				out.push(']');
 			}
 		}
+		Value::Import(i) => write_nix_import(&i.import, out, padding),
 		Value::Object(obj) => {
 			if obj.is_empty() {
-				out.push_str("{ }")
+				out.push_str("{ }");
+			} else if obj.len() == 2
+				&& let Some([(importk, Value::String(importv)), (markerk, Value::Null)]) =
+					obj.iter().next_array::<2>()
+				&& markerk == "__magic_marker"
+				&& importk == "__magic_import"
+			{
+				write_nix_import(importv, out, padding)
 			} else {
 				out.push_str("{\n");
 				*padding += 1;