Merge pull request 'Repo: move all methods to repository' (#14) from repo into main

Reviewed-on: #14
This commit is contained in:
Dmitriy Pleshevskiy 2022-08-16 12:44:15 +00:00 committed by Gitea
commit 512514f721
No known key found for this signature in database
GPG key ID: 55B75599806CD426
13 changed files with 385 additions and 251 deletions

View file

@ -12,18 +12,19 @@ pub fn execute(repo: impl Repository, args: Args) {
let res = repo.insert_task(repo::InsertTaskData {
name: args.name,
link: args.link,
index: None,
});
match res {
Ok(task) => {
println!("Task was added successfully");
println!("The task was added successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot insert data: {}", err);
eprintln!("Cannot insert a new task: {}", err);
}
}

View file

@ -1,7 +1,4 @@
use std::io::Write;
use std::path::PathBuf;
use crate::{CurrentTaskInfo, Task};
use crate::repo::{self, Repository};
#[derive(clap::Args)]
pub struct Args {
@ -16,36 +13,34 @@ pub struct Args {
idx: usize,
}
pub struct Request {
pub args: Args,
pub current_task_info: Option<CurrentTaskInfo>,
pub tasks: Vec<Task>,
pub tasks_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
if req.current_task_info.is_some() {
panic!("You can edit task only when you don't have an active task, yet");
}
let idx = req.args.idx;
if idx == 0 || idx > req.tasks.len() {
panic!("invalid index");
}
let mut task = &mut req.tasks[idx - 1];
if let Some(name) = req.args.name {
task.name = name;
}
if let Some(link) = req.args.link {
task.link = Some(link);
} else if req.args.no_link {
task.link = None;
}
let mut file = std::fs::File::create(&req.tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.tasks).unwrap())
.unwrap();
println!("changed");
pub fn execute(repo: impl Repository, args: Args) {
match repo.get_current_task_opt() {
Ok(Some(_)) => {
return eprintln!("You can edit task only when you don't have an active task, yet")
}
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
_ => {}
}
let res = repo.update_task(
args.idx,
repo::UpdateTaskData {
name: args.name,
link: args.no_link.then(|| None).or_else(|| args.link.map(Some)),
},
);
match res {
Ok(task) => {
println!("The task was changed successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot update the task: {}", err);
}
}
}

View file

@ -1,41 +1,17 @@
use crate::{CurrentTaskInfo, Task};
use std::io::Write;
use std::path::PathBuf;
use crate::repo::{self, Repository};
pub struct Request {
pub current_task_info: Option<CurrentTaskInfo>,
pub tasks: Vec<Task>,
pub finished_tasks_file_path: PathBuf,
pub tasks_file_path: PathBuf,
pub current_task_info_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
match req.current_task_info.take() {
None => {
panic!("You can use the finish subcommand only when you have an active task")
pub fn execute(repo: impl Repository) {
match repo.finish_task() {
Err(repo::Error::NotFound) => {
eprintln!("You can use the finish subcommand only when you have an active task")
}
Some(info) => {
let mut finished_tasks: Vec<Task> = std::fs::File::open(&req.finished_tasks_file_path)
.map(|file| serde_json::from_reader(file).unwrap())
.unwrap_or_default();
let task = req.tasks.remove(info.task_idx - 1);
finished_tasks.push(task);
let mut file = std::fs::File::create(req.tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.tasks).unwrap())
.unwrap();
let mut file = std::fs::File::create(req.finished_tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&finished_tasks).unwrap())
.unwrap();
let mut file = std::fs::File::create(req.current_task_info_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap())
.unwrap();
println!("finished");
Err(err) => eprintln!("Cannot finish the task: {}", err),
Ok(task) => {
println!("The task was finished successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
}
}

View file

@ -1,15 +1,23 @@
use crate::{CurrentTaskInfo, Task};
use crate::domain::CurrentTaskInfo;
use crate::repo::Repository;
pub struct Request {
pub tasks: Vec<Task>,
pub current_task_info: Option<CurrentTaskInfo>,
}
pub fn execute(repo: impl Repository) {
let tasks = match repo.get_tasks() {
Ok(tasks) => tasks,
Err(err) => return eprintln!("Cannot read tasks: {}", err),
};
pub fn execute(req: Request) {
for (i, task) in req.tasks.iter().enumerate() {
let cur_task = match repo.get_current_task_opt() {
Ok(cur_task) => cur_task,
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
};
for (i, task) in tasks.iter().enumerate() {
let idx = i + 1;
match req.current_task_info {
match cur_task {
Some(CurrentTaskInfo { task_idx, .. }) if task_idx == idx => print!("> "),
_ => print!(" "),
}

View file

@ -1,20 +1,26 @@
use std::io::Write;
use std::path::PathBuf;
use crate::repo::{self, Repository};
use crate::CurrentTaskInfo;
pub fn execute(repo: impl Repository) {
let task = match repo.stop_task() {
Err(repo::Error::NotFound) => {
return eprintln!("You can use the pause subcommand only when you have an active task")
}
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
Ok(task) => task,
};
pub struct Request {
pub current_task_info: Option<CurrentTaskInfo>,
pub current_task_info_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
if req.current_task_info.take().is_none() {
panic!("You can use the pause subcommand only when you have an active task");
match repo.stop_task() {
Ok(_) => {
println!("The task was paused successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot pause the task: {}", err);
}
}
let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap())
.unwrap();
println!("paused");
}

View file

@ -1,8 +1,6 @@
use crate::{CurrentTaskInfo, Task};
use crate::repo::{self, Repository};
use std::cmp::Ordering;
use std::io::Write;
use std::path::PathBuf;
#[derive(clap::Args)]
pub struct Args {
@ -17,46 +15,61 @@ pub enum Priority {
After { idx: usize },
}
pub struct Request {
pub args: Args,
pub tasks: Vec<Task>,
pub current_task_info: Option<CurrentTaskInfo>,
pub tasks_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
if req.current_task_info.is_some() {
panic!("You can change priority only when you don't have an active task, yet");
pub fn execute(repo: impl Repository, args: Args) {
match repo.get_current_task_opt() {
Ok(Some(_)) => {
return eprintln!(
"You can change priority only when you don't have an active task, yet"
)
}
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
_ => {}
}
let target_idx = req.args.idx;
if target_idx == 0 || target_idx > req.tasks.len() {
panic!("invalid index");
let target_idx = args.idx;
if let Err(err) = repo.get_task_opt(target_idx) {
return eprintln!("Task not found: {}", err);
}
let idx = match req.args.priority {
let idx = match args.priority {
Priority::Before { idx } | Priority::After { idx } => match target_idx.cmp(&idx) {
Ordering::Equal => return,
Ordering::Less => idx - 1,
Ordering::Greater => idx,
},
};
if idx == 0 || idx > req.tasks.len() {
panic!("invalid index");
if let Err(err) = repo.get_task_opt(idx) {
return eprintln!("Task not found: {}", err);
}
let target = req.tasks.remove(target_idx - 1);
let target = match repo.remove_task(target_idx) {
Ok(removed) => removed,
Err(err) => return eprintln!("Cannot remove the task: {}", err),
};
match req.args.priority {
Priority::Before { .. } => {
req.tasks.insert(idx - 1, target);
let new_idx = match args.priority {
Priority::Before { .. } => idx - 1,
Priority::After { .. } => idx,
};
let res = repo.insert_task(repo::InsertTaskData {
index: Some(new_idx),
name: target.name,
link: target.link,
});
match res {
Ok(task) => {
println!("The task was reordered successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Priority::After { .. } => {
req.tasks.insert(idx, target);
Err(err) => {
eprintln!("Cannot reorder the task: {}", err);
}
}
let mut file = std::fs::File::create(&req.tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.tasks).unwrap())
.unwrap();
}

View file

@ -1,31 +1,28 @@
use crate::repo::{self, Repository};
use std::io::{BufRead, Write};
use std::path::PathBuf;
use crate::{CurrentTaskInfo, Task};
#[derive(clap::Args)]
pub struct Args {
idx: usize,
}
pub struct Request {
pub args: Args,
pub current_task_info: Option<CurrentTaskInfo>,
pub tasks: Vec<Task>,
pub tasks_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
if req.current_task_info.is_some() {
panic!("You can remove task only when you don't have an active task, yet");
pub fn execute(repo: impl Repository, args: Args) {
match repo.get_current_task_opt() {
Ok(Some(_)) => {
return eprintln!("You can remove task only when you don't have an active task, yet");
}
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
_ => {}
}
let idx = req.args.idx;
if idx == 0 || idx > req.tasks.len() {
panic!("invalid index");
}
let task = match repo.get_task_opt(args.idx) {
Ok(Some(task)) => task,
Ok(None) | Err(repo::Error::NotFound) => return eprintln!("Task not found"),
Err(err) => return eprintln!("Cannot get task: {}", err),
};
let task = &req.tasks[idx - 1];
println!("You are deleting task:");
println!(" {}", task.name);
if let Some(ref link) = task.link {
@ -46,11 +43,16 @@ pub fn execute(mut req: Request) {
}
}
req.tasks.remove(idx - 1);
let mut file = std::fs::File::create(&req.tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.tasks).unwrap())
.unwrap();
println!("removed");
match repo.remove_task(args.idx) {
Ok(_) => {
println!("The task was removed successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot remove the task: {}", err);
}
}
}

View file

@ -1,5 +1,4 @@
use crate::{CurrentTaskInfo, Task};
use std::{io::Write, path::PathBuf};
use crate::repo::{self, Repository};
#[derive(clap::Args)]
pub struct Args {
@ -9,31 +8,20 @@ pub struct Args {
idx: Option<usize>,
}
pub struct Request {
pub args: Args,
pub tasks: Vec<Task>,
pub current_task_info: Option<CurrentTaskInfo>,
pub current_task_info_file_path: PathBuf,
}
pub fn execute(repo: impl Repository, args: Args) {
let task = match repo.start_task(args.idx.unwrap_or(1)) {
Ok(task) => task,
Err(repo::Error::NotFound) => return eprintln!("Task not found"),
Err(err) => return eprintln!("Cannot start task: {}", err),
};
pub fn execute(mut req: Request) {
let idx = req.args.idx.unwrap_or(1);
if idx == 0 || idx > req.tasks.len() {
panic!("invalid index");
println!("The task was started successfully");
println!(" {}", task.name);
if let Some(link) = task.link.as_ref() {
println!(" link: {}", link);
}
let task = &req.tasks[idx - 1];
req.current_task_info.replace(CurrentTaskInfo {
task_idx: idx,
task: task.clone(),
});
let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap())
.unwrap();
println!("started");
if let (Some(link), true) = (task.link.as_ref(), req.args.open) {
if let (Some(link), true) = (task.link.as_ref(), args.open) {
log::debug!("opening link...");
std::process::Command::new("xdg-open")
.arg(link)

View file

@ -1,16 +1,19 @@
use crate::CurrentTaskInfo;
use crate::repo::Repository;
pub fn execute(current_task_info: Option<CurrentTaskInfo>) {
match current_task_info {
None => {
pub fn execute(repo: impl Repository) {
match repo.get_current_task_opt() {
Ok(None) => {
eprintln!("You don't have an active task.");
}
Some(info) => {
Ok(Some(info)) => {
println!("Information about your current task:");
println!(" {}", info.task.name);
if let Some(link) = info.task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot read current task: {}", err);
}
}
}

View file

@ -1,5 +1,13 @@
pub type TaskId = usize;
pub struct Task {
pub name: String,
pub link: Option<String>,
// created_at
}
pub struct CurrentTaskInfo {
pub task_idx: usize,
pub task: Task,
// started_at
}

View file

@ -28,7 +28,6 @@
use clap::Parser;
use repo::fs::FsRepo;
use serde::{Deserialize, Serialize};
use xdg::BaseDirectories;
mod cli;
@ -39,83 +38,35 @@ fn main() {
let args = cli::Args::parse();
let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap();
let repo = FsRepo::new(xdg_dirs);
let finished_tasks_file_path = xdg_dirs.place_data_file("finished_data.json").unwrap();
let current_task_info_file_path = xdg_dirs.place_data_file("current.json").unwrap();
let current_task_info: Option<CurrentTaskInfo> =
std::fs::File::open(&current_task_info_file_path)
.map(|file| serde_json::from_reader(file).unwrap())
.unwrap_or_default();
match args.command {
cli::SubCommand::Add(args) => {
cli::add::execute(repo, args);
}
cli::SubCommand::Edit(args) => {
cli::edit::execute(cli::edit::Request {
args,
current_task_info,
tasks,
tasks_file_path,
});
cli::edit::execute(repo, args);
}
cli::SubCommand::Remove(args) => {
cli::remove::execute(cli::remove::Request {
args,
current_task_info,
tasks,
tasks_file_path,
});
cli::remove::execute(repo, args);
}
cli::SubCommand::List => {
cli::list::execute(cli::list::Request {
tasks,
current_task_info,
});
cli::list::execute(repo);
}
cli::SubCommand::Priority(args) => {
cli::priority::execute(cli::priority::Request {
args,
tasks,
tasks_file_path,
current_task_info,
});
cli::priority::execute(repo, args);
}
cli::SubCommand::Start(args) => {
cli::start::execute(cli::start::Request {
args,
tasks,
current_task_info,
current_task_info_file_path,
});
cli::start::execute(repo, args);
}
cli::SubCommand::Pause => {
cli::pause::execute(cli::pause::Request {
current_task_info,
current_task_info_file_path,
});
cli::pause::execute(repo);
}
cli::SubCommand::Finish => {
cli::finish::execute(cli::finish::Request {
tasks,
current_task_info,
current_task_info_file_path,
tasks_file_path,
finished_tasks_file_path,
});
cli::finish::execute(repo);
}
cli::SubCommand::Status => {
cli::status::execute(current_task_info);
cli::status::execute(repo);
}
}
}
#[derive(Deserialize, Serialize)]
pub struct CurrentTaskInfo {
task_idx: usize,
task: Task,
// started_at
}

View file

@ -22,12 +22,36 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {}
pub struct InsertTaskData {
name: String,
link: Option<String>,
pub name: String,
pub link: Option<String>,
pub index: Option<usize>,
}
pub struct UpdateTaskData {
pub name: Option<String>,
pub link: Option<Option<String>>,
}
pub trait Repository {
fn get_current_task_opt(&self) -> Result<Option<domain::CurrentTaskInfo>, Error>;
fn get_task_opt(&self, id: domain::TaskId) -> Result<Option<domain::Task>, Error>;
fn get_tasks(&self) -> Result<Vec<domain::Task>, Error>;
fn remove_task(&self, id: domain::TaskId) -> Result<domain::Task, Error>;
fn update_task(
&self,
id: domain::TaskId,
update_data: UpdateTaskData,
) -> Result<domain::Task, Error>;
fn insert_task(&self, insert_data: InsertTaskData) -> Result<domain::Task, Error>;
fn start_task(&self, id: domain::TaskId) -> Result<domain::Task, Error>;
fn stop_task(&self) -> Result<domain::Task, Error>;
fn finish_task(&self) -> Result<domain::Task, Error>;
}

View file

@ -2,7 +2,7 @@ use std::fs::File;
use std::io::Write;
use crate::domain;
use crate::repo::{Error, InsertTaskData, Repository};
use crate::repo::{Error, InsertTaskData, Repository, UpdateTaskData};
use serde::{Deserialize, Serialize};
use xdg::BaseDirectories;
@ -23,7 +23,25 @@ impl From<Task> for domain::Task {
}
}
#[derive(Deserialize, Serialize, Clone)]
pub struct CurrentTaskInfo {
task_idx: usize,
task: Task,
// started_at
}
impl From<CurrentTaskInfo> for domain::CurrentTaskInfo {
fn from(repo: CurrentTaskInfo) -> Self {
domain::CurrentTaskInfo {
task_idx: repo.task_idx,
task: repo.task.into(),
}
}
}
const CURRENT_TASK_FILE: &str = "current.json";
const DATA_FILE: &str = "data.json";
const FINISHED_DATA_FILE: &str = "finished_data.json";
pub struct FsRepo {
xdg_dirs: BaseDirectories,
@ -36,11 +54,63 @@ impl FsRepo {
}
impl Repository for FsRepo {
fn get_current_task_opt(&self) -> Result<Option<domain::CurrentTaskInfo>, Error> {
self.get_current_task_impl()
.map(|cur_task| cur_task.map(From::from))
}
fn get_task_opt(&self, id: domain::TaskId) -> Result<Option<domain::Task>, Error> {
let tasks = self.get_tasks_impl()?;
if id == 0 || id > tasks.len() {
return Err(Error::NotFound);
}
Ok(Some(tasks[id - 1].clone().into()))
}
fn get_tasks(&self) -> Result<Vec<domain::Task>, Error> {
self.get_tasks_impl()
.map(|tasks| tasks.into_iter().map(Task::into).collect())
}
fn remove_task(&self, id: domain::TaskId) -> Result<domain::Task, Error> {
let mut tasks = self.get_tasks_impl()?;
if id == 0 || id > tasks.len() {
return Err(Error::NotFound);
}
let removed = tasks.remove(id - 1);
self.save_tasks_impl(tasks)?;
Ok(removed.into())
}
fn update_task(
&self,
id: domain::TaskId,
update_data: UpdateTaskData,
) -> Result<domain::Task, Error> {
let mut tasks = self.get_tasks_impl()?;
if id == 0 || id > tasks.len() {
return Err(Error::NotFound);
}
let mut task = &mut tasks[id];
if let Some(name) = update_data.name {
task.name = name;
}
if let Some(link) = update_data.link {
task.link = link;
}
let new_task = task.clone();
self.save_tasks_impl(tasks)?;
Ok(new_task.into())
}
fn insert_task(&self, insert_data: InsertTaskData) -> Result<domain::Task, Error> {
let new_task = Task {
name: insert_data.name,
@ -48,25 +118,114 @@ impl Repository for FsRepo {
};
let mut tasks = self.get_tasks_impl()?;
tasks.push(new_task.clone());
match insert_data.index {
None => tasks.push(new_task.clone()),
Some(idx) => tasks.insert(idx, new_task.clone()),
}
self.save_tasks_impl(tasks)?;
Ok(new_task.into())
}
fn start_task(&self, id: domain::TaskId) -> Result<domain::Task, Error> {
let tasks = self.get_tasks_impl()?;
if id == 0 || id > tasks.len() {
return Err(Error::NotFound);
}
let task = &tasks[id - 1];
let mut cur_task = self.get_current_task_impl()?;
cur_task.replace(CurrentTaskInfo {
task_idx: id,
task: task.clone(),
});
self.save_current_task_impl(cur_task)?;
Ok(task.clone().into())
}
fn stop_task(&self) -> Result<domain::Task, Error> {
let mut cur_task = self.get_current_task_impl()?;
let old = cur_task.take().ok_or(Error::NotFound)?;
self.save_current_task_impl(cur_task)?;
Ok(old.task.into())
}
fn finish_task(&self) -> Result<domain::Task, Error> {
let mut cur_task = self.get_current_task_impl()?;
let old = cur_task.take().ok_or(Error::NotFound)?;
let mut finished_tasks = self.get_finished_tasks_impl()?;
let mut tasks = self.get_tasks_impl()?;
let task = tasks.remove(old.task_idx - 1);
finished_tasks.push(task.clone());
self.save_current_task_impl(cur_task)?;
self.save_tasks_impl(tasks)?;
self.save_finished_tasks_impl(finished_tasks)?;
Ok(task.into())
}
}
impl FsRepo {
fn get_current_task_impl(&self) -> Result<Option<CurrentTaskInfo>, Error> {
let file_path = self.xdg_dirs.get_data_file(CURRENT_TASK_FILE);
File::open(&file_path)
.ok()
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
.transpose()
}
fn save_current_task_impl(&self, cur_task: Option<CurrentTaskInfo>) -> Result<(), Error> {
let file_path = self
.xdg_dirs
.place_data_file(CURRENT_TASK_FILE)
.map_err(|_| Error::InsertData)?;
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
let new_data = serde_json::to_vec(&cur_task).map_err(|_| Error::InvalidData)?;
file.write_all(&new_data).map_err(|_| Error::InsertData)
}
fn get_tasks_impl(&self) -> Result<Vec<Task>, Error> {
let file_path = self.xdg_dirs.get_data_file(DATA_FILE);
File::open(&file_path)
.ok()
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
.transpose()
.map(|tasks| tasks.unwrap_or_default())
}
fn save_tasks_impl(&self, tasks: Vec<Task>) -> Result<(), Error> {
let file_path = self
.xdg_dirs
.place_data_file(DATA_FILE)
.map_err(|_| Error::InsertData)?;
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?;
file.write_all(&new_data).map_err(|_| Error::InsertData);
Ok(new_task.into())
file.write_all(&new_data).map_err(|_| Error::InsertData)
}
}
impl FsRepo {
fn get_tasks_impl(&self) -> Result<Vec<Task>, Error> {
let file_path = self.xdg_dirs.get_data_file(DATA_FILE);
fn get_finished_tasks_impl(&self) -> Result<Vec<Task>, Error> {
let file_path = self.xdg_dirs.get_data_file(FINISHED_DATA_FILE);
File::open(&file_path)
.map_err(|_| Error::NotFound)
.and_then(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
.ok()
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
.transpose()
.map(|tasks| tasks.unwrap_or_default())
}
fn save_finished_tasks_impl(&self, tasks: Vec<Task>) -> Result<(), Error> {
let file_path = self
.xdg_dirs
.place_data_file(FINISHED_DATA_FILE)
.map_err(|_| Error::InsertData)?;
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?;
file.write_all(&new_data).map_err(|_| Error::InsertData)
}
}