use std::fmt;
use std::process::Command;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source {
GhLogin,
Environment,
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::GhLogin => "the GitHub CLI's login (gh auth token)",
Self::Environment => "GITHUB_TOKEN",
})
}
}
#[derive(Clone)]
pub struct Token {
value: String,
pub source: Source,
}
impl Token {
pub fn value(&self) -> &str {
&self.value
}
}
impl fmt::Debug for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Token").field("source", &self.source).finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoToken {
pub gh: String,
}
impl fmt::Display for NoToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"no GitHub token: {}, and GITHUB_TOKEN is not set. Log in with `gh auth login` \
(with the `project` scope for boards: `gh auth refresh -s project`), or set GITHUB_TOKEN",
self.gh
)
}
}
impl std::error::Error for NoToken {}
pub fn resolve() -> Result<Token, NoToken> {
let gh = match Command::new("gh").args(["auth", "token"]).output() {
Ok(out) if out.status.success() => {
let value = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !value.is_empty() {
return Ok(Token {
value,
source: Source::GhLogin,
});
}
"the GitHub CLI answered with no token".to_string()
}
Ok(out) => format!(
"the GitHub CLI has no login ({})",
String::from_utf8_lossy(&out.stderr).trim()
),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
"the GitHub CLI (gh) is not installed".to_string()
}
Err(error) => format!("the GitHub CLI could not be run ({error})"),
};
match std::env::var("GITHUB_TOKEN") {
Ok(value) if !value.trim().is_empty() => Ok(Token {
value: value.trim().to_string(),
source: Source::Environment,
}),
_ => Err(NoToken { gh }),
}
}