Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export menu #183

Merged
merged 8 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 66 additions & 19 deletions compiler-core/src/expo/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,76 @@
//! # Export (expo) phase
//!
//! This phase collects export metadata from the plugins. The export metadata
//! is used to display export options to the user.
//!
//! # Input
//! The input is a [`ExecContext`].
//!
//! # Output
//! The output is a [`ExpoContext`].
use serde_json::Value;

use crate::exec::ExecContext;
use crate::macros::derive_wasm;

/// Output of the export phase
#[derive_wasm]
pub struct ExpoContext<'p> {
#[serde(flatten)]
pub exec_ctx: ExecContext<'p>,
/// The export metadata
pub export_metadata: Vec<ExportMetadata>,
}

/// Data to define a plugin's export capability
#[derive(Debug, Clone)]
#[derive_wasm]
pub struct ExportMetadata {
/// The id of the plugin. This is the same string in the `use` of the plugin
/// The id of the export plugin to run. This is the same string in the `use` of the plugin
pub plugin_id: String,
/// The target of the exporter
pub target: ExportTarget,
/// Name of the export. For example "LiveSplit"
/// Name of the export. For example "LiveSplit". This is shown in the menu
pub name: String,
/// File extension of the export. For example "lss"
pub extension: Option<String>,
/// Long description. This shows as a tooltip
pub description: String,
// todo: icon

/// (Optional) Icon to show next to the name
#[serde(default)]
pub icon: ExportIcon,

/// (Optional) File extension of the export. For example "lss"
pub extension: Option<String>,

/// Extra properties to pass to the exporter when running
///
/// This can be used to distinguish multiple exports from the same exporter.
/// This is not part of the config and cannot be changed by the user
pub properties: Value,

/// (Optional) Example YAML configuration for the exporter to show to the user
pub example_config: Option<String>,
}

