ase_shell/commands/
parse.rs1use std::{env, path::PathBuf};
2
3use glob::glob;
4
5use super::targets::{StderrTarget, StdoutTarget};
6
7pub fn needs_more_input(raw: &str) -> bool {
8 let r = raw.trim();
9 !r.is_empty() && shlex::split(r).is_none()
10}
11
12pub struct ParsedInvocation {
13 pub cmd_name: String,
14 pub args: Vec<String>,
15 pub stdout: StdoutTarget,
16 pub stderr: StderrTarget,
17}
18
19impl ParsedInvocation {
20 pub fn from_tokens(tokens: Vec<String>) -> Option<Self> {
21 let mut iter = tokens.into_iter();
22 let cmd_name = iter.next()?;
23 let rest: Vec<String> = iter.collect();
24
25 let mut stdout = StdoutTarget::Stdout;
26 let mut stderr = StderrTarget::Stderr;
27 let mut args = Vec::new();
28 let mut i = 0;
29
30 while i < rest.len() {
31 match rest[i].as_str() {
32 ">>" | "1>>" => {
33 if i + 1 < rest.len() {
34 let target = expand_single_path(&rest[i + 1]);
35 stdout = StdoutTarget::Append(PathBuf::from(target));
36 i += 2;
37 continue;
38 } else {
39 args.push(rest[i].clone());
40 i += 1;
41 continue;
42 }
43 }
44 ">" | "1>" => {
45 if i + 1 < rest.len() {
46 let target = expand_single_path(&rest[i + 1]);
47 stdout = StdoutTarget::Overwrite(PathBuf::from(target));
48 i += 2;
49 continue;
50 } else {
51 args.push(rest[i].clone());
52 i += 1;
53 continue;
54 }
55 }
56 "2>>" => {
57 if i + 1 < rest.len() {
58 let target = expand_single_path(&rest[i + 1]);
59 stderr = StderrTarget::Append(PathBuf::from(target));
60 i += 2;
61 continue;
62 } else {
63 args.push(rest[i].clone());
64 i += 1;
65 continue;
66 }
67 }
68 "2>" => {
69 if i + 1 < rest.len() {
70 let target = expand_single_path(&rest[i + 1]);
71 stderr = StderrTarget::Overwrite(PathBuf::from(target));
72 i += 2;
73 continue;
74 } else {
75 args.push(rest[i].clone());
76 i += 1;
77 continue;
78 }
79 }
80 _ => {
81 let expanded = expand_arg(&rest[i]);
82 args.extend(expanded);
83 i += 1;
84 }
85 }
86 }
87
88 Some(ParsedInvocation {
89 cmd_name,
90 args,
91 stdout,
92 stderr,
93 })
94 }
95}
96
97fn expand_arg(token: &str) -> Vec<String> {
98 let token = expand_vars_and_tilde(token);
99
100 if has_glob_meta(&token) {
101 let mut results = Vec::new();
102 if let Ok(paths) = glob(&token) {
103 for entry in paths.flatten() {
104 if let Some(s) = entry.to_str() {
105 results.push(s.to_string());
106 }
107 }
108 }
109 if !results.is_empty() {
110 return results;
111 }
112 }
113
114 vec![token]
115}
116
117fn expand_single_path(token: &str) -> String {
118 expand_vars_and_tilde(token)
119}
120
121fn expand_vars_and_tilde(token: &str) -> String {
122 let mut s = token.to_string();
123
124 if let Some(home) = env::var_os("HOME") {
125 if let Some(stripped) = s.strip_prefix('~') {
126 if stripped.is_empty() || stripped.starts_with('/') {
127 if let Some(home_str) = home.to_str() {
128 s = format!("{home_str}{stripped}");
129 }
130 }
131 }
132 }
133
134 s = expand_vars(&s);
135 s
136}
137
138fn expand_vars(s: &str) -> String {
139 let mut out = String::with_capacity(s.len());
140 let bytes = s.as_bytes();
141 let mut i = 0;
142
143 while i < bytes.len() {
144 if bytes[i] == b'$' {
145 let start = i + 1;
146 let mut j = start;
147 while j < bytes.len() && (bytes[j] == b'_' || bytes[j].is_ascii_alphanumeric()) {
148 j += 1;
149 }
150 if j > start {
151 let name = &s[start..j];
152 if let Ok(val) = env::var(name) {
153 out.push_str(&val);
154 }
155 i = j;
156 continue;
157 }
158 }
159
160 out.push(bytes[i] as char);
161 i += 1;
162 }
163
164 out
165}
166
167fn has_glob_meta(s: &str) -> bool {
168 s.chars().any(|c| matches!(c, '*' | '?' | '['))
169}