ferrous_actions/actions/
core.rs1use crate::node::path::Path;
2use js_sys::{JsString, Number, Object};
3use wasm_bindgen::JsValue;
4
5#[macro_export]
7macro_rules! debug {
8 ($($arg:tt)*) => {{
9 $crate::actions::core::debug(std::format!($($arg)*).as_str());
10 }};
11}
12
13#[macro_export]
15macro_rules! info {
16 ($($arg:tt)*) => {{
17 $crate::actions::core::info(std::format!($($arg)*).as_str());
18 }};
19}
20
21#[macro_export]
24macro_rules! notice {
25 ($($arg:tt)*) => {{
26 $crate::actions::core::notice(std::format!($($arg)*).as_str());
27 }};
28}
29
30#[macro_export]
33macro_rules! warning {
34 ($($arg:tt)*) => {{
35 $crate::actions::core::warning(std::format!($($arg)*).as_str());
36 }};
37}
38
39#[macro_export]
42macro_rules! error {
43 ($($arg:tt)*) => {{
44 $crate::actions::core::error(std::format!($($arg)*).as_str());
45 }};
46}
47
48pub fn debug<S: Into<JsString>>(message: S) {
50 ffi::debug(&message.into());
51}
52
53pub fn info<S: Into<JsString>>(message: S) {
55 ffi::info(&message.into());
56}
57
58pub fn notice<A: Into<Annotation>>(message: A) {
61 message.into().notice();
62}
63
64pub fn warning<A: Into<Annotation>>(message: A) {
67 message.into().warning();
68}
69
70pub fn error<A: Into<Annotation>>(message: A) {
73 message.into().error();
74}
75
76pub fn set_output<N: Into<JsString>, V: Into<JsString>>(name: N, value: V) {
78 ffi::set_output(&name.into(), &value.into());
79}
80
81#[derive(Debug)]
83pub struct Input {
84 name: JsString,
85 required: bool,
86 trim_whitespace: bool,
87}
88
89impl<N: Into<JsString>> From<N> for Input {
90 fn from(name: N) -> Input {
92 Input {
93 name: name.into(),
94 required: false,
95 trim_whitespace: true,
96 }
97 }
98}
99
100impl Input {
101 pub fn required(&mut self, value: bool) -> &mut Input {
104 self.required = value;
105 self
106 }
107
108 pub fn trim_whitespace(&mut self, value: bool) -> &mut Input {
110 self.trim_whitespace = value;
111 self
112 }
113
114 fn to_ffi(&self) -> ffi::InputOptions {
115 ffi::InputOptions {
116 required: Some(self.required),
117 trim_whitespace: Some(self.trim_whitespace),
118 }
119 }
120
121 pub fn get(&mut self) -> Result<Option<String>, JsValue> {
123 let ffi = self.to_ffi();
124 let value = String::from(ffi::get_input(&self.name, Some(ffi))?);
125 Ok(if value.is_empty() { None } else { Some(value) })
126 }
127
128 pub fn get_required(&mut self) -> Result<String, JsValue> {
130 let mut ffi = self.to_ffi();
131 ffi.required = Some(true);
132 ffi::get_input(&self.name, Some(ffi)).map(String::from)
133 }
134}
135
136#[derive(Debug)]
138pub struct Annotation {
139 message: String,
140 title: Option<String>,
141 file: Option<Path>,
142 start_line: Option<usize>,
143 end_line: Option<usize>,
144 start_column: Option<usize>,
145 end_column: Option<usize>,
146}
147
148impl<M: Into<String>> From<M> for Annotation {
149 fn from(message: M) -> Annotation {
151 Annotation {
152 message: message.into(),
153 title: None,
154 file: None,
155 start_line: None,
156 end_line: None,
157 start_column: None,
158 end_column: None,
159 }
160 }
161}
162
163#[derive(Copy, Clone, Debug)]
165pub enum AnnotationLevel {
166 Notice,
168
169 Warning,
171
172 Error,
174}
175
176impl Annotation {
177 pub fn title(&mut self, title: &str) -> &mut Annotation {
179 self.title = Some(title.to_string());
180 self
181 }
182
183 pub fn file(&mut self, path: &Path) -> &mut Annotation {
185 self.file = Some(path.clone());
186 self
187 }
188
189 pub fn start_line(&mut self, start_line: usize) -> &mut Annotation {
191 self.start_line = Some(start_line);
192 self
193 }
194
195 pub fn end_line(&mut self, end_line: usize) -> &mut Annotation {
197 self.end_line = Some(end_line);
198 self
199 }
200
201 pub fn start_column(&mut self, start_column: usize) -> &mut Annotation {
203 self.start_column = Some(start_column);
204 self
205 }
206
207 pub fn end_column(&mut self, end_column: usize) -> &mut Annotation {
209 self.end_column = Some(end_column);
210 self
211 }
212
213 fn build_js_properties(&self) -> Object {
214 let properties = js_sys::Map::new();
215 if let Some(title) = &self.title {
216 properties.set(&"title".into(), JsString::from(title.as_str()).as_ref());
217 }
218 if let Some(file) = &self.file {
219 properties.set(&"file".into(), file.to_js_string().as_ref());
220 }
221 for (name, value) in [
222 ("startLine", &self.start_line),
223 ("endLine", &self.end_line),
224 ("startColumn", &self.start_column),
225 ("endColumn", &self.end_column),
226 ] {
227 if let Some(number) = value.and_then(|n| TryInto::<u32>::try_into(n).ok()) {
228 properties.set(&name.into(), Number::from(number).as_ref());
229 }
230 }
231 Object::from_entries(&properties).expect("Failed to convert options map to object")
232 }
233
234 pub fn error(&self) {
236 self.output(AnnotationLevel::Error);
237 }
238
239 pub fn notice(&self) {
241 self.output(AnnotationLevel::Notice);
242 }
243
244 pub fn warning(&self) {
246 self.output(AnnotationLevel::Warning);
247 }
248
249 pub fn output(&self, level: AnnotationLevel) {
251 let message = JsString::from(self.message.as_str());
252 let properties = self.build_js_properties();
253 match level {
254 AnnotationLevel::Error => ffi::error(&message, Some(properties)),
255 AnnotationLevel::Warning => ffi::warning(&message, Some(properties)),
256 AnnotationLevel::Notice => ffi::notice(&message, Some(properties)),
257 }
258 }
259}
260
261pub fn get_input<I: Into<Input>>(input: I) -> Result<Option<String>, JsValue> {
263 let mut input = input.into();
264 input.get()
265}
266
267pub fn set_failed<M: Into<JsString>>(message: M) {
269 ffi::set_failed(&message.into());
270}
271
272pub fn add_path(path: &Path) {
274 ffi::add_path(&path.into());
275}
276
277pub fn export_variable<N: Into<JsString>, V: Into<JsString>>(name: N, value: V) {
279 let name = name.into();
280 let value = value.into();
281 ffi::export_variable(&name, &value);
282}
283
284pub fn save_state<N: Into<JsString>, V: Into<JsString>>(name: N, value: V) {
286 let name = name.into();
287 let value = value.into();
288 ffi::save_state(&name, &value);
289}
290
291pub fn get_state<N: Into<JsString>>(name: N) -> Option<String> {
293 let name = name.into();
294 let value: String = ffi::get_state(&name).into();
295 let value = value.trim();
296 if value.is_empty() {
297 None
298 } else {
299 Some(value.into())
300 }
301}
302
303pub fn start_group<N: Into<JsString>>(name: N) {
305 ffi::start_group(&name.into());
306}
307
308pub fn end_group() {
310 ffi::end_group();
311}
312
313#[allow(clippy::drop_non_drop)]
315pub mod ffi {
316 use js_sys::{JsString, Object};
317 use wasm_bindgen::prelude::*;
318
319 #[wasm_bindgen]
320 pub struct InputOptions {
321 pub required: Option<bool>,
322
323 #[wasm_bindgen(js_name = "trimWhitespace")]
324 pub trim_whitespace: Option<bool>,
325 }
326
327 #[wasm_bindgen(module = "@actions/core")]
328 extern "C" {
329 #[wasm_bindgen(js_name = "getInput", catch)]
331 pub fn get_input(name: &JsString, options: Option<InputOptions>) -> Result<JsString, JsValue>;
332
333 #[wasm_bindgen]
335 pub fn info(message: &JsString);
336
337 #[wasm_bindgen]
339 pub fn debug(message: &JsString);
340
341 #[wasm_bindgen]
343 pub fn error(message: &JsString, annotation: Option<Object>);
344
345 #[wasm_bindgen]
347 pub fn warning(message: &JsString, annotation: Option<Object>);
348
349 #[wasm_bindgen]
351 pub fn notice(message: &JsString, annotation: Option<Object>);
352
353 #[wasm_bindgen(js_name = "setFailed")]
356 pub fn set_failed(message: &JsString);
357
358 #[wasm_bindgen(js_name = "setOutput")]
360 pub fn set_output(name: &JsString, value: &JsString);
361
362 #[wasm_bindgen(js_name = "addPath")]
363 pub fn add_path(path: &JsString);
364
365 #[wasm_bindgen(js_name = "exportVariable")]
366 pub fn export_variable(name: &JsString, value: &JsString);
367
368 #[wasm_bindgen(js_name = "saveState")]
369 pub fn save_state(name: &JsString, value: &JsString);
370
371 #[wasm_bindgen(js_name = "getState")]
372 pub fn get_state(name: &JsString) -> JsString;
373
374 #[wasm_bindgen(js_name = "startGroup")]
375 pub fn start_group(name: &JsString);
376
377 #[wasm_bindgen(js_name = "endGroup")]
378 pub fn end_group();
379 }
380}