use clap::{Args, Subcommand};
use serde::Serialize;
use super::CmdResult;
use homeboy::changelog::{self, AddItemsOutput, InitOutput, ShowOutput};
#[derive(Args)]
pub struct ChangelogArgs {
#[arg(long = "self")]
pub show_self: bool,
#[command(subcommand)]
pub command: Option<ChangelogCommand>,
}
#[derive(Subcommand)]
pub enum ChangelogCommand {
Show {
component_id: Option<String>,
},
#[command(after_long_help = "\
EXAMPLES:
Add a simple entry:
homeboy changelog add my-plugin \"Fixed login bug\"
Add with a type (Added, Changed, Removed, Fixed, etc.):
homeboy changelog add my-plugin \"Removed legacy API\" --type Removed
Add multiple entries at once:
homeboy changelog add my-plugin -m \"Added search\" -m \"Added filters\"
Add with type and multiple messages:
homeboy changelog add my-plugin -m \"New auth flow\" -m \"New API keys\" --type Added
")]
Add {
#[arg(long)]
json: Option<String>,
#[arg(index = 1)]
component_id: Option<String>,
#[arg(index = 2)]
positional_message: Option<String>,
#[arg(short = 'm', long = "message", action = clap::ArgAction::Append)]
messages: Vec<String>,
#[arg(short = 't', long = "type")]
entry_type: Option<String>,
},
Init {
#[arg(long)]
path: Option<String>,
#[arg(long)]
configure: bool,
component_id: Option<String>,
},
}
#[derive(Serialize)]
pub struct ChangelogShowOutput {
pub topic_label: String,
pub content: String,
}
#[derive(Serialize)]
#[serde(tag = "command")]
pub enum ChangelogOutput {
Show(ChangelogShowOutput),
ShowComponent(ShowOutput),
Add(AddItemsOutput),
Init(InitOutput),
}
pub fn run_markdown(args: ChangelogArgs) -> CmdResult<String> {
match (&args.command, args.show_self) {
(None, true) => show_homeboy_markdown(),
(Some(ChangelogCommand::Show { component_id: None }), _) => show_homeboy_markdown(),
(Some(ChangelogCommand::Show { component_id: Some(id) }), _) => {
let output = changelog::show(id)?;
Ok((output.content, 0))
}
(None, false) => Err(homeboy::Error::validation_invalid_argument(
"command",
"No subcommand provided. Use a subcommand (add, init, show) or --self to view Homeboy's changelog",
None,
Some(vec![
"homeboy changelog add <component_id> <message>".to_string(),
"homeboy changelog init <component_id>".to_string(),
"homeboy changelog show".to_string(),
"homeboy changelog show <component_id>".to_string(),
]),
)),
(Some(ChangelogCommand::Add { .. }) | Some(ChangelogCommand::Init { .. }), _) => {
Err(homeboy::Error::validation_invalid_argument(
"command",
"Markdown output is only supported for 'changelog show'",
None,
None,
))
}
}
}
pub fn is_show_markdown(args: &ChangelogArgs) -> bool {
matches!(args.command, Some(ChangelogCommand::Show { .. }))
|| (args.command.is_none() && args.show_self)
}
pub fn run(
args: ChangelogArgs,
_global: &crate::commands::GlobalArgs,
) -> CmdResult<ChangelogOutput> {
match (&args.command, args.show_self) {
(None, true) => {
let (out, code) = show_homeboy_json()?;
Ok((ChangelogOutput::Show(out), code))
}
(Some(ChangelogCommand::Show { component_id: None }), _) => {
let (out, code) = show_homeboy_json()?;
Ok((ChangelogOutput::Show(out), code))
}
(Some(ChangelogCommand::Show { component_id: Some(id) }), _) => {
let output = changelog::show(id)?;
Ok((ChangelogOutput::ShowComponent(output), 0))
}
(None, false) => Err(homeboy::Error::validation_invalid_argument(
"command",
"No subcommand provided. Use a subcommand (add, init, show) or --self to view Homeboy's changelog",
None,
Some(vec![
"homeboy changelog add <component_id> <message>".to_string(),
"homeboy changelog init <component_id>".to_string(),
"homeboy changelog show".to_string(),
"homeboy changelog show <component_id>".to_string(),
]),
)),
(Some(ChangelogCommand::Add {
json,
component_id,
positional_message,
messages,
entry_type,
}), _) => {
let mut all_messages: Vec<String> = Vec::new();
if let Some(msg) = positional_message {
all_messages.push(msg.clone());
}
all_messages.extend(messages.iter().cloned());
if let Some(spec) = json.as_deref() {
let output = changelog::add_items_bulk(spec)?;
return Ok((ChangelogOutput::Add(output), 0));
}
let output = changelog::add_items(component_id.as_deref(), &all_messages, entry_type.as_deref())?;
Ok((ChangelogOutput::Add(output), 0))
}
(Some(ChangelogCommand::Init {
path,
configure,
component_id,
}), _) => {
let id = component_id.as_ref().ok_or_else(|| {
homeboy::Error::validation_invalid_argument(
"componentId",
"Missing componentId",
None,
Some(vec![
"Provide a component ID: homeboy changelog init <component-id>".to_string(),
"List available components: homeboy component list".to_string(),
]),
)
})?;
let output = changelog::init(id, path.as_deref(), *configure)?;
Ok((ChangelogOutput::Init(output), 0))
}
}
}
const HOMEBOY_CHANGELOG: &str = include_str!("../../docs/changelog.md");
fn show_homeboy_markdown() -> CmdResult<String> {
Ok((HOMEBOY_CHANGELOG.to_string(), 0))
}
fn show_homeboy_json() -> CmdResult<ChangelogShowOutput> {
Ok((
ChangelogShowOutput {
topic_label: "changelog".to_string(),
content: HOMEBOY_CHANGELOG.to_string(),
},
0,
))
}