From 66dc324fad530043131860b742f84fa1cb77c104 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 20 May 2024 00:25:53 +0300 Subject: [PATCH 01/11] refactor(ark-cli): reorganize the code into folders Signed-off-by: Tarek --- ark-cli/Cargo.toml | 2 +- ark-cli/src/cli.rs | 11 + ark-cli/src/commands/backup.rs | 89 +++ ark-cli/src/commands/collisions.rs | 19 + ark-cli/src/commands/file/append.rs | 51 ++ ark-cli/src/commands/file/insert.rs | 51 ++ ark-cli/src/commands/file/mod.rs | 16 + ark-cli/src/commands/file/read.rs | 47 ++ .../src/commands/{file.rs => file/utils.rs} | 7 +- ark-cli/src/commands/link/create.rs | 39 ++ ark-cli/src/commands/link/load.rs | 26 + ark-cli/src/commands/link/mod.rs | 12 + .../src/commands/{link.rs => link/utils.rs} | 0 ark-cli/src/commands/list.rs | 338 ++++++++++ ark-cli/src/commands/mod.rs | 34 + ark-cli/src/commands/monitor.rs | 19 + ark-cli/src/commands/render.rs | 37 ++ ark-cli/src/commands/storage/list.rs | 51 ++ ark-cli/src/commands/storage/mod.rs | 9 + ark-cli/src/main.rs | 601 ++---------------- ark-cli/src/models/cli.rs | 220 ------- ark-cli/src/models/entry.rs | 9 - ark-cli/src/models/format.rs | 30 - ark-cli/src/models/mod.rs | 51 +- ark-cli/src/models/sort.rs | 7 - ark-cli/src/models/storage.rs | 25 +- 26 files changed, 951 insertions(+), 850 deletions(-) create mode 100644 ark-cli/src/cli.rs create mode 100644 ark-cli/src/commands/backup.rs create mode 100644 ark-cli/src/commands/collisions.rs create mode 100644 ark-cli/src/commands/file/append.rs create mode 100644 ark-cli/src/commands/file/insert.rs create mode 100644 ark-cli/src/commands/file/mod.rs create mode 100644 ark-cli/src/commands/file/read.rs rename ark-cli/src/commands/{file.rs => file/utils.rs} (95%) create mode 100644 ark-cli/src/commands/link/create.rs create mode 100644 ark-cli/src/commands/link/load.rs create mode 100644 ark-cli/src/commands/link/mod.rs rename ark-cli/src/commands/{link.rs => link/utils.rs} (100%) create mode 100644 ark-cli/src/commands/list.rs create mode 100644 ark-cli/src/commands/monitor.rs create mode 100644 ark-cli/src/commands/render.rs create mode 100644 ark-cli/src/commands/storage/list.rs create mode 100644 ark-cli/src/commands/storage/mod.rs delete mode 100644 ark-cli/src/models/cli.rs delete mode 100644 ark-cli/src/models/entry.rs delete mode 100644 ark-cli/src/models/format.rs delete mode 100644 ark-cli/src/models/sort.rs diff --git a/ark-cli/Cargo.toml b/ark-cli/Cargo.toml index d6550931..08655206 100644 --- a/ark-cli/Cargo.toml +++ b/ark-cli/Cargo.toml @@ -9,7 +9,7 @@ bench = false [dependencies] tokio = { version = "1.35.1", features = ["full"] } -clap = { version = "3.0.10", features = ["derive"] } +clap = { version = "4.5", features = ["derive"] } env_logger = "0.9.0" fs_extra = "1.2.0" home = "0.5.3" diff --git a/ark-cli/src/cli.rs b/ark-cli/src/cli.rs new file mode 100644 index 00000000..6a56d89b --- /dev/null +++ b/ark-cli/src/cli.rs @@ -0,0 +1,11 @@ +use crate::commands::Commands; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = "ark-cli")] +#[clap(about = "Manage ARK tag storages and indexes", long_about = None)] +pub struct Cli { + #[clap(subcommand)] + pub command: Commands, +} diff --git a/ark-cli/src/commands/backup.rs b/ark-cli/src/commands/backup.rs new file mode 100644 index 00000000..398220bd --- /dev/null +++ b/ark-cli/src/commands/backup.rs @@ -0,0 +1,89 @@ +use std::io::Write; +use std::path::PathBuf; + +use crate::{ + create_dir_all, dir, discover_roots, home_dir, storages_exists, timestamp, + AppError, CopyOptions, File, ARK_BACKUPS_PATH, ARK_FOLDER, + ROOTS_CFG_FILENAME, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "backup", about = "Backup the ark managed folder")] +pub struct Backup { + #[clap(value_parser, help = "Path to the root directory")] + roots_cfg: Option, +} + +impl Backup { + pub fn run(&self) -> Result<(), AppError> { + let timestamp = timestamp().as_secs(); + let backup_dir = home_dir() + .ok_or(AppError::HomeDirNotFound)? + .join(ARK_BACKUPS_PATH) + .join(timestamp.to_string()); + + if backup_dir.is_dir() { + println!("Wait at least 1 second, please!"); + std::process::exit(0) + } + + println!("Preparing backup:"); + let roots = discover_roots(&self.roots_cfg)?; + + let (valid, invalid): (Vec, Vec) = roots + .into_iter() + .partition(|root| storages_exists(root)); + + if !invalid.is_empty() { + println!("These folders don't contain any storages:"); + invalid + .into_iter() + .for_each(|root| println!("\t{}", root.display())); + } + + if valid.is_empty() { + println!("Nothing to backup. Bye!"); + std::process::exit(0) + } + + create_dir_all(&backup_dir).map_err(|_| { + AppError::BackupCreationError( + "Couldn't create backup directory!".to_owned(), + ) + })?; + + let mut roots_cfg_backup = + File::create(backup_dir.join(ROOTS_CFG_FILENAME))?; + + valid.iter().for_each(|root| { + let res = writeln!(roots_cfg_backup, "{}", root.display()); + if let Err(e) = res { + println!("Failed to write root to backup file: {}", e); + } + }); + + println!("Performing backups:"); + valid + .into_iter() + .enumerate() + .for_each(|(i, root)| { + println!("\tRoot {}", root.display()); + let storage_backup = backup_dir.join(i.to_string()); + + let mut options = CopyOptions::new(); + options.overwrite = true; + options.copy_inside = true; + + let result = + dir::copy(root.join(ARK_FOLDER), storage_backup, &options); + + if let Err(e) = result { + println!("\t\tFailed to copy storages!\n\t\t{}", e); + } + }); + + println!("Backup created:\n\t{}", backup_dir.display()); + + Ok(()) + } +} diff --git a/ark-cli/src/commands/collisions.rs b/ark-cli/src/commands/collisions.rs new file mode 100644 index 00000000..69c26c17 --- /dev/null +++ b/ark-cli/src/commands/collisions.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use crate::{monitor_index, AppError}; + +#[derive(Clone, Debug, clap::Args)] +#[clap( + name = "collisions", + about = "Find collisions in the ark managed folder" +)] +pub struct Collisions { + #[clap(value_parser, help = "Path to the root directory")] + root_dir: Option, +} + +impl Collisions { + pub fn run(&self) -> Result<(), AppError> { + monitor_index(&self.root_dir, None) + } +} diff --git a/ark-cli/src/commands/file/append.rs b/ark-cli/src/commands/file/append.rs new file mode 100644 index 00000000..c1ae62fd --- /dev/null +++ b/ark-cli/src/commands/file/append.rs @@ -0,0 +1,51 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use crate::{ + models::storage::Storage, models::storage::StorageType, translate_storage, + AppError, Format, ResourceId, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "append", about = "Append content to a resource")] +pub struct Append { + #[clap( + value_parser, + default_value = ".", + help = "Root directory of the ark managed folder" + )] + root_dir: PathBuf, + #[clap(help = "Storage name")] + storage: String, + #[clap(help = "ID of the resource to append to")] + id: String, + #[clap(help = "Content to append to the resource")] + content: String, + #[clap(short, long, value_enum, help = "Format of the resource")] + format: Option, + #[clap(short, long, value_enum, help = "Storage kind of the resource")] + kind: Option, +} + +impl Append { + pub fn run(&self) -> Result<(), AppError> { + let (file_path, storage_type) = + translate_storage(&Some(self.root_dir.to_owned()), &self.storage) + .ok_or(AppError::StorageNotFound(self.storage.to_owned()))?; + + let storage_type = storage_type.unwrap_or(match self.kind { + Some(t) => t, + None => StorageType::File, + }); + + let format = self.format.unwrap_or(Format::Raw); + + let mut storage = Storage::new(file_path, storage_type)?; + + let resource_id = ResourceId::from_str(&self.id)?; + + storage.append(resource_id, &self.content, format)?; + + Ok(()) + } +} diff --git a/ark-cli/src/commands/file/insert.rs b/ark-cli/src/commands/file/insert.rs new file mode 100644 index 00000000..d4cf98f2 --- /dev/null +++ b/ark-cli/src/commands/file/insert.rs @@ -0,0 +1,51 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use crate::{ + models::storage::Storage, models::storage::StorageType, translate_storage, + AppError, Format, ResourceId, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "insert", about = "Insert content into a resource")] +pub struct Insert { + #[clap( + value_parser, + default_value = ".", + help = "Root directory of the ark managed folder" + )] + root_dir: PathBuf, + #[clap(help = "Storage name")] + storage: String, + #[clap(help = "ID of the resource to append to")] + id: String, + #[clap(help = "Content to append to the resource")] + content: String, + #[clap(short, long, value_enum, help = "Format of the resource")] + format: Option, + #[clap(short, long, value_enum, help = "Storage kind of the resource")] + kind: Option, +} + +impl Insert { + pub fn run(&self) -> Result<(), AppError> { + let (file_path, storage_type) = + translate_storage(&Some(self.root_dir.to_owned()), &self.storage) + .ok_or(AppError::StorageNotFound(self.storage.to_owned()))?; + + let storage_type = storage_type.unwrap_or(match self.kind { + Some(t) => t, + None => StorageType::File, + }); + + let format = self.format.unwrap_or(Format::Raw); + + let mut storage = Storage::new(file_path, storage_type)?; + + let resource_id = ResourceId::from_str(&self.id)?; + + storage.insert(resource_id, &self.content, format)?; + + Ok(()) + } +} diff --git a/ark-cli/src/commands/file/mod.rs b/ark-cli/src/commands/file/mod.rs new file mode 100644 index 00000000..5cecfe74 --- /dev/null +++ b/ark-cli/src/commands/file/mod.rs @@ -0,0 +1,16 @@ +use clap::Subcommand; + +mod append; +mod insert; +mod read; +mod utils; + +/// Available commands for the `file` subcommand +#[derive(Subcommand, Debug)] +pub enum File { + Append(append::Append), + Insert(insert::Insert), + Read(read::Read), +} + +pub use utils::{file_append, file_insert, format_file, format_line}; diff --git a/ark-cli/src/commands/file/read.rs b/ark-cli/src/commands/file/read.rs new file mode 100644 index 00000000..6db7f18c --- /dev/null +++ b/ark-cli/src/commands/file/read.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use crate::{ + models::storage::Storage, models::storage::StorageType, translate_storage, + AppError, ResourceId, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "read", about = "Read content from a resource")] +pub struct Read { + #[clap( + value_parser, + default_value = ".", + help = "Root directory of the ark managed folder" + )] + root_dir: PathBuf, + #[clap(help = "Storage name")] + storage: String, + #[clap(help = "ID of the resource to append to")] + id: String, + #[clap(short, long, value_enum, help = "Storage kind of the resource")] + kind: Option, +} + +impl Read { + pub fn run(&self) -> Result<(), AppError> { + let (file_path, storage_type) = + translate_storage(&Some(self.root_dir.to_owned()), &self.storage) + .ok_or(AppError::StorageNotFound(self.storage.to_owned()))?; + + let storage_type = storage_type.unwrap_or(match self.kind { + Some(t) => t, + None => StorageType::File, + }); + + let mut storage = Storage::new(file_path, storage_type)?; + + let resource_id = ResourceId::from_str(&self.id)?; + + let output = storage.read(resource_id)?; + + println!("{}", output); + + Ok(()) + } +} diff --git a/ark-cli/src/commands/file.rs b/ark-cli/src/commands/file/utils.rs similarity index 95% rename from ark-cli/src/commands/file.rs rename to ark-cli/src/commands/file/utils.rs index 4a5ec8f8..8a3c4048 100644 --- a/ark-cli/src/commands/file.rs +++ b/ark-cli/src/commands/file/utils.rs @@ -1,5 +1,6 @@ use crate::error::AppError; -use crate::models::{format, format::Format}; +use crate::models::key_value_to_str; +use crate::models::Format; use data_error::Result as ArklibResult; use fs_atomic_versions::atomic::{modify, modify_json, AtomicFile}; @@ -15,7 +16,7 @@ pub fn file_append( combined_vec })?), Format::KeyValue => { - let values = format::key_value_to_str(content)?; + let values = key_value_to_str(content)?; Ok(append_json(atomic_file, values.to_vec())?) } @@ -32,7 +33,7 @@ pub fn file_insert( Ok(modify(atomic_file, |_| content.as_bytes().to_vec())?) } Format::KeyValue => { - let values = format::key_value_to_str(content)?; + let values = key_value_to_str(content)?; modify_json( atomic_file, diff --git a/ark-cli/src/commands/link/create.rs b/ark-cli/src/commands/link/create.rs new file mode 100644 index 00000000..1164e26b --- /dev/null +++ b/ark-cli/src/commands/link/create.rs @@ -0,0 +1,39 @@ +use std::path::PathBuf; + +use crate::{commands::link::utils::create_link, provide_root, AppError}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "create", about = "Create a new link")] +pub struct Create { + #[clap(value_parser, help = "Root directory of the ark managed folder")] + root_dir: Option, + #[clap(help = "URL of the link")] + url: Option, + #[clap(help = "Title of the link")] + title: Option, + #[clap(help = "Description of the link")] + desc: Option, +} + +impl Create { + pub async fn run(&self) -> Result<(), AppError> { + let root = provide_root(&self.root_dir)?; + let url = self.url.as_ref().ok_or_else(|| { + AppError::LinkCreationError("Url was not provided".to_owned()) + })?; + let title = self.title.as_ref().ok_or_else(|| { + AppError::LinkCreationError("Title was not provided".to_owned()) + })?; + + println!("Saving link..."); + + match create_link(&root, url, title, self.desc.to_owned()).await { + Ok(_) => { + println!("Link saved successfully!"); + } + Err(e) => println!("{}", e), + } + + Ok(()) + } +} diff --git a/ark-cli/src/commands/link/load.rs b/ark-cli/src/commands/link/load.rs new file mode 100644 index 00000000..bee51af6 --- /dev/null +++ b/ark-cli/src/commands/link/load.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; + +use crate::{ + commands::link::utils::load_link, provide_root, AppError, ResourceId, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "create", about = "Create a new link")] +pub struct Load { + #[clap(value_parser, help = "Root directory of the ark managed folder")] + root_dir: Option, + #[clap(value_parser, help = "Path to the file to load")] + file_path: Option, + #[clap(help = "ID of the resource to load")] + id: Option, +} + +impl Load { + pub fn run(&self) -> Result<(), AppError> { + let root = provide_root(&self.root_dir)?; + let link = load_link(&root, &self.file_path, &self.id)?; + println!("Link data:\n{:?}", link); + + Ok(()) + } +} diff --git a/ark-cli/src/commands/link/mod.rs b/ark-cli/src/commands/link/mod.rs new file mode 100644 index 00000000..8c47efd0 --- /dev/null +++ b/ark-cli/src/commands/link/mod.rs @@ -0,0 +1,12 @@ +use clap::Subcommand; + +pub mod create; +mod load; +mod utils; + +/// Available commands for the `link` subcommand +#[derive(Subcommand, Debug)] +pub enum Link { + Create(create::Create), + Load(load::Load), +} diff --git a/ark-cli/src/commands/link.rs b/ark-cli/src/commands/link/utils.rs similarity index 100% rename from ark-cli/src/commands/link.rs rename to ark-cli/src/commands/link/utils.rs diff --git a/ark-cli/src/commands/list.rs b/ark-cli/src/commands/list.rs new file mode 100644 index 00000000..4d74e5c8 --- /dev/null +++ b/ark-cli/src/commands/list.rs @@ -0,0 +1,338 @@ +use std::io::Read; +use std::path::PathBuf; + +use crate::{ + provide_index, provide_root, read_storage_value, AppError, DateTime, + EntryOutput, File, Sort, StorageEntry, Utc, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "list", about = "List the resources in the ark managed folder")] +pub struct List { + #[clap(value_parser, help = "The path to the root directory")] + root_dir: Option, + #[clap(long, short = 'i', long = "id", action, help = "Show entries' IDs")] + entry_id: bool, + #[clap( + long, + short = 'p', + long = "path", + action, + help = "Show entries' paths" + )] + entry_path: bool, + #[clap( + long, + short = 'l', + long = "link", + action, + help = "Show entries' links" + )] + entry_link: bool, + #[clap(long, short, action, help = "Show entries' last modified times")] + modified: bool, + #[clap(long, short, action, help = "Show entries' tags")] + tags: bool, + #[clap(long, short, action, help = "Show entries' scores")] + scores: bool, + #[clap(long, value_enum, help = "Sort the entries by score")] + sort: Option, + #[clap(long, help = "Filter the entries by tag")] + filter: Option, +} + +impl List { + /// Get the entry output format + /// Default to Id + pub fn entry(&self) -> Result { + // Link can only be used alone + if self.entry_link { + if self.entry_id || self.entry_path { + return Err(AppError::InvalidEntryOption)?; + } else { + return Ok(EntryOutput::Link); + } + } + + if self.entry_id && self.entry_path { + Ok(EntryOutput::Both) + } else if self.entry_path { + Ok(EntryOutput::Path) + } else { + // Default to id + Ok(EntryOutput::Id) + } + } + + pub fn run(&self) -> Result<(), AppError> { + let root = provide_root(&self.root_dir)?; + let entry_output = self.entry()?; + + let mut storage_entries: Vec = provide_index(&root) + .map_err(|_| { + AppError::IndexError("Could not provide index".to_owned()) + })? + .read() + .map_err(|_| { + AppError::IndexError("Could not read index".to_owned()) + })? + .path2id + .iter() + .filter_map(|(path, resource)| { + let tags = if self.tags { + Some( + read_storage_value( + &root, + "tags", + &resource.id.to_string(), + &None, + ) + .map_or(vec![], |s| { + s.split(',') + .map(|s| s.trim().to_string()) + .collect::>() + }), + ) + } else { + None + }; + + let scores = if self.scores { + Some( + read_storage_value( + &root, + "scores", + &resource.id.to_string(), + &None, + ) + .map_or(0, |s| s.parse::().unwrap_or(0)), + ) + } else { + None + }; + + let datetime = if self.modified { + let format = "%b %e %H:%M %Y"; + Some( + DateTime::::from(resource.modified) + .format(format) + .to_string(), + ) + } else { + None + }; + + let (path, resource, content) = match entry_output { + EntryOutput::Both => ( + Some(path.to_owned().into_path_buf()), + Some(resource.id), + None, + ), + EntryOutput::Path => { + (Some(path.to_owned().into_path_buf()), None, None) + } + EntryOutput::Id => (None, Some(resource.id), None), + EntryOutput::Link => match File::open(path) { + Ok(mut file) => { + let mut contents = String::new(); + match file.read_to_string(&mut contents) { + Ok(_) => (None, None, Some(contents)), + Err(_) => return None, + } + } + Err(_) => return None, + }, + }; + + Some(StorageEntry { + path, + resource, + content, + tags, + scores, + datetime, + }) + }) + .collect::>(); + + match self.sort { + Some(Sort::Asc) => { + storage_entries.sort_by(|a, b| a.datetime.cmp(&b.datetime)) + } + + Some(Sort::Desc) => { + storage_entries.sort_by(|a, b| b.datetime.cmp(&a.datetime)) + } + None => (), + }; + + if let Some(filter) = &self.filter { + storage_entries.retain(|entry| { + entry + .tags + .as_ref() + .map(|tags| tags.contains(&filter)) + .unwrap_or(false) + }); + } + + let no_tags = "NO_TAGS"; + let no_scores = "NO_SCORE"; + + let longest_path = storage_entries + .iter() + .map(|entry| { + if let Some(path) = entry.path.as_ref() { + path.display().to_string().len() + } else { + 0 + } + }) + .max_by(|a, b| a.cmp(b)) + .unwrap_or(0); + + let longest_id = storage_entries.iter().fold(0, |acc, entry| { + if let Some(resource) = &entry.resource { + let id_len = resource.to_string().len(); + if id_len > acc { + id_len + } else { + acc + } + } else { + acc + } + }); + + let longest_tags = storage_entries.iter().fold(0, |acc, entry| { + let tags_len = entry + .tags + .as_ref() + .map(|tags| { + if tags.is_empty() { + no_tags.len() + } else { + tags.join(", ").len() + } + }) + .unwrap_or(0); + if tags_len > acc { + tags_len + } else { + acc + } + }); + + let longest_scores = storage_entries.iter().fold(0, |acc, entry| { + let scores_len = entry + .scores + .as_ref() + .map(|score| { + if *score == 0 { + no_scores.len() + } else { + score.to_string().len() + } + }) + .unwrap_or(0); + if scores_len > acc { + scores_len + } else { + acc + } + }); + + let longest_datetime = storage_entries.iter().fold(0, |acc, entry| { + let datetime_len = entry + .datetime + .as_ref() + .map(|datetime| datetime.len()) + .unwrap_or(0); + if datetime_len > acc { + datetime_len + } else { + acc + } + }); + + let longest_content = storage_entries.iter().fold(0, |acc, entry| { + let content_len = entry + .content + .as_ref() + .map(|content| content.len()) + .unwrap_or(0); + if content_len > acc { + content_len + } else { + acc + } + }); + + for entry in &storage_entries { + let mut output = String::new(); + + if let Some(content) = &entry.content { + output.push_str(&format!( + "{:width$} ", + content, + width = longest_content + )); + } + + if let Some(path) = &entry.path { + output.push_str(&format!( + "{:width$} ", + path.display(), + width = longest_path + )); + } + + if let Some(resource) = &entry.resource { + output.push_str(&format!( + "{:width$} ", + resource.to_string(), + width = longest_id + )); + } + + if let Some(tags) = &entry.tags { + let tags_out = if tags.is_empty() { + no_tags.to_owned() + } else { + tags.join(", ") + }; + + output.push_str(&format!( + "{:width$} ", + tags_out, + width = longest_tags + )); + } + + if let Some(scores) = &entry.scores { + let scores_out = if *scores == 0 { + no_scores.to_owned() + } else { + scores.to_string() + }; + + output.push_str(&format!( + "{:width$} ", + scores_out, + width = longest_scores + )); + } + + if let Some(datetime) = &entry.datetime { + output.push_str(&format!( + "{:width$} ", + datetime, + width = longest_datetime + )); + } + + println!("{}", output); + } + Ok(()) + } +} diff --git a/ark-cli/src/commands/mod.rs b/ark-cli/src/commands/mod.rs index 7ee11691..eab0cc1a 100644 --- a/ark-cli/src/commands/mod.rs +++ b/ark-cli/src/commands/mod.rs @@ -1,2 +1,36 @@ +use clap::Subcommand; + +mod backup; +mod collisions; pub mod file; pub mod link; +mod list; +mod monitor; +mod render; +pub mod storage; + +pub use file::{file_append, file_insert, format_file, format_line}; + +#[derive(Debug, Subcommand)] +pub enum Commands { + Backup(backup::Backup), + Collisions(collisions::Collisions), + Monitor(monitor::Monitor), + Render(render::Render), + List(list::List), + #[command(about = "Manage links")] + Link { + #[clap(subcommand)] + subcommand: link::Link, + }, + #[command(about = "Manage files")] + File { + #[clap(subcommand)] + subcommand: file::File, + }, + #[command(about = "Manage storage")] + Storage { + #[clap(subcommand)] + subcommand: storage::Storage, + }, +} diff --git a/ark-cli/src/commands/monitor.rs b/ark-cli/src/commands/monitor.rs new file mode 100644 index 00000000..1ad1c3df --- /dev/null +++ b/ark-cli/src/commands/monitor.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use crate::{monitor_index, AppError}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "monitor", about = "Monitor the ark managed folder")] +pub struct Monitor { + #[clap(value_parser, help = "Path to the root directory")] + root_dir: Option, + #[clap(help = "Interval to check for changes")] + interval: Option, +} + +impl Monitor { + pub fn run(&self) -> Result<(), AppError> { + let millis = self.interval.unwrap_or(1000); + monitor_index(&self.root_dir, Some(millis)) + } +} diff --git a/ark-cli/src/commands/render.rs b/ark-cli/src/commands/render.rs new file mode 100644 index 00000000..cba8bbd5 --- /dev/null +++ b/ark-cli/src/commands/render.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +use crate::{render_preview_page, AppError, File, PDFQuality}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "render", about = "Render a PDF file to an image")] +pub struct Render { + #[clap(value_parser, help = "The path to the file to render")] + path: Option, + #[clap(help = "The quality of the rendering")] + quality: Option, +} + +impl Render { + pub fn run(&self) -> Result<(), AppError> { + let filepath = self.path.to_owned().unwrap(); + let quality = match self.quality.to_owned().unwrap().as_str() { + "high" => Ok(PDFQuality::High), + "medium" => Ok(PDFQuality::Medium), + "low" => Ok(PDFQuality::Low), + _ => Err(AppError::InvalidRenderOption), + }?; + let buf = File::open(&filepath).unwrap(); + let dest_path = filepath.with_file_name( + filepath + .file_stem() + .unwrap() + .to_str() + .unwrap() + .to_owned() + + ".png", + ); + let img = render_preview_page(buf, quality); + img.save(dest_path).unwrap(); + Ok(()) + } +} diff --git a/ark-cli/src/commands/storage/list.rs b/ark-cli/src/commands/storage/list.rs new file mode 100644 index 00000000..0a6f3873 --- /dev/null +++ b/ark-cli/src/commands/storage/list.rs @@ -0,0 +1,51 @@ +use std::path::PathBuf; + +use crate::{ + models::storage::Storage, models::storage::StorageType, translate_storage, + AppError, +}; + +#[derive(Clone, Debug, clap::Args)] +#[clap(name = "list", about = "List resources in a storage")] +pub struct List { + #[clap(value_parser, help = "Root directory of the ark managed folder")] + root_dir: Option, + #[clap(help = "Storage name")] + storage: Option, + #[clap(short, long, help = "TODO: what is this flag for?")] + versions: Option, + #[clap(short, long, value_enum, help = "Storage kind of the resource")] + kind: Option, +} + +impl List { + pub fn run(&self) -> Result<(), AppError> { + let storage = + self.storage + .as_ref() + .ok_or(AppError::StorageCreationError( + "Storage was not provided".to_owned(), + ))?; + + let versions = self.versions.unwrap_or(false); + + let (file_path, storage_type) = + translate_storage(&self.root_dir, storage) + .ok_or(AppError::StorageNotFound(storage.to_owned()))?; + + let storage_type = storage_type.unwrap_or(match self.kind { + Some(t) => t, + None => StorageType::File, + }); + + let mut storage = Storage::new(file_path, storage_type)?; + + storage.load()?; + + let output = storage.list(versions)?; + + println!("{}", output); + + Ok(()) + } +} diff --git a/ark-cli/src/commands/storage/mod.rs b/ark-cli/src/commands/storage/mod.rs new file mode 100644 index 00000000..ed5edbb5 --- /dev/null +++ b/ark-cli/src/commands/storage/mod.rs @@ -0,0 +1,9 @@ +use clap::Subcommand; + +mod list; + +/// Available commands for the `storage` subcommand +#[derive(Subcommand, Debug)] +pub enum Storage { + List(list::List), +} diff --git a/ark-cli/src/main.rs b/ark-cli/src/main.rs index a31cbc71..3a58e556 100644 --- a/ark-cli/src/main.rs +++ b/ark-cli/src/main.rs @@ -1,7 +1,5 @@ use std::fs::{create_dir_all, File}; -use std::io::{Read, Write}; use std::path::PathBuf; -use std::str::FromStr; use crate::index_registrar::provide_index; use data_pdf::{render_preview_page, PDFQuality}; @@ -15,20 +13,24 @@ pub(crate) use dev_hash::Crc32 as ResourceId; use fs_atomic_versions::app_id; use fs_storage::ARK_FOLDER; +use anyhow::Result; + use chrono::prelude::DateTime; use chrono::Utc; -use clap::Parser; +use clap::CommandFactory; +use clap::FromArgMatches; use fs_extra::dir::{self, CopyOptions}; use home::home_dir; -use crate::models::cli::{Command, FileCommand, Link, StorageCommand}; -use crate::models::entry::EntryOutput; -use crate::models::format::Format; -use crate::models::sort::Sort; -use crate::models::storage::{Storage, StorageType}; +use crate::cli::Cli; +use crate::commands::Commands::Link; +use crate::commands::Commands::*; +use crate::models::EntryOutput; +use crate::models::Format; +use crate::models::Sort; use crate::error::AppError; @@ -37,6 +39,7 @@ use util::{ storages_exists, timestamp, translate_storage, }; +mod cli; mod commands; mod error; mod index_registrar; @@ -56,569 +59,53 @@ struct StorageEntry { datetime: Option, } +async fn run() -> Result<()> { + let matches = Cli::command().get_matches(); + let cli = Cli::from_arg_matches(&matches)?; + match cli.command { + Backup(backup) => backup.run()?, + Collisions(collisions) => collisions.run()?, + Monitor(monitor) => monitor.run()?, + Render(render) => render.run()?, + List(list) => list.run()?, + Link { subcommand } => match subcommand { + crate::commands::link::Link::Create(create) => create.run().await?, + crate::commands::link::Link::Load(load) => load.run()?, + }, + crate::commands::Commands::File { subcommand } => match subcommand { + crate::commands::file::File::Append(append) => append.run()?, + crate::commands::file::File::Insert(insert) => insert.run()?, + crate::commands::file::File::Read(read) => read.run()?, + }, + crate::commands::Commands::Storage { subcommand } => match subcommand { + crate::commands::storage::Storage::List(list) => list.run()?, + }, + }; + + Ok(()) +} + #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::init(); - - let args = models::cli::Cli::parse(); + env_logger::init_from_env( + env_logger::Env::default().default_filter_or("info"), + ); let app_id_dir = home_dir().ok_or(AppError::HomeDirNotFound)?; - let ark_dir = app_id_dir.join(".ark"); - if !ark_dir.exists() { std::fs::create_dir(&ark_dir) .map_err(|e| AppError::ArkDirectoryCreationError(e.to_string()))?; } println!("Loading app id at {}...", ark_dir.display()); - let _ = app_id::load(ark_dir) .map_err(|e| AppError::AppIdLoadError(e.to_string()))?; - match &args.command { - Command::List { - entry_id: _, - entry_path: _, - entry_link: _, - - root_dir, - modified, - tags, - scores, - sort, - filter, - } => { - let root = provide_root(root_dir)?; - let entry_output = &args.command.entry()?; - - let mut storage_entries: Vec = provide_index(&root) - .map_err(|_| { - AppError::IndexError("Could not provide index".to_owned()) - })? - .read() - .map_err(|_| { - AppError::IndexError("Could not read index".to_owned()) - })? - .path2id - .iter() - .filter_map(|(path, resource)| { - let tags = if *tags { - Some( - read_storage_value( - &root, - "tags", - &resource.id.to_string(), - &None, - ) - .map_or(vec![], |s| { - s.split(',') - .map(|s| s.trim().to_string()) - .collect::>() - }), - ) - } else { - None - }; - - let scores = if *scores { - Some( - read_storage_value( - &root, - "scores", - &resource.id.to_string(), - &None, - ) - .map_or(0, |s| s.parse::().unwrap_or(0)), - ) - } else { - None - }; - - let datetime = if *modified { - let format = "%b %e %H:%M %Y"; - Some( - DateTime::::from(resource.modified) - .format(format) - .to_string(), - ) - } else { - None - }; - - let (path, resource, content) = match entry_output { - EntryOutput::Both => ( - Some(path.to_owned().into_path_buf()), - Some(resource.clone().id), - None, - ), - EntryOutput::Path => { - (Some(path.to_owned().into_path_buf()), None, None) - } - EntryOutput::Id => { - (None, Some(resource.clone().id), None) - } - EntryOutput::Link => match File::open(path) { - Ok(mut file) => { - let mut contents = String::new(); - match file.read_to_string(&mut contents) { - Ok(_) => (None, None, Some(contents)), - Err(_) => return None, - } - } - Err(_) => return None, - }, - }; - - Some(StorageEntry { - path, - resource, - content, - tags, - scores, - datetime, - }) - }) - .collect::>(); - - match sort { - Some(Sort::Asc) => { - storage_entries.sort_by(|a, b| a.datetime.cmp(&b.datetime)) - } - - Some(Sort::Desc) => { - storage_entries.sort_by(|a, b| b.datetime.cmp(&a.datetime)) - } - None => (), - }; - - if let Some(filter) = filter { - storage_entries.retain(|entry| { - entry - .tags - .as_ref() - .map(|tags| tags.contains(filter)) - .unwrap_or(false) - }); - } - - let no_tags = "NO_TAGS"; - let no_scores = "NO_SCORE"; - - let longest_path = storage_entries - .iter() - .map(|entry| { - if let Some(path) = entry.path.as_ref() { - path.display().to_string().len() - } else { - 0 - } - }) - .max_by(|a, b| a.cmp(b)) - .unwrap_or(0); - - let longest_id = storage_entries.iter().fold(0, |acc, entry| { - if let Some(resource) = &entry.resource { - let id_len = resource.to_string().len(); - if id_len > acc { - id_len - } else { - acc - } - } else { - acc - } - }); - - let longest_tags = storage_entries.iter().fold(0, |acc, entry| { - let tags_len = entry - .tags - .as_ref() - .map(|tags| { - if tags.is_empty() { - no_tags.len() - } else { - tags.join(", ").len() - } - }) - .unwrap_or(0); - if tags_len > acc { - tags_len - } else { - acc - } - }); - - let longest_scores = - storage_entries.iter().fold(0, |acc, entry| { - let scores_len = entry - .scores - .as_ref() - .map(|score| { - if *score == 0 { - no_scores.len() - } else { - score.to_string().len() - } - }) - .unwrap_or(0); - if scores_len > acc { - scores_len - } else { - acc - } - }); - - let longest_datetime = - storage_entries.iter().fold(0, |acc, entry| { - let datetime_len = entry - .datetime - .as_ref() - .map(|datetime| datetime.len()) - .unwrap_or(0); - if datetime_len > acc { - datetime_len - } else { - acc - } - }); - - let longest_content = - storage_entries.iter().fold(0, |acc, entry| { - let content_len = entry - .content - .as_ref() - .map(|content| content.len()) - .unwrap_or(0); - if content_len > acc { - content_len - } else { - acc - } - }); - - for entry in &storage_entries { - let mut output = String::new(); - - if let Some(content) = &entry.content { - output.push_str(&format!( - "{:width$} ", - content, - width = longest_content - )); - } - - if let Some(path) = &entry.path { - output.push_str(&format!( - "{:width$} ", - path.display(), - width = longest_path - )); - } - - if let Some(resource) = &entry.resource { - output.push_str(&format!( - "{:width$} ", - resource.to_string(), - width = longest_id - )); - } - - if let Some(tags) = &entry.tags { - let tags_out = if tags.is_empty() { - no_tags.to_owned() - } else { - tags.join(", ") - }; - - output.push_str(&format!( - "{:width$} ", - tags_out, - width = longest_tags - )); - } - - if let Some(scores) = &entry.scores { - let scores_out = if *scores == 0 { - no_scores.to_owned() - } else { - scores.to_string() - }; - - output.push_str(&format!( - "{:width$} ", - scores_out, - width = longest_scores - )); - } - - if let Some(datetime) = &entry.datetime { - output.push_str(&format!( - "{:width$} ", - datetime, - width = longest_datetime - )); - } - - println!("{}", output); - } - } - Command::Backup { roots_cfg } => { - let timestamp = timestamp().as_secs(); - let backup_dir = home_dir() - .ok_or(AppError::HomeDirNotFound)? - .join(ARK_BACKUPS_PATH) - .join(timestamp.to_string()); - - if backup_dir.is_dir() { - println!("Wait at least 1 second, please!"); - std::process::exit(0) - } - - println!("Preparing backup:"); - let roots = discover_roots(roots_cfg)?; - - let (valid, invalid): (Vec, Vec) = roots - .into_iter() - .partition(|root| storages_exists(root)); - - if !invalid.is_empty() { - println!("These folders don't contain any storages:"); - invalid - .into_iter() - .for_each(|root| println!("\t{}", root.display())); - } - - if valid.is_empty() { - println!("Nothing to backup. Bye!"); - std::process::exit(0) - } - - create_dir_all(&backup_dir).map_err(|_| { - AppError::BackupCreationError( - "Couldn't create backup directory!".to_owned(), - ) - })?; - - let mut roots_cfg_backup = - File::create(backup_dir.join(ROOTS_CFG_FILENAME))?; - - valid.iter().for_each(|root| { - let res = writeln!(roots_cfg_backup, "{}", root.display()); - if let Err(e) = res { - println!("Failed to write root to backup file: {}", e); - } - }); - - println!("Performing backups:"); - valid - .into_iter() - .enumerate() - .for_each(|(i, root)| { - println!("\tRoot {}", root.display()); - let storage_backup = backup_dir.join(i.to_string()); - - let mut options = CopyOptions::new(); - options.overwrite = true; - options.copy_inside = true; - - let result = dir::copy( - root.join(ARK_FOLDER), - storage_backup, - &options, - ); - - if let Err(e) = result { - println!("\t\tFailed to copy storages!\n\t\t{}", e); - } - }); - - println!("Backup created:\n\t{}", backup_dir.display()); - } - Command::Collisions { root_dir } => monitor_index(root_dir, None)?, - Command::Monitor { root_dir, interval } => { - let millis = interval.unwrap_or(1000); - monitor_index(root_dir, Some(millis))? - } - Command::Render { path, quality } => { - let filepath = path.to_owned().unwrap(); - let quality = match quality.to_owned().unwrap().as_str() { - "high" => Ok(PDFQuality::High), - "medium" => Ok(PDFQuality::Medium), - "low" => Ok(PDFQuality::Low), - _ => Err(AppError::InvalidRenderOption), - }?; - let buf = File::open(&filepath).unwrap(); - let dest_path = filepath.with_file_name( - filepath - .file_stem() - .unwrap() - .to_str() - .unwrap() - .to_owned() - + ".png", - ); - let img = render_preview_page(buf, quality); - img.save(dest_path).unwrap(); - } - Command::Link(link) => match &link { - Link::Create { - root_dir, - url, - title, - desc, - } => { - let root = provide_root(root_dir)?; - let url = url.as_ref().ok_or_else(|| { - AppError::LinkCreationError( - "Url was not provided".to_owned(), - ) - })?; - let title = title.as_ref().ok_or_else(|| { - AppError::LinkCreationError( - "Title was not provided".to_owned(), - ) - })?; - - println!("Saving link..."); - - match commands::link::create_link( - &root, - url, - title, - desc.to_owned(), - ) - .await - { - Ok(_) => { - println!("Link saved successfully!"); - } - Err(e) => println!("{}", e), - } - } - - Link::Load { - root_dir, - file_path, - id, - } => { - let root = provide_root(root_dir)?; - let link = commands::link::load_link(&root, file_path, id)?; - println!("Link data:\n{:?}", link); - } - }, - Command::File(file) => match &file { - FileCommand::Append { - root_dir, - storage, - id, - content, - format, - type_, - } => { - let (file_path, storage_type) = - translate_storage(&Some(root_dir.to_owned()), storage) - .ok_or(AppError::StorageNotFound(storage.to_owned()))?; - - let storage_type = storage_type.unwrap_or(match type_ { - Some(t) => *t, - None => StorageType::File, - }); - - let format = format.unwrap_or(Format::Raw); - - let mut storage = Storage::new(file_path, storage_type)?; - - let resource_id = ResourceId::from_str(id)?; - - storage.append(resource_id, content, format)?; - } - - FileCommand::Insert { - root_dir, - storage, - id, - content, - format, - type_, - } => { - let (file_path, storage_type) = - translate_storage(&Some(root_dir.to_owned()), storage) - .ok_or(AppError::StorageNotFound(storage.to_owned()))?; - - let storage_type = storage_type.unwrap_or(match type_ { - Some(t) => *t, - None => StorageType::File, - }); - - let format = format.unwrap_or(Format::Raw); - - let mut storage = Storage::new(file_path, storage_type)?; - - let resource_id = ResourceId::from_str(id)?; - - storage.insert(resource_id, content, format)?; - } - - FileCommand::Read { - root_dir, - storage, - id, - type_, - } => { - let (file_path, storage_type) = - translate_storage(&Some(root_dir.to_owned()), storage) - .ok_or(AppError::StorageNotFound(storage.to_owned()))?; - - let storage_type = storage_type.unwrap_or(match type_ { - Some(t) => *t, - None => StorageType::File, - }); - - let mut storage = Storage::new(file_path, storage_type)?; - - let resource_id = ResourceId::from_str(id)?; - - let output = storage.read(resource_id)?; - - println!("{}", output); - } - }, - Command::Storage(cmd) => match &cmd { - StorageCommand::List { - root_dir, - storage, - type_, - versions, - } => { - let storage = - storage - .as_ref() - .ok_or(AppError::StorageCreationError( - "Storage was not provided".to_owned(), - ))?; - - let versions = versions.unwrap_or(false); - - let (file_path, storage_type) = - translate_storage(root_dir, storage) - .ok_or(AppError::StorageNotFound(storage.to_owned()))?; - - let storage_type = storage_type.unwrap_or(match type_ { - Some(t) => *t, - None => StorageType::File, - }); - - let mut storage = Storage::new(file_path, storage_type)?; - - storage.load()?; - - let output = storage.list(versions)?; - - println!("{}", output); - } - }, - }; + if let Err(err) = run().await { + eprintln!("Error: {:#}", err); + std::process::exit(1); + } Ok(()) } diff --git a/ark-cli/src/models/cli.rs b/ark-cli/src/models/cli.rs deleted file mode 100644 index 83e52e18..00000000 --- a/ark-cli/src/models/cli.rs +++ /dev/null @@ -1,220 +0,0 @@ -use crate::AppError; -use anyhow::Result; -use std::path::PathBuf; - -use crate::ResourceId; -use clap::{Parser, Subcommand}; - -use super::{ - entry::EntryOutput, format::Format, sort::Sort, storage::StorageType, -}; - -#[derive(Parser, Debug)] -#[clap(name = "ark-cli")] -#[clap(about = "Manage ARK tag storages and indexes", long_about = None)] -pub struct Cli { - #[clap(subcommand)] - pub command: Command, -} - -#[derive(Subcommand, Debug)] -pub enum Command { - Backup { - #[clap(parse(from_os_str))] - roots_cfg: Option, - }, - - Collisions { - #[clap(parse(from_os_str))] - root_dir: Option, - }, - - Monitor { - #[clap(parse(from_os_str))] - root_dir: Option, - interval: Option, - }, - - Render { - #[clap(parse(from_os_str))] - path: Option, - quality: Option, - }, - - List { - #[clap(parse(from_os_str))] - root_dir: Option, - - #[clap( - long, - short = 'i', - long = "id", - action, - help = "Show entries' IDs" - )] - entry_id: bool, - - #[clap( - long, - short = 'p', - long = "path", - action, - help = "Show entries' paths" - )] - entry_path: bool, - - #[clap( - long, - short = 'l', - long = "link", - action, - help = "Show entries' links" - )] - entry_link: bool, - - #[clap(long, short, action)] - modified: bool, - - #[clap(long, short, action)] - tags: bool, - - #[clap(long, short, action)] - scores: bool, - - #[clap(long, value_enum)] - sort: Option, - - #[clap(long)] - filter: Option, - }, - - #[clap(subcommand)] - Link(Link), - - #[clap(subcommand)] - File(FileCommand), - - #[clap(subcommand)] - Storage(StorageCommand), -} - -impl Command { - /// Get the entry output format - /// Default to Id - pub fn entry(&self) -> Result { - match self { - Command::List { - entry_id, - entry_path, - entry_link, - .. - } => { - // Link can only be used alone - if *entry_link { - if *entry_id || *entry_path { - return Err(AppError::InvalidEntryOption)?; - } else { - return Ok(EntryOutput::Link); - } - } - - if *entry_id && *entry_path { - Ok(EntryOutput::Both) - } else if *entry_path { - Ok(EntryOutput::Path) - } else { - // Default to id - Ok(EntryOutput::Id) - } - } - _ => Ok(EntryOutput::Id), - } - } -} - -#[derive(Subcommand, Debug)] -pub enum StorageCommand { - List { - #[clap(parse(from_os_str))] - root_dir: Option, - - storage: Option, - - #[clap(short, long)] - versions: Option, - - #[clap(short, long, value_enum)] - type_: Option, - }, -} - -#[derive(Subcommand, Debug)] -pub enum FileCommand { - Append { - #[clap(parse(from_os_str))] - root_dir: PathBuf, - - storage: String, - - id: String, - - content: String, - - #[clap(short, long, value_enum)] - format: Option, - - #[clap(short, long, value_enum)] - type_: Option, - }, - - Insert { - #[clap(parse(from_os_str))] - root_dir: PathBuf, - - storage: String, - - id: String, - - content: String, - - #[clap(short, long, value_enum)] - format: Option, - - #[clap(short, long, value_enum)] - type_: Option, - }, - - Read { - #[clap(parse(from_os_str))] - root_dir: PathBuf, - - storage: String, - - id: String, - - #[clap(short, long, value_enum)] - type_: Option, - }, -} - -#[derive(Subcommand, Debug)] -pub enum Link { - Create { - #[clap(parse(from_os_str))] - root_dir: Option, - - url: Option, - title: Option, - desc: Option, - }, - - Load { - #[clap(parse(from_os_str))] - root_dir: Option, - - #[clap(parse(from_os_str))] - file_path: Option, - - id: Option, - }, -} diff --git a/ark-cli/src/models/entry.rs b/ark-cli/src/models/entry.rs deleted file mode 100644 index 7b75fd9b..00000000 --- a/ark-cli/src/models/entry.rs +++ /dev/null @@ -1,9 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug, Clone, Copy, PartialEq, Eq)] -pub enum EntryOutput { - Link, - Id, - Path, - Both, -} diff --git a/ark-cli/src/models/format.rs b/ark-cli/src/models/format.rs deleted file mode 100644 index 31c69f5e..00000000 --- a/ark-cli/src/models/format.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::error::InlineJsonParseError; - -#[derive(Debug, Clone, Copy, clap::ValueEnum)] -pub enum Format { - #[clap(name = "json")] - KeyValue, - #[clap(name = "raw")] - Raw, -} - -pub fn key_value_to_str( - s: &str, -) -> Result, InlineJsonParseError> { - let pairs: Vec<&str> = s.split(',').collect(); - - let mut values = Vec::new(); - - for pair in pairs { - let key_value: Vec<&str> = pair.split(':').collect(); - if key_value.len() == 2 { - let key = key_value[0].trim().to_string(); - let value = key_value[1].trim().to_string(); - values.push((key, value)); - } else { - return Err(InlineJsonParseError::InvalidKeyValPair); - } - } - - Ok(values) -} diff --git a/ark-cli/src/models/mod.rs b/ark-cli/src/models/mod.rs index bc37c45a..946a42ee 100644 --- a/ark-cli/src/models/mod.rs +++ b/ark-cli/src/models/mod.rs @@ -1,5 +1,48 @@ -pub mod cli; -pub mod entry; -pub mod format; -pub mod sort; pub mod storage; + +use clap::Parser; + +use crate::error::InlineJsonParseError; + +#[derive(Parser, Debug, Clone, Copy, PartialEq, Eq)] +pub enum EntryOutput { + Link, + Id, + Path, + Both, +} + +#[derive(Parser, Debug, clap::ValueEnum, Clone)] +pub enum Sort { + Asc, + Desc, +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum Format { + #[clap(name = "json")] + KeyValue, + #[clap(name = "raw")] + Raw, +} + +pub fn key_value_to_str( + s: &str, +) -> Result, InlineJsonParseError> { + let pairs: Vec<&str> = s.split(',').collect(); + + let mut values = Vec::new(); + + for pair in pairs { + let key_value: Vec<&str> = pair.split(':').collect(); + if key_value.len() == 2 { + let key = key_value[0].trim().to_string(); + let value = key_value[1].trim().to_string(); + values.push((key, value)); + } else { + return Err(InlineJsonParseError::InvalidKeyValPair); + } + } + + Ok(values) +} diff --git a/ark-cli/src/models/sort.rs b/ark-cli/src/models/sort.rs deleted file mode 100644 index ddd6315c..00000000 --- a/ark-cli/src/models/sort.rs +++ /dev/null @@ -1,7 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug, clap::ValueEnum, Clone)] -pub enum Sort { - Asc, - Desc, -} diff --git a/ark-cli/src/models/storage.rs b/ark-cli/src/models/storage.rs index 0519e9ed..48fc0464 100644 --- a/ark-cli/src/models/storage.rs +++ b/ark-cli/src/models/storage.rs @@ -4,12 +4,9 @@ use std::fmt::Write; use std::path::PathBuf; use crate::{ - commands::{ - self, - file::{format_file, format_line}, - }, + commands::{file_append, file_insert, format_file, format_line}, error::AppError, - models::format::Format, + models::Format, }; #[derive(Debug, Clone, Copy, clap::ValueEnum)] @@ -131,11 +128,7 @@ impl Storage { Format::Raw => format!("{}:{}\n", id, content), }; - match commands::file::file_append( - &atomic_file, - &content, - Format::Raw, - ) { + match file_append(&atomic_file, &content, Format::Raw) { Ok(_) => Ok(()), Err(e) => Err(e), } @@ -159,8 +152,7 @@ impl Storage { )) })?; - match commands::file::file_append(&atomic_file, content, format) - { + match file_append(&atomic_file, content, format) { Ok(_) => Ok(()), Err(e) => Err(e), } @@ -278,11 +270,7 @@ impl Storage { Format::Raw => format!("{}:{}\n", id, content), }; - match commands::file::file_insert( - &atomic_file, - &content, - Format::Raw, - ) { + match file_insert(&atomic_file, &content, Format::Raw) { Ok(_) => Ok(()), Err(e) => Err(e), } @@ -306,8 +294,7 @@ impl Storage { )) })?; - match commands::file::file_insert(&atomic_file, content, format) - { + match file_insert(&atomic_file, content, format) { Ok(_) => Ok(()), Err(e) => Err(e), } From ae8ca7149bd402a3f9bc6c526bc939b16c9b3be5 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 20 May 2024 00:29:23 +0300 Subject: [PATCH 02/11] refactor: simplify imports Signed-off-by: Tarek --- ark-cli/src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ark-cli/src/main.rs b/ark-cli/src/main.rs index 3a58e556..3248585c 100644 --- a/ark-cli/src/main.rs +++ b/ark-cli/src/main.rs @@ -26,7 +26,10 @@ use fs_extra::dir::{self, CopyOptions}; use home::home_dir; use crate::cli::Cli; +use crate::commands::file::File::{Append, Insert, Read}; +use crate::commands::link::Link::{Create, Load}; use crate::commands::Commands::Link; +use crate::commands::Commands::Storage; use crate::commands::Commands::*; use crate::models::EntryOutput; use crate::models::Format; @@ -69,15 +72,15 @@ async fn run() -> Result<()> { Render(render) => render.run()?, List(list) => list.run()?, Link { subcommand } => match subcommand { - crate::commands::link::Link::Create(create) => create.run().await?, - crate::commands::link::Link::Load(load) => load.run()?, + Create(create) => create.run().await?, + Load(load) => load.run()?, }, crate::commands::Commands::File { subcommand } => match subcommand { - crate::commands::file::File::Append(append) => append.run()?, - crate::commands::file::File::Insert(insert) => insert.run()?, - crate::commands::file::File::Read(read) => read.run()?, + Append(append) => append.run()?, + Insert(insert) => insert.run()?, + Read(read) => read.run()?, }, - crate::commands::Commands::Storage { subcommand } => match subcommand { + Storage { subcommand } => match subcommand { crate::commands::storage::Storage::List(list) => list.run()?, }, }; From f2c7793f644610f8057681b956abe7d62b366f01 Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 24 May 2024 22:33:36 +0300 Subject: [PATCH 03/11] fix(ark-cli): modify resource id related code in cli crate Signed-off-by: Tarek --- ark-cli/src/commands/file/append.rs | 5 ++++- ark-cli/src/commands/file/insert.rs | 5 ++++- ark-cli/src/commands/file/read.rs | 5 ++++- ark-cli/src/commands/list.rs | 6 +++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ark-cli/src/commands/file/append.rs b/ark-cli/src/commands/file/append.rs index c1ae62fd..82236e31 100644 --- a/ark-cli/src/commands/file/append.rs +++ b/ark-cli/src/commands/file/append.rs @@ -6,6 +6,8 @@ use crate::{ AppError, Format, ResourceId, }; +use data_error::ArklibError; + #[derive(Clone, Debug, clap::Args)] #[clap(name = "append", about = "Append content to a resource")] pub struct Append { @@ -42,7 +44,8 @@ impl Append { let mut storage = Storage::new(file_path, storage_type)?; - let resource_id = ResourceId::from_str(&self.id)?; + let resource_id = ResourceId::from_str(&self.id) + .map_err(|_e| AppError::ArklibError(ArklibError::Parse))?; storage.append(resource_id, &self.content, format)?; diff --git a/ark-cli/src/commands/file/insert.rs b/ark-cli/src/commands/file/insert.rs index d4cf98f2..ff9b1ac9 100644 --- a/ark-cli/src/commands/file/insert.rs +++ b/ark-cli/src/commands/file/insert.rs @@ -6,6 +6,8 @@ use crate::{ AppError, Format, ResourceId, }; +use data_error::ArklibError; + #[derive(Clone, Debug, clap::Args)] #[clap(name = "insert", about = "Insert content into a resource")] pub struct Insert { @@ -42,7 +44,8 @@ impl Insert { let mut storage = Storage::new(file_path, storage_type)?; - let resource_id = ResourceId::from_str(&self.id)?; + let resource_id = ResourceId::from_str(&self.id) + .map_err(|_e| AppError::ArklibError(ArklibError::Parse))?; storage.insert(resource_id, &self.content, format)?; diff --git a/ark-cli/src/commands/file/read.rs b/ark-cli/src/commands/file/read.rs index 6db7f18c..8387d011 100644 --- a/ark-cli/src/commands/file/read.rs +++ b/ark-cli/src/commands/file/read.rs @@ -6,6 +6,8 @@ use crate::{ AppError, ResourceId, }; +use data_error::ArklibError; + #[derive(Clone, Debug, clap::Args)] #[clap(name = "read", about = "Read content from a resource")] pub struct Read { @@ -36,7 +38,8 @@ impl Read { let mut storage = Storage::new(file_path, storage_type)?; - let resource_id = ResourceId::from_str(&self.id)?; + let resource_id = ResourceId::from_str(&self.id) + .map_err(|_e| AppError::ArklibError(ArklibError::Parse))?; let output = storage.read(resource_id)?; diff --git a/ark-cli/src/commands/list.rs b/ark-cli/src/commands/list.rs index 4d74e5c8..acafc92c 100644 --- a/ark-cli/src/commands/list.rs +++ b/ark-cli/src/commands/list.rs @@ -125,13 +125,13 @@ impl List { let (path, resource, content) = match entry_output { EntryOutput::Both => ( Some(path.to_owned().into_path_buf()), - Some(resource.id), + Some(resource.clone().id), None, ), EntryOutput::Path => { (Some(path.to_owned().into_path_buf()), None, None) } - EntryOutput::Id => (None, Some(resource.id), None), + EntryOutput::Id => (None, Some(resource.clone().id), None), EntryOutput::Link => match File::open(path) { Ok(mut file) => { let mut contents = String::new(); @@ -171,7 +171,7 @@ impl List { entry .tags .as_ref() - .map(|tags| tags.contains(&filter)) + .map(|tags| tags.contains(filter)) .unwrap_or(false) }); } From bc6db268184c9d3d7e44600842d8dc7cbfea101a Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 24 May 2024 23:01:12 +0300 Subject: [PATCH 04/11] feat(ark-cli): add cli styles Signed-off-by: Tarek --- ark-cli/src/cli.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ark-cli/src/cli.rs b/ark-cli/src/cli.rs index 6a56d89b..6f89bfa1 100644 --- a/ark-cli/src/cli.rs +++ b/ark-cli/src/cli.rs @@ -1,11 +1,18 @@ use crate::commands::Commands; -use clap::Parser; +use clap::{builder::styling::AnsiColor, Parser}; #[derive(Parser, Debug)] #[clap(name = "ark-cli")] -#[clap(about = "Manage ARK tag storages and indexes", long_about = None)] +#[clap(about = "Manage ARK tag storages and indexes", styles=styles())] pub struct Cli { #[clap(subcommand)] pub command: Commands, } + +pub fn styles() -> clap::builder::Styles { + clap::builder::Styles::styled() + .header(AnsiColor::Yellow.on_default()) + .usage(AnsiColor::Yellow.on_default()) + .literal(AnsiColor::Green.on_default()) +} From 5dd9c0d7c5f43ff5c639d02d043594dbbbc411e0 Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 24 May 2024 23:38:33 +0300 Subject: [PATCH 05/11] fix(ark-cli): avoid unwrap() and add FIXMEs Signed-off-by: Tarek --- ark-cli/src/commands/backup.rs | 2 ++ ark-cli/src/commands/collisions.rs | 1 + ark-cli/src/commands/link/utils.rs | 1 + ark-cli/src/commands/list.rs | 3 +++ ark-cli/src/commands/monitor.rs | 3 +++ ark-cli/src/commands/render.rs | 13 +++++++++---- ark-cli/src/index_registrar.rs | 8 +++++--- ark-cli/src/main.rs | 2 ++ ark-cli/src/util.rs | 14 +++++++++++--- 9 files changed, 37 insertions(+), 10 deletions(-) diff --git a/ark-cli/src/commands/backup.rs b/ark-cli/src/commands/backup.rs index 398220bd..abaf54ed 100644 --- a/ark-cli/src/commands/backup.rs +++ b/ark-cli/src/commands/backup.rs @@ -15,6 +15,8 @@ pub struct Backup { } impl Backup { + // FIXME: The logic for backup should be moved out of `ark-cli` + // so that it can be used by other applications pub fn run(&self) -> Result<(), AppError> { let timestamp = timestamp().as_secs(); let backup_dir = home_dir() diff --git a/ark-cli/src/commands/collisions.rs b/ark-cli/src/commands/collisions.rs index 69c26c17..ea3c320a 100644 --- a/ark-cli/src/commands/collisions.rs +++ b/ark-cli/src/commands/collisions.rs @@ -14,6 +14,7 @@ pub struct Collisions { impl Collisions { pub fn run(&self) -> Result<(), AppError> { + // FIXME: How does `monitor_index` handle `ark-cli collisions`? monitor_index(&self.root_dir, None) } } diff --git a/ark-cli/src/commands/link/utils.rs b/ark-cli/src/commands/link/utils.rs index 122f7f1c..4830219b 100644 --- a/ark-cli/src/commands/link/utils.rs +++ b/ark-cli/src/commands/link/utils.rs @@ -38,6 +38,7 @@ pub fn load_link( Err(AppError::LinkLoadError(format!( "Path {:?} was requested. But id {} maps to path {:?}", path, + // FIXME: If `load_link` needs id, then it shouldn't be an optional parameter id.clone().unwrap(), path2, ))) diff --git a/ark-cli/src/commands/list.rs b/ark-cli/src/commands/list.rs index acafc92c..8dc70661 100644 --- a/ark-cli/src/commands/list.rs +++ b/ark-cli/src/commands/list.rs @@ -179,6 +179,9 @@ impl List { let no_tags = "NO_TAGS"; let no_scores = "NO_SCORE"; + // FIXME: We should use a table library to print this. + // probably `comfy-table` + let longest_path = storage_entries .iter() .map(|entry| { diff --git a/ark-cli/src/commands/monitor.rs b/ark-cli/src/commands/monitor.rs index 1ad1c3df..21cda1ef 100644 --- a/ark-cli/src/commands/monitor.rs +++ b/ark-cli/src/commands/monitor.rs @@ -7,12 +7,15 @@ use crate::{monitor_index, AppError}; pub struct Monitor { #[clap(value_parser, help = "Path to the root directory")] root_dir: Option, + // FIXME: help message should specify what metric the interval is in #[clap(help = "Interval to check for changes")] interval: Option, } impl Monitor { pub fn run(&self) -> Result<(), AppError> { + // FIXME: 1000 should be the default value in clap configuration + // so users know let millis = self.interval.unwrap_or(1000); monitor_index(&self.root_dir, Some(millis)) } diff --git a/ark-cli/src/commands/render.rs b/ark-cli/src/commands/render.rs index cba8bbd5..7f3fa9e6 100644 --- a/ark-cli/src/commands/render.rs +++ b/ark-cli/src/commands/render.rs @@ -6,24 +6,27 @@ use crate::{render_preview_page, AppError, File, PDFQuality}; #[clap(name = "render", about = "Render a PDF file to an image")] pub struct Render { #[clap(value_parser, help = "The path to the file to render")] - path: Option, + path: PathBuf, #[clap(help = "The quality of the rendering")] quality: Option, } impl Render { pub fn run(&self) -> Result<(), AppError> { - let filepath = self.path.to_owned().unwrap(); + let filepath = self.path.to_owned(); let quality = match self.quality.to_owned().unwrap().as_str() { "high" => Ok(PDFQuality::High), "medium" => Ok(PDFQuality::Medium), "low" => Ok(PDFQuality::Low), _ => Err(AppError::InvalidRenderOption), }?; - let buf = File::open(&filepath).unwrap(); + let buf = File::open(&filepath).map_err(|e| { + AppError::FileOperationError(format!("Failed to open file: {}", e)) + })?; let dest_path = filepath.with_file_name( filepath .file_stem() + // SAFETY: we know that the file stem is valid UTF-8 because it is a file name .unwrap() .to_str() .unwrap() @@ -31,7 +34,9 @@ impl Render { + ".png", ); let img = render_preview_page(buf, quality); - img.save(dest_path).unwrap(); + img.save(dest_path).map_err(|e| { + AppError::FileOperationError(format!("Failed to save image: {}", e)) + })?; Ok(()) } } diff --git a/ark-cli/src/index_registrar.rs b/ark-cli/src/index_registrar.rs index 0ab5678a..fc6a2e5b 100644 --- a/ark-cli/src/index_registrar.rs +++ b/ark-cli/src/index_registrar.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; extern crate canonical_path; -use data_error::Result; +use data_error::{ArklibError, Result}; use fs_index::ResourceIndex; use std::collections::HashMap; @@ -25,7 +25,7 @@ pub fn provide_index>( let root_path = CanonicalPathBuf::canonicalize(root_path)?; { - let registrar = REGISTRAR.read().unwrap(); + let registrar = REGISTRAR.read().map_err(|_| ArklibError::Parse)?; if let Some(index) = registrar.get(&root_path) { log::info!("Index has been registered before"); @@ -36,7 +36,9 @@ pub fn provide_index>( log::info!("Index has not been registered before"); match ResourceIndex::provide(&root_path) { Ok(index) => { - let mut registrar = REGISTRAR.write().unwrap(); + let mut registrar = REGISTRAR.write().map_err(|_| { + ArklibError::Other(anyhow::anyhow!("Failed to lock registrar")) + })?; let arc = Arc::new(RwLock::new(index)); registrar.insert(root_path, arc.clone()); diff --git a/ark-cli/src/main.rs b/ark-cli/src/main.rs index 3248585c..c8c718ca 100644 --- a/ark-cli/src/main.rs +++ b/ark-cli/src/main.rs @@ -105,6 +105,8 @@ async fn main() -> anyhow::Result<()> { let _ = app_id::load(ark_dir) .map_err(|e| AppError::AppIdLoadError(e.to_string()))?; + // Having a separate function for the main logic allows for easier + // error handling and testing. if let Err(err) = run().await { eprintln!("Error: {:#}", err); std::process::exit(1); diff --git a/ark-cli/src/util.rs b/ark-cli/src/util.rs index 2b1a6d16..9a370167 100644 --- a/ark-cli/src/util.rs +++ b/ark-cli/src/util.rs @@ -73,7 +73,7 @@ pub fn provide_root(root_dir: &Option) -> Result { pub fn provide_index(root_dir: &PathBuf) -> ResourceIndex { let rwlock = crate::provide_index(root_dir).expect("Failed to retrieve index"); - let index = &*rwlock.read().unwrap(); + let index = &*rwlock.read().expect("Failed to lock index"); index.clone() } @@ -94,7 +94,11 @@ pub fn monitor_index( println!("Build succeeded in {:?}\n", duration); if let Some(millis) = interval { - let mut index = rwlock.write().unwrap(); + let mut index = rwlock.write().map_err(|_| { + AppError::StorageCreationError( + "Failed to write lock index".to_owned(), + ) + })?; loop { let pause = Duration::from_millis(millis); thread::sleep(pause); @@ -117,7 +121,11 @@ pub fn monitor_index( } } } else { - let index = rwlock.read().unwrap(); + let index = rwlock.read().map_err(|_| { + AppError::StorageCreationError( + "Failed to read lock index".to_owned(), + ) + })?; println!("Here are {} entries in the index", index.size()); From 8bd0480582818789db01a5d8f43a9b248164c62d Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 28 May 2024 16:22:08 +0300 Subject: [PATCH 06/11] feat(ark-cli): update parameters with default_value in clap Signed-off-by: Tarek --- ark-cli/src/commands/file/append.rs | 11 +++++++++-- ark-cli/src/commands/list.rs | 3 --- ark-cli/src/commands/monitor.rs | 11 ++++++----- ark-cli/src/commands/storage/list.rs | 7 ++++--- ark-cli/src/commands/storage/mod.rs | 2 ++ 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ark-cli/src/commands/file/append.rs b/ark-cli/src/commands/file/append.rs index 82236e31..586a1f19 100644 --- a/ark-cli/src/commands/file/append.rs +++ b/ark-cli/src/commands/file/append.rs @@ -23,7 +23,13 @@ pub struct Append { id: String, #[clap(help = "Content to append to the resource")] content: String, - #[clap(short, long, value_enum, help = "Format of the resource")] + #[clap( + short, + long, + value_enum, + default_value = "raw", + help = "Format of the resource" + )] format: Option, #[clap(short, long, value_enum, help = "Storage kind of the resource")] kind: Option, @@ -35,12 +41,13 @@ impl Append { translate_storage(&Some(self.root_dir.to_owned()), &self.storage) .ok_or(AppError::StorageNotFound(self.storage.to_owned()))?; + // FIXME: Why do we have `self.kind` and `self.storage`? Both are used to determine the storage type let storage_type = storage_type.unwrap_or(match self.kind { Some(t) => t, None => StorageType::File, }); - let format = self.format.unwrap_or(Format::Raw); + let format = self.format.unwrap(); let mut storage = Storage::new(file_path, storage_type)?; diff --git a/ark-cli/src/commands/list.rs b/ark-cli/src/commands/list.rs index 8dc70661..acafc92c 100644 --- a/ark-cli/src/commands/list.rs +++ b/ark-cli/src/commands/list.rs @@ -179,9 +179,6 @@ impl List { let no_tags = "NO_TAGS"; let no_scores = "NO_SCORE"; - // FIXME: We should use a table library to print this. - // probably `comfy-table` - let longest_path = storage_entries .iter() .map(|entry| { diff --git a/ark-cli/src/commands/monitor.rs b/ark-cli/src/commands/monitor.rs index 21cda1ef..60d79b36 100644 --- a/ark-cli/src/commands/monitor.rs +++ b/ark-cli/src/commands/monitor.rs @@ -7,16 +7,17 @@ use crate::{monitor_index, AppError}; pub struct Monitor { #[clap(value_parser, help = "Path to the root directory")] root_dir: Option, - // FIXME: help message should specify what metric the interval is in - #[clap(help = "Interval to check for changes")] + #[clap( + default_value = "1000", + help = "Interval to check for changes in milliseconds" + )] interval: Option, } impl Monitor { pub fn run(&self) -> Result<(), AppError> { - // FIXME: 1000 should be the default value in clap configuration - // so users know - let millis = self.interval.unwrap_or(1000); + // SAFETY: interval is always Some since it has a default value in clap + let millis = self.interval.unwrap(); monitor_index(&self.root_dir, Some(millis)) } } diff --git a/ark-cli/src/commands/storage/list.rs b/ark-cli/src/commands/storage/list.rs index 0a6f3873..36d6597a 100644 --- a/ark-cli/src/commands/storage/list.rs +++ b/ark-cli/src/commands/storage/list.rs @@ -12,8 +12,9 @@ pub struct List { root_dir: Option, #[clap(help = "Storage name")] storage: Option, - #[clap(short, long, help = "TODO: what is this flag for?")] - versions: Option, + // TODO: What is the purpose of this field? Add a help message to clarify. + #[clap(short, long, action = clap::ArgAction::SetTrue)] + versions: bool, #[clap(short, long, value_enum, help = "Storage kind of the resource")] kind: Option, } @@ -27,7 +28,7 @@ impl List { "Storage was not provided".to_owned(), ))?; - let versions = self.versions.unwrap_or(false); + let versions = self.versions; let (file_path, storage_type) = translate_storage(&self.root_dir, storage) diff --git a/ark-cli/src/commands/storage/mod.rs b/ark-cli/src/commands/storage/mod.rs index ed5edbb5..dfef464a 100644 --- a/ark-cli/src/commands/storage/mod.rs +++ b/ark-cli/src/commands/storage/mod.rs @@ -2,6 +2,8 @@ use clap::Subcommand; mod list; +// FIXME: We should use new `fs-storage` crate to handle storage operations + /// Available commands for the `storage` subcommand #[derive(Subcommand, Debug)] pub enum Storage { From 4296f73de479725fae0f8103cdc0949a6ea766d7 Mon Sep 17 00:00:00 2001 From: Tarek Date: Sat, 1 Jun 2024 17:27:00 +0300 Subject: [PATCH 07/11] remove redundant FIXME Signed-off-by: Tarek --- ark-cli/src/commands/backup.rs | 2 -- ark-cli/src/commands/storage/list.rs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ark-cli/src/commands/backup.rs b/ark-cli/src/commands/backup.rs index abaf54ed..398220bd 100644 --- a/ark-cli/src/commands/backup.rs +++ b/ark-cli/src/commands/backup.rs @@ -15,8 +15,6 @@ pub struct Backup { } impl Backup { - // FIXME: The logic for backup should be moved out of `ark-cli` - // so that it can be used by other applications pub fn run(&self) -> Result<(), AppError> { let timestamp = timestamp().as_secs(); let backup_dir = home_dir() diff --git a/ark-cli/src/commands/storage/list.rs b/ark-cli/src/commands/storage/list.rs index 36d6597a..a3a3fe0c 100644 --- a/ark-cli/src/commands/storage/list.rs +++ b/ark-cli/src/commands/storage/list.rs @@ -12,8 +12,7 @@ pub struct List { root_dir: Option, #[clap(help = "Storage name")] storage: Option, - // TODO: What is the purpose of this field? Add a help message to clarify. - #[clap(short, long, action = clap::ArgAction::SetTrue)] + #[clap(short, long, action = clap::ArgAction::SetTrue, help = "Whether or not to use atomatic versioning")] versions: bool, #[clap(short, long, value_enum, help = "Storage kind of the resource")] kind: Option, From 687064f97ee8118cb1e1c18a645bb288c105e287 Mon Sep 17 00:00:00 2001 From: Tarek Date: Sat, 1 Jun 2024 21:15:16 +0300 Subject: [PATCH 08/11] ark-cli: remove FIXME about file append cmd Signed-off-by: Tarek --- ark-cli/src/commands/file/append.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ark-cli/src/commands/file/append.rs b/ark-cli/src/commands/file/append.rs index 586a1f19..46d0b132 100644 --- a/ark-cli/src/commands/file/append.rs +++ b/ark-cli/src/commands/file/append.rs @@ -41,7 +41,6 @@ impl Append { translate_storage(&Some(self.root_dir.to_owned()), &self.storage) .ok_or(AppError::StorageNotFound(self.storage.to_owned()))?; - // FIXME: Why do we have `self.kind` and `self.storage`? Both are used to determine the storage type let storage_type = storage_type.unwrap_or(match self.kind { Some(t) => t, None => StorageType::File, From 4bd6aa769a657f4d14850ac141e5689f2db809d7 Mon Sep 17 00:00:00 2001 From: Tarek Date: Sun, 2 Jun 2024 10:12:58 +0300 Subject: [PATCH 09/11] fix(ark-cli): better help message for ark-cli storage list Signed-off-by: Tarek --- ark-cli/src/commands/storage/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ark-cli/src/commands/storage/list.rs b/ark-cli/src/commands/storage/list.rs index a3a3fe0c..6b0c6c4e 100644 --- a/ark-cli/src/commands/storage/list.rs +++ b/ark-cli/src/commands/storage/list.rs @@ -12,7 +12,7 @@ pub struct List { root_dir: Option, #[clap(help = "Storage name")] storage: Option, - #[clap(short, long, action = clap::ArgAction::SetTrue, help = "Whether or not to use atomatic versioning")] + #[clap(short, long, action = clap::ArgAction::SetTrue, help = "Print previous versions of the list")] versions: bool, #[clap(short, long, value_enum, help = "Storage kind of the resource")] kind: Option, From 88e3bfcb036abf41d4bba07a6c318f77cb87c58b Mon Sep 17 00:00:00 2001 From: Tarek Date: Sat, 15 Jun 2024 03:53:06 +0300 Subject: [PATCH 10/11] docs: remove FIXME about ark-cli storage Signed-off-by: Tarek --- ark-cli/src/commands/storage/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ark-cli/src/commands/storage/mod.rs b/ark-cli/src/commands/storage/mod.rs index dfef464a..ed5edbb5 100644 --- a/ark-cli/src/commands/storage/mod.rs +++ b/ark-cli/src/commands/storage/mod.rs @@ -2,8 +2,6 @@ use clap::Subcommand; mod list; -// FIXME: We should use new `fs-storage` crate to handle storage operations - /// Available commands for the `storage` subcommand #[derive(Subcommand, Debug)] pub enum Storage { From 2ec4eebaad1acc5d1b31280c913bf4a9482f066e Mon Sep 17 00:00:00 2001 From: Tarek Date: Sat, 15 Jun 2024 04:29:32 +0300 Subject: [PATCH 11/11] fix(ark-cli): only list valid links Signed-off-by: Tarek --- ark-cli/src/commands/list.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ark-cli/src/commands/list.rs b/ark-cli/src/commands/list.rs index acafc92c..bc557b42 100644 --- a/ark-cli/src/commands/list.rs +++ b/ark-cli/src/commands/list.rs @@ -136,7 +136,17 @@ impl List { Ok(mut file) => { let mut contents = String::new(); match file.read_to_string(&mut contents) { - Ok(_) => (None, None, Some(contents)), + Ok(_) => { + // Check if the content of the file is a valid url + let url = contents.trim(); + let url = url::Url::parse(url); + match url { + Ok(url) => { + (None, None, Some(url.to_string())) + } + Err(_) => return None, + } + } Err(_) => return None, } }