use anyhow::{Context, Result};
use clap::Parser;
use serde::Deserialize;
use git2::Repository;
use git_url_parse::GitUrl;
pub const DEFAULT_ANNOTATION_LABELS: [&str; 2] = ["TODO", "FIXME"];
pub const DEFAULT_TITLE_FORMAT: &str = "[Postpone] {label}: {line}";
pub const DEFAULT_BODY_FORMAT: &str = r#"
This issue was automatically generated by [Postpone bot](https://github.com/n01e0/ppb).
{label}
{file}:{line_number}
```
{line}
```
"#;
pub const PPB_ISSUE_LABEL: &str = "postpone";
#[derive(Debug, Parser)]
#[clap(author, version, about, long_about=None)]
pub struct Args {
#[clap(short, long)]
pub config: Option<String>,
pub host: Option<String>,
#[clap(long = "organization")]
pub organization: Option<String>,
#[clap(long = "repository")]
pub repository: Option<String>,
#[clap(long = "token")]
pub token: Option<String>,
#[clap(long="annotation-labels", value_parser, num_args = 1.., value_delimiter = ',', default_value="TODO,FIXME")]
pub annotation_labels: Option<Vec<String>>,
#[clap(long="title-format", default_value=DEFAULT_TITLE_FORMAT)]
pub title_format: Option<String>,
#[clap(long="body-format", default_value=DEFAULT_BODY_FORMAT)]
pub body_format: Option<String>,
#[clap(long = "dry-run")]
pub dry_run: bool,
#[clap(long = "ignore-file", value_parser, num_args = 1.., value_delimiter = ',')]
pub ignore_file: Option<Vec<String>>,
#[clap(long = "target-dir", default_value = ".")]
pub target_dir: String,
}
#[derive(Debug, Deserialize)]
pub struct ConfigFile {
host: Option<String>,
organization: Option<String>,
repository: Option<String>,
token: Option<String>,
pub annotation_labels: Option<Vec<String>>,
pub title_format: Option<String>,
pub body_format: Option<String>,
pub ignore_file: Option<Vec<String>>,
pub target_dir: Option<String>,
}
#[derive(Debug)]
pub struct Config {
pub host: String,
pub organization: String,
pub repository: String,
pub token: String,
pub annotation_labels: Vec<String>,
pub title_format: String,
pub body_format: String,
pub ignore_file: Vec<String>,
pub target_dir: String,
}
impl Config {
pub fn new(args: &Args) -> Result<Self> {
let remote: Option<GitUrl> = if let Ok(repo) = Repository::open(".") {
if let Ok(origin) = repo.find_remote("origin") {
origin.url().and_then(|url| GitUrl::parse(url).ok())
} else {
None
}
} else {
None
};
match args.config {
Some(ref config_file) => {
let config_file: ConfigFile = serde_yaml::from_str(&std::fs::read_to_string(config_file)?)?;
Ok(Config {
host: config_file
.host
.or(args.host.clone())
.or(remote.as_ref().map(|url| url.host.clone()).flatten())
.unwrap_or(String::from("github.com")),
organization: config_file
.organization
.or(args.organization.clone())
.or(remote.as_ref().map(|url| url.owner.clone()).flatten())
.with_context(|| "organization must be set")?,
repository: config_file
.repository
.or(args.repository.clone())
.or(remote.as_ref().map(|url| url.name.clone()))
.with_context(|| "repository must be set")?,
token: config_file
.token
.or(args.token.clone())
.or(remote.map(|url| url.token).flatten())
.with_context(|| "token must be set")?,
annotation_labels: config_file
.annotation_labels
.or(args.annotation_labels.clone())
.unwrap_or_else(|| {
DEFAULT_ANNOTATION_LABELS
.iter()
.map(|s| s.to_string())
.collect()
}),
title_format: config_file
.title_format
.or(args.title_format.clone())
.unwrap_or_else(|| DEFAULT_TITLE_FORMAT.to_string()),
body_format: config_file
.body_format
.or(args.title_format.clone())
.unwrap_or_else(|| DEFAULT_BODY_FORMAT.to_string()),
ignore_file: config_file
.ignore_file
.or(args.ignore_file.clone())
.unwrap_or(vec![]),
target_dir: config_file.target_dir.unwrap_or(args.target_dir.clone()),
})
}
None => Ok(Config {
host: args
.host
.clone()
.or(remote.as_ref().map(|url| url.host.clone()).flatten())
.with_context(|| "remote host must be set")?,
organization: args
.organization
.clone()
.or(remote.as_ref().map(|url| url.owner.clone()).flatten())
.with_context(|| "organization must be set")?,
repository: args
.repository
.clone()
.or(remote.as_ref().map(|url| url.name.clone()))
.with_context(|| "repository must be set")?,
token: args
.token
.clone()
.or(remote.map(|url| url.token).flatten())
.with_context(|| "token must be set")?,
annotation_labels: args.annotation_labels.clone().unwrap_or_else(|| {
DEFAULT_ANNOTATION_LABELS
.iter()
.map(|s| s.to_string())
.collect()
}),
title_format: args
.title_format
.clone()
.unwrap_or_else(|| DEFAULT_TITLE_FORMAT.to_string()),
body_format: args
.body_format
.clone()
.unwrap_or_else(|| DEFAULT_BODY_FORMAT.to_string()),
ignore_file: args.ignore_file.clone().unwrap_or_else(|| vec![]),
target_dir: args.target_dir.clone(),
}),
}
}
}