git.delta.rocks / jrsonnet / refs/commits / 505f82ed3097

difftreelog

source

crates/nix-eval/src/value.rs7.4 KiBsourcehistory
1use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};23use better_command::NixHandler;4use serde::{de::DeserializeOwned, Serialize};56use crate::{macros::NixExprBuilder, nix_go, Error, NixSession, Result};78#[derive(Clone)]9pub enum Index {10	Var(String),11	String(String),12	#[allow(dead_code)]13	Apply(String),14	#[allow(dead_code)]15	Expr(NixExprBuilder),16	ExprApply(NixExprBuilder),17	Pipe(NixExprBuilder),18}19impl Index {20	pub fn var(v: impl AsRef<str>) -> Self {21		let v = v.as_ref();22		assert!(23			!(v.contains('.') | v.contains(' ')),24			"bad variable name: {v}"25		);26		Self::Var(v.to_owned())27	}28	pub fn attr(v: impl AsRef<str>) -> Self {29		Self::String(v.as_ref().to_owned())30	}31	#[allow(dead_code)]32	pub fn apply(v: impl Serialize) -> Self {33		let serialized = nixlike::serialize(v).expect("invalid value for apply");34		Self::Apply(serialized.trim_end().to_owned())35	}36}37impl fmt::Display for Index {38	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {39		match self {40			Index::Var(v) => {41				write!(f, "{v}")42			}43			Index::String(k) => {44				let v = nixlike::format_identifier(k.as_str());45				write!(f, ".{v}")46			}47			Index::Apply(_) => {48				write!(f, "<apply>(...)")49			}50			Index::Expr(e) => {51				write!(f, "[{}]", e.out)52			}53			Index::ExprApply(_) => {54				write!(f, "<apply>(...)")55			}56			Index::Pipe(e) => {57				write!(f, "<map>({})", e.out)58			}59		}60	}61}62impl fmt::Debug for Index {63	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {64		write!(f, "{self}")65	}66}67struct PathDisplay<'i>(&'i [Index]);68impl fmt::Display for PathDisplay<'_> {69	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {70		for i in self.0 {71			write!(f, "{i}")?;72		}73		Ok(())74	}75}76struct ValueInner {77	full_path: Option<Vec<Index>>,78	session: NixSession,79	value: Option<u32>,80}81#[derive(Clone)]82pub struct Value(Arc<ValueInner>);83impl Value {84	fn root(session: NixSession) -> Self {85		Self(Arc::new(ValueInner {86			full_path: Some(vec![]),87			session,88			value: None,89		}))90	}91	async fn new(session: NixSession, query: &str) -> Result<Self> {92		let vid = session.0.lock().await.execute_assign(query).await?;93		Ok(Self(Arc::new(ValueInner {94			full_path: None,95			session,96			value: Some(vid),97		})))98	}99	/// Get a top-level binding.100	///101	/// In flake repl session, every output is exposed as top-level binding.102	pub async fn binding(session: NixSession, field: &str) -> Result<Self> {103		Self::root(session).select([Index::var(field)]).await104	}105	pub async fn select<'a>(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {106		let mut used_fields = Vec::new();107		let mut name = name.into_iter();108109		let mut full_path = self.0.full_path.clone();110		let mut query = if let Some(id) = self.0.value {111			format!("sess_field_{id}")112		} else {113			let first = name.next();114			if let Some(Index::Var(i)) = first {115				if let Some(full_path) = &mut full_path {116					full_path.push(Index::Var(i.clone()));117				}118				i.clone()119			} else {120				panic!("first path item should be variable, got {first:?}")121			}122		};123		for v in name {124			if let Some(full_path) = &mut full_path {125				full_path.push(v.clone());126			}127			match v {128				Index::Var(_) => panic!("var item may only be first"),129				Index::String(s) => {130					let escaped =131						nixlike::serialize(s).expect("strings are always serialized successfully");132					query.push('.');133					query.push_str(escaped.trim());134				}135				Index::Apply(a) => {136					// In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`137					query = format!("({query} {a})");138				}139				Index::Expr(e) => {140					let index = Value::new(self.0.session.clone(), &e.out).await?;141					used_fields.push(index.clone());142					query.push('.');143					let index = format!("${{sess_field_{}}}", index.0.value.expect("value"));144					query.push_str(&index);145				}146				Index::ExprApply(e) => {147					let index = Value::new(self.0.session.clone(), &e.out).await?;148					used_fields.push(index.clone());149					query.push(' ');150					let index = format!("sess_field_{}", index.0.value.expect("value"));151					query.push_str(&index);152					query = format!("({query})");153				}154				Index::Pipe(v) => {155					let index = Value::new(self.0.session.clone(), &v.out).await?;156					used_fields.push(index.clone());157					let index = format!("sess_field_{}", index.0.value.expect("value"));158					query = format!("({index} {query})");159				}160			}161		}162163		let vid = self164			.0165			.session166			.0167			.lock()168			.await169			.execute_assign(&query)170			.await171			.map_err(|e| e.context(self.attribute()))?;172		Ok(Self(Arc::new(ValueInner {173			full_path,174			session: self.0.session.clone(),175			value: Some(vid),176		})))177	}178	pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {179		let id = self.0.value.expect("can't serialize root field");180		let query = format!("sess_field_{id}");181		self.0182			.session183			.0184			.lock()185			.await186			.execute_expression_to_json(&query)187			.await188			.map_err(|e| e.context(self.attribute()))189	}190	#[allow(dead_code)]191	pub async fn has_field(&self, name: &str) -> Result<bool> {192		let id = self.0.value.expect("can't list root fields");193		let key = nixlike::escape_string(name);194		let query = format!("sess_field_{id} ? {key}");195		self.0196			.session197			.0198			.lock()199			.await200			.execute_expression_to_json(&query)201			.await202			.map_err(|e| e.context(self.attribute()))203	}204	pub async fn list_fields(&self) -> Result<Vec<String>> {205		let id = self.0.value.expect("can't list root fields");206		let query = format!("builtins.attrNames sess_field_{id}");207		self.0208			.session209			.0210			.lock()211			.await212			.execute_expression_to_json(&query)213			.await214			.map_err(|e| e.context(self.attribute()))215	}216	pub async fn type_of(&self) -> Result<String> {217		let id = self.0.value.expect("can't list root fields");218		let query = format!("builtins.typeOf sess_field_{id}");219		self.0220			.session221			.0222			.lock()223			.await224			.execute_expression_to_json(&query)225			.await226			.map_err(|e| e.context(self.attribute()))227	}228	#[allow(dead_code)]229	pub async fn import(&self) -> Result<Self> {230		let import = Self::new(self.0.session.clone(), "import").await?;231		Ok(nix_go!(self | import))232	}233	pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {234		let id = self.0.value.expect("can't use build on not-value");235		let query = format!(":b sess_field_{id}");236		let vid = self237			.0238			.session239			.0240			.lock()241			.await242			.execute_expression_raw(&query, &mut NixHandler::default())243			.await?;244		if vid.is_empty() {245			return Err(Error::BuildFailed {246				attribute: self.attribute(),247				error: "build produced no output".to_owned(),248			});249		}250		let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")251		else {252			return Err(Error::BuildFailed {253				attribute: self.attribute(),254				error: format!("failed to parse output: {vid}"),255			});256		};257		let outputs = vid258			.split('\n')259			.filter(|v| !v.is_empty())260			.map(|v| v.split_once(" -> ").expect("unexpected build output"))261			.map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))262			.collect();263		Ok(outputs)264	}265266	fn attribute(&self) -> String {267		if let Some(full_path) = &self.0.full_path {268			PathDisplay(full_path).to_string()269		} else {270			"<root>".to_owned()271		}272	}273274	pub(crate) fn session(&self) -> NixSession {275		self.0.session.clone()276	}277278	pub(crate) fn session_field_id(&self) -> u32 {279		self.0.value.expect("not root")280	}281}282impl Drop for ValueInner {283	fn drop(&mut self) {284		if let Some(id) = self.value {285			if let Ok(mut lock) = self.session.0.try_lock() {286				lock.free_list.push(id)287			}288			// Leaked289		}290	}291}