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,
/// 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,
},

View file

@ -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")

View file

@ -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<Resource, ResourceEditingError> {
pub fn open_resource_in_editor(resource: Resource) -> Result<Resource, 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)?;
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 {
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);
}

View file

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

View file

@ -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<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)
},
}
}
}