1//! An interface to github actions workflow commands.
2
3use std::{
4 fmt::{Debug, Write},
5 num::NonZeroUsize,
6};
7
8/// Shows an error message directly in a github diff view on drop.
9pub struct Error {
10 file: String,
11 line: usize,
12 title: String,
13 message: String,
14}
15impl 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.
24pub 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
33impl Write for Error {
34 fn write_str(&mut self, s: &str) -> std::fmt::Result {
35 self.message.write_str(s)
36 }
37}
38
39impl 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.
60pub 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
65fn 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.
74pub 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.
82pub struct Group(());
83
84impl 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
90impl 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