use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
#[derive(Debug,PartialEq)]
pub enum TaskError {
InvalidState(char),
Malformed,
}
impl fmt::Display for TaskError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = self.description();
match *self {
TaskError::InvalidState(token) => write!(
f,
"{}: {}",
description,
token),
TaskError::Malformed => write!(
f,
"{}",
description),
}
}
}
impl Error for TaskError {
fn description(&self) -> &str {
match *self {
TaskError::InvalidState(_) => "Invalid token for task state",
TaskError::Malformed => "Malformed definition",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
TaskError::InvalidState(_) => None,
TaskError::Malformed => None,
}
}
}
#[derive(Debug,Eq,PartialEq,PartialOrd)]
pub enum TaskState {
Current,
Backlog,
Blocked,
Done,
}
impl TaskState {
pub fn from_char(c: char) -> Result<TaskState, TaskError> {
match c {
'~' => Result::Ok(TaskState::Current),
'-' => Result::Ok(TaskState::Backlog),
'=' => Result::Ok(TaskState::Blocked),
'+' => Result::Ok(TaskState::Done),
token @ _ => Result::Err(TaskError::InvalidState(token))
}
}
pub fn to_char(&self) -> char {
match *self {
TaskState::Current => '~',
TaskState::Backlog => '-',
TaskState::Blocked => '=',
TaskState::Done => '+',
}
}
fn order_value(&self) -> u8 {
match *self {
TaskState::Current => 0,
TaskState::Backlog => 1,
TaskState::Blocked => 2,
TaskState::Done => 3,
}
}
}
impl Ord for TaskState {
fn cmp(&self, other: &TaskState) -> Ordering {
self.order_value().cmp(&other.order_value())
}
}
#[derive(Debug,Eq,PartialEq,PartialOrd)]
pub struct Task {
pub name: String,
pub state: TaskState,
}
impl Task {
pub fn new(task_str: &str) -> Result<Task, TaskError> {
let mut lines = task_str.lines();
if lines.clone().count() == 0 {
return Result::Err(TaskError::Malformed)
}
let main_line = lines.next().unwrap();
let mut main_line_chars = main_line.chars();
let state = try!(TaskState::from_char(main_line_chars.next().unwrap()));
let mut passed_leading_whitespace = false;
let mut name = String::new();
for next_char in main_line_chars {
if !passed_leading_whitespace {
if next_char.is_whitespace() {
continue;
} else {
passed_leading_whitespace = true;
}
}
name.push(next_char)
}
Result::Ok(Task {
name: name,
state: state,
})
}
pub fn to_plaintext(&self) -> String {
format!("{} {}", self.state.to_char(), self.name)
}
}
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Task(state: {}, name: {})", self.state.to_char(), self.name)
}
}
impl Ord for Task {
fn cmp(&self, other: &Task) -> Ordering {
let state_order = self.state.cmp(&other.state);
match state_order {
Ordering::Equal => self.name.cmp(&other.name),
_ => state_order,
}
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use std::collections::HashMap;
use super::*;
#[test]
fn test_task_state_to_char() {
assert_eq!(TaskState::Current.to_char(), '~');
assert_eq!(TaskState::Backlog.to_char(), '-');
assert_eq!(TaskState::Blocked.to_char(), '=');
assert_eq!(TaskState::Done.to_char(), '+');
}
#[test]
fn test_task_state_from_char() {
assert_eq!(TaskState::from_char('~').unwrap(), TaskState::Current);
assert_eq!(TaskState::from_char('-').unwrap(), TaskState::Backlog);
assert_eq!(TaskState::from_char('=').unwrap(), TaskState::Blocked);
assert_eq!(TaskState::from_char('+').unwrap(), TaskState::Done);
assert!(TaskState::from_char('!').is_err());
}
#[test]
fn test_task_state_ord() {
assert_eq!(TaskState::Current.cmp(&TaskState::Backlog), Ordering::Less);
assert_eq!(TaskState::Backlog.cmp(&TaskState::Blocked), Ordering::Less);
assert_eq!(TaskState::Blocked.cmp(&TaskState::Done), Ordering::Less);
}
#[test]
fn test_task_new_states() {
let mut valid_tasks = HashMap::new();
valid_tasks.insert("~ current task", Task {
name: "current task".to_string(),
state: TaskState::Current,
});
valid_tasks.insert("- backlog task", Task {
name: "backlog task".to_string(),
state: TaskState::Backlog,
});
valid_tasks.insert("= blocked task", Task {
name: "blocked task".to_string(),
state: TaskState::Blocked,
});
valid_tasks.insert("+ done task", Task {
name: "done task".to_string(),
state: TaskState::Done,
});
for (task_str, task) in valid_tasks {
assert_eq!(Task::new(task_str).unwrap(), task);
}
let mut invalid_tasks = HashMap::new();
invalid_tasks.insert("! task name", TaskError::InvalidState('!'));
invalid_tasks.insert("", TaskError::Malformed);
for (task_str, task) in invalid_tasks {
assert_eq!(Task::new(task_str).err().unwrap(), task);
}
}
#[test]
fn test_task_to_plaintext() {
let task = Task {
name: "task name".to_string(),
state: TaskState::Current,
};
assert_eq!(task.to_plaintext(), "~ task name");
}
#[test]
fn test_task_ord() {
let current_task = Task { name: "current".to_string(), state: TaskState::Current };
let backlog_task = Task { name: "backlog".to_string(), state: TaskState::Backlog };
let blocked_task = Task { name: "blocked".to_string(), state: TaskState::Blocked };
let done_task = Task { name: "done".to_string(), state: TaskState::Done };
assert_eq!(current_task.cmp(&backlog_task), Ordering::Less);
assert_eq!(backlog_task.cmp(&blocked_task), Ordering::Less);
assert_eq!(blocked_task.cmp(&done_task), Ordering::Less);
}
}