Skip to main content

sva_cli/
composition.rs

1// Concern: the directory a subcommand reads its composition from, and a target's refs inside it | Non-concern: loading it (sva-core), where a reading goes | IO: (cwd, target) -> a directory, a target
2
3use 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
24/// `here` while every ref sits under it; else the nearest directory above the first ref
25/// outside it that holds `variables/`, else that ref's own. Each ref is rewritten under it.
26pub 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}