From 0d5adbffc2102e8adad3d3e5583adf5ecf6ff788 Mon Sep 17 00:00:00 2001 From: Pistonight Date: Wed, 4 Oct 2023 21:24:22 -0700 Subject: [PATCH 1/5] implement link plugin and working on plugin config parsing --- compiler-core/src/api.rs | 3 +- compiler-core/src/comp/prop.rs | 2 + compiler-core/src/pack/mod.rs | 3 + compiler-core/src/pack/pack_config.rs | 52 +++++++-- compiler-core/src/plug/link.rs | 146 ++++++++++++++++++++++++++ compiler-core/src/plug/mod.rs | 27 +++++ compiler-core/src/plug/operation.rs | 41 ++++++++ 7 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 compiler-core/src/plug/link.rs create mode 100644 compiler-core/src/plug/operation.rs diff --git a/compiler-core/src/api.rs b/compiler-core/src/api.rs index 42f5af91..3858f146 100644 --- a/compiler-core/src/api.rs +++ b/compiler-core/src/api.rs @@ -8,7 +8,7 @@ use crate::comp::{CompDoc, Compiler, CompilerError}; use crate::lang::Preset; use crate::metrics::CompilerMetrics; use crate::pack::{self, PackedProject, PackerError, PackerResult, Resource, ValidUse}; -use crate::plug::run_plugins; +use crate::plug::{run_plugins, PluginRuntime}; /// Output of the compiler API #[derive(Debug, Clone)] @@ -35,6 +35,7 @@ pub struct OkOutput { #[derive(Default, Debug, Clone)] pub struct CompilerMetadata { pub presets: HashMap, + pub plugins: Vec, pub default_icon_priority: i64, } diff --git a/compiler-core/src/comp/prop.rs b/compiler-core/src/comp/prop.rs index 5a02fde5..875f0831 100644 --- a/compiler-core/src/comp/prop.rs +++ b/compiler-core/src/comp/prop.rs @@ -28,6 +28,7 @@ pub const NAME: &str = "name"; pub const NOTES: &str = "notes"; pub const PRESETS: &str = "presets"; pub const PRIORITY: &str = "priority"; +pub const PLUGINS: &str = "plugins"; pub const ROUTE: &str = "route"; pub const SCALE: &str = "scale"; pub const SIZE: &str = "size"; @@ -43,4 +44,5 @@ pub const TRANSLATE: &str = "translate"; pub const USE: &str = "use"; pub const VERSION: &str = "version"; pub const WARP: &str = "warp"; +pub const WITH: &str = "with"; pub const ZOOM_BOUNDS: &str = "zoom-bounds"; diff --git a/compiler-core/src/pack/mod.rs b/compiler-core/src/pack/mod.rs index b593f978..99c05184 100644 --- a/compiler-core/src/pack/mod.rs +++ b/compiler-core/src/pack/mod.rs @@ -115,6 +115,9 @@ pub enum PackerError { #[error("{0}")] NotImpl(String), + #[error("`{0}` is not a valid built-in plugin or reference to a plugin script")] + InvalidPlugin(String), + #[cfg(feature = "wasm")] #[error("Wasm execution error: {0}")] Wasm(#[from] WasmError), diff --git a/compiler-core/src/pack/pack_config.rs b/compiler-core/src/pack/pack_config.rs index 969fac30..a5c1eca0 100644 --- a/compiler-core/src/pack/pack_config.rs +++ b/compiler-core/src/pack/pack_config.rs @@ -7,6 +7,7 @@ use crate::api::Setting; use crate::comp::prop; use crate::json::{Cast, Coerce}; use crate::lang::Preset; +use crate::plug::{Plugin, PluginRuntime}; use crate::util::async_for; use super::{pack_map, pack_presets, PackerError, PackerResult, Resource, Use, ValidUse}; @@ -17,6 +18,7 @@ pub struct ConfigBuilder { pub icons: HashMap, pub tags: HashMap, pub presets: HashMap, + pub plugins: Vec, pub default_icon_priority: Option, } @@ -106,16 +108,23 @@ async fn process_config( _ => return Err(PackerError::InvalidConfigType(index)), }; - let icons = match config_obj.get_mut(prop::ICONS) { - Some(v) => v, - None => return Ok(config_obj), - }; + if let Some(icons) = config_obj.get_mut(prop::ICONS) { + process_icons_config(resource, icons).await?; + } + + + Ok(config_obj) +} +async fn process_icons_config( + resource: &Resource, + icons: &mut Value, +) -> PackerResult<()> { let icons = match icons.as_object_mut() { Some(obj) => obj, // just returning ok here // the error will be caught later - _ => return Ok(config_obj), + _ => return Ok(()), }; async_for!(value in icons.values_mut(), { @@ -133,5 +142,36 @@ async fn process_config( } })?; - Ok(config_obj) + Ok(()) } + +async fn process_plugins_config( + resource: &Resource, + plugins: &mut Value, + index: usize, +) -> PackerResult<()> { + let plugins = match plugins.as_array_mut() { + Some(x) => x, + // just returning ok here + // the error will be caught later + _ => return Ok(()), + }; + + async_for!(value in plugins.iter_mut(), { + let v = value.take(); + match Use::from(v) { + Use::Invalid(path) => return Err(PackerError::InvalidUse(path)), + Use::NotUse(v) => { + *value = v; + } + Use::Valid(valid_use) => { + let icon_resource = resource.resolve(&valid_use).await?; + let image_url = icon_resource.load_image_url().await?; + *value = Value::String(image_url); + } + } + })?; + + todo!() +} + diff --git a/compiler-core/src/plug/link.rs b/compiler-core/src/plug/link.rs new file mode 100644 index 00000000..dd5ee0ad --- /dev/null +++ b/compiler-core/src/plug/link.rs @@ -0,0 +1,146 @@ +//! Link plugin +//! +//! This plugin looks for the `link` tag and transforms it into a link. + +use celerctypes::DocRichText; + +use crate::comp::CompDoc; +use crate::comp::prop; +use super::operation; + +pub async fn run_link_plugin(comp_doc: &mut CompDoc) { + operation::for_all_lines(comp_doc, |mut line| async { + operation::for_all_rich_text(&mut line, transform_link_tag).await; + line + }).await +} + +fn transform_link_tag(rich_text: &mut DocRichText) { + if !rich_text.tag.as_ref().filter(|tag| tag == &prop::LINK).is_some() { + return; + } + if rich_text.link.is_some() { + return; + } + + if rich_text.text.starts_with('[') { + match rich_text.text.find(']') { + Some(i) => { + rich_text.link = Some(rich_text.text[i+1..].trim().to_string()); + rich_text.text = rich_text.text[1..i].to_string(); + } + None => { + rich_text.link = Some(rich_text.text.trim().to_string()); + } + } + } else { + rich_text.link = Some(rich_text.text.trim().to_string()); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_ignore_no_tag() { + let mut text = DocRichText::text("hello world"); + let expected = text.clone(); + transform_link_tag(&mut text); + assert_eq!(expected, text); + } + + #[test] + fn test_ignore_link() { + let mut text = DocRichText { + tag: Some("link".to_string()), + text: "hello world".to_string(), + link: Some("https://example.com".to_string()), + }; + let expected = text.clone(); + transform_link_tag(&mut text); + assert_eq!(expected, text); + } + + #[test] + fn test_ignore_non_link_tag() { + let mut text = DocRichText { + tag: Some("test".to_string()), + text: "hello world".to_string(), + link: None, + }; + let expected = text.clone(); + transform_link_tag(&mut text); + assert_eq!(expected, text); + } + + #[test] + fn test_transform_link_tag() { + let mut text = DocRichText { + tag: Some(prop::LINK.to_string()), + text: "hello world".to_string(), + link: None, + }; + transform_link_tag(&mut text); + assert_eq!(text, DocRichText { + tag: Some(prop::LINK.to_string()), + text: "hello world".to_string(), + link: Some("hello world".to_string()), + }); + } + + #[test] + fn test_transform_link_tag_with_text() { + let mut text = DocRichText { + tag: Some(prop::LINK.to_string()), + text: "[hello world] i am link".to_string(), + link: None, + }; + transform_link_tag(&mut text); + assert_eq!(text, DocRichText { + tag: Some(prop::LINK.to_string()), + text: "hello world".to_string(), + // link should be trimmed + link: Some("i am link".to_string()), + }); + } + + #[test] + fn test_transform_partial_bracket() { + let mut text = DocRichText { + tag: Some(prop::LINK.to_string()), + text: "[hello world i am link".to_string(), + link: None, + }; + transform_link_tag(&mut text); + assert_eq!(text, DocRichText { + tag: Some(prop::LINK.to_string()), + text: "[hello world i am link".to_string(), + link: Some("[hello world i am link".to_string()), + }); + + let mut text = DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abc[hello world] i am link".to_string(), + link: None, + }; + transform_link_tag(&mut text); + assert_eq!(text, DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abc[hello world] i am link".to_string(), + link: Some("abc[hello world] i am link".to_string()), + }); + + let mut text = DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abchello world] i am link".to_string(), + link: None, + }; + transform_link_tag(&mut text); + assert_eq!(text, DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abchello world] i am link".to_string(), + link: Some("abchello world] i am link".to_string()), + }); + } +} diff --git a/compiler-core/src/plug/mod.rs b/compiler-core/src/plug/mod.rs index 0abe0494..8e6c2624 100644 --- a/compiler-core/src/plug/mod.rs +++ b/compiler-core/src/plug/mod.rs @@ -1,6 +1,33 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + use crate::comp::CompDoc; +mod link; +mod operation; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PluginRuntime { + plugin: Plugin, + props: Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Plugin { + BuiltIn(BuiltInPlugin), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged, rename_all = "kebab-case")] +pub enum BuiltInPlugin { + /// Transform link tags to clickable links. See [`link`] + Link +} + pub fn run_plugins(comp_doc: CompDoc) -> CompDoc { // currently just a pass-through comp_doc } + diff --git a/compiler-core/src/plug/operation.rs b/compiler-core/src/plug/operation.rs new file mode 100644 index 00000000..535e26fa --- /dev/null +++ b/compiler-core/src/plug/operation.rs @@ -0,0 +1,41 @@ +use celerctypes::DocRichText; +use futures::Future; + +use crate::{comp::{CompLine, CompDoc}, util::async_for}; + +/// Transform all [`CompLine`] in a document with function F +pub async fn for_all_lines(comp_doc: &mut CompDoc, fun: Func) +where + Func: Fn(CompLine) -> Fut, + Fut: Future, +{ + // TODO #78: async_for no longer needed in the future + let _ = async_for!(section in comp_doc.route.iter_mut(), { + let lines = std::mem::take(&mut section.lines); + let _ = async_for!(line in lines.into_iter(), { + section.lines.push(fun(line).await); + }); + }); +} + +/// Transform all rich text in a line with function F +pub async fn for_all_rich_text(comp_line: &mut CompLine, fun: F) +where + F: Fn(&mut DocRichText), +{ + // TODO #78: async_for no longer needed in the future + let _ = async_for!(t in comp_line.text.iter_mut(), { + fun(t); + }); + let _ = async_for!(t in comp_line.secondary_text.iter_mut(), { + fun(t); + }); + if let Some(t) = comp_line.counter_text.as_mut() { + fun(t); + } + if let Some(v) = comp_line.split_name.as_mut() { + let _ = async_for!(t in v.iter_mut(), { + fun(t); + }); + } +} From 4e925e7a1fd1c5fd5dab9a1076d0eec7eec3e122 Mon Sep 17 00:00:00 2001 From: Pistonight Date: Sat, 7 Oct 2023 15:34:01 -0700 Subject: [PATCH 2/5] finish plugin system simple implementation, writing docs --- compiler-core/src/api.rs | 2 +- compiler-core/src/comp/comp_doc.rs | 5 + compiler-core/src/comp/compiler_builder.rs | 1 + compiler-core/src/exec/exec_doc.rs | 14 +- compiler-core/src/pack/mod.rs | 9 +- compiler-core/src/pack/pack_config.rs | 195 ++++++++++-------- compiler-core/src/pack/pack_project.rs | 1 + compiler-core/src/pack/pack_route.rs | 16 +- compiler-core/src/pack/pack_use.rs | 111 +++++----- compiler-core/src/pack/resource/loader.rs | 8 + .../src/pack/resource/resource_impl.rs | 1 + compiler-core/src/plug/mod.rs | 63 +++++- compiler-types/src/lib.rs | 2 + docs/src/.vitepress/config.ts | 16 +- docs/src/.vitepress/nav/index.ts | 1 + docs/src/.vitepress/nav/plugins.ts | 35 ++++ docs/src/.vitepress/nav/writing-routes.ts | 1 + docs/src/api-examples.md | 49 ----- docs/src/markdown-examples.md | 85 -------- docs/src/plugin/getting-started.md | 30 +++ docs/src/plugin/index.md | 9 + .../nav/writing-plugins.ts => plugin/link.md} | 0 docs/src/route/configuration.md | 45 +++- docs/src/route/getting-started.md | 2 +- docs/src/user/index.md | 1 - 25 files changed, 397 insertions(+), 305 deletions(-) create mode 100644 docs/src/.vitepress/nav/plugins.ts delete mode 100644 docs/src/api-examples.md delete mode 100644 docs/src/markdown-examples.md create mode 100644 docs/src/plugin/getting-started.md create mode 100644 docs/src/plugin/index.md rename docs/src/{.vitepress/nav/writing-plugins.ts => plugin/link.md} (100%) delete mode 100644 docs/src/user/index.md diff --git a/compiler-core/src/api.rs b/compiler-core/src/api.rs index 3858f146..b5aaad40 100644 --- a/compiler-core/src/api.rs +++ b/compiler-core/src/api.rs @@ -97,7 +97,7 @@ pub async fn compile(root_resource: &Resource, setting: &Setting) -> CompilerOut let ms = metrics.comp_done(); info!("comp phase done in {ms}ms"); - let comp_doc = run_plugins(comp_doc); + let comp_doc = run_plugins(comp_doc, &comp_meta.plugins).await; let ms = metrics.plug_done(); info!("plug phase done in {ms}ms"); diff --git a/compiler-core/src/comp/comp_doc.rs b/compiler-core/src/comp/comp_doc.rs index 237d2ea2..aa9abefc 100644 --- a/compiler-core/src/comp/comp_doc.rs +++ b/compiler-core/src/comp/comp_doc.rs @@ -1,3 +1,4 @@ +use celerctypes::DocDiagnostic; use celerctypes::{DocPoorText, RouteMetadata}; use serde::{Deserialize, Serialize}; @@ -21,6 +22,8 @@ pub struct CompDoc { pub preface: Vec>, /// The route pub route: Vec, + /// Overall diagnostics (that don't apply to any line) + pub diagnostics: Vec, } impl Compiler { @@ -50,6 +53,7 @@ impl Compiler { project: self.project, preface, route: route_vec, + diagnostics: vec![], }, self.meta, )) @@ -118,6 +122,7 @@ impl Compiler { route: vec![self.create_empty_section_for_error(errors).await], project: self.project, preface: vec![], + diagnostics: vec![], }, self.meta, ) diff --git a/compiler-core/src/comp/compiler_builder.rs b/compiler-core/src/comp/compiler_builder.rs index c8c67ddb..9420e95d 100644 --- a/compiler-core/src/comp/compiler_builder.rs +++ b/compiler-core/src/comp/compiler_builder.rs @@ -43,6 +43,7 @@ impl CompilerBuilder { meta: CompilerMetadata { presets: self.presets, default_icon_priority: self.default_icon_priority, + ..Default::default() }, color: self.color, coord: self.coord, diff --git a/compiler-core/src/exec/exec_doc.rs b/compiler-core/src/exec/exec_doc.rs index be2f62e6..ed1c0457 100644 --- a/compiler-core/src/exec/exec_doc.rs +++ b/compiler-core/src/exec/exec_doc.rs @@ -19,6 +19,7 @@ impl CompDoc { project: self.project, preface: self.preface, route: sections, + diagnostics: self.diagnostics, }) } } @@ -27,10 +28,11 @@ impl CompDoc { mod test { use celerctypes::{ DocPoorText, ExecLine, ExecMapSection, ExecSection, GameCoord, MapLine, MapMetadata, - RouteMetadata, + RouteMetadata, DocDiagnostic, }; use crate::comp::{CompLine, CompMovement, CompSection}; + use crate::lang::parse_poor; use super::*; @@ -44,15 +46,25 @@ mod test { let test_preface = vec![vec![DocPoorText::Text("test".to_string())]]; + let test_diagnostics = vec![ + DocDiagnostic { + msg: parse_poor("test msg"), + msg_type: "test".to_string(), + source: "test".to_string(), + } + ]; + let test_doc = CompDoc { project: test_metadata.clone(), preface: test_preface.clone(), + diagnostics: test_diagnostics.clone(), ..Default::default() }; let exec_doc = test_doc.exec().await.unwrap(); assert_eq!(exec_doc.project, test_metadata); assert_eq!(exec_doc.preface, test_preface); + assert_eq!(exec_doc.diagnostics, test_diagnostics); } #[tokio::test] diff --git a/compiler-core/src/pack/mod.rs b/compiler-core/src/pack/mod.rs index 99c05184..5c957558 100644 --- a/compiler-core/src/pack/mod.rs +++ b/compiler-core/src/pack/mod.rs @@ -71,6 +71,9 @@ pub enum PackerError { #[error("Error when parsing structured data in file {0}: {1}")] InvalidFormat(String, String), + #[error("Error when parsing file {0}: file is not UTF-8")] + InvalidUtf8(String), + #[error("")] InvalidIcon, @@ -106,6 +109,9 @@ pub enum PackerError { )] DuplicateMap(usize), + #[error("`{0}` is not a valid built-in plugin or reference to a plugin script")] + InvalidPlugin(String), + #[error("No map defined in project config")] MissingMap, @@ -115,9 +121,6 @@ pub enum PackerError { #[error("{0}")] NotImpl(String), - #[error("`{0}` is not a valid built-in plugin or reference to a plugin script")] - InvalidPlugin(String), - #[cfg(feature = "wasm")] #[error("Wasm execution error: {0}")] Wasm(#[from] WasmError), diff --git a/compiler-core/src/pack/pack_config.rs b/compiler-core/src/pack/pack_config.rs index a5c1eca0..ef3c01f1 100644 --- a/compiler-core/src/pack/pack_config.rs +++ b/compiler-core/src/pack/pack_config.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use celerctypes::{DocTag, MapMetadata}; -use serde_json::{Map, Value}; +use serde_json::{Value, json}; use crate::api::Setting; use crate::comp::prop; use crate::json::{Cast, Coerce}; use crate::lang::Preset; -use crate::plug::{Plugin, PluginRuntime}; +use crate::plug::{Plugin, PluginRuntime, BuiltInPlugin}; use crate::util::async_for; use super::{pack_map, pack_presets, PackerError, PackerResult, Resource, Use, ValidUse}; @@ -30,18 +30,46 @@ pub async fn pack_config( index: usize, setting: &Setting, ) -> PackerResult<()> { - // Load and resolve top-level `use` properties - let config_value = match Use::from(config) { - Use::Invalid(path) => return Err(PackerError::InvalidUse(path)), - Use::NotUse(v) => v, - Use::Valid(valid_use) => load_config_from_use(project_resource, valid_use, index).await?, - }; + match Use::try_from(config) { + Ok(Use::Invalid(path)) => return Err(PackerError::InvalidUse(path)), + Ok(Use::Valid(valid_use)) => { + // load a config from top-level use object + process_config_from_use(builder, project_resource, valid_use, index, setting).await + } + Err(v) => { + // load a config directly from the object + process_config(builder, project_resource, v, index, setting).await + } + } +} + +/// Load a top-level `use` +async fn process_config_from_use( + builder: &mut ConfigBuilder, + project_resource: &Resource, + use_prop: ValidUse, + index: usize, + setting: &Setting, +) -> PackerResult<()> { + let config_resource = project_resource.resolve(&use_prop).await?; + let config = config_resource.load_structured().await?; + // process this config with the config resource context instead of the project context + // so `use`'s inside are resolved correctly + process_config(builder, &config_resource, config, index, setting).await +} - // Resolve `use`s inside the properties - let config_value = process_config(project_resource, config_value, index).await?; +/// Process a config, adding values to Builder and use the resource to resolve `use`'s +async fn process_config( + builder: &mut ConfigBuilder, + resource: &Resource, + config: Value, + index: usize, + setting: &Setting, +) -> PackerResult<()> { + let config = config.try_into_object().map_err(|_| PackerError::InvalidConfigType(index))?; // add values to builder - async_for!((key, value) in config_value.into_iter(), { + async_for!((key, value) in config.into_iter(), { match key.as_ref() { prop::MAP => { if builder.map.is_some() { @@ -50,13 +78,7 @@ pub async fn pack_config( builder.map = Some(pack_map(value, index).await?); } prop::ICONS => { - let icons = value.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::ICONS.to_string()))?; - async_for!((key, value) in icons.into_iter(), { - if value.is_array() || value.is_object() { - return Err(PackerError::InvalidConfigProperty(index, format!("{}.{}", prop::ICONS, key))); - } - builder.icons.insert(key, value.coerce_to_string()); - })?; + process_icons_config(builder, resource, value, index).await?; } prop::TAGS => { let tags = value.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::TAGS.to_string()))?; @@ -75,69 +97,42 @@ pub async fn pack_config( let priority = value.try_coerce_to_i64().ok_or_else(|| PackerError::InvalidConfigProperty(index, prop::DEFAULT_ICON_PRIORITY.to_string()))?; builder.default_icon_priority = Some(priority); } + prop::PLUGINS => { + process_plugins_config(builder, resource, value, index).await?; + } _ => return Err(PackerError::UnusedConfigProperty(index, key)), } })?; Ok(()) -} -/// Load a top-level `use` -async fn load_config_from_use( - project_resource: &Resource, - use_prop: ValidUse, - index: usize, -) -> PackerResult { - let config_resource = project_resource.resolve(&use_prop).await?; - let config = config_resource.load_structured().await?; - // Calling process_config here - // because any `use` inside the config needs to be resolved by the config resource - // not the project resource - let config = process_config(&config_resource, config, index).await?; - Ok(Value::Object(config)) -} - -/// Process a config and resolve all `use`s inside -async fn process_config( - resource: &Resource, - config: Value, - index: usize, -) -> PackerResult> { - let mut config_obj = match config { - Value::Object(obj) => obj, - _ => return Err(PackerError::InvalidConfigType(index)), - }; - - if let Some(icons) = config_obj.get_mut(prop::ICONS) { - process_icons_config(resource, icons).await?; - } - - - Ok(config_obj) } +/// Process the `icons` property +/// +/// Resolves `use`'s using the resource context and add the icon URLs to the builder async fn process_icons_config( + builder: &mut ConfigBuilder, resource: &Resource, - icons: &mut Value, + icons: Value, + index: usize, ) -> PackerResult<()> { - let icons = match icons.as_object_mut() { - Some(obj) => obj, - // just returning ok here - // the error will be caught later - _ => return Ok(()), - }; - - async_for!(value in icons.values_mut(), { - let v = value.take(); - match Use::from(v) { - Use::Invalid(path) => return Err(PackerError::InvalidUse(path)), - Use::NotUse(v) => { - *value = v; + let icons = icons.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::ICONS.to_string()))?; + + async_for!((key, v) in icons.into_iter(), { + match Use::try_from(v) { + Err(v) => { + // not a use, just a icon url + if v.is_array() || v.is_object() { + return Err(PackerError::InvalidConfigProperty(index, format!("{}.{}", prop::ICONS, key))); + } + builder.icons.insert(key, v.coerce_to_string()); } - Use::Valid(valid_use) => { + Ok(Use::Invalid(path)) => return Err(PackerError::InvalidUse(path)), + Ok(Use::Valid(valid_use)) => { let icon_resource = resource.resolve(&valid_use).await?; let image_url = icon_resource.load_image_url().await?; - *value = Value::String(image_url); + builder.icons.insert(key, image_url); } } })?; @@ -145,33 +140,57 @@ async fn process_icons_config( Ok(()) } +/// Process the `plugins` property +/// +/// Resolves `use`'s using the resource context and add the plugins to the builder async fn process_plugins_config( + builder: &mut ConfigBuilder, resource: &Resource, - plugins: &mut Value, + plugins: Value, index: usize, ) -> PackerResult<()> { - let plugins = match plugins.as_array_mut() { - Some(x) => x, - // just returning ok here - // the error will be caught later - _ => return Ok(()), - }; - - async_for!(value in plugins.iter_mut(), { - let v = value.take(); - match Use::from(v) { - Use::Invalid(path) => return Err(PackerError::InvalidUse(path)), - Use::NotUse(v) => { - *value = v; - } - Use::Valid(valid_use) => { - let icon_resource = resource.resolve(&valid_use).await?; - let image_url = icon_resource.load_image_url().await?; - *value = Value::String(image_url); + let plugins = plugins.try_into_array().map_err(|_| PackerError::InvalidConfigProperty(index, prop::PLUGINS.to_string()))?; + + async_for!((i, v) in plugins.into_iter().enumerate(), { + let v = v.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, format!("{}[{}]", prop::PLUGINS, i)))?; + let mut plugin = None; + let mut props = json!(null); + async_for!((key, value) in v.into_iter(), { + match key.as_ref() { + prop::USE => { + let use_path_string = value.coerce_to_string(); + plugin = match serde_json::from_value::(value) { + Ok(built_in) => Some(Plugin::BuiltIn(built_in)), + Err(_) => { + // it's a script path, parse as use + match Use::from(use_path_string) { + Use::Invalid(path) => return Err(PackerError::InvalidPlugin(path)), + Use::Valid(valid_use) => { + // load the script + let script_resource = resource.resolve(&valid_use).await?; + let script = script_resource.load_utf8().await?; + Some(Plugin::Script(script)) + } + } + } + }; + } + prop::WITH => { + props = value; + } + _ => return Err(PackerError::UnusedConfigProperty(index, format!("{}[{}].{}", prop::PLUGINS, i, key))), } - } + })?; + let plugin = match plugin { + Some(v) => v, + None => return Err(PackerError::MissingConfigProperty(index, format!("{}[{}].{}", prop::PLUGINS, i, prop::USE))), + }; + builder.plugins.push(PluginRuntime { + plugin, + props, + }); })?; - todo!() + Ok(()) } diff --git a/compiler-core/src/pack/pack_project.rs b/compiler-core/src/pack/pack_project.rs index 7493d3ef..07cc39e0 100644 --- a/compiler-core/src/pack/pack_project.rs +++ b/compiler-core/src/pack/pack_project.rs @@ -86,6 +86,7 @@ pub async fn pack_project( let compiler_metadata = CompilerMetadata { presets: builder.presets, + plugins: builder.plugins, default_icon_priority: builder.default_icon_priority.unwrap_or(2), }; diff --git a/compiler-core/src/pack/pack_route.rs b/compiler-core/src/pack/pack_route.rs index 42c5fab1..f4dc4ced 100644 --- a/compiler-core/src/pack/pack_route.rs +++ b/compiler-core/src/pack/pack_route.rs @@ -51,11 +51,11 @@ async fn pack_route_internal( Ok(arr) => { let mut output = vec![]; let _ = async_for!(x in arr.into_iter(), { - match Use::from(x) { - Use::Invalid(path) => { + match Use::try_from(x) { + Ok(Use::Invalid(path)) => { output.push(PackerValue::Err(PackerError::InvalidUse(path))); } - Use::NotUse(x) => { + Err(x) => { let result = pack_route_internal( resource, x, @@ -66,7 +66,7 @@ async fn pack_route_internal( ).await; output.push(result); } - Use::Valid(valid_use) => { + Ok(Use::Valid(valid_use)) => { let result = resolve_use( resource, valid_use, @@ -91,9 +91,9 @@ async fn pack_route_internal( Err(route) => route, }; - match Use::from(route) { - Use::Invalid(path) => PackerValue::Err(PackerError::InvalidUse(path)), - Use::NotUse(x) => { + match Use::try_from(route) { + Ok(Use::Invalid(path)) => PackerValue::Err(PackerError::InvalidUse(path)), + Err(x) => { // array case is covered above match x.try_into_object() { Ok(obj) => { @@ -118,7 +118,7 @@ async fn pack_route_internal( } } } - Use::Valid(valid_use) => { + Ok(Use::Valid(valid_use)) => { resolve_use(resource, valid_use, use_depth, max_use_depth, max_ref_depth).await } } diff --git a/compiler-core/src/pack/pack_use.rs b/compiler-core/src/pack/pack_use.rs index 279da642..9e61cba8 100644 --- a/compiler-core/src/pack/pack_use.rs +++ b/compiler-core/src/pack/pack_use.rs @@ -11,8 +11,6 @@ use crate::json::Coerce; pub enum Use { /// Correctly formed `use` property Valid(ValidUse), - /// Not loading a resource - NotUse(Value), /// Invalid path specified in the use property Invalid(String), } @@ -33,24 +31,11 @@ pub enum ValidUse { }, } -impl From for Use { - fn from(value: Value) -> Self { - let obj = match value.as_object() { - Some(obj) => obj, - None => return Self::NotUse(value), - }; - let mut iter = obj.iter(); - let (key, v) = match iter.next() { - Some((key, value)) => (key, value), - None => return Self::NotUse(value), - }; - if iter.next().is_some() { - return Self::NotUse(value); - } - if key != prop::USE { - return Self::NotUse(value); - } - let v = v.coerce_to_string(); +impl From for Use { + /// Convert a path in the `use` property to a Use object + /// + /// If the path is malformed, this returns a [`Use::Invalid`] + fn from(v: String) -> Self { if v.starts_with('/') { if v.ends_with('/') { Self::Invalid(v) @@ -95,6 +80,36 @@ impl From for Use { } } +impl TryFrom for Use { + type Error = Value; + + /// Try converting a json object in the form of `{ "use": "..." }` to a `Use` + /// + /// If the object is not in the correct form, the original value is returned. + /// This includes if the object has keys other than `use`. + /// + /// If the object is in the correct form but the path is not, this returns an Ok variant with + /// [`Use::Invalid`] + fn try_from(value: Value) -> Result { + let obj = match value.as_object() { + Some(obj) => obj, + None => return Err(value), + }; + let mut iter = obj.iter(); + let (key, v) = match iter.next() { + Some((key, value)) => (key, value), + None => return Err(value), + }; + if iter.next().is_some() { + return Err(value); + } + if key != prop::USE { + return Err(value); + } + Ok(Self::from(v.coerce_to_string())) + } +} + #[cfg(test)] mod test { use serde_json::json; @@ -114,7 +129,7 @@ mod test { ]; for test in tests { - assert_eq!(Use::from(test.clone()), Use::NotUse(test)); + assert_eq!(Use::try_from(test.clone()), Err(test)); } } @@ -132,104 +147,104 @@ mod test { ]; for test in tests { - assert_eq!(Use::from(test.clone()), Use::NotUse(test)); + assert_eq!(Use::try_from(test.clone()), Err(test)); } } #[test] fn test_use_relative() { assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "./hello" })), - Use::Valid(ValidUse::Relative("./hello".to_string())) + Ok(Use::Valid(ValidUse::Relative("./hello".to_string()))) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "../foo/hello" })), - Use::Valid(ValidUse::Relative("../foo/hello".to_string())) + Ok(Use::Valid(ValidUse::Relative("../foo/hello".to_string()))) ); } #[test] fn test_use_absolute() { assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "/hello" })), - Use::Valid(ValidUse::Absolute("/hello".to_string())) + Ok(Use::Valid(ValidUse::Absolute("/hello".to_string()))) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "/foo/hello" })), - Use::Valid(ValidUse::Absolute("/foo/hello".to_string())) + Ok(Use::Valid(ValidUse::Absolute("/foo/hello".to_string()))) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "//foo/hello" })), - Use::Valid(ValidUse::Absolute("//foo/hello".to_string())) + Ok(Use::Valid(ValidUse::Absolute("//foo/hello".to_string()))) ); } #[test] fn test_use_remote() { assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "foo/hello/bar" })), - Use::Valid(ValidUse::Remote { + Ok(Use::Valid(ValidUse::Remote { owner: "foo".to_string(), repo: "hello".to_string(), path: "bar".to_string(), reference: None, - }) + })) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "foo/hello/bar:test" })), - Use::Valid(ValidUse::Remote { + Ok(Use::Valid(ValidUse::Remote { owner: "foo".to_string(), repo: "hello".to_string(), path: "bar".to_string(), reference: Some("test".to_string()), - }) + })) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": ".foo/hello/bar/giz" })), - Use::Valid(ValidUse::Remote { + Ok(Use::Valid(ValidUse::Remote { owner: ".foo".to_string(), repo: "hello".to_string(), path: "bar/giz".to_string(), reference: None, - }) + })) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "foo/hello/bar/giz/biz:test" })), - Use::Valid(ValidUse::Remote { + Ok(Use::Valid(ValidUse::Remote { owner: "foo".to_string(), repo: "hello".to_string(), path: "bar/giz/biz".to_string(), reference: Some("test".to_string()), - }) + })) ); assert_eq!( - Use::from(json!({ + Use::try_from(json!({ "use": "foo/hello/bar/giz/biz:" })), - Use::Valid(ValidUse::Remote { + Ok(Use::Valid(ValidUse::Remote { owner: "foo".to_string(), repo: "hello".to_string(), path: "bar/giz/biz".to_string(), reference: None, - }) + })) ); } @@ -255,7 +270,7 @@ mod test { ]; for test in tests { - assert_eq!(Use::from(make_use(test)), Use::Invalid(test.to_string())); + assert_eq!(Use::try_from(make_use(test)), Ok(Use::Invalid(test.to_string()))); } } } diff --git a/compiler-core/src/pack/resource/loader.rs b/compiler-core/src/pack/resource/loader.rs index 5a531360..77683c8d 100644 --- a/compiler-core/src/pack/resource/loader.rs +++ b/compiler-core/src/pack/resource/loader.rs @@ -9,6 +9,14 @@ pub trait ResourceLoader { /// Load a resource as raw bytes async fn load_raw(&self, path: &str) -> PackerResult>; + async fn load_utf8(&self, path: &str) -> PackerResult { + let bytes = self.load_raw(path).await?; + match String::from_utf8(bytes) { + Ok(v) => Ok(v), + Err(e) => Err(PackerError::InvalidUtf8(path.to_string())), + } + } + /// Load structured value. The type depends on the file extension read from the ref async fn load_structured(&self, path: &str) -> PackerResult { let v = if path.ends_with(".yaml") || path.ends_with(".yml") { diff --git a/compiler-core/src/pack/resource/resource_impl.rs b/compiler-core/src/pack/resource/resource_impl.rs index c0146bef..2255f5aa 100644 --- a/compiler-core/src/pack/resource/resource_impl.rs +++ b/compiler-core/src/pack/resource/resource_impl.rs @@ -73,6 +73,7 @@ impl Resource { } loader_delegate!(load_structured, Value); + loader_delegate!(load_utf8, String); loader_delegate!(load_image_url, String); pub async fn resolve(&self, target: &ValidUse) -> PackerResult { diff --git a/compiler-core/src/plug/mod.rs b/compiler-core/src/plug/mod.rs index 8e6c2624..988dd087 100644 --- a/compiler-core/src/plug/mod.rs +++ b/compiler-core/src/plug/mod.rs @@ -1,22 +1,53 @@ +use celerctypes::DocDiagnostic; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::comp::CompDoc; +use crate::comp::{CompDoc, CompSection}; +use crate::lang::parse_poor; mod link; mod operation; +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +pub enum PlugError { + #[error("{0}")] + NotImpl(String), +} +impl PlugError { + pub fn add_to_diagnostics(&self, output: &mut Vec) { + output.push(DocDiagnostic { + msg: parse_poor(&self.to_string()), + msg_type: "error".to_string(), + // TODO #24 get plugin name dynamically + source: "celerc/plugin".to_string(), + }); + } +} + + +pub type PlugResult = Result; + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PluginRuntime { - plugin: Plugin, - props: Value, + pub plugin: Plugin, + pub props: Value, +} + +impl PluginRuntime { + pub async fn run(&self, comp_doc: &mut CompDoc) -> PlugResult<()> { + match &self.plugin { + Plugin::BuiltIn(built_in) => built_in.run(comp_doc).await, + Plugin::Script(_) => Err(PlugError::NotImpl("Script plugins are not implemented yet".to_string())), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum Plugin { BuiltIn(BuiltInPlugin), + Script(String), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -26,8 +57,30 @@ pub enum BuiltInPlugin { Link } -pub fn run_plugins(comp_doc: CompDoc) -> CompDoc { - // currently just a pass-through +impl BuiltInPlugin { + pub async fn run(&self, comp_doc: &mut CompDoc) -> PlugResult<()> { + match self { + BuiltInPlugin::Link => { + link::run_link_plugin(comp_doc).await; + Ok(()) + } + } + } +} + +pub async fn run_plugins(mut comp_doc: CompDoc, plugins: &[PluginRuntime]) -> CompDoc { + let mut errors = Vec::new(); + for plugin in plugins { + if let Err(e) = plugin.run(&mut comp_doc).await { + errors.push(e); + } + } + if !errors.is_empty() { + for error in errors { + error.add_to_diagnostics(&mut comp_doc.diagnostics); + } + } comp_doc + } diff --git a/compiler-types/src/lib.rs b/compiler-types/src/lib.rs index 5a620858..4fc96460 100644 --- a/compiler-types/src/lib.rs +++ b/compiler-types/src/lib.rs @@ -27,6 +27,8 @@ pub struct ExecDoc { pub preface: Vec>, /// The route pub route: Vec, + /// Overall diagnostics (that don't apply to any line) + pub diagnostics: Vec, } /// Metadata of the route project diff --git a/docs/src/.vitepress/config.ts b/docs/src/.vitepress/config.ts index 97cb7c26..ec21f36b 100644 --- a/docs/src/.vitepress/config.ts +++ b/docs/src/.vitepress/config.ts @@ -1,5 +1,6 @@ import { TransformContext, defineConfig } from "vitepress"; import { writingRoutesNav, writingRoutesSidebar } from "./nav"; +import { pluginsNav, pluginsSideBar } from "./nav"; // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -63,24 +64,13 @@ export default defineConfig({ nav: [ { text: "Home", link: "/" }, writingRoutesNav, - { text: "Writing Plugins", link: "/plugin/" }, + pluginsNav, { text: "Developer", link: "/developer/" }, ], sidebar: { ...writingRoutesSidebar, - "/usage/": [ - { - text: "Examples", - items: [ - { - text: "Markdown Examples", - link: "/markdown-examples", - }, - { text: "Runtime API Examples", link: "/api-examples" }, - ], - }, - ], + ...pluginsSideBar, "/developer/": [ { text: "Web Client", diff --git a/docs/src/.vitepress/nav/index.ts b/docs/src/.vitepress/nav/index.ts index 554d8fef..b6fc1a2d 100644 --- a/docs/src/.vitepress/nav/index.ts +++ b/docs/src/.vitepress/nav/index.ts @@ -1 +1,2 @@ export * from "./writing-routes"; +export * from "./plugins"; diff --git a/docs/src/.vitepress/nav/plugins.ts b/docs/src/.vitepress/nav/plugins.ts new file mode 100644 index 00000000..f9e068ad --- /dev/null +++ b/docs/src/.vitepress/nav/plugins.ts @@ -0,0 +1,35 @@ +export const pluginsNav = { + text: "Plugins", + link: "/plugin/", +}; + +export const pluginsSideBar = { + "/plugin/": [ + { + text: "Built-in Plugins", + items: [ + { text: "Getting Started", link: "/plugin/getting-started" }, + // { text: "Configuration", link: "/route/configuration" }, + // { text: "File Structure", link: "/route/file-structure" }, + // { text: "Route Structure", link: "/route/route-structure" }, + // { text: "Customizing Lines", link: "/route/customizing-lines" }, + // { text: "Customizing Text", link: "/route/customizing-text" }, + // { + // text: "Customizing Movements", + // link: "/route/customizing-movements", + // }, + // { text: "Using Presets", link: "/route/using-presets" }, + ], + }, + // { + // text: "JavaScript Plugins", + // items: [ + // // { text: "Icons", link: "/route/config/icons" }, + // // { text: "Tags", link: "/route/config/tags" }, + // // { text: "Presets", link: "/route/config/presets" }, + // // { text: "Map", link: "/route/config/map" }, + // // { text: "Other", link: "/route/config/other" }, + // ], + // }, + ], +}; diff --git a/docs/src/.vitepress/nav/writing-routes.ts b/docs/src/.vitepress/nav/writing-routes.ts index bdfee0f7..859967a0 100644 --- a/docs/src/.vitepress/nav/writing-routes.ts +++ b/docs/src/.vitepress/nav/writing-routes.ts @@ -31,6 +31,7 @@ export const writingRoutesSidebar = { { text: "Tags", link: "/route/config/tags" }, { text: "Presets", link: "/route/config/presets" }, { text: "Map", link: "/route/config/map" }, + { text: "Plugins", link: "/plugin/getting-started" }, { text: "Other", link: "/route/config/other" }, ], }, diff --git a/docs/src/api-examples.md b/docs/src/api-examples.md deleted file mode 100644 index 6bd8bb5c..00000000 --- a/docs/src/api-examples.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -outline: deep ---- - -# Runtime API Examples - -This page demonstrates usage of some of the runtime APIs provided by VitePress. - -The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: - -```md - - -## Results - -### Theme Data -
{{ theme }}
- -### Page Data -
{{ page }}
- -### Page Frontmatter -
{{ frontmatter }}
-``` - - - -## Results - -### Theme Data -
{{ theme }}
- -### Page Data -
{{ page }}
- -### Page Frontmatter -
{{ frontmatter }}
- -## More - -Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/src/markdown-examples.md b/docs/src/markdown-examples.md deleted file mode 100644 index 8e55eb8a..00000000 --- a/docs/src/markdown-examples.md +++ /dev/null @@ -1,85 +0,0 @@ -# Markdown Extension Examples - -This page demonstrates some of the built-in markdown extensions provided by VitePress. - -## Syntax Highlighting - -VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: - -**Input** - -```` -```js{4} -export default { - data () { - return { - msg: 'Highlighted!' - } - } -} -``` -```` - -**Output** - -```js{4} -export default { - data () { - return { - msg: 'Highlighted!' - } - } -} -``` - -## Custom Containers - -**Input** - -```md -::: info -This is an info box. -::: - -::: tip -This is a tip. -::: - -::: warning -This is a warning. -::: - -::: danger -This is a dangerous warning. -::: - -::: details -This is a details block. -::: -``` - -**Output** - -::: info -This is an info box. -::: - -::: tip -This is a tip. -::: - -::: warning -This is a warning. -::: - -::: danger -This is a dangerous warning. -::: - -::: details -This is a details block. -::: - -## More - -Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/src/plugin/getting-started.md b/docs/src/plugin/getting-started.md new file mode 100644 index 00000000..e0dec87d --- /dev/null +++ b/docs/src/plugin/getting-started.md @@ -0,0 +1,30 @@ +# Getting Started +A plugin in Celer is a piece of program that runs as part of the compiler. The process goes as the following: + +1. The compiler parses the income files and celer-specific syntax like presets +2. The compiler hands the compiled document to a plugin +3. The plugin is free to make any changes to the document. Then it hands the document to the next plugin in line. +4. After the last plugin is done modifying the document, it hands the document back to the compiler. + +The principle of the plugin system is to separate core Celer functionalities from additional (and mostly optional) functionalities. + +## Configuration +To add a plugin to the compiler, use the `plugins` property in your `config`. + +The example below adds the built-in [Link Plugin](./link.md) to the compiler, which transforms `link` tags into clickable links. +```yaml +config: +- plugins: + - use: link +``` +:::tip +Note that the `use` property takes `"link"`, which is not any of the syntax mentioned in +[File structure](../route/file-structure.md). This signals Celer that you want to use a built-in +plugin. Built-in plugins are implemented in Rust and has higher performance. +::: + +## Built-in Plugins +Here is a list of all built-in plugins. The `ID` column is what you put after `use` in the config. +|Name|ID|Description| +|-|-|-| +|[Link](./link.md)|`link`|Turns `link` tags into clickable links| diff --git a/docs/src/plugin/index.md b/docs/src/plugin/index.md new file mode 100644 index 00000000..6dc334a2 --- /dev/null +++ b/docs/src/plugin/index.md @@ -0,0 +1,9 @@ +# Plugins +This section contains guides for how to utilize Celer's plugin system to add +extra features to your route doc. + +Start with [Getting Started](./getting-started.md) to get a basic understanding +of what plugins can do in Celer. Then you can take a look at the built-in plugins +like the [Link Plugin](./link.md). + + diff --git a/docs/src/.vitepress/nav/writing-plugins.ts b/docs/src/plugin/link.md similarity index 100% rename from docs/src/.vitepress/nav/writing-plugins.ts rename to docs/src/plugin/link.md diff --git a/docs/src/route/configuration.md b/docs/src/route/configuration.md index 7b5ab419..22e5f2bd 100644 --- a/docs/src/route/configuration.md +++ b/docs/src/route/configuration.md @@ -12,6 +12,7 @@ In case you want to make your own, here are the available properties: |`icons`|Add icon definition. See [Icons](./config/icons.md) for detail| |`tags`|Add tag definition for use in Rich Text. See [Tags](./config/tags.md) for detail| |`presets`|Add preset definition. See [Presets](./config/presets.md) for detail| +|`plugins`|Add plugin definition. See [Plugins](../plugin/getting-started.md) for detail| |`map`|Define map properties. See [Map](./config/map.md) for detail| Configurations are meant to be composed and reused with other configurations. @@ -22,14 +23,20 @@ The compiler will give an error if multiple configurations define the map. ```yaml # project.yaml config: -- use: Pistonite/celer/presets/botw-map.yaml -- use: Pistonite/celer/presets/botw-presets.yaml +- use: Pistonite/celer/presets/botw/map.yaml +- use: someone/someones-preset/awesome.yaml +- use: ./path/to/local.yaml - icons: example-icon: use: hello/world/example.png tags: colorful: color: blue + +# path/to/local.yaml +presets: + HelloWorld: + text: Hello, world! ``` ## Configuration Files @@ -44,6 +51,10 @@ tags: colorful: color: blue ``` +:::warning +Note that properties in `something.yaml` (`icons` and `tags`) don't have a +`-` in front because the file needs to contain a mapping, not an array at the root level. +::: Be careful that top-level `use` is not permitted. The following config file is invalid for others to include with `use`: @@ -51,7 +62,37 @@ is invalid for others to include with `use`: # something.yaml use: another/file/something.yaml ``` +:::warning +CAUTION: The example above is for what is NOT supported +::: :::tip You can still use `use` in the config properties themselves, like the example above in the `icons` property ::: + +## Grouping +Celer does NOT support complex grouping structures for dependency. For example, +you CANNOT make a config file A which includes 3 other config files, and include +the 3 files by including config file A. +```yaml +# This maybe desired in some cases, but Celer does not support this + +# project.yaml +config: +- use: ./A.yaml + +# A.yaml +- use: ./1.yaml +- use: ./2.yaml +- use: ./3.yaml +``` +:::warning +CAUTION: The example above is for what is NOT supported +::: + +The reason for not supporting this is that Celer discourages complex dependency structure. +Complex dependencies make it tricker to debug when a configuration is not functioning and +make it hard to display meaningful error messages. + +There are workarounds to this if you really want this behavior, particularly when distributing the config files. +For example, you can generate `A.yaml` from the 3 other files ahead of time. Or you could use CI like GitHub Actions to do that automatically. diff --git a/docs/src/route/getting-started.md b/docs/src/route/getting-started.md index dbd8c219..1d0fe23f 100644 --- a/docs/src/route/getting-started.md +++ b/docs/src/route/getting-started.md @@ -17,7 +17,7 @@ config: [] The file names are case-sensitive. This file should be named `project.yaml`, not `Project.yaml` or `project.yml` ::: -Then, create a `main.yaml` file with the following content: +Then, create a `main.yaml` file in the same folder with the following content: ```yaml - hello world! - Section 1: diff --git a/docs/src/user/index.md b/docs/src/user/index.md deleted file mode 100644 index 99f10893..00000000 --- a/docs/src/user/index.md +++ /dev/null @@ -1 +0,0 @@ -# todo \ No newline at end of file From b9b54def0c25122fbd3899b82e2fb702d2fbc1e9 Mon Sep 17 00:00:00 2001 From: Pistonight Date: Sat, 7 Oct 2023 19:40:59 -0700 Subject: [PATCH 3/5] tested and fixed stuff --- compiler-core/src/lang/rich/parse.rs | 39 ++++++++++++++- compiler-core/src/lang/rich/rich.grammar | 5 +- compiler-core/src/pack/mod.rs | 17 +++---- compiler-core/src/pack/pack_config.rs | 27 ++++++----- compiler-core/src/pack/pack_map.rs | 4 +- compiler-core/src/pack/pack_preset.rs | 4 +- compiler-core/src/pack/pack_project.rs | 4 +- compiler-core/src/plug/link.rs | 2 + compiler-core/src/plug/mod.rs | 2 +- compiler-types/src/lib.rs | 4 ++ compiler-wasm/src/loader_file.rs | 2 +- compiler-wasm/src/loader_url.rs | 2 +- docs/src/.vitepress/nav/plugins.ts | 11 +---- docs/src/plugin/getting-started.md | 6 ++- docs/src/plugin/index.md | 3 ++ docs/src/plugin/link.md | 61 ++++++++++++++++++++++++ docs/src/route/built-in-plugins.md | 0 docs/src/route/customizing-lines.md | 5 +- 18 files changed, 149 insertions(+), 49 deletions(-) delete mode 100644 docs/src/route/built-in-plugins.md diff --git a/compiler-core/src/lang/rich/parse.rs b/compiler-core/src/lang/rich/parse.rs index 24aaf0ce..00bf3897 100644 --- a/compiler-core/src/lang/rich/parse.rs +++ b/compiler-core/src/lang/rich/parse.rs @@ -91,7 +91,7 @@ fn parse_tagexp(pt: &pt::TagExp) -> DocRichText { arg.push_str(str); } for pt_unit in pt.m_arg.iter() { - append_unit_to_string(pt_unit, &mut arg); + append_unit_inside_tag_to_string(pt_unit, &mut arg); } DocRichText { tag: Some(tag), @@ -99,6 +99,19 @@ fn parse_tagexp(pt: &pt::TagExp) -> DocRichText { link: None, } } +fn append_unit_inside_tag_to_string(pt: &pt::UnitInsideTag, out: &mut String) { + match pt { + pt::UnitInsideTag::Unit(pt) => { + append_unit_to_string(pt, out); + } + pt::UnitInsideTag::UnitDotSymbol(_) => { + out.push('.'); + } + pt::UnitInsideTag::UnitOpenParenSymbol(_) => { + out.push('('); + } + } +} fn append_unit_to_string(pt: &pt::Unit, out: &mut String) { match pt { @@ -253,4 +266,28 @@ mod test { }] ); } + + #[test] + fn test_dot_in_text() { + assert_eq!( + parse_rich(".tag([hello]continue.me)"), + vec![DocRichText { + tag: Some("tag".to_string()), + text: "[hello]continue.me".to_string(), + link: None + }] + ); + } + + #[test] + fn test_open_paren_in_text() { + assert_eq!( + parse_rich(".tag([hello]co(ntinue.me)"), + vec![DocRichText { + tag: Some("tag".to_string()), + text: "[hello]co(ntinue.me".to_string(), + link: None + }] + ); + } } diff --git a/compiler-core/src/lang/rich/rich.grammar b/compiler-core/src/lang/rich/rich.grammar index e49f8d21..0c7127aa 100644 --- a/compiler-core/src/lang/rich/rich.grammar +++ b/compiler-core/src/lang/rich/rich.grammar @@ -21,12 +21,15 @@ rule TagExp( (Tag) tag: token Identifier, _: token Symbol"(", space: optional token Space, - (Arg) arg: optional Unit+, + (Arg) arg: optional UnitInsideTag+, _: token Symbol")" ); rule Unit = UnitId | UnitEscape; +rule UnitInsideTag = Unit | UnitDotSymbol | UnitOpenParenSymbol; rule UnitId(t: token Identifier, s: optional token Space); rule UnitEscape(t: token Escape, s: optional token Space); +rule UnitDotSymbol((Text)_: token Symbol".", s: optional token Space); +rule UnitOpenParenSymbol((Text)_: token Symbol"(", s: optional token Space); rule Symbol((Text)t: token Symbol); rule Space(t: token Space); diff --git a/compiler-core/src/pack/mod.rs b/compiler-core/src/pack/mod.rs index 5c957558..7a34dcf3 100644 --- a/compiler-core/src/pack/mod.rs +++ b/compiler-core/src/pack/mod.rs @@ -7,6 +7,7 @@ //! The output of the packer is a [`RouteMetadata`](celerctypes::RouteMetadata) //! and a json blob of the route. +use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::collections::BTreeMap; use std::convert::Infallible; @@ -36,7 +37,8 @@ use crate::lang::parse_poor; #[cfg(feature = "wasm")] use crate::util::WasmError; -#[derive(Debug, Clone, PartialEq, thiserror::Error)] +#[derive(Debug, Clone, PartialEq, thiserror::Error, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content="data")] pub enum PackerError { #[error("The project file (project.yaml) is missing or invalid.")] InvalidProject, @@ -120,10 +122,6 @@ pub enum PackerError { #[error("{0}")] NotImpl(String), - - #[cfg(feature = "wasm")] - #[error("Wasm execution error: {0}")] - Wasm(#[from] WasmError), } impl PackerError { @@ -136,11 +134,7 @@ impl PackerError { } pub fn is_cancel(&self) -> bool { - #[cfg(feature = "wasm")] - let x = matches!(self, Self::Wasm(WasmError::Cancel)); - #[cfg(not(feature = "wasm"))] - let x = false; - x + false } } @@ -156,7 +150,8 @@ pub type PackerResult = Result; /// /// This is used to expose errors to the compiler, so it can be displayed /// using the diagnostics API -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] pub enum PackerValue { Ok(Value), Err(PackerError), diff --git a/compiler-core/src/pack/pack_config.rs b/compiler-core/src/pack/pack_config.rs index ef3c01f1..66542eab 100644 --- a/compiler-core/src/pack/pack_config.rs +++ b/compiler-core/src/pack/pack_config.rs @@ -69,7 +69,7 @@ async fn process_config( let config = config.try_into_object().map_err(|_| PackerError::InvalidConfigType(index))?; // add values to builder - async_for!((key, value) in config.into_iter(), { + let _ = async_for!((key, value) in config.into_iter(), { match key.as_ref() { prop::MAP => { if builder.map.is_some() { @@ -82,16 +82,16 @@ async fn process_config( } prop::TAGS => { let tags = value.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::TAGS.to_string()))?; - async_for!((key, value) in tags.into_iter(), { + let _ = async_for!((key, value) in tags.into_iter(), { let tag = serde_json::from_value::(value).map_err(|_| PackerError::InvalidConfigProperty(index, format!("{}.{}", prop::TAGS, key)))?; builder.tags.insert(key, tag); - })?; + }); } prop::PRESETS => { let presets = pack_presets(value, index, setting.max_preset_namespace_depth).await?; - async_for!((key, value) in presets.into_iter(), { + let _ = async_for!((key, value) in presets.into_iter(), { builder.presets.insert(key, value); - })?; + }); } prop::DEFAULT_ICON_PRIORITY => { let priority = value.try_coerce_to_i64().ok_or_else(|| PackerError::InvalidConfigProperty(index, prop::DEFAULT_ICON_PRIORITY.to_string()))?; @@ -102,7 +102,7 @@ async fn process_config( } _ => return Err(PackerError::UnusedConfigProperty(index, key)), } - })?; + }); Ok(()) @@ -119,7 +119,7 @@ async fn process_icons_config( ) -> PackerResult<()> { let icons = icons.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::ICONS.to_string()))?; - async_for!((key, v) in icons.into_iter(), { + let _ = async_for!((key, v) in icons.into_iter(), { match Use::try_from(v) { Err(v) => { // not a use, just a icon url @@ -135,7 +135,7 @@ async fn process_icons_config( builder.icons.insert(key, image_url); } } - })?; + }); Ok(()) } @@ -151,17 +151,18 @@ async fn process_plugins_config( ) -> PackerResult<()> { let plugins = plugins.try_into_array().map_err(|_| PackerError::InvalidConfigProperty(index, prop::PLUGINS.to_string()))?; - async_for!((i, v) in plugins.into_iter().enumerate(), { + let _ = async_for!((i, v) in plugins.into_iter().enumerate(), { let v = v.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, format!("{}[{}]", prop::PLUGINS, i)))?; let mut plugin = None; let mut props = json!(null); - async_for!((key, value) in v.into_iter(), { + let _ = async_for!((key, value) in v.into_iter(), { match key.as_ref() { prop::USE => { let use_path_string = value.coerce_to_string(); plugin = match serde_json::from_value::(value) { Ok(built_in) => Some(Plugin::BuiltIn(built_in)), - Err(_) => { + Err(e) => { + log::info!("e: {e}"); // it's a script path, parse as use match Use::from(use_path_string) { Use::Invalid(path) => return Err(PackerError::InvalidPlugin(path)), @@ -180,7 +181,7 @@ async fn process_plugins_config( } _ => return Err(PackerError::UnusedConfigProperty(index, format!("{}[{}].{}", prop::PLUGINS, i, key))), } - })?; + }); let plugin = match plugin { Some(v) => v, None => return Err(PackerError::MissingConfigProperty(index, format!("{}[{}].{}", prop::PLUGINS, i, prop::USE))), @@ -189,7 +190,7 @@ async fn process_plugins_config( plugin, props, }); - })?; + }); Ok(()) } diff --git a/compiler-core/src/pack/pack_map.rs b/compiler-core/src/pack/pack_map.rs index b8494e54..269419fc 100644 --- a/compiler-core/src/pack/pack_map.rs +++ b/compiler-core/src/pack/pack_map.rs @@ -89,9 +89,9 @@ pub async fn pack_map(value: Value, index: usize) -> PackerResult { let layers = { let mut packed_layers = Vec::with_capacity(layers.len()); - async_for!((i, layer) in layers.into_iter().enumerate(), { + let _ = async_for!((i, layer) in layers.into_iter().enumerate(), { packed_layers.push(pack_map_layer(layer, index, i)?); - })?; + }); packed_layers }; diff --git a/compiler-core/src/pack/pack_preset.rs b/compiler-core/src/pack/pack_preset.rs index 73544bca..3389e34d 100644 --- a/compiler-core/src/pack/pack_preset.rs +++ b/compiler-core/src/pack/pack_preset.rs @@ -42,7 +42,7 @@ async fn pack_presets_internal( } })?; - async_for!((key, value) in obj.into_iter(), { + let _ = async_for!((key, value) in obj.into_iter(), { if let Some(namespace) = key.strip_prefix('_') { // sub namespace let full_key = if preset_name.is_empty() { @@ -63,7 +63,7 @@ async fn pack_presets_internal( })?; output.push((full_key, preset)); } - })?; + }); Ok(()) } diff --git a/compiler-core/src/pack/pack_project.rs b/compiler-core/src/pack/pack_project.rs index 07cc39e0..5dba8292 100644 --- a/compiler-core/src/pack/pack_project.rs +++ b/compiler-core/src/pack/pack_project.rs @@ -71,9 +71,9 @@ pub async fn pack_project( .map_err(|_| PackerError::InvalidMetadataPropertyType(prop::CONFIG.to_string()))?; let mut builder = ConfigBuilder::default(); - async_for!((i, config) in config.into_iter().enumerate(), { + let _ = async_for!((i, config) in config.into_iter().enumerate(), { pack_config(&mut builder, project_resource, config, i, setting).await?; - })?; + }); let route_metadata = RouteMetadata { name: project_resource.name().to_string(), diff --git a/compiler-core/src/plug/link.rs b/compiler-core/src/plug/link.rs index dd5ee0ad..66e0493d 100644 --- a/compiler-core/src/plug/link.rs +++ b/compiler-core/src/plug/link.rs @@ -9,6 +9,8 @@ use crate::comp::prop; use super::operation; pub async fn run_link_plugin(comp_doc: &mut CompDoc) { + // add the link tag if not defined already + comp_doc.project.tags.entry(prop::LINK.to_string()).or_default(); operation::for_all_lines(comp_doc, |mut line| async { operation::for_all_rich_text(&mut line, transform_link_tag).await; line diff --git a/compiler-core/src/plug/mod.rs b/compiler-core/src/plug/mod.rs index 988dd087..41146cbb 100644 --- a/compiler-core/src/plug/mod.rs +++ b/compiler-core/src/plug/mod.rs @@ -51,7 +51,7 @@ pub enum Plugin { } #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged, rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case")] pub enum BuiltInPlugin { /// Transform link tags to clickable links. See [`link`] Link diff --git a/compiler-types/src/lib.rs b/compiler-types/src/lib.rs index 4fc96460..80c3216f 100644 --- a/compiler-types/src/lib.rs +++ b/compiler-types/src/lib.rs @@ -60,12 +60,16 @@ pub struct RouteMetadata { #[ts(export)] pub struct DocTag { /// Bold style + #[serde(default)] bold: bool, /// Italic style + #[serde(default)] italic: bool, /// Underline style + #[serde(default)] underline: bool, /// Strikethrough style + #[serde(default)] strikethrough: bool, /// Color of the text color: Option, diff --git a/compiler-wasm/src/loader_file.rs b/compiler-wasm/src/loader_file.rs index 11917c7d..b1ceaab0 100644 --- a/compiler-wasm/src/loader_file.rs +++ b/compiler-wasm/src/loader_file.rs @@ -33,7 +33,7 @@ impl FileLoader { #[async_trait::async_trait(?Send)] impl ResourceLoader for FileLoader { async fn load_raw(&self, path: &str) -> PackerResult> { - yield_now!()?; + let _ = yield_now!(); let result: Result = async { let promise = self .load_file diff --git a/compiler-wasm/src/loader_url.rs b/compiler-wasm/src/loader_url.rs index f229594b..2c34dc34 100644 --- a/compiler-wasm/src/loader_url.rs +++ b/compiler-wasm/src/loader_url.rs @@ -30,7 +30,7 @@ impl UrlLoader { #[async_trait::async_trait(?Send)] impl ResourceLoader for UrlLoader { async fn load_raw(&self, url: &str) -> PackerResult> { - yield_now!()?; + let _ = yield_now!(); let result: Result = async { let promise = self .fetch diff --git a/docs/src/.vitepress/nav/plugins.ts b/docs/src/.vitepress/nav/plugins.ts index f9e068ad..347a8b1f 100644 --- a/docs/src/.vitepress/nav/plugins.ts +++ b/docs/src/.vitepress/nav/plugins.ts @@ -9,16 +9,7 @@ export const pluginsSideBar = { text: "Built-in Plugins", items: [ { text: "Getting Started", link: "/plugin/getting-started" }, - // { text: "Configuration", link: "/route/configuration" }, - // { text: "File Structure", link: "/route/file-structure" }, - // { text: "Route Structure", link: "/route/route-structure" }, - // { text: "Customizing Lines", link: "/route/customizing-lines" }, - // { text: "Customizing Text", link: "/route/customizing-text" }, - // { - // text: "Customizing Movements", - // link: "/route/customizing-movements", - // }, - // { text: "Using Presets", link: "/route/using-presets" }, + { text: "Link", link: "/plugin/link" }, ], }, // { diff --git a/docs/src/plugin/getting-started.md b/docs/src/plugin/getting-started.md index e0dec87d..7dac5d2a 100644 --- a/docs/src/plugin/getting-started.md +++ b/docs/src/plugin/getting-started.md @@ -1,4 +1,9 @@ # Getting Started +:::tip +The plugin system is currently unstable. +::: +The principle of the plugin system is to separate core Celer functionalities from additional (and mostly optional) functionalities. + A plugin in Celer is a piece of program that runs as part of the compiler. The process goes as the following: 1. The compiler parses the income files and celer-specific syntax like presets @@ -6,7 +11,6 @@ A plugin in Celer is a piece of program that runs as part of the compiler. The p 3. The plugin is free to make any changes to the document. Then it hands the document to the next plugin in line. 4. After the last plugin is done modifying the document, it hands the document back to the compiler. -The principle of the plugin system is to separate core Celer functionalities from additional (and mostly optional) functionalities. ## Configuration To add a plugin to the compiler, use the `plugins` property in your `config`. diff --git a/docs/src/plugin/index.md b/docs/src/plugin/index.md index 6dc334a2..a19ffb9a 100644 --- a/docs/src/plugin/index.md +++ b/docs/src/plugin/index.md @@ -1,4 +1,7 @@ # Plugins +:::tip +The plugin system is currently unstable. +::: This section contains guides for how to utilize Celer's plugin system to add extra features to your route doc. diff --git a/docs/src/plugin/link.md b/docs/src/plugin/link.md index e69de29b..cf87118b 100644 --- a/docs/src/plugin/link.md +++ b/docs/src/plugin/link.md @@ -0,0 +1,61 @@ +# Link Plugin +:::tip +The plugin system is currently unstable. +::: +The link plugin transforms `link` tags in texts into clickable link. + +Add the plugin with +```yaml +config: +- plugins: + use: link +``` + +## Examples +Add clickable links in primary, secondary, or note section of a line +```yaml +config: +- plugins: + use: link +route: +- Example Section: + - Link in the notes: + notes: .link(https://example.com) + - Link in the comment: + comment: .link(https://example.com) + - .link(https://example.com) +``` +Override the link text by putting it in `[` and `]` +```yaml +config: +- plugins: + use: link +route: +- Example Section: + - Example Line: + # Text will show "click here to see the notes", with "here" clickable + notes: Click .link([here]https://example.com) to see the notes +``` +Override the link style by defining the `link` tag in config + +```yaml +config: +- plugins: + use: link + tags: + link: + italic: true # show links in italic +route: +- Example Section: + - Example Line: + # "here" will be clickable and will be italic + notes: Click .link([here]https://example.com) to see the notes +``` +:::tip +The link already inherits styles from the Celer UI (with a different color and underline). +Only override the style if needed! +::: +:::warning +Specifying `underline: false` will probably have no effect because Celer UI renders +links with underlines. +::: diff --git a/docs/src/route/built-in-plugins.md b/docs/src/route/built-in-plugins.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/src/route/customizing-lines.md b/docs/src/route/customizing-lines.md index 963741dd..b99a84ba 100644 --- a/docs/src/route/customizing-lines.md +++ b/docs/src/route/customizing-lines.md @@ -155,9 +155,8 @@ The `presets` property allow you to define additional presets for the line. See [Using Presets](./using-presets.md) for details. ## Other Properties -Built-in and third-party plugins may let you set additional properties. Check the -documentation for [Built-in plugins](./built-in-plugins) or the third-party plugin -you are using. +If you are using [Plugins](../plugin/index.md). You may be able to specify additional properties. Please refer to the +documentation for the plugin you are using. If you specify a property that is not recognized by Celer or any plugin, you will see a warning message saying that property is unused. From a3b4bb5da80c75742e1093a8f129195b68cc2040 Mon Sep 17 00:00:00 2001 From: Pistonight Date: Sat, 7 Oct 2023 19:47:45 -0700 Subject: [PATCH 4/5] fix import, etc --- compiler-core/src/comp/comp_doc.rs | 3 +-- compiler-core/src/plug/link.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler-core/src/comp/comp_doc.rs b/compiler-core/src/comp/comp_doc.rs index aa9abefc..6255f2bf 100644 --- a/compiler-core/src/comp/comp_doc.rs +++ b/compiler-core/src/comp/comp_doc.rs @@ -1,5 +1,4 @@ -use celerctypes::DocDiagnostic; -use celerctypes::{DocPoorText, RouteMetadata}; +use celerctypes::{DocDiagnostic, DocPoorText, RouteMetadata}; use serde::{Deserialize, Serialize}; use crate::api::CompilerMetadata; diff --git a/compiler-core/src/plug/link.rs b/compiler-core/src/plug/link.rs index 66e0493d..6088addb 100644 --- a/compiler-core/src/plug/link.rs +++ b/compiler-core/src/plug/link.rs @@ -4,8 +4,7 @@ use celerctypes::DocRichText; -use crate::comp::CompDoc; -use crate::comp::prop; +use crate::comp::{prop, CompDoc}; use super::operation; pub async fn run_link_plugin(comp_doc: &mut CompDoc) { From bb838181bb5fbd5d137c499be02d6271ed90dfdf Mon Sep 17 00:00:00 2001 From: Pistonight Date: Sat, 7 Oct 2023 20:02:58 -0700 Subject: [PATCH 5/5] fix linter --- compiler-core/src/comp/comp_doc.rs | 2 +- compiler-core/src/exec/exec_doc.rs | 16 ++-- compiler-core/src/pack/mod.rs | 4 +- compiler-core/src/pack/pack_config.rs | 24 +++--- compiler-core/src/pack/pack_use.rs | 5 +- compiler-core/src/pack/resource/loader.rs | 2 +- compiler-core/src/plug/link.rs | 89 +++++++++++++++-------- compiler-core/src/plug/mod.rs | 11 ++- compiler-core/src/plug/operation.rs | 5 +- 9 files changed, 94 insertions(+), 64 deletions(-) diff --git a/compiler-core/src/comp/comp_doc.rs b/compiler-core/src/comp/comp_doc.rs index 6255f2bf..7bbce5ee 100644 --- a/compiler-core/src/comp/comp_doc.rs +++ b/compiler-core/src/comp/comp_doc.rs @@ -121,7 +121,7 @@ impl Compiler { route: vec![self.create_empty_section_for_error(errors).await], project: self.project, preface: vec![], - diagnostics: vec![], + diagnostics: vec![], }, self.meta, ) diff --git a/compiler-core/src/exec/exec_doc.rs b/compiler-core/src/exec/exec_doc.rs index ed1c0457..719e8f1d 100644 --- a/compiler-core/src/exec/exec_doc.rs +++ b/compiler-core/src/exec/exec_doc.rs @@ -27,8 +27,8 @@ impl CompDoc { #[cfg(test)] mod test { use celerctypes::{ - DocPoorText, ExecLine, ExecMapSection, ExecSection, GameCoord, MapLine, MapMetadata, - RouteMetadata, DocDiagnostic, + DocDiagnostic, DocPoorText, ExecLine, ExecMapSection, ExecSection, GameCoord, MapLine, + MapMetadata, RouteMetadata, }; use crate::comp::{CompLine, CompMovement, CompSection}; @@ -46,13 +46,11 @@ mod test { let test_preface = vec![vec![DocPoorText::Text("test".to_string())]]; - let test_diagnostics = vec![ - DocDiagnostic { - msg: parse_poor("test msg"), - msg_type: "test".to_string(), - source: "test".to_string(), - } - ]; + let test_diagnostics = vec![DocDiagnostic { + msg: parse_poor("test msg"), + msg_type: "test".to_string(), + source: "test".to_string(), + }]; let test_doc = CompDoc { project: test_metadata.clone(), diff --git a/compiler-core/src/pack/mod.rs b/compiler-core/src/pack/mod.rs index 7a34dcf3..50e55729 100644 --- a/compiler-core/src/pack/mod.rs +++ b/compiler-core/src/pack/mod.rs @@ -34,11 +34,9 @@ pub use resource::*; use crate::json::Cast; use crate::lang::parse_poor; -#[cfg(feature = "wasm")] -use crate::util::WasmError; #[derive(Debug, Clone, PartialEq, thiserror::Error, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", tag = "type", content="data")] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] pub enum PackerError { #[error("The project file (project.yaml) is missing or invalid.")] InvalidProject, diff --git a/compiler-core/src/pack/pack_config.rs b/compiler-core/src/pack/pack_config.rs index 66542eab..bc692eae 100644 --- a/compiler-core/src/pack/pack_config.rs +++ b/compiler-core/src/pack/pack_config.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use celerctypes::{DocTag, MapMetadata}; -use serde_json::{Value, json}; +use serde_json::{json, Value}; use crate::api::Setting; use crate::comp::prop; use crate::json::{Cast, Coerce}; use crate::lang::Preset; -use crate::plug::{Plugin, PluginRuntime, BuiltInPlugin}; +use crate::plug::{BuiltInPlugin, Plugin, PluginRuntime}; use crate::util::async_for; use super::{pack_map, pack_presets, PackerError, PackerResult, Resource, Use, ValidUse}; @@ -31,7 +31,7 @@ pub async fn pack_config( setting: &Setting, ) -> PackerResult<()> { match Use::try_from(config) { - Ok(Use::Invalid(path)) => return Err(PackerError::InvalidUse(path)), + Ok(Use::Invalid(path)) => Err(PackerError::InvalidUse(path)), Ok(Use::Valid(valid_use)) => { // load a config from top-level use object process_config_from_use(builder, project_resource, valid_use, index, setting).await @@ -66,7 +66,9 @@ async fn process_config( index: usize, setting: &Setting, ) -> PackerResult<()> { - let config = config.try_into_object().map_err(|_| PackerError::InvalidConfigType(index))?; + let config = config + .try_into_object() + .map_err(|_| PackerError::InvalidConfigType(index))?; // add values to builder let _ = async_for!((key, value) in config.into_iter(), { @@ -78,7 +80,7 @@ async fn process_config( builder.map = Some(pack_map(value, index).await?); } prop::ICONS => { - process_icons_config(builder, resource, value, index).await?; + process_icons_config(builder, resource, value, index).await?; } prop::TAGS => { let tags = value.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::TAGS.to_string()))?; @@ -99,13 +101,12 @@ async fn process_config( } prop::PLUGINS => { process_plugins_config(builder, resource, value, index).await?; - } + } _ => return Err(PackerError::UnusedConfigProperty(index, key)), } }); Ok(()) - } /// Process the `icons` property @@ -117,7 +118,9 @@ async fn process_icons_config( icons: Value, index: usize, ) -> PackerResult<()> { - let icons = icons.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, prop::ICONS.to_string()))?; + let icons = icons + .try_into_object() + .map_err(|_| PackerError::InvalidConfigProperty(index, prop::ICONS.to_string()))?; let _ = async_for!((key, v) in icons.into_iter(), { match Use::try_from(v) { @@ -149,7 +152,9 @@ async fn process_plugins_config( plugins: Value, index: usize, ) -> PackerResult<()> { - let plugins = plugins.try_into_array().map_err(|_| PackerError::InvalidConfigProperty(index, prop::PLUGINS.to_string()))?; + let plugins = plugins + .try_into_array() + .map_err(|_| PackerError::InvalidConfigProperty(index, prop::PLUGINS.to_string()))?; let _ = async_for!((i, v) in plugins.into_iter().enumerate(), { let v = v.try_into_object().map_err(|_| PackerError::InvalidConfigProperty(index, format!("{}[{}]", prop::PLUGINS, i)))?; @@ -194,4 +199,3 @@ async fn process_plugins_config( Ok(()) } - diff --git a/compiler-core/src/pack/pack_use.rs b/compiler-core/src/pack/pack_use.rs index 9e61cba8..dda64ea2 100644 --- a/compiler-core/src/pack/pack_use.rs +++ b/compiler-core/src/pack/pack_use.rs @@ -270,7 +270,10 @@ mod test { ]; for test in tests { - assert_eq!(Use::try_from(make_use(test)), Ok(Use::Invalid(test.to_string()))); + assert_eq!( + Use::try_from(make_use(test)), + Ok(Use::Invalid(test.to_string())) + ); } } } diff --git a/compiler-core/src/pack/resource/loader.rs b/compiler-core/src/pack/resource/loader.rs index 77683c8d..d93af3e0 100644 --- a/compiler-core/src/pack/resource/loader.rs +++ b/compiler-core/src/pack/resource/loader.rs @@ -13,7 +13,7 @@ pub trait ResourceLoader { let bytes = self.load_raw(path).await?; match String::from_utf8(bytes) { Ok(v) => Ok(v), - Err(e) => Err(PackerError::InvalidUtf8(path.to_string())), + Err(_) => Err(PackerError::InvalidUtf8(path.to_string())), } } diff --git a/compiler-core/src/plug/link.rs b/compiler-core/src/plug/link.rs index 6088addb..aeb8871d 100644 --- a/compiler-core/src/plug/link.rs +++ b/compiler-core/src/plug/link.rs @@ -4,22 +4,32 @@ use celerctypes::DocRichText; -use crate::comp::{prop, CompDoc}; use super::operation; +use crate::comp::{prop, CompDoc}; pub async fn run_link_plugin(comp_doc: &mut CompDoc) { // add the link tag if not defined already - comp_doc.project.tags.entry(prop::LINK.to_string()).or_default(); + comp_doc + .project + .tags + .entry(prop::LINK.to_string()) + .or_default(); operation::for_all_lines(comp_doc, |mut line| async { operation::for_all_rich_text(&mut line, transform_link_tag).await; line - }).await + }) + .await } fn transform_link_tag(rich_text: &mut DocRichText) { - if !rich_text.tag.as_ref().filter(|tag| tag == &prop::LINK).is_some() { + if rich_text + .tag + .as_ref() + .filter(|tag| tag == &prop::LINK) + .is_none() + { return; - } + } if rich_text.link.is_some() { return; } @@ -27,7 +37,7 @@ fn transform_link_tag(rich_text: &mut DocRichText) { if rich_text.text.starts_with('[') { match rich_text.text.find(']') { Some(i) => { - rich_text.link = Some(rich_text.text[i+1..].trim().to_string()); + rich_text.link = Some(rich_text.text[i + 1..].trim().to_string()); rich_text.text = rich_text.text[1..i].to_string(); } None => { @@ -83,11 +93,14 @@ mod test { link: None, }; transform_link_tag(&mut text); - assert_eq!(text, DocRichText { - tag: Some(prop::LINK.to_string()), - text: "hello world".to_string(), - link: Some("hello world".to_string()), - }); + assert_eq!( + text, + DocRichText { + tag: Some(prop::LINK.to_string()), + text: "hello world".to_string(), + link: Some("hello world".to_string()), + } + ); } #[test] @@ -98,12 +111,15 @@ mod test { link: None, }; transform_link_tag(&mut text); - assert_eq!(text, DocRichText { - tag: Some(prop::LINK.to_string()), - text: "hello world".to_string(), - // link should be trimmed - link: Some("i am link".to_string()), - }); + assert_eq!( + text, + DocRichText { + tag: Some(prop::LINK.to_string()), + text: "hello world".to_string(), + // link should be trimmed + link: Some("i am link".to_string()), + } + ); } #[test] @@ -114,11 +130,14 @@ mod test { link: None, }; transform_link_tag(&mut text); - assert_eq!(text, DocRichText { - tag: Some(prop::LINK.to_string()), - text: "[hello world i am link".to_string(), - link: Some("[hello world i am link".to_string()), - }); + assert_eq!( + text, + DocRichText { + tag: Some(prop::LINK.to_string()), + text: "[hello world i am link".to_string(), + link: Some("[hello world i am link".to_string()), + } + ); let mut text = DocRichText { tag: Some(prop::LINK.to_string()), @@ -126,11 +145,14 @@ mod test { link: None, }; transform_link_tag(&mut text); - assert_eq!(text, DocRichText { - tag: Some(prop::LINK.to_string()), - text: "abc[hello world] i am link".to_string(), - link: Some("abc[hello world] i am link".to_string()), - }); + assert_eq!( + text, + DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abc[hello world] i am link".to_string(), + link: Some("abc[hello world] i am link".to_string()), + } + ); let mut text = DocRichText { tag: Some(prop::LINK.to_string()), @@ -138,10 +160,13 @@ mod test { link: None, }; transform_link_tag(&mut text); - assert_eq!(text, DocRichText { - tag: Some(prop::LINK.to_string()), - text: "abchello world] i am link".to_string(), - link: Some("abchello world] i am link".to_string()), - }); + assert_eq!( + text, + DocRichText { + tag: Some(prop::LINK.to_string()), + text: "abchello world] i am link".to_string(), + link: Some("abchello world] i am link".to_string()), + } + ); } } diff --git a/compiler-core/src/plug/mod.rs b/compiler-core/src/plug/mod.rs index 41146cbb..9ac07ec4 100644 --- a/compiler-core/src/plug/mod.rs +++ b/compiler-core/src/plug/mod.rs @@ -2,7 +2,7 @@ use celerctypes::DocDiagnostic; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::comp::{CompDoc, CompSection}; +use crate::comp::CompDoc; use crate::lang::parse_poor; mod link; @@ -24,7 +24,6 @@ impl PlugError { } } - pub type PlugResult = Result; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -38,7 +37,9 @@ impl PluginRuntime { pub async fn run(&self, comp_doc: &mut CompDoc) -> PlugResult<()> { match &self.plugin { Plugin::BuiltIn(built_in) => built_in.run(comp_doc).await, - Plugin::Script(_) => Err(PlugError::NotImpl("Script plugins are not implemented yet".to_string())), + Plugin::Script(_) => Err(PlugError::NotImpl( + "Script plugins are not implemented yet".to_string(), + )), } } } @@ -54,7 +55,7 @@ pub enum Plugin { #[serde(rename_all = "kebab-case")] pub enum BuiltInPlugin { /// Transform link tags to clickable links. See [`link`] - Link + Link, } impl BuiltInPlugin { @@ -81,6 +82,4 @@ pub async fn run_plugins(mut comp_doc: CompDoc, plugins: &[PluginRuntime]) -> Co } } comp_doc - } - diff --git a/compiler-core/src/plug/operation.rs b/compiler-core/src/plug/operation.rs index 535e26fa..cf2ac09c 100644 --- a/compiler-core/src/plug/operation.rs +++ b/compiler-core/src/plug/operation.rs @@ -1,7 +1,10 @@ use celerctypes::DocRichText; use futures::Future; -use crate::{comp::{CompLine, CompDoc}, util::async_for}; +use crate::{ + comp::{CompDoc, CompLine}, + util::async_for, +}; /// Transform all [`CompLine`] in a document with function F pub async fn for_all_lines(comp_doc: &mut CompDoc, fun: Func)