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, NixBuildBatch, 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 100 101 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 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_maybe_batch(234 &self,235 batch: Option<NixBuildBatch>,236 ) -> Result<HashMap<String, PathBuf>> {237 if let Some(batch) = batch {238 batch.submit(self.clone()).await239 } else {240 self.build().await241 }242 }243 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {244 let id = self.0.value.expect("can't use build on not-value");245 let query = format!(":b sess_field_{id}");246 let vid = self247 .0248 .session249 .0250 .lock()251 .await252 .execute_expression_raw(&query, &mut NixHandler::default())253 .await?;254 if vid.is_empty() {255 return Err(Error::BuildFailed {256 attribute: self.attribute(),257 error: "build produced no output".to_owned(),258 });259 }260 let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")261 else {262 return Err(Error::BuildFailed {263 attribute: self.attribute(),264 error: format!("failed to parse output: {vid}"),265 });266 };267 let outputs = vid268 .split('\n')269 .filter(|v| !v.is_empty())270 .map(|v| v.split_once(" -> ").expect("unexpected build output"))271 .map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))272 .collect();273 Ok(outputs)274 }275 276 pub async fn to_string_weak(&self) -> Result<String> {277 let id = self.0.value.expect("can't use build on not-value");278 let query = format!("\"${{sess_field_{id}}}\"");279 let vid: String = self280 .0281 .session282 .0283 .lock()284 .await285 .execute_expression_to_json(&query)286 .await?;287 Ok(vid)288 }289290 fn attribute(&self) -> String {291 if let Some(full_path) = &self.0.full_path {292 PathDisplay(full_path).to_string()293 } else {294 "<root>".to_owned()295 }296 }297298 pub(crate) fn session(&self) -> NixSession {299 self.0.session.clone()300 }301302 pub(crate) fn session_field_id(&self) -> u32 {303 self.0.value.expect("not root")304 }305}306impl Drop for ValueInner {307 fn drop(&mut self) {308 if let Some(id) = self.value {309 if let Ok(mut lock) = self.session.0.try_lock() {310 lock.free_list.push(id)311 }312 313 }314 }315}