Compare commits
No commits in common. "3f2d3b1520a49b50a75b11df0891d771530f69fd" and "48e74082edf17b54afa40cf45bc883cdb8aa0d53" have entirely different histories.
3f2d3b1520
...
48e74082ed
4 changed files with 23 additions and 188 deletions
|
|
@ -22,13 +22,10 @@ enum Commands {
|
||||||
List,
|
List,
|
||||||
|
|
||||||
/// Create a new issue.
|
/// Create a new issue.
|
||||||
New { description: Option<String> },
|
New {
|
||||||
|
title: Option<String>,
|
||||||
/// Edit the description of an issue.
|
description: Option<String>,
|
||||||
Edit { issue_id: String },
|
},
|
||||||
|
|
||||||
/// Show the full description of an issue.
|
|
||||||
Show { issue_id: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> {
|
fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> {
|
||||||
|
|
@ -40,44 +37,11 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<(
|
||||||
println!("{} {} ({:?})", uuid, issue.title(), issue.state);
|
println!("{} {} ({:?})", uuid, issue.title(), issue.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::New {
|
Commands::New { title, description } => {
|
||||||
description: Some(description),
|
println!(
|
||||||
} => {
|
"should make a new issue, title={:?}, description={:?}",
|
||||||
let mut issue = entomologist::issue::Issue::new(issues_dir)?;
|
title, description
|
||||||
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.issues.get_mut(issue_id) {
|
|
||||||
Some(issue) => {
|
|
||||||
issue.edit_description()?;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("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.issues.get(issue_id) {
|
|
||||||
Some(issue) => {
|
|
||||||
println!("issue {}", issue_id);
|
|
||||||
println!("state {:?}", issue.state);
|
|
||||||
println!("");
|
|
||||||
println!("{}", issue.description);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("issue {} not found", issue_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
35
src/git.rs
35
src/git.rs
|
|
@ -89,41 +89,6 @@ pub fn git_branch_exists(branch: &str) -> Result<bool, GitError> {
|
||||||
return Ok(result.status.success());
|
return Ok(result.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> {
|
|
||||||
let mut git_dir = std::path::PathBuf::from(file);
|
|
||||||
git_dir.pop();
|
|
||||||
|
|
||||||
let result = std::process::Command::new("git")
|
|
||||||
.args(["add", &file.file_name().unwrap().to_string_lossy()])
|
|
||||||
.current_dir(&git_dir)
|
|
||||||
.output()?;
|
|
||||||
if !result.status.success() {
|
|
||||||
println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap());
|
|
||||||
println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap());
|
|
||||||
return Err(GitError::Oops);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = std::process::Command::new("git")
|
|
||||||
.args([
|
|
||||||
"commit",
|
|
||||||
"-m",
|
|
||||||
&format!(
|
|
||||||
"update '{}' in issue {}",
|
|
||||||
file.file_name().unwrap().to_string_lossy(),
|
|
||||||
git_dir.file_name().unwrap().to_string_lossy()
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.current_dir(&git_dir)
|
|
||||||
.output()?;
|
|
||||||
if !result.status.success() {
|
|
||||||
println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap());
|
|
||||||
println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap());
|
|
||||||
return Err(GitError::Oops);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> {
|
pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> {
|
||||||
{
|
{
|
||||||
let tmp_worktree = tempfile::tempdir().unwrap();
|
let tmp_worktree = tempfile::tempdir().unwrap();
|
||||||
|
|
|
||||||
69
src/issue.rs
69
src/issue.rs
|
|
@ -1,4 +1,3 @@
|
||||||
use std::io::Write;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, serde::Deserialize)]
|
#[derive(Debug, PartialEq, serde::Deserialize)]
|
||||||
|
|
@ -19,26 +18,18 @@ pub struct Issue {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub state: State,
|
pub state: State,
|
||||||
pub dependencies: Option<Vec<IssueHandle>>,
|
pub dependencies: Option<Vec<IssueHandle>>,
|
||||||
|
|
||||||
/// This is the directory that the issue lives in. Only used
|
|
||||||
/// internally by the entomologist library.
|
|
||||||
pub dir: std::path::PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IssueError {
|
pub enum ReadIssueError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
StdIoError(#[from] std::io::Error),
|
StdIoError(#[from] std::io::Error),
|
||||||
#[error("Failed to parse issue")]
|
#[error("Failed to parse issue")]
|
||||||
IssueParseError,
|
IssueParseError,
|
||||||
#[error("Failed to run git")]
|
|
||||||
GitError(#[from] crate::git::GitError),
|
|
||||||
#[error("Failed to run editor")]
|
|
||||||
EditorError,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for State {
|
impl FromStr for State {
|
||||||
type Err = IssueError;
|
type Err = ReadIssueError;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let s = s.to_lowercase();
|
let s = s.to_lowercase();
|
||||||
if s == "new" {
|
if s == "new" {
|
||||||
|
|
@ -54,13 +45,13 @@ impl FromStr for State {
|
||||||
} else if s == "wontdo" {
|
} else if s == "wontdo" {
|
||||||
Ok(State::WontDo)
|
Ok(State::WontDo)
|
||||||
} else {
|
} else {
|
||||||
Err(IssueError::IssueParseError)
|
Err(ReadIssueError::IssueParseError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Issue {
|
impl Issue {
|
||||||
pub fn new_from_dir(dir: &std::path::Path) -> Result<Self, IssueError> {
|
pub fn new_from_dir(dir: &std::path::Path) -> Result<Self, ReadIssueError> {
|
||||||
let mut description: Option<String> = None;
|
let mut description: Option<String> = None;
|
||||||
let mut state = State::New; // default state, if not specified in the issue
|
let mut state = State::New; // default state, if not specified in the issue
|
||||||
let mut dependencies: Option<Vec<String>> = None;
|
let mut dependencies: Option<Vec<String>> = None;
|
||||||
|
|
@ -89,64 +80,16 @@ impl Issue {
|
||||||
}
|
}
|
||||||
|
|
||||||
if description == None {
|
if description == None {
|
||||||
return Err(IssueError::IssueParseError);
|
return Err(ReadIssueError::IssueParseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
description: description.unwrap(),
|
description: description.unwrap(),
|
||||||
state: state,
|
state: state,
|
||||||
dependencies,
|
dependencies,
|
||||||
dir: std::path::PathBuf::from(dir),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(dir: &std::path::Path) -> Result<Self, IssueError> {
|
|
||||||
let mut issue_dir = std::path::PathBuf::from(dir);
|
|
||||||
let rnd: u128 = rand::random();
|
|
||||||
issue_dir.push(&format!("{:0x}", rnd));
|
|
||||||
std::fs::create_dir(&issue_dir)?;
|
|
||||||
Ok(Self {
|
|
||||||
description: String::from(""), // FIXME: kind of bogus to use the empty string as None
|
|
||||||
state: State::New,
|
|
||||||
dependencies: None,
|
|
||||||
dir: issue_dir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_description(&mut self, description: &str) -> Result<(), IssueError> {
|
|
||||||
self.description = String::from(description);
|
|
||||||
let mut description_filename = std::path::PathBuf::from(&self.dir);
|
|
||||||
description_filename.push("description");
|
|
||||||
let mut description_file = std::fs::File::create(&description_filename)?;
|
|
||||||
write!(description_file, "{}", description)?;
|
|
||||||
crate::git::git_commit_file(&description_filename)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_description(&mut self) -> Result<(), IssueError> {
|
|
||||||
let mut description_filename = std::path::PathBuf::from(&self.dir);
|
|
||||||
description_filename.push("description");
|
|
||||||
self.description = std::fs::read_to_string(description_filename)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn edit_description(&mut self) -> Result<(), IssueError> {
|
|
||||||
let mut description_filename = std::path::PathBuf::from(&self.dir);
|
|
||||||
description_filename.push("description");
|
|
||||||
let result = std::process::Command::new("vi")
|
|
||||||
.arg(&description_filename.as_mut_os_str())
|
|
||||||
.spawn()?
|
|
||||||
.wait_with_output()?;
|
|
||||||
if !result.status.success() {
|
|
||||||
println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap());
|
|
||||||
println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap());
|
|
||||||
return Err(IssueError::EditorError);
|
|
||||||
}
|
|
||||||
crate::git::git_commit_file(&description_filename)?;
|
|
||||||
self.read_description()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title<'a>(&'a self) -> &'a str {
|
pub fn title<'a>(&'a self) -> &'a str {
|
||||||
match self.description.find("\n") {
|
match self.description.find("\n") {
|
||||||
Some(index) => &self.description.as_str()[..index],
|
Some(index) => &self.description.as_str()[..index],
|
||||||
|
|
@ -167,7 +110,6 @@ mod tests {
|
||||||
description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"),
|
description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"),
|
||||||
state: State::New,
|
state: State::New,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir: std::path::PathBuf::from(issue_dir),
|
|
||||||
};
|
};
|
||||||
assert_eq!(issue, expected);
|
assert_eq!(issue, expected);
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +122,6 @@ mod tests {
|
||||||
description: String::from("minimal"),
|
description: String::from("minimal"),
|
||||||
state: State::InProgress,
|
state: State::InProgress,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir: std::path::PathBuf::from(issue_dir),
|
|
||||||
};
|
};
|
||||||
assert_eq!(issue, expected);
|
assert_eq!(issue, expected);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ pub struct Issues {
|
||||||
pub enum ReadIssuesError {
|
pub enum ReadIssuesError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
StdIoError(#[from] std::io::Error),
|
StdIoError(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error("Failed to parse issue")]
|
||||||
IssueError(#[from] crate::issue::IssueError),
|
IssueParseError(#[from] crate::issue::ReadIssueError),
|
||||||
#[error("cannot handle filename")]
|
#[error("cannot handle filename")]
|
||||||
FilenameError(std::ffi::OsString),
|
FilenameError(std::ffi::OsString),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -77,30 +77,20 @@ mod tests {
|
||||||
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
||||||
|
|
||||||
let mut expected = Issues::new();
|
let mut expected = Issues::new();
|
||||||
|
|
||||||
let uuid = String::from("7792b063eef6d33e7da5dc1856750c149ba678c6");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("minimal"),
|
description: String::from("minimal"),
|
||||||
state: crate::issue::State::InProgress,
|
state: crate::issue::State::InProgress,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let uuid = String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"),
|
description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"),
|
||||||
state: crate::issue::State::New,
|
state: crate::issue::State::New,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(issues, expected);
|
assert_eq!(issues, expected);
|
||||||
|
|
@ -112,30 +102,20 @@ mod tests {
|
||||||
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
||||||
|
|
||||||
let mut expected = Issues::new();
|
let mut expected = Issues::new();
|
||||||
|
|
||||||
let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("oh yeah we got titles"),
|
description: String::from("oh yeah we got titles"),
|
||||||
state: crate::issue::State::Done,
|
state: crate::issue::State::Done,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"),
|
description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"),
|
||||||
state: crate::issue::State::WontDo,
|
state: crate::issue::State::WontDo,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(issues, expected);
|
assert_eq!(issues, expected);
|
||||||
|
|
@ -147,38 +127,24 @@ mod tests {
|
||||||
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
let issues = Issues::new_from_dir(issues_dir).unwrap();
|
||||||
|
|
||||||
let mut expected = Issues::new();
|
let mut expected = Issues::new();
|
||||||
|
|
||||||
let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("oh yeah we got titles\n"),
|
description: String::from("oh yeah we got titles\n"),
|
||||||
state: crate::issue::State::Done,
|
state: crate::issue::State::Done,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"),
|
description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"),
|
||||||
state: crate::issue::State::WontDo,
|
state: crate::issue::State::WontDo,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let uuid = String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7");
|
|
||||||
let mut dir = std::path::PathBuf::from(issues_dir);
|
|
||||||
dir.push(&uuid);
|
|
||||||
expected.add_issue(
|
expected.add_issue(
|
||||||
uuid,
|
String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"),
|
||||||
crate::issue::Issue {
|
crate::issue::Issue {
|
||||||
description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"),
|
description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"),
|
||||||
state: crate::issue::State::WontDo,
|
state: crate::issue::State::WontDo,
|
||||||
|
|
@ -186,7 +152,6 @@ mod tests {
|
||||||
crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"),
|
crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"),
|
||||||
crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"),
|
crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"),
|
||||||
]),
|
]),
|
||||||
dir,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(issues, expected);
|
assert_eq!(issues, expected);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue