1 | //! An interface to github actions workflow commands. |
2 | |
3 | use std::{ |
4 | fmt::{Debug, Write}, |
5 | num::NonZeroUsize, |
6 | }; |
7 | |
8 | /// Shows an error message directly in a github diff view on drop. |
9 | pub struct Error { |
10 | file: String, |
11 | line: usize, |
12 | title: String, |
13 | message: String, |
14 | } |
15 | impl Error { |
16 | /// Set a line for this error. By default the message is shown at the top of the file. |
17 | pub fn line(mut self, line: NonZeroUsize) -> Self { |
18 | self.line = line.get(); |
19 | self |
20 | } |
21 | } |
22 | |
23 | /// Create an error to be shown for the given file and with the given title. |
24 | pub fn error(file: impl std::fmt::Display, title: impl Into<String>) -> Error { |
25 | Error { |
26 | file: file.to_string(), |
27 | line: 0, |
28 | title: title.into(), |
29 | message: String::new(), |
30 | } |
31 | } |
32 | |
33 | impl Write for Error { |
34 | fn write_str(&mut self, s: &str) -> std::fmt::Result { |
35 | self.message.write_str(s) |
36 | } |
37 | } |
38 | |
39 | impl Drop for Error { |
40 | fn drop(&mut self) { |
41 | if std::env::var_os(key:"GITHUB_ACTION" ).is_some() { |
42 | let Error { |
43 | file: &mut String, |
44 | line: &mut usize, |
45 | title: &mut String, |
46 | message: &mut String, |
47 | } = self; |
48 | let message: &str = message.trim(); |
49 | let message: String = if message.is_empty() { |
50 | "::no message" .into() |
51 | } else { |
52 | format!(":: {}" , github_action_multiline_escape(message)) |
53 | }; |
54 | println!("::error file= {file},line= {line},title= {title}{message}" ); |
55 | } |
56 | } |
57 | } |
58 | |
59 | /// Append to the summary file that will be shown for the entire CI run. |
60 | pub fn summary() -> Option<impl std::io::Write> { |
61 | let path: OsString = std::env::var_os(key:"GITHUB_STEP_SUMMARY" )?; |
62 | Some(std::fs::OpenOptions::new().append(true).open(path).unwrap()) |
63 | } |
64 | |
65 | fn github_action_multiline_escape(s: &str) -> String { |
66 | s.replace('%' , "%25" ) |
67 | .replace(' \n' , "%0A" ) |
68 | .replace(from:' \r' , to:"%0D" ) |
69 | } |
70 | |
71 | /// All github actions log messages from this call to the Drop of the return value |
72 | /// will be grouped and hidden by default in logs. Note that nesting these does |
73 | /// not really work. |
74 | pub fn group(name: impl std::fmt::Display) -> Group { |
75 | if std::env::var_os(key:"GITHUB_ACTION" ).is_some() { |
76 | println!("::group:: {name}" ); |
77 | } |
78 | Group(()) |
79 | } |
80 | |
81 | /// A guard that closes the current github actions log group on drop. |
82 | pub struct Group(()); |
83 | |
84 | impl Debug for Group { |
85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
86 | f.write_str(data:"a handle that will close the github action group on drop" ) |
87 | } |
88 | } |
89 | |
90 | impl Drop for Group { |
91 | fn drop(&mut self) { |
92 | if std::env::var_os(key:"GITHUB_ACTION" ).is_some() { |
93 | println!("::endgroup::" ); |
94 | } |
95 | } |
96 | } |
97 | |