Ability to create a new resource and slightly improved editor view of data

This commit is contained in:
Karcsesz 2024-02-14 23:00:55 +01:00
parent b643fceca3
commit 0bae05342f
7 changed files with 153 additions and 11 deletions

View file

@ -66,7 +66,7 @@ pub struct SaveSettings {
pub save: bool, pub save: bool,
/// Save behaviour when encountering a collision /// 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, pub collision_handling: CollisionHandling,
} }
@ -100,6 +100,9 @@ pub enum Command {
save: SaveSettings, save: SaveSettings,
/// The resource to query for and edit /// The resource to query for and edit
resource: String, resource: String,
/// Create a blank resource with RESOURCE as the subject if the query fails
#[arg(long, short = 'c')]
create: bool,
#[command(flatten)] #[command(flatten)]
server_reload: ServerReloadOptions, server_reload: ServerReloadOptions,
}, },

View file

@ -1,18 +1,31 @@
use crate::args_parser::SaveSettings; use crate::args_parser::SaveSettings;
use crate::editor::open_in_editor::open_resource_in_editor; use crate::editor::open_in_editor::open_resource_in_editor;
use crate::schema::lookup_handler::LookupHandler; use crate::schema::lookup_handler::LookupHandler;
use crate::schema::resource::Resource;
use std::path::PathBuf; 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 resources = LookupHandler::load(&database_path).unwrap();
let (index, resource) = resources let (index, resource) = match resources.lookup_with_index(resource.as_str()) {
.lookup_with_index(resource.as_str()) None => {
.expect("Couldn't find a resource for that query"); 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(); let resource = open_resource_in_editor(resource).unwrap();
if save_settings.save { if save_settings.save {
let mut resources = resources.into_inner(); 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(); resources.save(database_path).unwrap();
} else { } else {
info!("To save edits, run this command with the -s flag") info!("To save edits, run this command with the -s flag")

View file

@ -1,4 +1,5 @@
use crate::schema::resource::Resource; use crate::schema::resource::Resource;
use crate::schema::resource_complete_serialisation::ResourceComplete;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::process::Command; 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. /// Opens the provided `resource` in the system editor and returns the edited version or an error if something goes wrong.
#[instrument(skip(resource))] #[instrument(skip(resource))]
pub fn open_resource_in_editor(resource: &Resource) -> Result<Resource, ResourceEditingError> { pub fn open_resource_in_editor(resource: Resource) -> Result<Resource, ResourceEditingError> {
use ResourceEditingError::*; 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)?; 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())
} }

View file

@ -57,9 +57,10 @@ fn main() {
Command::Editor { Command::Editor {
save, save,
resource, resource,
create,
server_reload, 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); reload(data_paths.pid_file_path, server_reload);
} }

View file

@ -1,3 +1,4 @@
pub mod lookup_handler; pub mod lookup_handler;
pub mod resource; pub mod resource;
pub mod resource_complete_serialisation;
pub mod resource_list; pub mod resource_list;

View file

@ -23,6 +23,15 @@ impl Display for Resource {
} }
impl 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 /// Returns the aliases of the given record. If the `aliases` field is
/// entirely missing, returns &[]. /// entirely missing, returns &[].
pub fn keys(&self) -> impl Iterator<Item = &String> { pub fn keys(&self) -> impl Iterator<Item = &String> {

View file

@ -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<String>,
properties: HashMap<String, Option<String>>,
links: Vec<LinkComplete>,
}
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<String>,
href: Option<String>,
titles: HashMap<String, String>,
properties: HashMap<String, Option<String>>,
}
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)
},
}
}
}