1use crate::{
8 store::definitions::{Artifact, HumanTool},
9 types::ArtifactName,
10};
11use std::collections::BTreeMap;
12
13use crate::config::Builtin;
14
15const ROOT: &str = ".";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Runs {
21 Command(String),
23 Open(String),
25 OpenUrl(String),
27 Read(String),
29 List(String),
31 Section { file: String, heading: String },
33 Help(String),
35}
36
37impl Runs {
38 pub(super) fn parse(
40 artifacts: &BTreeMap<ArtifactName, Artifact>,
41 owner: &ArtifactName,
42 tool: &HumanTool,
43 ) -> Self {
44 let (builtin, command, args) = match tool {
45 HumanTool::Command(tool) => (None, tool.command.as_str(), tool.args.as_slice()),
46 HumanTool::Builtin(tool) => (Some(tool.builtin), "", tool.args.as_slice()),
47 };
48 let args: Vec<&str> = args.iter().map(String::as_str).collect();
49 let target = || {
50 args.first()
51 .map(|arg| logical(artifacts, owner, arg))
52 .unwrap_or_default()
53 };
54 match (builtin, args.as_slice()) {
55 (Some(Builtin::Open), [url]) if artifactize_tools::builtin::is_url(url) => {
56 Self::OpenUrl((*url).to_owned())
57 }
58 (Some(Builtin::Open), [_]) => Self::Open(target()),
59 (Some(Builtin::Read), [_]) => Self::Read(target()),
60 (Some(Builtin::List), [_]) => Self::List(target()),
61 (Some(Builtin::Section), [_, heading]) => Self::Section {
62 file: target(),
63 heading: (*heading).to_owned(),
64 },
65 (Some(Builtin::Help), [_, ..]) => Self::Help(super::shell(args.iter().copied())),
66 _ => {
67 let program = builtin
68 .map(|builtin| {
69 match builtin {
70 Builtin::Read => "read",
71 Builtin::List => "list",
72 Builtin::Glob => "glob",
73 Builtin::Grep => "grep",
74 Builtin::ViewImage => "view_image",
75 Builtin::Section => "section",
76 Builtin::Help => "help",
77 Builtin::Open => "open",
78 Builtin::Graph => "graph",
79 }
80 .to_owned()
81 })
82 .unwrap_or_else(|| crate::platform::path_text(std::path::Path::new(command)));
83 Self::Command(super::shell(
84 std::iter::once(program.as_str()).chain(args.iter().copied()),
85 ))
86 }
87 }
88 }
89
90 pub fn line(&self) -> String {
92 match self {
93 Self::Command(command) => format!("$ {command}"),
94 Self::Open(path) => format!("opens {path} in its default app"),
95 Self::OpenUrl(url) => format!("opens {url} in the browser"),
96 Self::Read(path) => format!("prints {path}"),
97 Self::List(path) => format!("lists {}", folder(path)),
98 Self::Section { file, heading } => format!("prints section \"{heading}\" of {file}"),
99 Self::Help(program) => format!("prints {program} --help"),
100 }
101 }
102}
103
104fn folder(path: &str) -> String {
106 if path.ends_with('/') {
107 path.to_owned()
108 } else {
109 format!("{path}/")
110 }
111}
112
113fn logical(
117 artifacts: &BTreeMap<ArtifactName, Artifact>,
118 owner: &ArtifactName,
119 arg: &str,
120) -> String {
121 let (id, rest) = match arg.strip_prefix('{').and_then(|arg| arg.split_once('}')) {
122 Some(("artifactPath", rest)) => (owner.as_str(), rest),
123 Some((name, rest)) => (name, rest),
124 None => (owner.as_str(), arg),
125 };
126 let rest = rest.strip_prefix('/').unwrap_or(rest);
127 let Some(artifact) = artifacts.get(id) else {
128 return arg.to_owned();
129 };
130 let Some(path) = artifact.path.value() else {
131 return arg.to_owned();
132 };
133 let path = crate::platform::path_text(path);
134 let base =
135 if rest.is_empty() || artifact.kind.value() != Some(&crate::config::ArtifactKind::File) {
136 path.as_str()
137 } else {
138 path.rsplit_once('/').map_or("", |(parent, _)| parent)
139 };
140 match (base, rest) {
141 ("", "") => ROOT.to_owned(),
142 ("", rest) => rest.to_owned(),
143 (base, "") => base.to_owned(),
144 (base, rest) => format!("{base}/{rest}"),
145 }
146}