1#[inline]
3pub(crate) fn is_digit(string: &str) -> bool {
4 string.chars().all(|c| c.is_ascii_digit())
5}
6
7#[inline]
9pub(crate) fn is_exec(string: &str) -> bool {
10 string.starts_with("$ ")
11}
12
13#[inline]
15pub(crate) fn is_garden(string: &str) -> bool {
16 string.starts_with(':')
17}
18
19#[inline]
21pub(crate) fn is_group(string: &str) -> bool {
22 string.starts_with('%')
23}
24
25#[inline]
27pub(crate) fn is_tree(string: &str) -> bool {
28 string.starts_with('@')
29}
30
31#[inline]
33pub(crate) fn is_append_op(string: &str) -> bool {
34 string.ends_with('+')
35}
36
37#[inline]
39pub(crate) fn is_replace_op(string: &str) -> bool {
40 string.ends_with('=')
41}
42
43#[inline]
45pub(crate) fn is_graft(string: &str) -> bool {
46 string.contains("::")
47}
48
49#[inline]
52pub(crate) fn is_eval_candidate(string: &str) -> bool {
53 string.contains('$')
54}
55
56#[inline]
58pub(crate) fn is_git_dir(string: &str) -> bool {
59 string.len() > 4 && string.ends_with(".git") && !string.ends_with("/.git")
60}
61
62#[inline]
64pub(crate) fn is_post_command(string: &str) -> bool {
65 string.ends_with('>')
66}
67
68#[inline]
70pub(crate) fn is_pre_command(string: &str) -> bool {
71 string.ends_with('<')
72}
73
74#[inline]
76pub fn is_pre_or_post_command(string: &str) -> bool {
77 is_pre_command(string) || is_post_command(string)
78}
79
80#[inline]
82pub(crate) fn is_shebang(string: &str) -> bool {
83 string.starts_with("#!")
84}
85
86#[inline]
88pub(crate) fn trim(string: &str) -> &str {
89 let needs_trim = is_group(string) || is_tree(string) || is_garden(string);
90 if !string.is_empty() && needs_trim {
91 &string[1..]
92 } else {
93 string
94 }
95}
96
97#[inline]
99pub(crate) fn trim_exec(string: &str) -> &str {
100 let prefix = "$ ";
101 let prefix_len = prefix.len();
102 if string.len() >= prefix_len && string.starts_with(prefix) {
103 &string[prefix_len..]
104 } else {
105 string
106 }
107}
108
109#[inline]
111pub fn trim_op_inplace(string: &mut String) {
112 let len = string.len();
113 if len > 1 {
114 string.remove(len - 1);
115 }
116}
117
118#[inline]
120pub(crate) fn split_graft(string: &str) -> Option<(&str, &str)> {
121 string.split_once("::")
122}
123
124#[inline]
126pub(crate) fn trim_graft(string: &str) -> Option<String> {
127 let (_before, after) = match split_graft(string) {
128 Some((before, after)) => (before, after),
129 None => return None,
130 };
131 let result;
132 if is_garden(string) {
135 result = string!(":") + after;
136 } else if is_group(string) {
137 result = string!("%") + after;
138 } else if is_tree(string) {
139 result = string!("@") + after;
140 } else {
141 result = after.to_string();
142 }
143
144 Some(result)
145}
146
147#[inline]
149pub(crate) fn graft_basename(string: &str) -> Option<&str> {
150 let (before, _after) = match split_graft(string) {
151 Some((before, after)) => (before, after),
152 None => return None,
153 };
154 let result = if is_garden(string) || is_group(string) || is_tree(string) {
155 trim(before)
156 } else {
157 before
158 };
159
160 Some(result)
161}
162
163#[inline]
165pub(crate) fn trim_shebang(string: &str) -> Option<&str> {
166 if is_shebang(string) {
167 Some(&string[2..])
168 } else {
169 None
170 }
171}
172
173pub(crate) fn split_shebang(string: &str) -> Option<(&str, &str)> {
176 if let Some(trimmed) = trim_shebang(string) {
177 trimmed.split_once('\n')
178 } else {
179 None
180 }
181}
182
183#[inline]
185pub(crate) fn escape_shell_variables(string: &str) -> String {
186 let mut result = String::new();
187
188 let mut potential_variable = false;
190 for c in string.chars() {
191 if potential_variable {
192 if c.is_alphanumeric() || c == '_' {
193 result.push('$'); result.push(c);
195 } else if c == '$' {
196 result.push('$'); } else {
198 result.push(c);
199 }
200 potential_variable = false;
201 } else {
202 result.push(c);
204
205 potential_variable = c == '$';
207 }
208 }
209
210 result
211}
212
213#[inline]
215pub(crate) fn bool_to_string(value: bool) -> String {
216 match value {
217 true => string!("true"),
218 false => string!("false"),
219 }
220}
221
222#[inline]
224pub(crate) fn string_to_bool(value: &str) -> Option<bool> {
225 match value {
226 "true" | "1" => Some(true),
227 "false" | "0" => Some(false),
228 _ => None,
229 }
230}
231
232#[inline]
234pub(crate) fn pre_command(name: &str) -> String {
235 format!("{name}<")
236}
237
238#[inline]
240pub(crate) fn post_command(name: &str) -> String {
241 format!("{name}>")
242}
243
244#[cfg(test)]
246mod tests {
247 #[test]
248 fn is_garden() {
249 assert!(super::is_garden(":garden"), ":garden is a garden");
250 assert!(!super::is_garden("garden"), "garden is not a garden");
251 }
252
253 #[test]
254 fn is_graft() {
255 assert!(super::is_graft("foo::bar"), "foo::bar is a graft");
256 assert!(!super::is_graft("foo"), "foo is not a graft");
257 }
258
259 #[test]
260 fn is_group() {
261 assert!(super::is_group("%group"), "%group is a group");
262 assert!(!super::is_group("group"), "group is not a group");
263 }
264
265 #[test]
266 fn is_tree() {
267 assert!(super::is_tree("@tree"), "@tree is a tree");
268 assert!(!super::is_tree("tree"), "tree is not a tree");
269 }
270
271 #[test]
272 fn is_git_dir() {
273 assert!(super::is_git_dir("tree.git"), "tree.git is a git dir");
274 assert!(
275 super::is_git_dir("/src/tree.git"),
276 "/src/tree.git is a git dir"
277 );
278 assert!(
279 !super::is_git_dir("src/tree/.git"),
280 "src/tree/.git is a git dir"
281 );
282 assert!(!super::is_git_dir(".git"), ".git is a git dir");
283 assert!(!super::is_git_dir("/.git"), "/.git is a git dir");
284 }
285
286 #[test]
287 fn split_graft_ok() {
288 let split = super::split_graft("foo::bar");
289 assert!(split.is_some(), "split_graft on foo::bar is ok");
290 assert_eq!(split, Some(("foo", "bar")));
291 }
292
293 #[test]
294 fn split_graft_nested_ok() {
295 let split = super::split_graft("@foo::bar::baz");
296 assert!(split.is_some(), "split_graft on @foo::bar::baz is ok");
297 assert_eq!(split, Some(("@foo", "bar::baz")));
298 }
299
300 #[test]
301 fn split_graft_empty() {
302 let split = super::split_graft("foo::");
303 assert!(split.is_some(), "split_graft on foo:: is ok");
304 assert_eq!(split, Some(("foo", "")));
305 }
306
307 #[test]
308 fn split_graft_not_found() {
309 let split = super::split_graft("foo");
310 assert!(split.is_none(), "split_graft on foo is None");
311 }
312
313 #[test]
314 fn trim_exec() {
315 assert_eq!("cmd", super::trim_exec("$ cmd"));
316 assert_eq!("$cmd", super::trim_exec("$cmd"));
317 assert_eq!("cmd", super::trim_exec("cmd"));
318 assert_eq!("", super::trim_exec("$ "));
319 assert_eq!("$", super::trim_exec("$"));
320 assert_eq!("", super::trim_exec(""));
321 }
322
323 #[test]
324 fn trim_graft() {
325 let value = super::trim_graft("foo::bar::baz");
326 assert!(value.is_some());
327 assert_eq!("bar::baz", value.unwrap());
328
329 let value = super::trim_graft("@foo::bar::baz");
330 assert!(value.is_some());
331 assert_eq!("@bar::baz", value.unwrap());
332
333 let value = super::trim_graft("%foo::bar::baz");
334 assert!(value.is_some());
335 assert_eq!("%bar::baz", value.unwrap());
336
337 let value = super::trim_graft(":foo::bar::baz");
338 assert!(value.is_some());
339 assert_eq!(":bar::baz", value.unwrap());
340
341 let value = super::trim_graft("foo::bar");
342 assert!(value.is_some());
343 assert_eq!("bar", value.unwrap());
344
345 let value = super::trim_graft("foo");
346 assert!(value.is_none());
347 }
348
349 #[test]
350 fn graft_basename() {
351 let value = super::graft_basename("foo");
352 assert!(value.is_none());
353
354 let value = super::graft_basename(":foo");
355 assert!(value.is_none());
356
357 let value = super::graft_basename("%foo");
358 assert!(value.is_none());
359
360 let value = super::graft_basename("@foo");
361 assert!(value.is_none());
362
363 let value = super::graft_basename("foo::bar");
364 assert!(value.is_some());
365 assert_eq!("foo", value.unwrap());
366
367 let value = super::graft_basename(":foo::bar");
368 assert!(value.is_some());
369 assert_eq!("foo", value.unwrap());
370
371 let value = super::graft_basename("%foo::bar");
372 assert!(value.is_some());
373 assert_eq!("foo", value.unwrap());
374
375 let value = super::graft_basename("@foo::bar");
376 assert!(value.is_some());
377 assert_eq!("foo", value.unwrap());
378
379 let value = super::graft_basename("foo::bar::baz");
380 assert!(value.is_some());
381 assert_eq!("foo", value.unwrap());
382
383 let value = super::graft_basename(":foo::bar::baz");
384 assert!(value.is_some());
385 assert_eq!("foo", value.unwrap());
386
387 let value = super::graft_basename("%foo::bar::baz");
388 assert!(value.is_some());
389 assert_eq!("foo", value.unwrap());
390
391 let value = super::graft_basename("@foo::bar::baz");
392 assert!(value.is_some());
393 assert_eq!("foo", value.unwrap());
394 }
395
396 #[test]
397 fn escape_shell_variables() {
398 let value = super::escape_shell_variables("$");
399 assert_eq!(value, "$");
400
401 let value = super::escape_shell_variables("$ ");
402 assert_eq!(value, "$ ");
403
404 let value = super::escape_shell_variables("$$");
405 assert_eq!(value, "$$");
406
407 let value = super::escape_shell_variables("$_");
408 assert_eq!(value, "$$_");
409
410 let value = super::escape_shell_variables("$a");
411 assert_eq!(value, "$$a");
412
413 let value = super::escape_shell_variables("$_a");
414 assert_eq!(value, "$$_a");
415
416 let value = super::escape_shell_variables("$ echo");
417 assert_eq!(value, "$ echo");
418
419 let value = super::escape_shell_variables("embedded $$ value");
420 assert_eq!(value, "embedded $$ value");
421
422 let value = super::escape_shell_variables("$variable");
423 assert_eq!(value, "$$variable");
424
425 let value = super::escape_shell_variables("$$variable");
426 assert_eq!(value, "$$variable");
427
428 let value = super::escape_shell_variables("${braces}${ignored}");
429 assert_eq!(value, "${braces}${ignored}");
430
431 let value = super::escape_shell_variables("$a ${b} $c $");
432 assert_eq!(value, "$$a ${b} $$c $");
433
434 let value = super::escape_shell_variables("echo $${value[@]:0:1}");
436 assert_eq!(value, "echo $${value[@]:0:1}");
437 }
438
439 #[test]
440 fn trim_and_split_shebang() {
441 assert!(super::is_shebang("#!test"));
442
443 let value = super::trim_shebang("#not-shebang\nvalue\n");
444 assert!(value.is_none());
445
446 let value = super::trim_shebang("#!test\nvalue\n");
447 assert!(value.is_some());
448 assert_eq!(value, Some("test\nvalue\n"));
449
450 let value = super::split_shebang("#not-shebang\nvalue\n");
451 assert!(value.is_none());
452
453 let value = super::split_shebang("#!test\nvalue\n");
454 assert_eq!(value, Some(("test", "value\n")));
455
456 let value = super::split_shebang("#comment\nvalue\n");
457 assert_eq!(value, None);
458 }
459}