1use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};23use better_command::NixHandler;4use serde::{Serialize, de::DeserializeOwned};56use crate::{Error, NixBuildBatch, NixSession, Result, macros::NixExprBuilder, nix_go};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 Merge(NixExprBuilder),19}20impl Index {21 pub fn var(v: impl AsRef<str>) -> Self {22 let v = v.as_ref();23 assert!(24 !(v.contains('.') | v.contains(' ')),25 "bad variable name: {v}"26 );27 Self::Var(v.to_owned())28 }29 pub fn attr(v: impl AsRef<str>) -> Self {30 Self::String(v.as_ref().to_owned())31 }32 #[allow(dead_code)]33 pub fn apply(v: impl Serialize) -> Self {34 let serialized = nixlike::serialize(v).expect("invalid value for apply");35 Self::Apply(serialized.trim_end().to_owned())36 }37}38impl fmt::Display for Index {39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {40 match self {41 Index::Var(v) => {42 write!(f, "{v}")43 }44 Index::String(k) => {45 let v = nixlike::format_identifier(k.as_str());46 write!(f, ".{v}")47 }48 Index::Apply(_) => {49 write!(f, "<apply>(...)")50 }51 Index::Expr(e) => {52 write!(f, "[{}]", e.out)53 }54 Index::ExprApply(_) => {55 write!(f, "<apply>(...)")56 }57 Index::Pipe(e) => {58 write!(f, "<map>({})", e.out)59 }60 Index::Merge(e) => {61 write!(f, "//({})", e.out)62 }63 }64 }65}66impl fmt::Debug for Index {67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {68 write!(f, "{self}")69 }70}71struct PathDisplay<'i>(&'i [Index]);72impl fmt::Display for PathDisplay<'_> {73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {74 if !matches!(self.0.first(), Some(Index::Var(_))) {75 write!(f, "<unknown>")?;76 }77 for i in self.0 {78 write!(f, "{i}")?;79 }80 Ok(())81 }82}83struct ValueInner {84 full_path: Vec<Index>,85 session: NixSession,86 value: u32,87}88#[derive(Clone)]89pub struct Value(Arc<ValueInner>);90impl Value {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: vec![],95 session,96 value: vid,97 })))98 }99 100 101 102 pub async fn binding(session: NixSession, query: &str) -> Result<Self> {103 104 let vid = session.0.lock().await.execute_assign(query).await?;105 Ok(Self(Arc::new(ValueInner {106 full_path: vec![Index::Var(query.to_owned())],107 session,108 value: vid,109 })))110 }111 pub async fn select(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {112 let mut used_fields = Vec::new();113 let name = name.into_iter();114115 let mut full_path = self.0.full_path.clone();116 let mut query = self.sess_field_name();117 for v in name {118 full_path.push(v.clone());119 match v {120 Index::Var(_) => panic!("var item may only be first"),121 Index::String(s) => {122 let escaped =123 nixlike::serialize(s).expect("strings are always serialized successfully");124 query.push('.');125 query.push_str(escaped.trim());126 }127 Index::Apply(a) => {128 129 query = format!("({query} {a})");130 }131 Index::Expr(e) => {132 let index = Value::new(self.0.session.clone(), &e.out).await?;133 used_fields.push(index.clone());134 query.push('.');135 let index = format!("${{sess_field_{}}}", index.0.value);136 query.push_str(&index);137 }138 Index::ExprApply(e) => {139 let index = Value::new(self.0.session.clone(), &e.out).await?;140 used_fields.push(index.clone());141 query.push(' ');142 let index = format!("sess_field_{}", index.0.value);143 query.push_str(&index);144 query = format!("({query})");145 }146 Index::Pipe(v) => {147 let index = Value::new(self.0.session.clone(), &v.out).await?;148 used_fields.push(index.clone());149 let index = format!("sess_field_{}", index.0.value);150 query = format!("({index} {query})");151 }152 Index::Merge(v) => {153 let index = Value::new(self.0.session.clone(), &v.out).await?;154 used_fields.push(index.clone());155 let index = format!("sess_field_{}", index.0.value);156 query = format!("({query} // {index})");157 }158 }159 }160161 let vid = self162 .0163 .session164 .0165 .lock()166 .await167 .execute_assign(&query)168 .await169 .map_err(|e| e.context(self.attribute()))?;170 Ok(Self(Arc::new(ValueInner {171 full_path,172 session: self.0.session.clone(),173 value: vid,174 })))175 }176 pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {177 let query = self.sess_field_name();178 self.0179 .session180 .0181 .lock()182 .await183 .execute_expression_to_json(&query)184 .await185 .map_err(|e| e.context(self.attribute()))186 }187 #[allow(dead_code)]188 pub async fn has_field(&self, name: &str) -> Result<bool> {189 let key = nixlike::escape_string(name);190 let query = format!("{} ? {key}", self.sess_field_name());191 self.0192 .session193 .0194 .lock()195 .await196 .execute_expression_to_json(&query)197 .await198 .map_err(|e| e.context(self.attribute()))199 }200 pub async fn list_fields(&self) -> Result<Vec<String>> {201 let query = format!("builtins.attrNames {}", self.sess_field_name());202 self.0203 .session204 .0205 .lock()206 .await207 .execute_expression_to_json(&query)208 .await209 .map_err(|e| e.context(self.attribute()))210 }211 pub async fn type_of(&self) -> Result<String> {212 let query = format!("builtins.typeOf {}", self.sess_field_name());213 self.0214 .session215 .0216 .lock()217 .await218 .execute_expression_to_json(&query)219 .await220 .map_err(|e| e.context(self.attribute()))221 }222 #[allow(dead_code)]223 pub async fn import(&self) -> Result<Self> {224 let import = Self::new(self.0.session.clone(), "import").await?;225 Ok(nix_go!(self | import))226 }227 fn sess_field_name(&self) -> String {228 format!("sess_field_{}", self.0.value)229 }230 pub async fn build_maybe_batch(231 &self,232 batch: Option<NixBuildBatch>,233 ) -> Result<HashMap<String, PathBuf>> {234 if let Some(batch) = batch {235 batch.submit(self.clone()).await236 } else {237 self.build().await238 }239 }240 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {241 let query = format!(":b {}", self.sess_field_name());242 let vid = self243 .0244 .session245 .0246 .lock()247 .await248 .execute_expression_raw(&query, &mut NixHandler::default())249 .await?;250 if vid.is_empty() {251 return Err(Error::BuildFailed {252 attribute: self.attribute(),253 error: "build produced no output".to_owned(),254 });255 }256 let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")257 else {258 return Err(Error::BuildFailed {259 attribute: self.attribute(),260 error: format!("failed to parse output: {vid}"),261 });262 };263 let outputs = vid264 .split('\n')265 .filter(|v| !v.is_empty())266 .map(|v| v.split_once(" -> ").expect("unexpected build output"))267 .map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))268 .collect();269 Ok(outputs)270 }271 272 pub async fn to_string_weak(&self) -> Result<String> {273 let query = format!("\"${{{}}}\"", self.sess_field_name());274 let vid: String = self275 .0276 .session277 .0278 .lock()279 .await280 .execute_expression_to_json(&query)281 .await?;282 Ok(vid)283 }284285 fn attribute(&self) -> String {286 PathDisplay(&self.0.full_path).to_string()287 }288289 pub(crate) fn session(&self) -> NixSession {290 self.0.session.clone()291 }292293 pub(crate) fn session_field_id(&self) -> u32 {294 self.0.value295 }296}297impl Drop for ValueInner {298 fn drop(&mut self) {299 if let Ok(mut lock) = self.session.0.try_lock() {300 lock.free_list.push(self.value)301 }302 303 }304}