use clap::{Args, Parser, Subcommand, ValueEnum};
use kanban_core::CLI_VERSION_DISPLAY;
#[derive(Parser)]
#[command(name = "kanban")]
#[command(about = "A terminal-based kanban board", long_about = None)]
#[command(version = CLI_VERSION_DISPLAY, arg_required_else_help = false)]
pub struct Cli {
#[arg(value_name = "FILE", env = "KANBAN_FILE")]
pub file: Option<String>,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand)]
pub enum Commands {
Board(BoardCommand),
Column(ColumnCommand),
Card(CardCommand),
Relation(RelationCommand),
Sprint(SprintCommand),
Export(ExportArgs),
Import(ImportArgs),
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Migrate(MigrateArgs),
Init {
#[arg(long)]
board: Option<String>,
},
}
#[derive(Args)]
pub struct BoardCommand {
#[command(subcommand)]
pub action: BoardAction,
}
#[derive(Subcommand)]
pub enum BoardAction {
Create {
#[arg(long)]
name: String,
#[arg(long)]
card_prefix: Option<String>,
},
List {
#[arg(long)]
page: Option<u32>,
#[arg(long)]
page_size: Option<u32>,
},
Get {
board: String,
},
Update(BoardUpdateArgs),
Delete {
board: String,
},
}
#[derive(Args)]
pub struct BoardUpdateArgs {
pub board: String,
#[arg(long)]
pub name: Option<String>,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub sprint_prefix: Option<String>,
#[arg(long)]
pub card_prefix: Option<String>,
#[arg(long, value_enum)]
pub sort_field: Option<SortKey>,
#[arg(long, value_enum)]
pub sort_order: Option<SortDir>,
}
#[derive(Args)]
pub struct ColumnCommand {
#[command(subcommand)]
pub action: ColumnAction,
}
#[derive(Subcommand)]
pub enum ColumnAction {
Create {
#[arg(long)]
board: String,
#[arg(long)]
name: String,
#[arg(long)]
position: Option<i32>,
},
List {
#[arg(long)]
board: String,
#[arg(long)]
page: Option<u32>,
#[arg(long)]
page_size: Option<u32>,
},
Get {
column: String,
},
Update(ColumnUpdateArgs),
Delete {
column: String,
},
Reorder {
column: String,
#[arg(long)]
position: i32,
},
}
#[derive(Args)]
pub struct ColumnUpdateArgs {
pub column: String,
#[arg(long)]
pub name: Option<String>,
#[arg(long)]
pub position: Option<i32>,
#[arg(long)]
pub wip_limit: Option<u32>,
#[arg(long)]
pub clear_wip_limit: bool,
}
#[derive(Args)]
pub struct CardCommand {
#[command(subcommand)]
pub action: CardAction,
}
#[derive(Subcommand)]
pub enum CardAction {
Create(CardCreateArgs),
List(CardListArgs),
Get {
card: String,
},
Update(CardUpdateArgs),
Move {
card: String,
#[arg(long)]
column: String,
#[arg(long)]
position: Option<i32>,
},
Archive {
card: String,
},
Restore {
card: String,
#[arg(long)]
column: Option<String>,
},
Delete {
card: String,
},
AssignSprint {
card: String,
#[arg(long)]
sprint: String,
},
UnassignSprint {
card: String,
},
BranchName {
card: String,
},
GitCheckout {
card: String,
},
#[command(name = "archive-cards")]
ArchiveCards {
#[arg(long, value_delimiter = ',')]
cards: Vec<String>,
},
#[command(name = "move-cards")]
MoveCards {
#[arg(long, value_delimiter = ',')]
cards: Vec<String>,
#[arg(long)]
column: String,
},
#[command(name = "assign-cards-to-sprint")]
AssignCardsToSprint {
#[arg(long, value_delimiter = ',')]
cards: Vec<String>,
#[arg(long)]
sprint: String,
},
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum SortKey {
CardNumber,
Priority,
Points,
CreatedAt,
UpdatedAt,
DueDate,
Status,
Position,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum SortDir {
Asc,
Desc,
}
impl SortKey {
pub fn to_sort_by(self) -> kanban_domain::sort::SortBy {
use kanban_domain::sort::SortBy;
match self {
SortKey::CardNumber => SortBy::CardNumber,
SortKey::Priority => SortBy::Priority,
SortKey::Points => SortBy::Points,
SortKey::CreatedAt => SortBy::CreatedAt,
SortKey::UpdatedAt => SortBy::UpdatedAt,
SortKey::DueDate => SortBy::DueDate,
SortKey::Status => SortBy::Status,
SortKey::Position => SortBy::Position,
}
}
pub fn to_sort_field(self) -> kanban_domain::SortField {
use kanban_domain::SortField;
match self {
SortKey::CardNumber => SortField::Default,
SortKey::Priority => SortField::Priority,
SortKey::Points => SortField::Points,
SortKey::CreatedAt => SortField::CreatedAt,
SortKey::UpdatedAt => SortField::UpdatedAt,
SortKey::DueDate => SortField::DueDate,
SortKey::Status => SortField::Status,
SortKey::Position => SortField::Position,
}
}
}
impl SortDir {
pub fn to_sort_order(self) -> kanban_domain::SortOrder {
match self {
SortDir::Asc => kanban_domain::SortOrder::Ascending,
SortDir::Desc => kanban_domain::SortOrder::Descending,
}
}
}
#[cfg(test)]
mod sort_key_tests {
use super::*;
use kanban_domain::sort::SortBy;
use kanban_domain::SortField;
#[test]
fn test_sort_key_due_date_maps_to_sort_by_due_date() {
assert!(matches!(SortKey::DueDate.to_sort_by(), SortBy::DueDate));
}
#[test]
fn test_sort_key_due_date_maps_to_sort_field_due_date() {
assert_eq!(SortKey::DueDate.to_sort_field(), SortField::DueDate);
}
}
#[derive(Args)]
pub struct RelationCommand {
#[command(subcommand)]
pub action: RelationAction,
}
#[derive(Subcommand)]
pub enum RelationAction {
Add {
parent: String,
#[arg(required = true, num_args = 1..)]
children: Vec<String>,
},
Remove {
parent: String,
#[arg(required = true, num_args = 1..)]
children: Vec<String>,
},
Parents {
card: String,
#[arg(long, value_enum, default_value_t = SortKey::CardNumber)]
sort: SortKey,
#[arg(long, value_enum, default_value_t = SortDir::Asc)]
order: SortDir,
},
Children {
card: String,
#[arg(long, value_enum, default_value_t = SortKey::CardNumber)]
sort: SortKey,
#[arg(long, value_enum, default_value_t = SortDir::Asc)]
order: SortDir,
},
}
#[derive(Args)]
pub struct CardCreateArgs {
#[arg(long)]
pub board: String,
#[arg(long)]
pub column: String,
#[arg(long)]
pub title: String,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub priority: Option<String>,
#[arg(long)]
pub points: Option<u8>,
#[arg(long)]
pub due_date: Option<String>,
#[arg(long = "assign", short = 'a', num_args = 0..=1, default_missing_value = "")]
pub assign_sprint: Option<String>,
}
#[derive(Args)]
pub struct CardListArgs {
#[arg(long)]
pub board: Option<String>,
#[arg(long)]
pub column: Option<String>,
#[arg(long)]
pub sprint: Option<String>,
#[arg(long)]
pub status: Option<String>,
#[arg(long)]
pub archived: bool,
#[arg(long, value_enum)]
pub sort: Option<SortKey>,
#[arg(long, value_enum)]
pub order: Option<SortDir>,
#[arg(long)]
pub page: Option<u32>,
#[arg(long)]
pub page_size: Option<u32>,
}
#[derive(Args)]
pub struct CardUpdateArgs {
pub card: String,
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub priority: Option<String>,
#[arg(long)]
pub status: Option<String>,
#[arg(long)]
pub points: Option<u8>,
#[arg(long)]
pub due_date: Option<String>,
#[arg(long)]
pub clear_due_date: bool,
}
#[derive(Args)]
pub struct SprintCommand {
#[command(subcommand)]
pub action: SprintAction,
}
#[derive(Subcommand)]
pub enum SprintAction {
Create {
#[arg(long)]
board: String,
#[arg(long)]
prefix: Option<String>,
#[arg(long)]
name: Option<String>,
},
List {
#[arg(long)]
board: String,
#[arg(long)]
page: Option<u32>,
#[arg(long)]
page_size: Option<u32>,
},
Get {
sprint: String,
},
Update(SprintUpdateArgs),
Activate {
sprint: String,
#[arg(long)]
duration_days: Option<i32>,
},
Complete {
sprint: String,
},
Cancel {
sprint: String,
},
Delete {
sprint: String,
},
CarryOver {
#[arg(long)]
from: String,
#[arg(long)]
to: String,
},
}
#[derive(Args)]
pub struct SprintUpdateArgs {
pub sprint: String,
#[arg(long)]
pub name: Option<String>,
#[arg(long)]
pub prefix: Option<String>,
#[arg(long)]
pub card_prefix: Option<String>,
#[arg(long)]
pub start_date: Option<String>,
#[arg(long)]
pub end_date: Option<String>,
#[arg(long)]
pub clear_start_date: bool,
#[arg(long)]
pub clear_end_date: bool,
}
#[derive(Args)]
#[command(after_help = "EXAMPLES:
kanban migrate boards.json sqlite
kanban migrate boards.json sqlite -o /path/to/output.sqlite
kanban migrate boards.sqlite json -o boards.json
kanban migrate data.bin json --source-backend sqlite")]
pub struct MigrateArgs {
pub source: String,
pub backend: String,
#[arg(long, short)]
pub output: Option<String>,
#[arg(long)]
pub source_backend: Option<String>,
}
#[derive(Args)]
pub struct ExportArgs {
#[arg(long)]
pub board: Option<String>,
}
#[derive(Args)]
pub struct ImportArgs {
#[arg(long)]
pub file: String,
}