basic feature set
This commit is contained in:
31
src/colour.rs
Normal file
31
src/colour.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use colored::Colorize;
|
||||
|
||||
// Yellow
|
||||
pub fn vault(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(243, 156, 18).bold()
|
||||
}
|
||||
|
||||
// Red
|
||||
pub fn error(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(192, 57, 43).bold()
|
||||
}
|
||||
|
||||
// Purple
|
||||
pub fn command(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(155, 89, 182).bold()
|
||||
}
|
||||
|
||||
// Green
|
||||
pub fn task_name(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(39, 174, 96).bold()
|
||||
}
|
||||
|
||||
// Beige
|
||||
pub fn file(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(255, 184, 184).bold()
|
||||
}
|
||||
|
||||
// Pink
|
||||
pub fn id(text : &str) -> colored::ColoredString {
|
||||
text.truecolor(232, 67, 147).bold()
|
||||
}
|
103
src/config.rs
Normal file
103
src/config.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::error;
|
||||
|
||||
use crate::colour;
|
||||
|
||||
use std::path;
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
/// Paths for all vaults, ordered according to recent usage, with current at the front.
|
||||
pub vaults : Vec<(String, path::PathBuf)>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn current_vault(&self) -> Result<&(String, path::PathBuf), error::Error> {
|
||||
self.vaults.get(0).ok_or_else(|| error::Error::Generic(String::from("The attempted operation requires a vault, none of which have been set up")))
|
||||
}
|
||||
|
||||
pub fn save(self) -> Result<(), error::Error> {
|
||||
Ok(confy::store::<Config>("toru", self)?)
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Config, error::Error> {
|
||||
Ok(confy::load::<Config>("toru")?)
|
||||
}
|
||||
|
||||
pub fn contains_name(&self, name : &String) -> bool {
|
||||
self.vaults.iter().any(|(n, _)| n == name)
|
||||
}
|
||||
|
||||
pub fn contains_path(&self, path : &path::PathBuf) -> bool {
|
||||
self.vaults.iter().any(|(_, p)| p == path)
|
||||
}
|
||||
|
||||
/// Adds the vault to the configuration.
|
||||
pub fn add(&mut self, name : String, path : path::PathBuf) {
|
||||
debug_assert!(!self.contains_name(&name));
|
||||
debug_assert!(!self.contains_path(&path));
|
||||
|
||||
self.vaults.push((name, path));
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, name : &String) -> Result<path::PathBuf, error::Error> {
|
||||
match self.vaults.iter().position(|(n, _)| n == name) {
|
||||
Some(index) => {
|
||||
let (_, path) = self.vaults.swap_remove(index);
|
||||
Ok(path)
|
||||
},
|
||||
None => {
|
||||
Err(error::Error::Generic(format!("No vault by the name {} exists", colour::vault(name))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch(&mut self, name : &String) -> Result<(), error::Error> {
|
||||
match self.vaults.iter().position(|(n, _)| n == name) {
|
||||
Some(index) => {
|
||||
self.vaults.swap(index, 0);
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
Err(error::Error::Generic(format!("No vault by the name {} exists", colour::vault(name))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists all vaults to stdout.
|
||||
pub fn list_vaults(&self) {
|
||||
|
||||
let width = self.vaults.iter().fold(usize::MIN, |c, (n, _)| c.max(n.len()));
|
||||
|
||||
if self.vaults.is_empty() {
|
||||
println!("No vaults currently set up, try running: {}", colour::command("toru vault new <NAME> <PATH>"));
|
||||
}
|
||||
else {
|
||||
for (i, (name, path)) in self.vaults.iter().enumerate() {
|
||||
|
||||
if i == 0 {
|
||||
print!("* ");
|
||||
}
|
||||
else {
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!("{}", colour::vault(name));
|
||||
|
||||
let padding = width - name.len() + 1;
|
||||
|
||||
for _ in 0..padding {
|
||||
print!(" ")
|
||||
}
|
||||
|
||||
print!("{}", path.display());
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
57
src/error.rs
Normal file
57
src/error.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::colour;
|
||||
|
||||
use std::io;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
Confy(confy::ConfyError),
|
||||
Trash(trash::Error),
|
||||
TomlDe(toml::de::Error),
|
||||
TomlSer(toml::ser::Error),
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Io(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||
Error::Confy(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||
Error::Trash(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||
Error::TomlDe(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||
Error::TomlSer(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||
Error::Generic(message) => write!(f, "{}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err : io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<confy::ConfyError> for Error {
|
||||
fn from(err : confy::ConfyError) -> Self {
|
||||
Error::Confy(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<trash::Error> for Error {
|
||||
fn from(err : trash::Error) -> Self {
|
||||
Error::Trash(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(err : toml::de::Error) -> Self {
|
||||
Error::TomlDe(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::ser::Error> for Error {
|
||||
fn from(err : toml::ser::Error) -> Self {
|
||||
Error::TomlSer(err)
|
||||
}
|
||||
}
|
164
src/main.rs
Normal file
164
src/main.rs
Normal file
@ -0,0 +1,164 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
mod vault;
|
||||
mod error;
|
||||
mod tasks;
|
||||
mod state;
|
||||
mod config;
|
||||
mod colour;
|
||||
|
||||
use std::path;
|
||||
|
||||
#[derive(clap::Parser, Debug)]
|
||||
struct Args {
|
||||
#[clap(subcommand)]
|
||||
command : Command,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
#[clap(version, about, author, global_setting = clap::AppSettings::DisableHelpSubcommand)]
|
||||
enum Command {
|
||||
/// Create a new task.
|
||||
New {
|
||||
#[clap(short, long)]
|
||||
name : String,
|
||||
#[clap(short, long)]
|
||||
info : Option<String>,
|
||||
#[clap(short, long)]
|
||||
tags : Vec<String>,
|
||||
#[clap(short, long)]
|
||||
dependencies : Vec<tasks::Id>,
|
||||
#[clap(short, long, value_enum)]
|
||||
priority : Option<tasks::Priority>,
|
||||
},
|
||||
/// Delete a task completely.
|
||||
Delete {
|
||||
id : tasks::Id,
|
||||
},
|
||||
/// Discard a task without deleting the underlying file.
|
||||
Discard {
|
||||
id : tasks::Id,
|
||||
},
|
||||
/// Mark a task as complete.
|
||||
Complete {
|
||||
id : tasks::Id,
|
||||
},
|
||||
/// Commands for interacting with vaults.
|
||||
#[clap(subcommand)]
|
||||
Vault(VaultCommand),
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum VaultCommand {
|
||||
/// Creates a new vault at the specified location of the given name.
|
||||
New {
|
||||
name : String,
|
||||
path : path::PathBuf,
|
||||
},
|
||||
/// Disconnects the specified vault from toru, without altering the files.
|
||||
Disconnect {
|
||||
name : String,
|
||||
},
|
||||
/// Connects an existing fault to toru.
|
||||
Connect {
|
||||
name : String,
|
||||
path : path::PathBuf,
|
||||
},
|
||||
/// Deletes the specified vault along with all of its data.
|
||||
Delete {
|
||||
name : String,
|
||||
},
|
||||
/// Lists all configured vaults.
|
||||
List,
|
||||
/// Switches to the specified vault.
|
||||
Switch {
|
||||
name : String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = program();
|
||||
|
||||
match result {
|
||||
Ok(()) => (),
|
||||
Err(error::Error::Generic(message)) => {
|
||||
println!("{} {}", colour::error("Error:"), message);
|
||||
}
|
||||
result => println!("{:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
fn program() -> Result<(), error::Error> {
|
||||
let command = {
|
||||
use clap::Parser;
|
||||
Args::parse().command
|
||||
};
|
||||
|
||||
let mut config = config::Config::load()?;
|
||||
|
||||
use Command::*;
|
||||
match command {
|
||||
Vault(command) => {
|
||||
use VaultCommand::*;
|
||||
match command {
|
||||
New { name, path } => {
|
||||
vault::new(name.clone(), path, &mut config)?;
|
||||
println!("Created vault {}", colour::vault(&name));
|
||||
},
|
||||
Disconnect { name } => {
|
||||
vault::disconnect(&name, &mut config)?;
|
||||
println!("Disconnected vault {}", colour::vault(&name));
|
||||
},
|
||||
Connect { name , path } => {
|
||||
vault::connect(name.clone(), path, &mut config)?;
|
||||
println!("Connected vault {}", colour::vault(&name));
|
||||
},
|
||||
Delete { name } => {
|
||||
vault::delete(&name, &mut config)?;
|
||||
println!("Deleted vault {}", colour::vault(&name));
|
||||
},
|
||||
List => {
|
||||
config.list_vaults();
|
||||
},
|
||||
Switch { name } => {
|
||||
config.switch(&name)?;
|
||||
println!("Switched to vault {}", colour::vault(&name));
|
||||
},
|
||||
}
|
||||
}
|
||||
command => {
|
||||
let vault_folder = &config.current_vault()?.1;
|
||||
let mut state = state::State::load(vault_folder)?;
|
||||
|
||||
match command {
|
||||
New { name, info, tags, dependencies, priority } => {
|
||||
let task = tasks::Task::new(name, info, tags, dependencies, priority, vault_folder, &mut state)?;
|
||||
println!("Created task {}", colour::task_name(&task.data.name));
|
||||
},
|
||||
Delete { id } => {
|
||||
tasks::Task::delete_by_id(id, vault_folder)?;
|
||||
println!("Deleted task {}", colour::id(&id.to_string()));
|
||||
}
|
||||
Discard { id } => {
|
||||
let mut task = tasks::Task::load(id, vault_folder.clone(), false)?;
|
||||
task.data.discarded = true;
|
||||
task.save()?;
|
||||
println!("Discarded task {}", colour::id(&id.to_string()));
|
||||
},
|
||||
Complete { id } => {
|
||||
let mut task = tasks::Task::load(id, vault_folder.clone(), false)?;
|
||||
task.data.complete = true;
|
||||
task.save()?;
|
||||
println!("Marked task {} as complete", colour::id(&id.to_string()));
|
||||
},
|
||||
Vault(_) => unreachable!(),
|
||||
}
|
||||
|
||||
state.save()?;
|
||||
}
|
||||
}
|
||||
|
||||
config.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
86
src/state.rs
Normal file
86
src/state.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use std::fs;
|
||||
use std::path;
|
||||
use std::io;
|
||||
use std::io::{Write, Seek};
|
||||
|
||||
use crate::error;
|
||||
use crate::tasks::Id;
|
||||
|
||||
pub struct State {
|
||||
file : fs::File,
|
||||
pub data : InternalState,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct InternalState {
|
||||
pub next_id : Id,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// This function should be called after creating or checking that the "notes" folder exists.
|
||||
pub fn load(vault_location : &path::Path) -> Result<Self, error::Error> {
|
||||
let path = vault_location.join("state.toml");
|
||||
|
||||
if path.exists() && path.is_file() {
|
||||
// Read file before opening (and truncating).
|
||||
let contents = fs::read_to_string(&path)?;
|
||||
|
||||
let file = fs::File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let data = toml::from_str::<InternalState>(&contents)?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
data,
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
||||
let mut max_id : i128 = -1;
|
||||
|
||||
for id in vault_location.join("notes").read_dir()?.filter_map(|p| p.ok()).map(|p| p.path()).filter(|p| p.extension().map(|s| s.to_str()) == Some(Some("toml"))).filter_map(|p| p.file_stem().map(|x| x.to_str().map(|y| y.to_string()))).flatten().filter_map(|p| p.parse::<Id>().ok()) {
|
||||
|
||||
if i128::try_from(id).unwrap() > max_id {
|
||||
max_id = i128::from(id);
|
||||
}
|
||||
}
|
||||
|
||||
let data = InternalState {
|
||||
next_id : u64::try_from(max_id + 1).unwrap(),
|
||||
};
|
||||
|
||||
let mut file = fs::File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
file.set_len(0)?;
|
||||
file.seek(io::SeekFrom::Start(0))?;
|
||||
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||
|
||||
let task = Self {
|
||||
file,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok(task)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(self) -> Result<(), error::Error> {
|
||||
|
||||
let Self {
|
||||
mut file,
|
||||
data,
|
||||
} = self;
|
||||
|
||||
file.set_len(0)?;
|
||||
file.seek(io::SeekFrom::Start(0))?;
|
||||
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
157
src/tasks.rs
Normal file
157
src/tasks.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use crate::error;
|
||||
use crate::state;
|
||||
use crate::colour;
|
||||
|
||||
use std::fs;
|
||||
use std::mem;
|
||||
use std::path;
|
||||
use std::io;
|
||||
use std::io::{Write, Seek};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub type Id = u64;
|
||||
|
||||
pub struct Task {
|
||||
path : path::PathBuf,
|
||||
file : fs::File,
|
||||
pub data : InternalTask,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Priority {
|
||||
#[default]
|
||||
Unspecified,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct TimeEntry {
|
||||
hours : u32,
|
||||
minutes : u8,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct InternalTask {
|
||||
pub id : Id,
|
||||
pub name : String,
|
||||
pub info : Option<String>,
|
||||
pub tags : HashSet<String>,
|
||||
pub dependencies : HashSet<Id>,
|
||||
pub priority : Priority,
|
||||
//due : Option<chrono::NaiveDateTime>,
|
||||
pub time_entries : Vec<TimeEntry>,
|
||||
pub created : chrono::NaiveDateTime,
|
||||
pub complete : bool,
|
||||
pub discarded : bool,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, vault_folder : &path::Path, state : &mut state::State) -> Result<Self, error::Error> {
|
||||
|
||||
let id = state.data.next_id;
|
||||
state.data.next_id += 1;
|
||||
|
||||
let path = vault_folder.join("notes").join(&format!("{}.toml", id));
|
||||
|
||||
let mut file = fs::File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let data = InternalTask {
|
||||
id,
|
||||
name,
|
||||
info,
|
||||
tags : tags.into_iter().collect(),
|
||||
dependencies : dependencies.into_iter().collect(),
|
||||
priority : priority.unwrap_or_default(),
|
||||
time_entries : Vec::new(),
|
||||
created : chrono::Utc::now().naive_local(),
|
||||
complete : false,
|
||||
discarded : false,
|
||||
};
|
||||
|
||||
|
||||
file.set_len(0)?;
|
||||
file.seek(io::SeekFrom::Start(0))?;
|
||||
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||
|
||||
Ok(Task {
|
||||
path,
|
||||
file,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// The read_only flag is so that the file will not be truncated, and therefore doesn't need to
|
||||
/// be saved when finished.
|
||||
pub fn load(id : Id, vault_folder : path::PathBuf, read_only : bool) -> Result<Self, error::Error> {
|
||||
let path = Task::check_exists(id, &vault_folder)?;
|
||||
|
||||
let file_contents = fs::read_to_string(&path)?;
|
||||
let file = if read_only {
|
||||
fs::File::open(&path)?
|
||||
}
|
||||
else {
|
||||
fs::File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?
|
||||
};
|
||||
|
||||
let data = toml::from_str(&file_contents)?;
|
||||
|
||||
Ok(Self {
|
||||
path,
|
||||
file,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_exists(id : Id, vault_folder : &path::Path) -> Result<path::PathBuf, error::Error> {
|
||||
let path = vault_folder.join("notes").join(format!("{}.toml", id));
|
||||
if path.exists() && path.is_file() {
|
||||
Ok(path)
|
||||
}
|
||||
else {
|
||||
Err(error::Error::Generic(format!("No task with the ID {} exists", colour::id(&id.to_string()))))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(self) -> Result<(), error::Error> {
|
||||
let Self {
|
||||
path,
|
||||
mut file,
|
||||
data,
|
||||
} = self;
|
||||
|
||||
file.set_len(0)?;
|
||||
file.seek(io::SeekFrom::Start(0))?;
|
||||
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<(), error::Error> {
|
||||
let Self {
|
||||
path,
|
||||
file,
|
||||
data,
|
||||
} = self;
|
||||
|
||||
mem::drop(file);
|
||||
fs::remove_file(&path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_by_id(id : Id, vault_folder : &path::Path) -> Result<(), error::Error> {
|
||||
let path = Task::check_exists(id, vault_folder)?;
|
||||
fs::remove_file(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
105
src/vault.rs
Normal file
105
src/vault.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::error;
|
||||
use crate::state;
|
||||
use crate::colour;
|
||||
use crate::config;
|
||||
|
||||
use std::fs;
|
||||
use std::path;
|
||||
|
||||
pub fn new(name : String, path : path::PathBuf, config : &mut config::Config) -> Result<(), error::Error> {
|
||||
|
||||
fn create_all_metadata(path : &path::Path) -> Result<(), error::Error> {
|
||||
fs::create_dir(path.join("notes"))?;
|
||||
let state = state::State::load(path)?;
|
||||
//state.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Configuration already contains a vault by the given name.
|
||||
if config.contains_name(&name) {
|
||||
Err(error::Error::Generic(format!("A vault named \"{}\" already exists", name)))
|
||||
}
|
||||
else if config.contains_path(&path) {
|
||||
Err(error::Error::Generic(format!("A vault at the path {:?} already exists", path)))
|
||||
}
|
||||
else {
|
||||
// Folder exists and contains data.
|
||||
if path.exists() && path.is_dir() && path.read_dir()?.next().is_some() {
|
||||
Err(error::Error::Generic(String::from("The specified folder already exists and contains other data, please provide a path to a new or empty folder")))
|
||||
}
|
||||
// Folder exists and is empty, so set up the vault metadata.
|
||||
else if path.exists() && path.is_dir() {
|
||||
|
||||
// Create the vault metadata.
|
||||
create_all_metadata(&path)?;
|
||||
|
||||
config.add(name, path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// Provided path is to a file, not a directory.
|
||||
else if path.exists() {
|
||||
Err(error::Error::Generic(String::from("The specified path already points to a file, please provide a path to a new or empty folder")))
|
||||
}
|
||||
// Path does not yet exist, and should be created.
|
||||
else {
|
||||
fs::create_dir_all(&path)?;
|
||||
|
||||
// Create the vault metadata.
|
||||
create_all_metadata(&path)?;
|
||||
|
||||
config.add(name, path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(name : String, path : path::PathBuf, config : &mut config::Config) -> Result<(), error::Error> {
|
||||
// Configuration already contains a vault by the given name.
|
||||
if config.contains_name(&name) {
|
||||
Err(error::Error::Generic(format!("A vault named \"{}\" already exists", name)))
|
||||
}
|
||||
else if config.contains_path(&path) {
|
||||
Err(error::Error::Generic(format!("A vault at the path {:?} is already set up", path)))
|
||||
}
|
||||
else {
|
||||
// Folder exists and contains data.
|
||||
if path.exists() && path.is_dir() {
|
||||
// Vault is missing required metadata files.
|
||||
if !path.join("notes").exists() {
|
||||
Err(error::Error::Generic(format!("Cannot connect the vault as it is missing the {} folder", colour::file("notes"))))
|
||||
}
|
||||
else if !path.join("state.toml").exists() {
|
||||
Err(error::Error::Generic(format!("Cannot connect the vault as it is missing the {} file", colour::file("state.toml"))))
|
||||
}
|
||||
// Required metadata exists, so the vault is connected.
|
||||
else {
|
||||
config.add(name, path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// Provided path is to a file, not a directory.
|
||||
else if path.exists() {
|
||||
Err(error::Error::Generic(String::from("The specified path points to a file, not a folder")))
|
||||
}
|
||||
// Path does not yet exist.
|
||||
else {
|
||||
Err(error::Error::Generic(format!("The path {:?} does not exist", path)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(name : &String, config : &mut config::Config) -> Result<(), error::Error> {
|
||||
config.remove(name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(name : &String, config : &mut config::Config) -> Result<(), error::Error> {
|
||||
let path = config.remove(name)?;
|
||||
trash::delete(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user