228 lines
7.6 KiB
Rust
228 lines
7.6 KiB
Rust
use clap::Parser;
|
|
|
|
use entomologist::issue::State;
|
|
#[cfg(feature = "log")]
|
|
use simple_logger;
|
|
|
|
#[derive(Debug, clap::Parser)]
|
|
#[command(version, about, long_about = None)]
|
|
struct Args {
|
|
/// Directory containing issues.
|
|
#[arg(short = 'd', long)]
|
|
issues_dir: Option<String>,
|
|
|
|
/// Branch containing issues.
|
|
#[arg(short = 'b', long)]
|
|
issues_branch: Option<String>,
|
|
|
|
/// Type of behavior/output.
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Commands {
|
|
/// List issues.
|
|
List {
|
|
/// Filter string, describes issues to include in the list.
|
|
#[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))]
|
|
filter: String,
|
|
},
|
|
|
|
/// Create a new issue.
|
|
New { description: Option<String> },
|
|
|
|
/// Edit the description of an issue.
|
|
Edit { issue_id: String },
|
|
|
|
/// Show the full description of an issue.
|
|
Show { issue_id: String },
|
|
|
|
/// Modify the state of an issue
|
|
State {
|
|
issue_id: String,
|
|
new_state: Option<State>,
|
|
},
|
|
|
|
/// Create a new comment on an issue.
|
|
Comment {
|
|
issue_id: String,
|
|
description: Option<String>,
|
|
},
|
|
|
|
/// Sync entomologist data with remote. This fetches from the remote,
|
|
/// merges the remote entomologist data branch with the local one,
|
|
/// and pushes the result back to the remote.
|
|
Sync {
|
|
/// Name of the git remote to sync with.
|
|
#[arg(default_value_t = String::from("origin"))]
|
|
remote: String,
|
|
},
|
|
}
|
|
|
|
fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> {
|
|
match &args.command {
|
|
Commands::List { filter } => {
|
|
let issues =
|
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
|
let filter = entomologist::parse_filter(filter)?;
|
|
for (uuid, issue) in issues.issues.iter() {
|
|
if filter.include_states.contains(&issue.state) {
|
|
println!("{} {} ({:?})", uuid, issue.title(), issue.state);
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::New {
|
|
description: Some(description),
|
|
} => {
|
|
let mut issue = entomologist::issue::Issue::new(issues_dir)?;
|
|
issue.set_description(description)?;
|
|
println!("created new issue '{}'", issue.title());
|
|
}
|
|
|
|
Commands::New { description: None } => {
|
|
let mut issue = entomologist::issue::Issue::new(issues_dir)?;
|
|
issue.edit_description()?;
|
|
println!("created new issue '{}'", issue.title());
|
|
}
|
|
|
|
Commands::Edit { issue_id } => {
|
|
let mut issues =
|
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
|
match issues.get_mut_issue(issue_id) {
|
|
Some(issue) => {
|
|
issue.edit_description()?;
|
|
}
|
|
None => {
|
|
return Err(anyhow::anyhow!("issue {} not found", issue_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Show { issue_id } => {
|
|
let issues =
|
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
|
match issues.get_issue(issue_id) {
|
|
Some(issue) => {
|
|
println!("issue {}", issue_id);
|
|
println!("state: {:?}", issue.state);
|
|
if let Some(dependencies) = &issue.dependencies {
|
|
println!("dependencies: {:?}", dependencies);
|
|
}
|
|
println!("");
|
|
println!("{}", issue.description);
|
|
for comment in &issue.comments {
|
|
println!("");
|
|
println!("comment: {}", comment.uuid);
|
|
println!("author: {}", comment.author);
|
|
println!("timestamp: {}", comment.timestamp);
|
|
println!("{}", comment.description);
|
|
}
|
|
}
|
|
None => {
|
|
return Err(anyhow::anyhow!("issue {} not found", issue_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::State {
|
|
issue_id,
|
|
new_state,
|
|
} => {
|
|
let mut issues =
|
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
|
match issues.issues.get_mut(issue_id) {
|
|
Some(issue) => {
|
|
let current_state = issue.state.clone();
|
|
match new_state {
|
|
Some(s) => {
|
|
issue.set_state(s.clone())?;
|
|
println!("issue: {}", issue_id);
|
|
println!("state: {} -> {}", current_state, s);
|
|
}
|
|
None => {
|
|
println!("issue: {}", issue_id);
|
|
println!("state: {}", current_state);
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
return Err(anyhow::anyhow!("issue {} not found", issue_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Comment {
|
|
issue_id,
|
|
description,
|
|
} => {
|
|
let mut issues =
|
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
|
let Some(issue) = issues.get_mut_issue(issue_id) else {
|
|
return Err(anyhow::anyhow!("issue {} not found", issue_id));
|
|
};
|
|
let mut comment = issue.new_comment()?;
|
|
match description {
|
|
Some(description) => {
|
|
comment.set_description(description)?;
|
|
}
|
|
None => {
|
|
comment.edit_description()?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Sync { remote } => {
|
|
if args.issues_dir.is_some() {
|
|
return Err(anyhow::anyhow!(
|
|
"`sync` operates on a branch, don't specify `issues_dir`"
|
|
));
|
|
}
|
|
// FIXME: Kinda bogus to re-do this thing we just did in
|
|
// `main()`. Maybe `main()` shouldn't create the worktree,
|
|
// maybe we should do it here in `handle_command()`?
|
|
// That way also each command could decide if it wants a
|
|
// read-only worktree or a read/write one.
|
|
let branch = match &args.issues_branch {
|
|
Some(branch) => branch,
|
|
None => "entomologist-data",
|
|
};
|
|
entomologist::git::sync(issues_dir, remote, branch)?;
|
|
println!("synced {:?} with {:?}", branch, remote);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
#[cfg(feature = "log")]
|
|
simple_logger::SimpleLogger::new().env().init().unwrap();
|
|
|
|
let args: Args = Args::parse();
|
|
// println!("{:?}", args);
|
|
|
|
if let (Some(_), Some(_)) = (&args.issues_dir, &args.issues_branch) {
|
|
return Err(anyhow::anyhow!(
|
|
"don't specify both `--issues-dir` and `--issues-branch`"
|
|
));
|
|
}
|
|
|
|
if let Some(dir) = &args.issues_dir {
|
|
let dir = std::path::Path::new(dir);
|
|
handle_command(&args, dir)?;
|
|
} else {
|
|
let branch = match &args.issues_branch {
|
|
Some(branch) => branch,
|
|
None => "entomologist-data",
|
|
};
|
|
if !entomologist::git::git_branch_exists(branch)? {
|
|
entomologist::git::create_orphan_branch(branch)?;
|
|
}
|
|
let worktree = entomologist::git::Worktree::new(branch)?;
|
|
handle_command(&args, worktree.path())?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|