aboutsummaryrefslogtreecommitdiff
path: root/ticket
diff options
context:
space:
mode:
Diffstat (limited to 'ticket')
-rw-r--r--ticket/Cargo.toml6
-rw-r--r--ticket/rust-toolchain1
-rw-r--r--ticket/src/main.rs227
3 files changed, 228 insertions, 6 deletions
diff --git a/ticket/Cargo.toml b/ticket/Cargo.toml
index d811935..7d42f5f 100644
--- a/ticket/Cargo.toml
+++ b/ticket/Cargo.toml
@@ -7,6 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow = "*"
+colored = "*"
paw = "*"
+rustyline = "5.0"
+serde = { version = "1.0", features = ["derive"] }
structopt = { version = "*", features = ["paw"] }
-anyhow = "*"
+toml = "*"
diff --git a/ticket/rust-toolchain b/ticket/rust-toolchain
deleted file mode 100644
index 5edffce..0000000
--- a/ticket/rust-toolchain
+++ /dev/null
@@ -1 +0,0 @@
-1.39.0
diff --git a/ticket/src/main.rs b/ticket/src/main.rs
index bdd936e..8b9b542 100644
--- a/ticket/src/main.rs
+++ b/ticket/src/main.rs
@@ -1,21 +1,240 @@
-use anyhow::Result;
+use anyhow::{
+ bail,
+ Result,
+};
+use colored::*;
+use rustyline::{
+ error::ReadlineError,
+ Editor,
+};
+use serde::{
+ Deserialize,
+ Serialize,
+};
+use std::{
+ env,
+ fs,
+ path::PathBuf,
+ process,
+ process::Command,
+};
#[derive(structopt::StructOpt)]
enum Args {
/// Initialize the repo to use ticket
- Init,
+ Init {
+ #[structopt(default_value = ".", parse(from_os_str))]
+ location: PathBuf,
+ },
+ New,
+ Show {
+ id: usize,
+ },
+ Close {
+ id: usize,
+ },
}
#[paw::main]
fn main(args: Args) {
if let Err(e) = match args {
- Args::Init => init(),
+ Args::Init { location } => init(location),
+ Args::New => new(),
+ Args::Show { id } => show(id),
+ Args::Close { id } => close(id),
} {
eprintln!("{}", e);
std::process::exit(1);
}
}
-fn init() -> Result<()> {
+fn init(location: PathBuf) -> Result<()> {
+ if location.join(".git").is_dir() {
+ let root = location.join(".dev-suite").join("ticket");
+ fs::create_dir_all(&root)?;
+ fs::create_dir_all(&root.join("open"))?;
+ fs::create_dir_all(&root.join("closed"))?;
+ Ok(())
+ } else {
+ bail!(
+ "{} is not a valid git repository",
+ location.canonicalize()?.display()
+ )
+ }
+}
+
+fn new() -> Result<()> {
+ let ticket_root = ticket_root()?;
+ let open = ticket_root.join("open");
+ let closed = ticket_root.join("closed");
+ let description = ticket_root.join("description");
+ let mut ticket_num = 1;
+
+ // Fast enough for now but maybe not in the future
+ for entry in fs::read_dir(&open)?.chain(fs::read_dir(&closed)?) {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_file() {
+ ticket_num += 1;
+ }
+ }
+
+ let mut rl = Editor::<()>::new();
+ let title = match rl.readline("Title: ") {
+ Ok(line) => {
+ if line.is_empty() {
+ bail!("Title may not be empty");
+ }
+ line
+ }
+ Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
+ process::exit(0);
+ }
+ Err(e) => return Err(e.into()),
+ };
+
+ fs::File::create(&description)?;
+ Command::new(&env::var("EDITOR").unwrap_or_else(|_| "vi".into()))
+ .arg(&description)
+ .spawn()?
+ .wait()?;
+ let description_contents = fs::read_to_string(&description)?;
+ fs::remove_file(&description)?;
+
+ let t = Ticket {
+ title,
+ status: Status::Open,
+ number: ticket_num,
+ assignee: None,
+ description: description_contents,
+ };
+
+ fs::write(
+ open.join(&format!(
+ "{}-{}.toml",
+ ticket_num,
+ t.title
+ .to_lowercase()
+ .split_whitespace()
+ .collect::<Vec<&str>>()
+ .join("-")
+ )),
+ toml::to_string_pretty(&t)?,
+ )?;
+
Ok(())
}
+
+fn find_root() -> Result<PathBuf> {
+ let mut location = env::current_dir()?;
+ let mut found_root = false;
+
+ for loc in location.ancestors() {
+ let loc = loc.join(".dev-suite");
+ if loc.exists() {
+ found_root = true;
+ location = loc.canonicalize()?;
+ break;
+ }
+ }
+
+ if found_root {
+ Ok(location)
+ } else {
+ bail!("Unable to find a dev-suite enabled repo");
+ }
+}
+
+fn ticket_root() -> Result<PathBuf> {
+ Ok(find_root()?.join("ticket"))
+}
+
+fn show(id: usize) -> Result<()> {
+ let ticket_root = ticket_root()?;
+ let open = ticket_root.join("open");
+ let closed = ticket_root.join("closed");
+ let mut found = false;
+
+ // Fast enough for now but maybe not in the future
+ for entry in fs::read_dir(&open)?.chain(fs::read_dir(&closed)?) {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
+ if file_name.starts_with(&id.to_string()) {
+ let ticket = toml::from_slice::<Ticket>(&fs::read(&path)?)?;
+ println!(
+ "{}",
+ format!("{} - {}\n", ticket.number, ticket.title)
+ .bold()
+ .red()
+ );
+ if let Some(a) = ticket.assignee {
+ println!("{}{}", "Assignee: ".bold().purple(), a);
+ }
+
+ print!(
+ "{}{}\n\n{}",
+ "Status: ".bold().purple(),
+ match ticket.status {
+ Status::Open => "Open".bold().green(),
+ Status::Closed => "Closed".bold().red(),
+ },
+ ticket.description
+ );
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ if found {
+ Ok(())
+ } else {
+ bail!("No ticket with id {} exists", id);
+ }
+}
+
+fn close(id: usize) -> Result<()> {
+ let ticket_root = ticket_root()?;
+ let open = ticket_root.join("open");
+ let closed = ticket_root.join("closed");
+ let mut found = false;
+ // Fast enough for now but maybe not in the future
+ for entry in fs::read_dir(&open)? {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
+ if file_name.starts_with(&id.to_string()) {
+ let mut ticket = toml::from_slice::<Ticket>(&fs::read(&path)?)?;
+ ticket.status = Status::Closed;
+ fs::write(closed.join(file_name), toml::to_string_pretty(&ticket)?)?;
+ fs::remove_file(&path)?;
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ if found {
+ Ok(())
+ } else {
+ bail!("No ticket with id {} exists", id);
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+struct Ticket {
+ title: String,
+ status: Status,
+ number: usize,
+ assignee: Option<String>,
+ description: String,
+}
+
+#[derive(Serialize, Deserialize)]
+enum Status {
+ Open,
+ Closed,
+}