use std::fs::OpenOptions;
use std::io::Write;
use crate::error::{Error, Result};
use crate::redact;
pub fn is_github_actions() -> bool {
std::env::var("GITHUB_ACTIONS").map(|v| v == "true").unwrap_or(false)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Debug,
Notice,
Warning,
Error,
}
impl Level {
fn command(self) -> &'static str {
match self {
Level::Debug => "debug",
Level::Notice => "notice",
Level::Warning => "warning",
Level::Error => "error",
}
}
}
pub fn add_mask(value: &str) {
if value.is_empty() {
return;
}
redact::register(value);
if is_github_actions() {
eprintln!("::add-mask::{}", escape_data(value));
}
}
pub fn log(level: Level, message: &str) {
let message = redact::redact(message);
if is_github_actions() {
eprintln!("::{}::{}", level.command(), escape_data(&message));
} else if level != Level::Debug || std::env::var_os("NOTIFLOW_DEBUG").is_some() {
eprintln!("notiflow: {}: {}", level.command(), message);
}
}
pub fn set_output(key: &str, value: &str) -> Result<()> {
let Some(path) = std::env::var_os("GITHUB_OUTPUT") else {
return Ok(());
};
let value = redact::redact(value);
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.map_err(|e| Error::Io(format!("cannot open $GITHUB_OUTPUT: {e}")))?;
if value.contains('\n') {
let delimiter = format!("notiflow_eof_{}", value.len());
writeln!(file, "{key}<<{delimiter}\n{value}\n{delimiter}")
} else {
writeln!(file, "{key}={value}")
}
.map_err(|e| Error::Io(format!("cannot write $GITHUB_OUTPUT: {e}")))
}
pub fn step_summary(markdown: &str) -> Result<()> {
let Some(path) = std::env::var_os("GITHUB_STEP_SUMMARY") else {
return Ok(());
};
let markdown = redact::redact(markdown);
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.map_err(|e| Error::Io(format!("cannot open $GITHUB_STEP_SUMMARY: {e}")))?;
writeln!(file, "{markdown}")
.map_err(|e| Error::Io(format!("cannot write $GITHUB_STEP_SUMMARY: {e}")))
}
fn escape_data(value: &str) -> String {
value.replace('%', "%25").replace('\r', "%0D").replace('\n', "%0A")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_data_neutralises_newlines_and_percent() {
assert_eq!(escape_data("a\nb"), "a%0Ab");
assert_eq!(escape_data("100%"), "100%25");
assert_eq!(escape_data("a\r\nb"), "a%0D%0Ab");
assert_eq!(escape_data("%\n"), "%25%0A");
}
#[test]
fn set_output_is_a_noop_without_the_env_var() {
unsafe { std::env::remove_var("GITHUB_OUTPUT") };
assert!(set_output("ok", "true").is_ok());
}
}