From: Ruben Beltran del Rio Date: Sun, 25 Feb 2024 22:06:19 +0000 (+0000) Subject: Add part of the implementation for add X-Git-Tag: 7.0.0~48 X-Git-Url: https://git.r.bdr.sh/rbdr/blog/commitdiff_plain/a9c6be4162bd15bd41ba3605127b56cb1eb32f32?ds=sidebyside;hp=53812065cf124b6c1bc40fceec46f8c161033e29 Add part of the implementation for add --- diff --git a/src/command/add.rs b/src/command/add.rs index bc1bea0..3e0e987 100644 --- a/src/command/add.rs +++ b/src/command/add.rs @@ -1,10 +1,12 @@ -use std::io::Result; +use std::fs::{create_dir_all, remove_dir_all, rename}; +use std::io::{Result, Error}; use super::{ generate::Generate, sync_down::SyncDown, sync_up::SyncUp, update::Update }; +use crate::configuration::Configuration; pub struct Add; @@ -12,6 +14,24 @@ impl Add { pub fn new() -> Self { Add } + + // moves posts to their next + fn shift(&self, configuration: &Configuration) -> Result<()> { + for i in (0..configuration.max_posts).rev() { + let source = configuration.posts_directory.join(i.to_string()); + let target = configuration.posts_directory.join((i + 1).to_string()); + + println!("Moving {} source to {}", source.display(), target.display()); + + if source.exists() { + match rename(&source, &target) { + Ok(_) => continue, + Err(e) => return Err(Error::new(e.kind(), format!("Could not shift post {} to {}", source.display(), target.display()))) + } + } + } + Ok(()) + } } impl super::Command for Add { @@ -19,9 +39,23 @@ impl super::Command for Add { vec![Box::new(SyncDown::new())] } - fn execute(&self, input: Option<&String>) -> Result<()> { - println!("Add: {:?}!", input); - return Ok(()) + fn execute(&self, _: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { + match create_dir_all(&configuration.posts_directory) { + Ok(_) => { + match self.shift(configuration) { + Ok(_) => { + let first_directory = configuration.posts_directory.join("0"); + let _ = remove_dir_all(&first_directory); + match create_dir_all(&configuration.posts_directory) { + Ok(_) => Ok(()), + Err(e) => Err(Error::new(e.kind(), format!("Could not create first post directory"))) + } + }, + Err(e) => Err(e) + } + }, + Err(e) => Err(Error::new(e.kind(), format!("Could not create posts directory"))) + } } fn after_dependencies(&self) -> Vec> { diff --git a/src/command/add_remote.rs b/src/command/add_remote.rs index 3d90103..c98330f 100644 --- a/src/command/add_remote.rs +++ b/src/command/add_remote.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct AddRemote; @@ -13,7 +14,7 @@ impl super::Command for AddRemote { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("Add Remote: {:?}!", input); return Ok(()) } diff --git a/src/command/generate.rs b/src/command/generate.rs index 59babd5..fac73ff 100644 --- a/src/command/generate.rs +++ b/src/command/generate.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct Generate; @@ -13,7 +14,7 @@ impl super::Command for Generate { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("GENERATE! {:?}", input); return Ok(()) } diff --git a/src/command/help.rs b/src/command/help.rs index 7ddcba8..8d2a2c0 100644 --- a/src/command/help.rs +++ b/src/command/help.rs @@ -1,5 +1,6 @@ use std::io::Result; use super::available_commands; +use crate::configuration::Configuration; pub struct Help; @@ -14,7 +15,7 @@ impl super::Command for Help { vec![] } - fn execute(&self, _: Option<&String>) -> Result<()> { + fn execute(&self, _: Option<&String>, _: &Configuration, _: &String) -> Result<()> { let commands = available_commands(); println!("Usage:"); println!(""); diff --git a/src/command/mod.rs b/src/command/mod.rs index 211b10f..0c96e6f 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -27,9 +27,11 @@ use version::Version; use status::Status; use help::Help; +use crate::configuration::Configuration; + pub trait Command { fn before_dependencies(&self) -> Vec>; - fn execute(&self, input: Option<&String>) -> Result<()>; + fn execute(&self, input: Option<&String>, configuration: &Configuration, command: &String) -> Result<()>; fn after_dependencies(&self) -> Vec>; fn command(&self) -> &'static str; fn help(&self) -> &'static str; diff --git a/src/command/publish.rs b/src/command/publish.rs index d6116a4..881c987 100644 --- a/src/command/publish.rs +++ b/src/command/publish.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct Publish; @@ -13,7 +14,7 @@ impl super::Command for Publish { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("Publish: {:?}!", input); return Ok(()) } diff --git a/src/command/publish_archive.rs b/src/command/publish_archive.rs index 6b98b1a..f5f114a 100644 --- a/src/command/publish_archive.rs +++ b/src/command/publish_archive.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct PublishArchive; @@ -13,7 +14,7 @@ impl super::Command for PublishArchive { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("Publish Archive: {:?}!", input); return Ok(()) } diff --git a/src/command/remove_remote.rs b/src/command/remove_remote.rs index cfc5cdb..8188beb 100644 --- a/src/command/remove_remote.rs +++ b/src/command/remove_remote.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct RemoveRemote; @@ -13,7 +14,7 @@ impl super::Command for RemoveRemote { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("Remove Remote: {:?}!", input); return Ok(()) } diff --git a/src/command/status/configuration_status.rs b/src/command/status/configuration_status.rs index 67bf277..6d4e56a 100644 --- a/src/command/status/configuration_status.rs +++ b/src/command/status/configuration_status.rs @@ -2,36 +2,35 @@ use std::fs; use std::path::PathBuf; use crate::configuration::Configuration; -pub fn status() -> String { - let configuration = Configuration::new(); +pub fn status(configuration: &Configuration) -> String { let mut status_message = String::new(); status_message.push_str("# Configuration\n"); - status_message.push_str("## Directories\n"); + status_message.push_str("\n## Directories\n"); // Main Configuration Locations - status_message.push_str(&get_directory_stats("Configuration", configuration.config_directory)); - status_message.push_str(&get_directory_stats("Data", configuration.data_directory)); - status_message.push_str(&get_directory_stats("Output", configuration.output_directory)); + status_message.push_str(&get_directory_stats("Configuration", &configuration.config_directory)); + status_message.push_str(&get_directory_stats("Data", &configuration.data_directory)); + status_message.push_str(&get_directory_stats("Output", &configuration.output_directory)); - status_message.push_str("## Blog Settings\n"); + status_message.push_str("\n## Blog Settings\n"); status_message.push_str(&format!("Number of posts to keep: {}\n", configuration.max_posts)); status_message } -fn get_directory_stats(label: &str, directory: PathBuf) -> String { +fn get_directory_stats(label: &str, directory: &PathBuf) -> String { let mut status_message = String::new(); - status_message.push_str(&format!("{}: {}\n", label, directory.display())); + status_message.push_str(&format!("{}: {}. ", label, directory.display())); if directory.exists() { - status_message.push_str(&format!("{} directory exists.\n", label)); + status_message.push_str("Exists "); if fs::read_dir(&directory).is_ok() { - status_message.push_str(&format!("{} directory is readable.\n", label)); + status_message.push_str("and is readable.\n"); } else { - status_message.push_str(&format!("{} directory is not readable.\n", label)); + status_message.push_str("but is not readable.\n"); } } else { - status_message.push_str(&format!("{} directory does not exist.\n", label)); + status_message.push_str("Does not exist.\n"); } status_message diff --git a/src/command/status/mod.rs b/src/command/status/mod.rs index ba73184..e8754f5 100644 --- a/src/command/status/mod.rs +++ b/src/command/status/mod.rs @@ -1,6 +1,7 @@ mod configuration_status; use std::io::Result; +use crate::configuration::Configuration; pub struct Status; @@ -15,10 +16,10 @@ impl super::Command for Status { vec![] } - fn execute(&self, _: Option<&String>) -> Result<()> { + fn execute(&self, _: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { let status_providers = available_status_providers(); for status_provider in status_providers { - println!("{}", status_provider()); + println!("{}", status_provider(configuration)); } return Ok(()) } @@ -36,7 +37,7 @@ impl super::Command for Status { } } -fn available_status_providers() -> Vec String> { +fn available_status_providers() -> Vec String> { vec![ configuration_status::status, ] diff --git a/src/command/sync_down.rs b/src/command/sync_down.rs index c00b01a..dc4114e 100644 --- a/src/command/sync_down.rs +++ b/src/command/sync_down.rs @@ -1,4 +1,6 @@ -use std::io::Result; +use std::fs::create_dir_all; +use std::io::{Result, Error}; +use crate::configuration::Configuration; pub struct SyncDown; @@ -13,9 +15,17 @@ impl super::Command for SyncDown { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { - println!("Sync Down: {:?}!", input); - return Ok(()) + fn execute(&self, _: Option<&String>, configuration: &Configuration, command: &String) -> Result<()> { + match create_dir_all(&configuration.data_directory) { + Ok(_) => { + // We only care to show these warnings if this is the primary command. + if command == self.command() { + println!("WARNING: Sync Down Not yet implemented"); + } + return Ok(()) + }, + Err(e) => Err(Error::new(e.kind(), format!("Could not create data directory"))) + } } fn after_dependencies(&self) -> Vec> { diff --git a/src/command/sync_up.rs b/src/command/sync_up.rs index d35ce7f..635de10 100644 --- a/src/command/sync_up.rs +++ b/src/command/sync_up.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct SyncUp; @@ -13,7 +14,7 @@ impl super::Command for SyncUp { vec![] } - fn execute(&self, input: Option<&String>) -> Result<()> { + fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> { println!("Sync Up: {:?}!", input); return Ok(()) } diff --git a/src/command/update.rs b/src/command/update.rs index 0a0211c..ed6b066 100644 --- a/src/command/update.rs +++ b/src/command/update.rs @@ -1,5 +1,10 @@ -use std::io::Result; +use std::fs::create_dir_all; +use std::io::{Result, Error, ErrorKind}; +use std::path::PathBuf; use super::{sync_down::SyncDown, generate::Generate, sync_up::SyncUp}; +use crate::configuration::Configuration; +use crate::constants::METADATA_FILENAME; +use crate::metadata::Metadata; pub struct Update; @@ -7,6 +12,18 @@ impl Update { pub fn new() -> Self { Update } + + fn copy_post(&self, post_location: &PathBuf) { + + } + + fn write_metadata(&self, metadata: Metadata, metadata_location: &PathBuf) { + + } + + fn archive(&self, post_location: &PathBuf) { + + } } impl super::Command for Update { @@ -14,8 +31,23 @@ impl super::Command for Update { vec![Box::new(SyncDown::new())] } - fn execute(&self, input: Option<&String>) -> Result<()> { - println!("Update: {:?}!", input); + fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { + let input = input.expect("You must provide a path to a post"); + let post_location = PathBuf::from(input); + if !post_location.exists() { + return Err(Error::new(ErrorKind::NotFound, "The path provided does not exist")); + } + + create_dir_all(&configuration.posts_directory)?; + + let metadata_file_path = configuration.posts_directory + .join("0") + .join(METADATA_FILENAME); + let metadata = Metadata::read_or_create(&metadata_file_path); + + self.copy_post(&post_location); + self.write_metadata(metadata, &metadata_file_path); + self.archive(&post_location); return Ok(()) } diff --git a/src/command/version.rs b/src/command/version.rs index a8b970b..e566c5a 100644 --- a/src/command/version.rs +++ b/src/command/version.rs @@ -1,4 +1,5 @@ use std::io::Result; +use crate::configuration::Configuration; pub struct Version; @@ -13,7 +14,7 @@ impl super::Command for Version { vec![] } - fn execute(&self, _: Option<&String>) -> Result<()> { + fn execute(&self, _: Option<&String>, _: &Configuration, _: &String) -> Result<()> { let version = env!("CARGO_PKG_VERSION"); println!("{}", version); return Ok(()) diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..4519426 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1 @@ +pub const METADATA_FILENAME: &str = "metadata.json"; diff --git a/src/main.rs b/src/main.rs index bf1e184..4cb8036 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,17 @@ // mod argument_parser; mod configuration; mod command; +mod constants; +mod metadata; use std::iter::once; use std::env::args; use std::io::Result; use command::{available_commands, Command, help::Help}; +use configuration::Configuration; fn main() -> Result<()> { + let configuration = Configuration::new(); let commands = available_commands(); let arguments: Vec = args().collect(); @@ -23,7 +27,7 @@ fn main() -> Result<()> { .collect(); for command in command_chain { - let result = command.execute(arguments.get(2)); + let result = command.execute(arguments.get(2), &configuration, command_name); if let Err(_) = result { return result; } @@ -33,5 +37,5 @@ fn main() -> Result<()> { } } - Help::new().execute(None) + Help::new().execute(None, &configuration, &"help".to_string()) } diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..97ef0d3 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,70 @@ +use std::fs::File; +use std::path::PathBuf; +use std::io::Read; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub struct Metadata { + pub id: String, + pub created_on: u128 +} + +impl Metadata { + pub fn serialize(&self) -> String { + format!(r#"{{\n "id": "{}",\n "created_on": {}\n}}"#, self.id, self.created_on) + } + + pub fn deserialize(input: &str) -> Option { + let clean_input = input + .chars() + .filter(|c| !c.is_whitespace()) + .collect::(); + + let id = Metadata::read_field(&clean_input, "id")?; + let created_on = Metadata::read_field(&clean_input, "createdOn") + .or_else(|| Metadata::read_field(&clean_input, "created_on"))?; + + Some(Metadata { + id, + created_on: created_on.parse().ok()? + }) + } + + pub fn read_or_create(file_path: &PathBuf) -> Metadata { + match Metadata::read_metadata_file(file_path) { + Some(metadata) => metadata, + None => { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_millis() as u128) + .unwrap_or_else(|_| 0); + return Metadata { + id: timestamp.to_string(), + created_on: timestamp + } + } + } + } + + fn read_metadata_file(file_path: &PathBuf) -> Option { + let mut file = File::open(file_path).ok()?; + let mut contents = String::new(); + file.read_to_string(&mut contents).ok()?; + Metadata::deserialize(&contents) + } + + + fn read_field(input: &str, field_name: &str) -> Option { + let key_pattern = format!(r#""{}":""#, field_name); + input.find(&key_pattern) + .and_then(|start| { + let value_start = start + key_pattern.len(); + let value_end = input[value_start..] + .find(|c: char| c == ',' || c == '}' || c == '\n') + .unwrap_or_else(|| input[value_start..].len()) + value_start; + + let raw_value = &input[value_start..value_end]; + let value = raw_value.trim_matches('"').to_string(); + Some(value) + }) + } +}