From 0bae05342fc2811b311f619c12e2cd3f27892a28 Mon Sep 17 00:00:00 2001 From: Karcsesz Date: Wed, 14 Feb 2024 23:00:55 +0100 Subject: [PATCH] Ability to create a new resource and slightly improved editor view of data --- src/args_parser.rs | 5 +- src/editor/commands/editor.rs | 25 +++- src/editor/open_in_editor.rs | 9 +- src/main.rs | 3 +- src/schema/mod.rs | 1 + src/schema/resource.rs | 9 ++ src/schema/resource_complete_serialisation.rs | 112 ++++++++++++++++++ 7 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 src/schema/resource_complete_serialisation.rs diff --git a/src/args_parser.rs b/src/args_parser.rs index 2218ce6..1ee9300 100644 --- a/src/args_parser.rs +++ b/src/args_parser.rs @@ -66,7 +66,7 @@ pub struct SaveSettings { pub save: bool, /// Save behaviour when encountering a collision - #[arg(long, short = 'c', default_value = "overwrite-single-skip-multiple")] + #[arg(long, default_value = "overwrite-single-skip-multiple")] pub collision_handling: CollisionHandling, } @@ -100,6 +100,9 @@ pub enum Command { save: SaveSettings, /// The resource to query for and edit resource: String, + /// Create a blank resource with RESOURCE as the subject if the query fails + #[arg(long, short = 'c')] + create: bool, #[command(flatten)] server_reload: ServerReloadOptions, }, diff --git a/src/editor/commands/editor.rs b/src/editor/commands/editor.rs index f31ed9a..93603bc 100644 --- a/src/editor/commands/editor.rs +++ b/src/editor/commands/editor.rs @@ -1,18 +1,31 @@ use crate::args_parser::SaveSettings; use crate::editor::open_in_editor::open_resource_in_editor; use crate::schema::lookup_handler::LookupHandler; +use crate::schema::resource::Resource; use std::path::PathBuf; -use tracing::info; +use tracing::{error, info}; -pub fn editor(database_path: PathBuf, save_settings: SaveSettings, resource: String) { +pub fn editor(database_path: PathBuf, save_settings: SaveSettings, resource: String, create: bool) { let resources = LookupHandler::load(&database_path).unwrap(); - let (index, resource) = resources - .lookup_with_index(resource.as_str()) - .expect("Couldn't find a resource for that query"); + let (index, resource) = match resources.lookup_with_index(resource.as_str()) { + None => { + if create { + (None, Resource::new(resource)) + } else { + error!("Couldn't find a resource for the query \"{resource}\""); + return; + } + } + Some((index, resource)) => (Some(index), resource.clone()), + }; let resource = open_resource_in_editor(resource).unwrap(); if save_settings.save { let mut resources = resources.into_inner(); - resources.0[index] = resource; + if let Some(index) = index { + resources.0[index] = resource; + } else { + resources.0.push(resource); + } resources.save(database_path).unwrap(); } else { info!("To save edits, run this command with the -s flag") diff --git a/src/editor/open_in_editor.rs b/src/editor/open_in_editor.rs index 2dd6ca8..fd97868 100644 --- a/src/editor/open_in_editor.rs +++ b/src/editor/open_in_editor.rs @@ -1,4 +1,5 @@ use crate::schema::resource::Resource; +use crate::schema::resource_complete_serialisation::ResourceComplete; use std::fmt::{Debug, Display}; use std::io::{Read, Seek, SeekFrom, Write}; use std::process::Command; @@ -80,9 +81,11 @@ pub enum ResourceEditingError { /// Opens the provided `resource` in the system editor and returns the edited version or an error if something goes wrong. #[instrument(skip(resource))] -pub fn open_resource_in_editor(resource: &Resource) -> Result { +pub fn open_resource_in_editor(resource: Resource) -> Result { use ResourceEditingError::*; - let printed = serde_json::to_string_pretty(resource).map_err(PrettyPrintFailed)?; + let resource = ResourceComplete::from_resource(resource); + let printed = serde_json::to_string_pretty(&resource).map_err(PrettyPrintFailed)?; let edited = spawn_editor(printed)?; - serde_json::from_str(edited.as_str()).map_err(ParseFailed) + let parsed: ResourceComplete = serde_json::from_str(edited.as_str()).map_err(ParseFailed)?; + Ok(parsed.into_resource()) } diff --git a/src/main.rs b/src/main.rs index e54fc2b..2433ba7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,9 +57,10 @@ fn main() { Command::Editor { save, resource, + create, server_reload, } => { - editor(data_paths.database_path, save, resource); + editor(data_paths.database_path, save, resource, create); reload(data_paths.pid_file_path, server_reload); } diff --git a/src/schema/mod.rs b/src/schema/mod.rs index 485aae9..c87de93 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -1,3 +1,4 @@ pub mod lookup_handler; pub mod resource; +pub mod resource_complete_serialisation; pub mod resource_list; diff --git a/src/schema/resource.rs b/src/schema/resource.rs index 057e902..3fac470 100644 --- a/src/schema/resource.rs +++ b/src/schema/resource.rs @@ -23,6 +23,15 @@ impl Display for Resource { } impl Resource { + pub fn new(subject: String) -> Self { + Self { + subject, + aliases: None, + properties: None, + links: None, + } + } + /// Returns the aliases of the given record. If the `aliases` field is /// entirely missing, returns &[]. pub fn keys(&self) -> impl Iterator { diff --git a/src/schema/resource_complete_serialisation.rs b/src/schema/resource_complete_serialisation.rs new file mode 100644 index 0000000..e074110 --- /dev/null +++ b/src/schema/resource_complete_serialisation.rs @@ -0,0 +1,112 @@ +use crate::schema::resource::{Link, Resource}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Same as `Resource`, but doesn't have any of the skip_serializing_if fields and options set so the structure is more friendly to editing through the `editor` command +#[derive(Serialize, Deserialize)] +pub struct ResourceComplete { + subject: String, + aliases: Vec, + properties: HashMap>, + links: Vec, +} + +impl ResourceComplete { + pub fn from_resource(value: Resource) -> Self { + let Resource { + subject, + aliases, + properties, + links, + } = value; + Self { + subject, + aliases: aliases.unwrap_or_default(), + properties: properties.unwrap_or_default(), + links: links + .map(|vec| vec.into_iter().map(LinkComplete::from_link).collect()) + .unwrap_or_default(), + } + } + + pub fn into_resource(self) -> Resource { + let ResourceComplete { + subject, + aliases, + properties, + links, + } = self; + Resource { + subject, + aliases: if aliases.is_empty() { + None + } else { + Some(aliases) + }, + properties: if properties.is_empty() { + None + } else { + Some(properties) + }, + links: if links.is_empty() { + None + } else { + Some(links.into_iter().map(LinkComplete::into_link).collect()) + }, + } + } +} + +/// Same as `Link`, but doesn't have any of the skip_serializing_if fields set so the structure is more friendly to editing through the `editor` command +#[derive(Serialize, Deserialize)] +pub struct LinkComplete { + rel: String, + media_type: Option, + href: Option, + titles: HashMap, + properties: HashMap>, +} + +impl LinkComplete { + fn from_link(value: Link) -> Self { + let Link { + rel, + media_type, + href, + titles, + properties, + } = value; + Self { + rel, + media_type, + href, + titles: titles.unwrap_or_default(), + properties: properties.unwrap_or_default(), + } + } + + fn into_link(self) -> Link { + let LinkComplete { + rel, + media_type, + href, + titles, + properties, + } = self; + Link { + rel, + media_type, + href, + titles: if titles.is_empty() { + None + } else { + Some(titles) + }, + properties: if properties.is_empty() { + None + } else { + Some(properties) + }, + } + } +}