1use std::path::{Path, PathBuf};
2
3pub(crate) async fn write<P: AsRef<Path> + Unpin, C: AsRef<[u8]>>(
5 path: P,
6 contents: C,
7) -> std::io::Result<()> {
8 tokio::fs::write(path.as_ref(), contents.as_ref()).await
9}
10
11pub(crate) async fn canonicalize<P: AsRef<Path> + Unpin>(path: P) -> std::io::Result<PathBuf> {
16 let path = tokio::fs::canonicalize(path.as_ref()).await?;
17
18 Ok(dunce::simplified(&path).to_path_buf())
19}
20
21pub(crate) fn absolute(path: PathBuf) -> std::io::Result<PathBuf> {
24 let path = if path.is_absolute() {
25 path
26 } else {
27 std::env::current_dir()?.join(path)
28 };
29 Ok(dunce::simplified(&path).to_path_buf())
30}
31
32pub(crate) async fn canonicalize_except_snap(path: PathBuf) -> std::io::Result<PathBuf> {
35 let executable_cleaned: PathBuf = canonicalize(&path).await?;
37
38 Ok(if executable_cleaned.to_str().unwrap().ends_with("/snap") {
40 absolute(path).unwrap()
41 } else {
42 executable_cleaned
43 })
44}
45
46pub mod base64 {
47 use base64::engine::general_purpose::STANDARD;
48 use base64::{DecodeError, Engine};
49
50 pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
52 STANDARD.decode(input)
53 }
54}
55
56pub fn evaluation_string(function: impl AsRef<str>, params: &[impl AsRef<str>]) -> String {
59 let params = params
60 .iter()
61 .map(|s| format!("\"{}\"", s.as_ref()))
62 .collect::<Vec<_>>()
63 .join(",");
64 format!("({})({params})", function.as_ref())
65}
66
67pub fn is_likely_js_function(function: impl AsRef<str>) -> bool {
69 let mut fun = function.as_ref().trim_start();
70 if fun.is_empty() {
71 return false;
72 }
73 let mut offset = 0;
74
75 if fun.starts_with("async ") {
76 offset = "async ".len() - 1
77 }
78
79 if fun[offset..].trim_start().starts_with("function ") {
80 return true;
81 } else if skip_args(&mut fun) {
82 if fun.trim_start().starts_with("=>") {
85 return true;
86 }
87 }
88 false
89}
90
91fn skip_args(input: &mut &str) -> bool {
96 if !input.starts_with('(') {
97 return false;
98 }
99 let mut open = 1;
100 let mut closed = 0;
101 *input = &input[1..];
102 while !input.is_empty() && open != closed {
103 if let Some(idx) = input.find(&['(', ')'] as &[_]) {
104 if &input[idx..=idx] == ")" {
105 closed += 1;
106 } else {
107 open += 1;
108 }
109 *input = &input[idx + 1..];
110 } else {
111 break;
112 }
113 }
114
115 open == closed
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn is_js_function() {
124 assert!(is_likely_js_function("function abc() {}"));
125 assert!(is_likely_js_function("async function abc() {}"));
126 assert!(is_likely_js_function("() => {}"));
127 assert!(is_likely_js_function("(abc, def) => {}"));
128 assert!(is_likely_js_function("((abc), (def)) => {}"));
129 assert!(is_likely_js_function("() => Promise.resolve(100 / 25)"));
130 }
131}