use super::CiProvider;
use std::io::{self, Write};
#[derive(Debug, Clone)]
pub struct CiOutput {
provider: CiProvider,
}
#[allow(dead_code)] impl CiOutput {
pub fn new(provider: CiProvider) -> Self {
Self { provider }
}
pub fn provider(&self) -> CiProvider {
self.provider
}
pub fn group_start(&self, name: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::group::{}", name);
}
CiProvider::GitLabCi => {
let section_id = name.to_lowercase().replace(' ', "_");
println!(
"\x1b[0Ksection_start:{}:{}[collapsed=true]\r\x1b[0K{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
section_id,
name
);
}
CiProvider::AzureDevOps => {
println!("##[group]{}", name);
}
CiProvider::Buildkite => {
println!("--- {}", name);
}
_ => {
println!("=== {} ===", name);
}
}
}
pub fn group_end(&self, name: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::endgroup::");
}
CiProvider::GitLabCi => {
let section_id = name.to_lowercase().replace(' ', "_");
println!(
"\x1b[0Ksection_end:{}:{}\r\x1b[0K",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
section_id
);
}
CiProvider::AzureDevOps => {
println!("##[endgroup]");
}
_ => {
}
}
}
pub fn group(&self, name: &str) -> GroupGuard {
self.group_start(name);
GroupGuard {
output: self.clone(),
name: name.to_string(),
}
}
pub fn warning(&self, message: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::warning::{}", message);
}
CiProvider::AzureDevOps => {
println!("##[warning]{}", message);
}
CiProvider::GitLabCi => {
println!("\x1b[33mWarning: {}\x1b[0m", message);
}
_ => {
eprintln!("Warning: {}", message);
}
}
}
pub fn warning_with_location(&self, message: &str, file: &str, line: Option<u32>) {
match self.provider {
CiProvider::GitHubActions => {
if let Some(line) = line {
println!("::warning file={},line={}::{}", file, line, message);
} else {
println!("::warning file={}::{}", file, message);
}
}
CiProvider::AzureDevOps => {
if let Some(line) = line {
println!(
"##vso[task.logissue type=warning;sourcepath={};linenumber={}]{}",
file, line, message
);
} else {
println!(
"##vso[task.logissue type=warning;sourcepath={}]{}",
file, message
);
}
}
_ => {
if let Some(line) = line {
eprintln!("Warning: {}:{}: {}", file, line, message);
} else {
eprintln!("Warning: {}: {}", file, message);
}
}
}
}
pub fn error(&self, message: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::error::{}", message);
}
CiProvider::AzureDevOps => {
println!("##[error]{}", message);
}
CiProvider::GitLabCi => {
println!("\x1b[31mError: {}\x1b[0m", message);
}
_ => {
eprintln!("Error: {}", message);
}
}
}
pub fn error_with_location(&self, message: &str, file: &str, line: Option<u32>) {
match self.provider {
CiProvider::GitHubActions => {
if let Some(line) = line {
println!("::error file={},line={}::{}", file, line, message);
} else {
println!("::error file={}::{}", file, message);
}
}
CiProvider::AzureDevOps => {
if let Some(line) = line {
println!(
"##vso[task.logissue type=error;sourcepath={};linenumber={}]{}",
file, line, message
);
} else {
println!(
"##vso[task.logissue type=error;sourcepath={}]{}",
file, message
);
}
}
_ => {
if let Some(line) = line {
eprintln!("Error: {}:{}: {}", file, line, message);
} else {
eprintln!("Error: {}: {}", file, message);
}
}
}
}
pub fn set_output(&self, name: &str, value: &str) {
match self.provider {
CiProvider::GitHubActions => {
if let Ok(output_file) = std::env::var("GITHUB_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_file)
{
let _ = writeln!(file, "{}={}", name, value);
}
} else {
println!("::set-output name={}::{}", name, value);
}
}
CiProvider::AzureDevOps => {
println!("##vso[task.setvariable variable={}]{}", name, value);
}
_ => {
}
}
}
pub fn mask_value(&self, value: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::add-mask::{}", value);
}
CiProvider::AzureDevOps => {
println!("##vso[task.setsecret]{}", value);
}
_ => {
}
}
}
pub fn debug(&self, message: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::debug::{}", message);
}
CiProvider::AzureDevOps => {
println!("##[debug]{}", message);
}
_ => {
}
}
}
pub fn notice(&self, message: &str) {
match self.provider {
CiProvider::GitHubActions => {
println!("::notice::{}", message);
}
_ => {
println!("Note: {}", message);
}
}
}
pub fn print(&self, message: &str) {
print!("{}", message);
let _ = io::stdout().flush();
}
pub fn println(&self, message: &str) {
println!("{}", message);
}
}
#[allow(dead_code)] pub struct GroupGuard {
output: CiOutput,
name: String,
}
impl Drop for GroupGuard {
fn drop(&mut self) {
self.output.group_end(&self.name);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ci_output_creation() {
let output = CiOutput::new(CiProvider::GitHubActions);
assert_eq!(output.provider(), CiProvider::GitHubActions);
}
#[test]
fn test_group_guard_creation() {
let output = CiOutput::new(CiProvider::Generic);
let _guard = output.group("test group");
}
}