-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;
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 {
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<Box<dyn super::Command>> {
use std::io::Result;
+use crate::configuration::Configuration;
pub struct AddRemote;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("Add Remote: {:?}!", input);
return Ok(())
}
use std::io::Result;
+use crate::configuration::Configuration;
pub struct Generate;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("GENERATE! {:?}", input);
return Ok(())
}
use std::io::Result;
use super::available_commands;
+use crate::configuration::Configuration;
pub struct Help;
vec![]
}
- fn execute(&self, _: Option<&String>) -> Result<()> {
+ fn execute(&self, _: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
let commands = available_commands();
println!("Usage:");
println!("");
use status::Status;
use help::Help;
+use crate::configuration::Configuration;
+
pub trait Command {
fn before_dependencies(&self) -> Vec<Box<dyn Command>>;
- fn execute(&self, input: Option<&String>) -> Result<()>;
+ fn execute(&self, input: Option<&String>, configuration: &Configuration, command: &String) -> Result<()>;
fn after_dependencies(&self) -> Vec<Box<dyn Command>>;
fn command(&self) -> &'static str;
fn help(&self) -> &'static str;
use std::io::Result;
+use crate::configuration::Configuration;
pub struct Publish;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("Publish: {:?}!", input);
return Ok(())
}
use std::io::Result;
+use crate::configuration::Configuration;
pub struct PublishArchive;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("Publish Archive: {:?}!", input);
return Ok(())
}
use std::io::Result;
+use crate::configuration::Configuration;
pub struct RemoveRemote;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("Remove Remote: {:?}!", input);
return Ok(())
}
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
mod configuration_status;
use std::io::Result;
+use crate::configuration::Configuration;
pub struct 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(())
}
}
}
-fn available_status_providers() -> Vec<fn() -> String> {
+fn available_status_providers() -> Vec<fn(&Configuration) -> String> {
vec![
configuration_status::status,
]
-use std::io::Result;
+use std::fs::create_dir_all;
+use std::io::{Result, Error};
+use crate::configuration::Configuration;
pub struct 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<Box<dyn super::Command>> {
use std::io::Result;
+use crate::configuration::Configuration;
pub struct SyncUp;
vec![]
}
- fn execute(&self, input: Option<&String>) -> Result<()> {
+ fn execute(&self, input: Option<&String>, _: &Configuration, _: &String) -> Result<()> {
println!("Sync Up: {:?}!", input);
return Ok(())
}
-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;
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 {
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(())
}
use std::io::Result;
+use crate::configuration::Configuration;
pub struct 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(())
--- /dev/null
+pub const METADATA_FILENAME: &str = "metadata.json";
// 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<String> = args().collect();
.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;
}
}
}
- Help::new().execute(None)
+ Help::new().execute(None, &configuration, &"help".to_string())
}
--- /dev/null
+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<Metadata> {
+ let clean_input = input
+ .chars()
+ .filter(|c| !c.is_whitespace())
+ .collect::<String>();
+
+ 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<Metadata> {
+ 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<String> {
+ 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)
+ })
+ }
+}