feat(tvix/eval): teach builtins.toXML context
XmlEmitter gains a NixContext field, and `write_typed_value` extends it with all context elements present in the passed value. Once all serialization is done, a into_context() function returns the collected context, so we can construct a NixString with context. Tests for this live in tvix-glue, as we use builtins.derivation, which is not present in the tvix-eval crate. Fixes b/398. Change-Id: I85feaaa17b753885f8a017a54e419ec4e602af21 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11704 Tested-by: BuildkiteCI Reviewed-by: flokli <flokli@flokli.de> Autosubmit: flokli <flokli@flokli.de> Reviewed-by: Alyssa Ross <hi@alyssa.is>
This commit is contained in:
		
							parent
							
								
									a4a313cdd2
								
							
						
					
					
						commit
						ec8d79f3db
					
				
					 4 changed files with 53 additions and 6 deletions
				
			
		|  | @ -1504,8 +1504,19 @@ mod pure_builtins { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let mut buf: Vec<u8> = vec![]; |         let mut buf: Vec<u8> = vec![]; | ||||||
|         to_xml::value_to_xml(&mut buf, &value)?; |         let context = to_xml::value_to_xml(&mut buf, &value)?; | ||||||
|         Ok(buf.into()) | 
 | ||||||
|  |         Ok(( | ||||||
|  |             buf, | ||||||
|  |             // FUTUREWORK: We have a distinction between an empty context, and
 | ||||||
|  |             // no context at all. Fix this.
 | ||||||
|  |             if !context.is_empty() { | ||||||
|  |                 Some(Box::new(context)) | ||||||
|  |             } else { | ||||||
|  |                 None | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |             .into()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[builtin("placeholder")] |     #[builtin("placeholder")] | ||||||
|  |  | ||||||
|  | @ -6,11 +6,12 @@ use bstr::ByteSlice; | ||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| use std::{io::Write, rc::Rc}; | use std::{io::Write, rc::Rc}; | ||||||
| 
 | 
 | ||||||
| use crate::{ErrorKind, Value}; | use crate::{ErrorKind, NixContext, NixContextElement, Value}; | ||||||
| 
 | 
 | ||||||
| /// Recursively serialise a value to XML. The value *must* have been
 | /// Recursively serialise a value to XML. The value *must* have been
 | ||||||
| /// deep-forced before being passed to this function.
 | /// deep-forced before being passed to this function.
 | ||||||
| pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorKind> { | /// On success, returns the NixContext.
 | ||||||
|  | pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<NixContext, ErrorKind> { | ||||||
|     // Write a literal document declaration, using C++-Nix-style
 |     // Write a literal document declaration, using C++-Nix-style
 | ||||||
|     // single quotes.
 |     // single quotes.
 | ||||||
|     writeln!(writer, "<?xml version='1.0' encoding='utf-8'?>")?; |     writeln!(writer, "<?xml version='1.0' encoding='utf-8'?>")?; | ||||||
|  | @ -21,7 +22,7 @@ pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorK | ||||||
|     value_variant_to_xml(&mut emitter, value)?; |     value_variant_to_xml(&mut emitter, value)?; | ||||||
|     emitter.write_closing_tag("expr")?; |     emitter.write_closing_tag("expr")?; | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(emitter.into_context()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn write_typed_value<W: Write, V: ToString>( | fn write_typed_value<W: Write, V: ToString>( | ||||||
|  | @ -45,7 +46,12 @@ fn value_variant_to_xml<W: Write>(w: &mut XmlEmitter<W>, value: &Value) -> Resul | ||||||
|         Value::Bool(b) => return write_typed_value(w, "bool", b), |         Value::Bool(b) => return write_typed_value(w, "bool", b), | ||||||
|         Value::Integer(i) => return write_typed_value(w, "int", i), |         Value::Integer(i) => return write_typed_value(w, "int", i), | ||||||
|         Value::Float(f) => return write_typed_value(w, "float", f), |         Value::Float(f) => return write_typed_value(w, "float", f), | ||||||
|         Value::String(s) => return write_typed_value(w, "string", s.to_str()?), |         Value::String(s) => { | ||||||
|  |             if let Some(context) = s.context() { | ||||||
|  |                 w.extend_context(context.iter().cloned()); | ||||||
|  |             } | ||||||
|  |             return write_typed_value(w, "string", s.to_str()?); | ||||||
|  |         } | ||||||
|         Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()), |         Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()), | ||||||
| 
 | 
 | ||||||
|         Value::List(list) => { |         Value::List(list) => { | ||||||
|  | @ -137,6 +143,7 @@ struct XmlEmitter<W> { | ||||||
|     /// The current indentation
 |     /// The current indentation
 | ||||||
|     cur_indent: usize, |     cur_indent: usize, | ||||||
|     writer: W, |     writer: W, | ||||||
|  |     context: NixContext, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<W: Write> XmlEmitter<W> { | impl<W: Write> XmlEmitter<W> { | ||||||
|  | @ -144,6 +151,7 @@ impl<W: Write> XmlEmitter<W> { | ||||||
|         XmlEmitter { |         XmlEmitter { | ||||||
|             cur_indent: 0, |             cur_indent: 0, | ||||||
|             writer, |             writer, | ||||||
|  |             context: Default::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -245,6 +253,19 @@ impl<W: Write> XmlEmitter<W> { | ||||||
|             _ => None, |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /// Extends the existing context with more context elements.
 | ||||||
|  |     fn extend_context<T>(&mut self, iter: T) | ||||||
|  |     where | ||||||
|  |         T: IntoIterator<Item = NixContextElement>, | ||||||
|  |     { | ||||||
|  |         self.context.extend(iter) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Consumes [Self] and returns the [NixContext] collected.
 | ||||||
|  |     fn into_context(self) -> NixContext { | ||||||
|  |         self.context | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | [ { "/nix/store/y1s2fiq89v2h9vkb38w508ir20dwv6v2-test.drv" = { allOutputs = true; }; } false ] | ||||||
							
								
								
									
										14
									
								
								tvix/glue/src/tests/tvix_tests/eval-okay-toxml-context.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tvix/glue/src/tests/tvix_tests/eval-okay-toxml-context.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | [ | ||||||
|  |   # builtins.toXML retains context where there is. | ||||||
|  |   (builtins.getContext (builtins.toXML { | ||||||
|  |     inherit (derivation { | ||||||
|  |       name = "test"; | ||||||
|  |       builder = "/bin/sh"; | ||||||
|  |       system = builtins.currentSystem; | ||||||
|  |     }) drvPath; | ||||||
|  |   })) | ||||||
|  | 
 | ||||||
|  |   # this should have no context. | ||||||
|  |   (builtins.hasContext | ||||||
|  |     (builtins.toXML { })) | ||||||
|  | ] | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue