aboutsummaryrefslogtreecommitdiff
path: root/ticket
diff options
context:
space:
mode:
authorMichael Gattozzi <mgattozzi@gmail.com>2019-11-18 18:48:56 -0500
committerGitHub <noreply@github.com>2019-11-18 18:48:56 -0500
commitb5dcf420c98a93dc2aa5f5f2d9a0ef028d921e80 (patch)
treed7b92e72b4b1d805b83f7c7788ba4b2087f89d6e /ticket
parent0ad852aa4a2bf81ce7367c150835893dac0cea02 (diff)
Add ticket functionality to dev-suite (#3)
This adds the ability to open new tickets, close them, and show them from the commandline. This functionality is enough to get started adding more tickets to the repo from here on out and work on new tools with tickets associated with them.
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,
+}