1use std::env;
2use std::io::{self, Write};
3
4use crate::logger::{Log, LogLevel};
5use crate::util;
6
7use uuid::Uuid;
8
9const PATH_VAR: &str = "PATH";
10
11#[cfg(not(windows))]
12pub(crate) const DELIMITER: &str = ":";
13
14#[cfg(windows)]
15pub(crate) const DELIMITER: &str = ";";
16
17pub struct Core<W> {
18 out: W,
19}
20
21impl Default for Core<std::io::Stdout> {
22 fn default() -> Self {
23 Self {
24 out: std::io::stdout(),
25 }
26 }
27}
28
29impl Core<std::io::Stdout> {
30 pub fn new() -> Self {
31 Default::default()
32 }
33}
34
35impl<W: Write> From<W> for Core<W> {
36 fn from(out: W) -> Self {
37 Core { out }
38 }
39}
40
41impl<W> Core<W>
42where
43 W: Write,
44{
45 fn issue<V: ToString>(&mut self, k: &str, v: V) -> io::Result<()> {
46 writeln!(self.out, "::{}::{}", k, util::escape_data(v))
47 }
48
49 fn issue_named<K: ToString, V: ToString>(
50 &mut self,
51 name: &str,
52 k: K,
53 v: V,
54 ) -> io::Result<()> {
55 writeln!(
56 self.out,
57 "::{} {}::{}",
58 name,
59 util::cmd_arg("name", k),
60 util::escape_data(v),
61 )
62 }
63
64 pub fn input<K: ToString>(
65 _: &Self,
66 name: K,
67 ) -> Result<String, env::VarError> {
68 crate::input(name)
69 }
70
71 pub fn set_output<K: ToString, V: ToString>(
72 &mut self,
73 k: K,
74 v: V,
75 ) -> io::Result<()> {
76 self.issue_named("set-output", k, v.to_string())
77 }
78
79 pub fn set_env<K: ToString, V: ToString>(
80 &mut self,
81 k: K,
82 v: V,
83 ) -> io::Result<()> {
84 let v = v.to_string();
85
86 env::set_var(k.to_string(), &v);
88
89 self.issue_named("set-env", k, v)
90 }
91
92 pub fn add_mask<V: ToString>(&mut self, v: V) -> io::Result<()> {
93 self.issue("add-mask", v)
94 }
95
96 pub fn add_path<P: ToString>(&mut self, v: P) -> io::Result<()> {
97 let v = v.to_string();
98
99 self.issue("add-path", &v)?;
100
101 let path = if let Some(mut path) = env::var_os(PATH_VAR) {
103 path.push(DELIMITER);
104 path.push(v);
105
106 path
107 } else {
108 v.into()
109 };
110
111 env::set_var(PATH_VAR, path);
112
113 Ok(())
114 }
115
116 pub fn save_state<K: ToString, V: ToString>(
117 &mut self,
118 k: K,
119 v: V,
120 ) -> io::Result<()> {
121 self.issue_named("save-state", k, v.to_string())
122 }
123
124 pub fn state<K: ToString>(
125 _: &Self,
126 name: K,
127 ) -> Result<String, env::VarError> {
128 crate::state(name)
129 }
130
131 pub fn stop_logging<F, T>(&mut self, f: F) -> io::Result<T>
134 where
135 F: FnOnce() -> T,
136 {
137 let token = Uuid::new_v4().to_string();
139
140 self.issue("stop-commands", &token)?;
141
142 let result = f();
143
144 self.issue(&token, "")?;
145
146 Ok(result)
147 }
148
149 pub fn is_debug(_: &Self) -> bool {
150 crate::is_debug()
151 }
152
153 pub fn log_message<M: ToString>(
154 &mut self,
155 level: LogLevel,
156 message: M,
157 ) -> io::Result<()> {
158 self.issue(level.as_ref(), message)
159 }
160
161 pub fn debug<M: ToString>(&mut self, message: M) -> io::Result<()> {
162 self.log_message(LogLevel::Debug, message)
163 }
164
165 pub fn error<M: ToString>(&mut self, message: M) -> io::Result<()> {
166 self.log_message(LogLevel::Error, message)
167 }
168
169 pub fn warning<M: ToString>(&mut self, message: M) -> io::Result<()> {
170 self.log_message(LogLevel::Warning, message)
171 }
172
173 pub fn log<M: ToString>(
174 &mut self,
175 level: LogLevel,
176 log: Log<M>,
177 ) -> io::Result<()> {
178 writeln!(self.out, "::{}{}", level.as_ref(), log)
179 }
180
181 pub fn log_debug<M: ToString>(&mut self, log: Log<M>) -> io::Result<()> {
182 self.log(LogLevel::Debug, log)
183 }
184
185 pub fn log_error<M: ToString>(&mut self, log: Log<M>) -> io::Result<()> {
186 self.log(LogLevel::Error, log)
187 }
188
189 pub fn log_warning<M: ToString>(&mut self, log: Log<M>) -> io::Result<()> {
190 self.log(LogLevel::Warning, log)
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use std::cell::RefCell;
197 use std::env;
198 use std::io;
199 use std::rc::Rc;
200
201 use crate::core::DELIMITER;
202 use crate::*;
203
204 #[derive(Clone)]
205 struct TestBuf {
206 inner: Rc<RefCell<Vec<u8>>>,
207 }
208
209 impl TestBuf {
210 fn new() -> Self {
211 Self {
212 inner: Rc::new(RefCell::new(Vec::new())),
213 }
214 }
215
216 fn clear(&self) {
217 self.inner.borrow_mut().clear();
218 }
219
220 fn to_string(&self) -> String {
221 String::from_utf8(self.inner.borrow().to_vec()).unwrap()
222 }
223 }
224
225 impl io::Write for TestBuf {
226 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
227 self.inner.borrow_mut().write(buf)
228 }
229
230 fn flush(&mut self) -> io::Result<()> {
231 self.inner.borrow_mut().flush()
232 }
233 }
234
235 fn test<F>(expected: &str, f: F)
236 where
237 F: FnOnce(Core<TestBuf>) -> io::Result<()>,
238 {
239 let buf = TestBuf::new();
240
241 f(Core::from(buf.clone())).unwrap();
242
243 assert_eq!(buf.to_string(), expected);
244 }
245
246 #[test]
247 fn set_output() {
248 test("::set-output name=greeting::hello\n", |mut core| {
249 core.set_output("greeting", "hello")
250 });
251 }
252
253 #[test]
254 fn set_env() {
255 test("::set-env name=greeting::hello\n", |mut core| {
256 core.set_env("greeting", "hello")
257 });
258
259 assert_eq!(env::var("greeting").unwrap().as_str(), "hello");
260 }
261
262 #[test]
263 fn add_mask() {
264 test("::add-mask::super secret message\n", |mut core| {
265 core.add_mask("super secret message")
266 });
267 }
268
269 #[test]
270 fn add_path() {
271 test("::add-path::/this/is/a/test\n", |mut core| {
272 core.add_path("/this/is/a/test")
273 });
274
275 let path = env::var("PATH").unwrap();
276 let last_path = path.split(DELIMITER).last().unwrap();
277
278 assert_eq!(last_path, "/this/is/a/test");
279 }
280
281 #[test]
282 fn save_state() {
283 test("::save-state name=greeting::hello\n", |mut core| {
284 core.save_state("greeting", "hello")
285 });
286 }
287
288 #[test]
289 fn stop_logging() {
290 let buf = TestBuf::new();
291 let mut core = Core::from(buf.clone());
292 let mut token = String::new();
293
294 core.stop_logging(|| {
295 let output = buf.to_string();
296
297 assert!(output.starts_with("::stop-commands::"));
298
299 token = output.trim().split("::").last().unwrap().to_string();
300 buf.clear();
301 })
302 .unwrap();
303
304 assert_eq!(buf.to_string(), format!("::{}::\n", token));
305 }
306
307 #[test]
308 fn test_debug() {
309 test("::debug::Hello, World!\n", |mut core| {
310 core.debug("Hello, World!")
311 });
312 }
313
314 #[test]
315 fn test_error_complex() {
316 test(
317 "::error file=/test/file.rs,line=5,col=10::hello\n",
318 |mut core| {
319 core.log_error(Log {
320 message: "hello",
321 file: Some("/test/file.rs"),
322 line: Some(5),
323 col: Some(10),
324 })
325 },
326 );
327 }
328
329 #[test]
330 fn test_warning_omit() {
331 test("::warning::hello\n", |mut core| {
332 core.log_warning(Log {
333 message: "hello",
334 ..Default::default()
335 })
336 });
337 }
338}