git.delta.rocks / jrsonnet / refs/commits / 7da68eaa8a4d

difftreelog

source

crates/jrsonnet-evaluator/src/async_import.rs4.6 KiBsourcehistory
1use std::rc::Rc;2use std::{any::Any, cell::RefCell, future::Future};34use jrsonnet_gcmodule::Acyclic;5use jrsonnet_ir::visit::Visitor;6use jrsonnet_ir::{IStr, Source, SourcePath};7use rustc_hash::FxHashMap;89use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1011pub struct Import {12	path: ResolvePathOwned,13	expression: bool,14}1516pub struct FoundImports(Vec<Import>);17impl Visitor for FoundImports {18	fn visit_import(&mut self, expression: bool, value: IStr) {19		self.0.push(Import {20			path: ResolvePathOwned::Str(value.to_string()),21			expression,22		});23	}24}2526pub trait AsyncImportResolver {27	type Error;28	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond29	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`30	/// where `${vendor}` is a library path.31	///32	/// `from` should only be returned from [`ImportResolver::resolve`],33	/// or from other defined file, any other value may result in panic34	fn resolve_from(35		&self,36		from: &SourcePath,37		path: &dyn AsPathLike,38	) -> impl Future<Output = Result<SourcePath, Self::Error>>;39	fn resolve_from_default(40		&self,41		path: &dyn AsPathLike,42	) -> impl Future<Output = Result<SourcePath, Self::Error>> {43		async { self.resolve_from(&SourcePath::default(), path).await }44	}4546	/// Load resolved file47	/// This should only be called with value returned48	/// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],49	/// this cannot be resolved using associated type,50	/// as the evaluator uses object instead of generic for [`ImportResolver`]51	fn load_file_contents(52		&self,53		resolved: &SourcePath,54	) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;55}5657#[derive(Acyclic)]58struct ResolvedImportResolver {59	resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,60}61impl ImportResolver for ResolvedImportResolver {62	fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {63		unreachable!("all files should be loaded at this point");64	}6566	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {67		Ok(self68			.resolved69			.borrow()70			.get(&(from.clone(), path.as_path().to_owned()))71			.expect("all imports should be resolved at this point")72			.073			.clone())74	}7576	fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {77		self.resolve_from(&SourcePath::default(), path)78	}79}8081enum Job {82	LoadFile { path: SourcePath, parse: bool },83	ParseFile(SourcePath),84	ResolveImport { from: SourcePath, import: Import },85}8687#[allow(clippy::future_not_send)]88pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>89where90	H: AsyncImportResolver,91{92	let resolved = (s.import_resolver() as &dyn Any)93		.downcast_ref::<ResolvedImportResolver>()94		.expect("for async imports, import_resolver should be set to ResolvedImportResolver");9596	let mut queue = vec![Job::LoadFile {97		path: handler.resolve_from_default(path).await?,98		parse: true,99	}];100	while let Some(job) = queue.pop() {101		match job {102			Job::LoadFile { path, parse } => {103				if !s.0.file_cache.borrow().contains_key(&path) {104					let data = handler.load_file_contents(&path).await?;105					s.0.file_cache106						.borrow_mut()107						.insert(path.clone(), FileData::new_bytes(data.as_slice().into()));108				}109				if parse {110					queue.push(Job::ParseFile(path));111				}112			}113			Job::ParseFile(path) => {114				if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {115					if file.parsed.is_none() {116						let Some(code) = file.get_string() else {117							continue;118						};119						let source = Source::new(path.clone(), code.clone());120						// If failed - then skip import121						file.parsed = crate::parse_jsonnet(&code, source).map(Rc::new).ok();122						if let Some(parsed) = &file.parsed {123							let mut imports = FoundImports(vec![]);124							imports.visit_expr(parsed);125							for import in imports.0 {126								queue.push(Job::ResolveImport {127									from: path.clone(),128									import,129								});130							}131						}132					}133				}134			}135			Job::ResolveImport { from, import } => {136				{137					let mut resolved_map = resolved.resolved.borrow_mut();138					if let Some((resolved, expression)) =139						resolved_map.get_mut(&(from.clone(), import.path.clone()))140					{141						if import.expression && !*expression {142							*expression = true;143							queue.push(Job::ParseFile(resolved.clone()));144						}145						continue;146					}147				}148				let resolved = handler.resolve_from(&from, &import.path).await?;149				queue.push(Job::LoadFile {150					path: resolved,151					parse: import.expression,152				});153			}154		}155	}156	Ok(())157}