1use std::io::ErrorKind;
4use std::path::{Component, Path, PathBuf};
5
6use sva_ast::TokenKind;
7use sva_core::{CliError, cwd};
8
9pub fn composition() -> Result<PathBuf, CliError> {
10 let dir = cwd()?;
11 match std::fs::read_dir(&dir) {
12 Ok(_) => Ok(dir),
13 Err(e) if e.kind() == ErrorKind::NotFound => Err(CliError::NotFound(format!(
14 "no composition at `{}`",
15 dir.display()
16 ))),
17 Err(e) => Err(CliError::Io(format!(
18 "could not read the composition at `{}`: {e}",
19 dir.display()
20 ))),
21 }
22}
23
24pub fn located(here: &Path, target: &str) -> Result<(PathBuf, String), CliError> {
27 let Ok(tokens) = sva_ast::tokenize(target) else {
28 return Ok((here.to_path_buf(), target.to_string()));
29 };
30 let refs: Vec<(sva_ast::ByteSpan, PathBuf)> = tokens
31 .iter()
32 .filter_map(|t| match &t.kind {
33 TokenKind::Ref(path) => Some((t.span, normal(&here.join(path)))),
34 _ => None,
35 })
36 .collect();
37 let root = match refs.iter().find(|(_, file)| !file.starts_with(here)) {
38 None => here.to_path_buf(),
39 Some((_, file)) => holding(file),
40 };
41 let mut out = String::with_capacity(target.len());
42 let mut at = 0;
43 for (span, file) in &refs {
44 let inside = file.strip_prefix(&root).map_err(|_| {
45 CliError::usage(
46 "target.two_compositions",
47 format!(
48 "`{target}` reads `{}` and `{}`, which no one composition holds; read one \
49 composition's nodes at a time",
50 refs[0].1.display(),
51 file.display()
52 ),
53 )
54 })?;
55 out.push_str(&target[at..span.start]);
56 out.push('@');
57 out.push_str(&inside.to_string_lossy());
58 at = span.end;
59 }
60 out.push_str(&target[at..]);
61 Ok((root, out))
62}
63
64fn normal(path: &Path) -> PathBuf {
65 let mut out = PathBuf::new();
66 for part in path.components() {
67 match part {
68 Component::CurDir => {}
69 Component::ParentDir => {
70 out.pop();
71 }
72 other => out.push(other),
73 }
74 }
75 out
76}
77
78fn holding(file: &Path) -> PathBuf {
79 let own = file.parent().unwrap_or(Path::new("/")).to_path_buf();
80 own.ancestors()
81 .find(|dir| dir.join(sva_ast::VARIABLES).is_dir())
82 .map_or(own.clone(), Path::to_path_buf)
83}