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
--- 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"
modifiedcrates/nixlike/src/de_impl.rsdiffbeforeafterboth
22
3use linked_hash_map::LinkedHashMap;3use linked_hash_map::LinkedHashMap;
4use serde::{4use serde::{
5 Deserializer,5 Deserializer, Serialize,
6 de::{self, MapAccess, SeqAccess},6 de::{self, MapAccess, SeqAccess},
7};7};
88
138 Value::Object(o) => visitor.visit_map(ObjectAccess::new(o)),138 Value::Object(o) => visitor.visit_map(ObjectAccess::new(o)),
139 Value::Array(a) => visitor.visit_seq(ArrayAccess::new(a)),139 Value::Array(a) => visitor.visit_seq(ArrayAccess::new(a)),
140 Value::Null => visitor.visit_none(),140 Value::Null => visitor.visit_none(),
141 Value::Import(d) => {
142 let value = d.serialize(crate::se_impl::MySerialize)?;
143 value.deserialize_any(visitor)
144 }
141 }145 }
142 }146 }
143147
323 where327 where
324 V: serde::de::Visitor<'de>,328 V: serde::de::Visitor<'de>,
325 {329 {
330 match self {
331 Value::Import(d) => {
332 let value = d.serialize(crate::se_impl::MySerialize)?;
333 value.deserialize_map(visitor)
334 }
326 visitor.visit_map(self.parse_object().map(ObjectAccess::new)?)335 v => visitor.visit_map(v.parse_object().map(ObjectAccess::new)?),
336 }
327 }337 }
328338
329 fn deserialize_struct<V>(339 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;