pub enum ExportTarget {
/// The exporter only runs for the CompDoc
CompDoc,
/// The exporter only runs for the ExecDoc
ExecDoc,
/// The exporter should run for both phases,
/// and produce the output in the ExecDoc phase
Both,
/// Icon for the export.
///
/// This is only for visual. It does not restrict what the export can/cannot contain.
#[derive(Debug, Clone, Default)]
#[derive_wasm]
pub enum ExportIcon {
Archive,
Binary,
Cat,
Code,
Data,
#[default]
File,
Image,
Text,
Video,
}

/// The exported document type
#[derive(Debug, Clone)]
#[derive_wasm]
pub struct ExpoDoc {
/// The file name
pub file_name: String,
Expand All @@ -34,14 +79,16 @@ pub struct ExpoDoc {
}

impl<'p> ExecContext<'p> {
pub fn prepare_exports(&mut self) -> Vec<ExportMetadata> {
pub fn prepare_exports(mut self) -> ExpoContext<'p> {
let mut result = Vec::new();
for plugin in &mut self.plugin_runtimes {
// TODO #33: error handling
if let Ok(Some(meta)) = plugin.on_prepare_export() {
result.push(meta);
result.extend(meta);
}
}
result
ExpoContext {
exec_ctx: self,
export_metadata: result,
}
}
}
1 change: 1 addition & 0 deletions compiler-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod prep;
// public API re-exports
pub use comp::CompDoc;
pub use exec::{ExecContext, ExecDoc};
pub use expo::{ExpoContext, ExpoDoc};
pub use pack::{CompileContext, Compiler};
pub use prep::{ContextBuilder, PreparedContext};

Expand Down
30 changes: 30 additions & 0 deletions compiler-core/src/plugin/builtin/livesplit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Exporter plugin for LiveSplit split files

use std::borrow::Cow;

use serde_json::json;

use crate::expo::{ExportIcon, ExportMetadata};

use crate::plugin::{PluginResult, PluginRuntime};

pub struct ExportLiveSplitPlugin;

impl PluginRuntime for ExportLiveSplitPlugin {
fn get_id(&self) -> Cow<'static, str> {
Cow::Owned(super::BuiltInPlugin::ExportLiveSplit.id())
}

fn on_prepare_export(&mut self) -> PluginResult<Option<Vec<ExportMetadata>>> {
let metadata = ExportMetadata {
plugin_id: self.get_id().into_owned(),
name: "LiveSplit".to_string(),
description: "Export to a LiveSplit split file".to_string(),
icon: ExportIcon::Data,
extension: Some("lss".to_string()),
properties: json!(null),
example_config: Some(include_str!("./livesplit.yaml").to_string()),
};
Ok(Some(vec![metadata]))
}
}
13 changes: 13 additions & 0 deletions compiler-core/src/plugin/builtin/livesplit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Do you want subsplits?
# --------
# Subsplits are divided by sections in the route document
# Change below to `true` to enable subsplits
subsplits: false

# Do you want icons?
# --------
# Change below to `false` to disable icons
icons: true

# Keep this as-is to use the splits configured in the settings
split-types: null
14 changes: 9 additions & 5 deletions compiler-core/src/plugin/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ use super::{PluginResult, PluginRuntime};

mod botw_unstable;
mod link;
mod livesplit;
mod metrics;
mod variables;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltInPlugin {
Metrics,
BotwAbilityUnstable,
#[serde(rename = "export-livesplit")]
ExportLiveSplit,
Link,
Metrics,
Variables,
BotwAbilityUnstable,
}

impl BuiltInPlugin {
Expand All @@ -30,15 +33,16 @@ impl BuiltInPlugin {
props: &Value,
) -> PluginResult<Box<dyn PluginRuntime>> {
match &self {
BuiltInPlugin::BotwAbilityUnstable => Ok(Box::new(
botw_unstable::BotwAbilityUnstablePlugin::from_props(props),
)),
BuiltInPlugin::ExportLiveSplit => Ok(Box::new(livesplit::ExportLiveSplitPlugin)),
BuiltInPlugin::Link => Ok(Box::new(link::LinkPlugin)),
BuiltInPlugin::Metrics => Ok(Box::new(metrics::MetricsPlugin::from_props(
props,
&ctx.start_time,
))),
BuiltInPlugin::Variables => Ok(Box::new(variables::VariablesPlugin::from_props(props))),
BuiltInPlugin::BotwAbilityUnstable => Ok(Box::new(
botw_unstable::BotwAbilityUnstablePlugin::from_props(props),
)),
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler-core/src/plugin/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ use crate::lang::BaseError;
pub enum PluginError {
#[error("An exception occured while executing script: {0}")]
ScriptException(String),

#[error("Extra plugin at `{0}` from plugin options is invalid: {1}")]
InvalidAddPlugin(usize, String),

#[error("The plugin `{0}` does not implement the required `{1}` method!")]
NotImplemented(String, String),
}

pub type PluginResult<T> = Result<T, PluginError>;
Expand Down
33 changes: 29 additions & 4 deletions compiler-core/src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,49 @@ pub trait PluginRuntime {
fn on_before_compile(&mut self, _ctx: &mut CompileContext) -> PluginResult<()> {
Ok(())
}

/// Called after the route is compiled, to transform the route
fn on_after_compile(&mut self, _doc: &mut CompDoc) -> PluginResult<()> {
Ok(())
}

/// Called after the route is turned into ExecDoc
fn on_after_execute(&mut self, _doc: &mut ExecDoc) -> PluginResult<()> {
Ok(())
}

fn on_prepare_export(&mut self) -> PluginResult<Option<ExportMetadata>> {
/// Called at the end of compilation to check what exports are available
fn on_prepare_export(&mut self) -> PluginResult<Option<Vec<ExportMetadata>>> {
Ok(None)
}

fn on_export_comp_doc(&mut self, _doc: &CompDoc) -> PluginResult<Option<ExpoDoc>> {
/// Called only in export workflow, to let the exporter access the CompDoc
///
/// If the exporter needs to access the ExecDoc as well, it should return `None`.
/// Otherwise, the returned export data will be used and the exporter will not be called
/// with the ExecDoc
fn on_export_comp_doc(
&mut self,
_properties: &Value,
_payload: &Value,
_doc: &CompDoc,
) -> PluginResult<Option<ExpoDoc>> {
Ok(None)
}
fn on_export_exec_doc(&mut self, _doc: &ExecDoc) -> PluginResult<Option<ExpoDoc>> {
Ok(None)

/// Called only in export workflow, to let the exporter access the ExecDoc
///
/// The exporter must return the export data or throw an error
fn on_export_exec_doc(
&mut self,
_properties: Value,
_payload: Value,
_doc: &ExecDoc,
) -> PluginResult<ExpoDoc> {
Err(PluginError::NotImplemented(
self.get_display_name().into_owned(),
"on_export_exec_doc".into(),
))
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler-wasm/build/src/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
import { callWorker } from "low/utils";

// serde_json::Value
export type Value = null | boolean | number | string | Value[] | { [key: string]: Value };
export type Value = unknown;
19 changes: 9 additions & 10 deletions compiler-wasm/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use celerc::{
CompDoc, CompileContext, Compiler, ContextBuilder, ExecContext, PluginOptions, PreparedContext,
};

use crate::interop::OpaqueExecContext;
use crate::interop::OpaqueExpoContext;
use crate::loader::{self, LoadFileOutput, LoaderInWasm};
use crate::plugin;

Expand All @@ -23,15 +23,15 @@ thread_local! {
pub async fn compile_document(
entry_path: Option<String>,
use_cache: bool,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let plugin_options = match plugin::get_plugin_options() {
Ok(x) => x,
Err(message) => {
let message = format!("Failed to load user plugin options: {message}");
error!("{message}");
let diagnostic = DocDiagnostic::error(&message, "web-editor");
let exec_ctx = ExecContext::from_diagnostic(diagnostic);
return OpaqueExecContext::try_from(exec_ctx);
return OpaqueExpoContext::try_from(exec_ctx.prepare_exports());
}
};

Expand All @@ -58,8 +58,7 @@ pub async fn compile_document(
Err(e) => {
let comp_doc = CompDoc::from_prep_error(e, start_time);
let exec_context = comp_doc.execute().await;
// TODO #33: exports
return OpaqueExecContext::try_from(exec_context);
return OpaqueExpoContext::try_from(exec_context.prepare_exports());
}
};

Expand Down Expand Up @@ -100,7 +99,7 @@ async fn compile_in_context(
prep_ctx: &PreparedContext<LoaderInWasm>,
start_time: Option<Instant>,
plugin_options: Option<PluginOptions>,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let mut comp_ctx = prep_ctx.new_compilation(start_time).await;
match comp_ctx.configure_plugins(plugin_options).await {
Err(e) => compile_with_pack_error(comp_ctx, e).await,
Expand All @@ -114,16 +113,16 @@ async fn compile_in_context(
async fn compile_with_pack_error(
context: CompileContext<'_>,
error: PackError,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let comp_doc = CompDoc::from_diagnostic(error, context);
let exec_ctx = comp_doc.execute().await;
OpaqueExecContext::try_from(exec_ctx)
OpaqueExpoContext::try_from(exec_ctx.prepare_exports())
}

async fn compile_with_compiler(compiler: Compiler<'_>) -> Result<OpaqueExecContext, JsValue> {
async fn compile_with_compiler(compiler: Compiler<'_>) -> Result<OpaqueExpoContext, JsValue> {
let comp_doc = compiler.compile().await;
let exec_ctx = comp_doc.execute().await;
OpaqueExecContext::try_from(exec_ctx)
OpaqueExpoContext::try_from(exec_ctx.prepare_exports())
}

/// Create a context builder that corresponds to the root project.yaml
Expand Down
6 changes: 3 additions & 3 deletions compiler-wasm/src/interop/opaque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use wasm_bindgen::prelude::*;

use celerc::macros::derive_opaque;
use celerc::ExecContext;
use celerc::ExpoContext;

#[derive_opaque(ExecContext)]
pub struct OpaqueExecContext<'p>;
#[derive_opaque(ExpoContext)]
pub struct OpaqueExpoContext<'p>;
4 changes: 2 additions & 2 deletions compiler-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use celerc::prep::EntryPointsSorted;
use celerc::res::{ResPath, Resource};

mod interop;
use interop::OpaqueExecContext;
use interop::OpaqueExpoContext;
mod compile;
mod loader;
use loader::LoaderInWasm;
Expand Down Expand Up @@ -53,7 +53,7 @@ pub async fn get_entry_points() -> Result<EntryPointsSorted, JsValue> {
pub async fn compile_document(
entry_path: Option<String>,
use_cache: bool,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
compile::compile_document(entry_path, use_cache).await
}

Expand Down
5 changes: 3 additions & 2 deletions docs/src/plugin/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ First follow these steps to get to the user plugin settings:
1. Open the document you want to tweak.
2. Click on <FluentIcon name="Settings20Regular" /> `Settings` on the toolbar.
3. Select the <FluentIcon name="Wrench20Regular" /> `Plugins` category.
4. You should see a text area under `User Plugins`.
4. Under `User Plugins`, click the `Edit Config` button.
5. You should see a dialog with a text area popped up.

The syntax to configure user plugins is:
```yaml
Expand All @@ -40,7 +41,7 @@ Replace:
- `"Document Title"` with the exact title of the document you want to add the plugin to, surrounded by quotes.
There is a little hint above the text area that tells you what the title of the current document is. You can also use the wildcard`"*"` to
add the plugin to all documents.
- `my/plugin/file.js` with the path of the plugin. The plugin should be a file on GitHub,
- `my/extra/plugin1.js` with the path of the plugin. The plugin should be a file on GitHub,
and you can reference it by `<user>/<repo>/path/to/file.js`. See [here](../route/file-structure.md)
for more about the `use` property. However note that you cannot use a path to file on your computer.

Expand Down
Loading
Loading