Skip to content

Commit

Permalink
Export on server (#218)
Browse files Browse the repository at this point in the history
* split edit and view kernel implementation

* fix bug in livesplit export and implement client

* run formater
  • Loading branch information
Pistonight authored Mar 3, 2024
1 parent bed7312 commit bdbf08b
Show file tree
Hide file tree
Showing 47 changed files with 1,079 additions and 691 deletions.
6 changes: 6 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ tasks:
- task: client:check
- task: themes:check

fix:ts:
cmds:
- task: docs:fix
- task: client:fix
- task: themes:fix

check:rs:
deps: [base:grammar]
cmds:
Expand Down
30 changes: 17 additions & 13 deletions compiler-core/src/plugin/builtin/export_livesplit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,26 @@ impl PluginRuntime for ExportLiveSplitPlugin {

let mut segments_xml = String::new();
for section in &doc.route {
let length = section.lines.len();
for (i, line) in section.lines.iter().enumerate() {
let mut split_lines = vec![];
for line in &section.lines {
if should_split_on(line, &split_types) {
let mut name = match &line.split_name {
Some(name) => name.to_string(),
None => line.text.to_string(),
};
if subsplit {
if i == length - 1 {
name = format!("{{{}}}{name}", section.name);
} else {
name = format!("-{name}");
}
split_lines.push(line);
}
}
let length = split_lines.len();
for (i, line) in split_lines.iter().enumerate() {
let mut name = match &line.split_name {
Some(name) => name.to_string(),
None => line.text.to_string(),
};
if subsplit {
if i == length - 1 {
name = format!("{{{}}}{name}", section.name);
} else {
name = format!("-{name}");
}
append_segment(&mut segments_xml, &name, line);
}
append_segment(&mut segments_xml, &name, line);
}
}

Expand Down
22 changes: 9 additions & 13 deletions compiler-wasm/src/compiler/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use instant::Instant;
use log::{error, info};
use wasm_bindgen::prelude::*;

use celerc::pack::PackError;
use celerc::{Compiler, ExpoDoc, ExportRequest, PluginOptions, PreparedContext};
Expand All @@ -14,14 +13,14 @@ pub async fn export_document(
entry_path: Option<String>,
use_cache: bool,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
) -> ExpoDoc {
info!("exporting document");
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}");
return Ok(ExpoDoc::Error(message));
return ExpoDoc::Error(message);
}
};

Expand All @@ -39,7 +38,7 @@ pub async fn export_document(
let prep_ctx = match super::new_context(entry_path).await {
Ok(x) => x,
Err(e) => {
return Ok(ExpoDoc::Error(e.to_string()));
return ExpoDoc::Error(e.to_string());
}
};
let guard = CachedContextGuard::new(prep_ctx);
Expand All @@ -51,7 +50,7 @@ async fn export_in_context(
start_time: Option<Instant>,
plugin_options: Option<PluginOptions>,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
) -> ExpoDoc {
let mut comp_ctx = prep_ctx.new_compilation(start_time).await;
match comp_ctx.configure_plugins(plugin_options).await {
Err(e) => export_with_pack_error(e),
Expand All @@ -62,18 +61,15 @@ async fn export_in_context(
}
}

fn export_with_pack_error(error: PackError) -> Result<ExpoDoc, JsValue> {
Ok(ExpoDoc::Error(error.to_string()))
fn export_with_pack_error(error: PackError) -> ExpoDoc {
ExpoDoc::Error(error.to_string())
}

async fn export_with_compiler(
compiler: Compiler<'_>,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
async fn export_with_compiler(compiler: Compiler<'_>, req: ExportRequest) -> ExpoDoc {
let mut comp_doc = compiler.compile().await;
if let Some(expo_doc) = comp_doc.run_exporter(&req) {
return Ok(expo_doc);
return expo_doc;
}
let exec_ctx = comp_doc.execute().await;
Ok(exec_ctx.run_exporter(req))
exec_ctx.run_exporter(req)
}
2 changes: 1 addition & 1 deletion compiler-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub async fn export_document(
use_cache: bool,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
compiler::export_document(entry_path, use_cache, req).await
Ok(compiler::export_document(entry_path, use_cache, req).await)
}

/// Set user plugin options
Expand Down
26 changes: 26 additions & 0 deletions docs/src/api/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,29 @@ Otherwise, it will return
"data": "error message here"
}
```

## `GET /export/{owner}/{repo}/{ref}[/{path}]`
Export the document
### Parameters
Same as the `/compile` endpoint.

### Headers
|Name|Description|
|-|-|
|`Celer-Export-Request`|(Required) Base64 encoded JSON ExportRequest object|
|`Celer-Plugin-Options`|(Optional) Base64 encoded JSON PluginOptionsRaw object used to specify extra plugin options|

### Returns
It should always return status `200 OK`.

Returns an ExpoDoc, which could be success or error
```json
{
"success": { ... }
}
```
```json
{
"error": "message here",
}
```
59 changes: 13 additions & 46 deletions server/src/api/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use axum::extract::Path;
use axum::http::HeaderMap;
use axum::routing;
use axum::{Json, Router};
use base64::Engine;
use instant::Instant;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;
use tracing::error;

use crate::compiler;

use super::header;

pub fn init_api() -> Router {
Router::new()
.route(
Expand All @@ -38,86 +38,53 @@ async fn compile_owner_repo_ref(
Path((owner, repo, reference)): Path<(String, String, String)>,
headers: HeaderMap,
) -> Json<CompileResponse> {
let plugin_options = match get_plugin_options_from_headers(&headers) {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(CompileResponse::Failure(e)),
};
let response = compile_internal(&owner, &repo, None, &reference, plugin_options).await;
let response = compile_internal(&owner, &repo, None, &reference, &plugin_options).await;
Json(response)
}

async fn compile_owner_repo_ref_path(
Path((owner, repo, reference, path)): Path<(String, String, String, String)>,
headers: HeaderMap,
) -> Json<CompileResponse> {
let plugin_options = match get_plugin_options_from_headers(&headers) {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(CompileResponse::Failure(e)),
};
let response = compile_internal(&owner, &repo, Some(&path), &reference, plugin_options).await;
let response = compile_internal(&owner, &repo, Some(&path), &reference, &plugin_options).await;
Json(response)
}

fn get_plugin_options_from_headers(headers: &HeaderMap) -> Result<Option<String>, String> {
let header_value = match headers.get("Celer-Plugin-Options") {
None => return Ok(None),
Some(v) => v,
};
let header_value = match header_value.to_str() {
Ok(s) => s,
Err(e) => {
error!("Invalid Celer-Plugin-Options header: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};
if header_value.is_empty() {
return Ok(None);
}

let header_decoded = match base64::engine::general_purpose::STANDARD.decode(header_value) {
Ok(v) => v,
Err(e) => {
error!("Failed to decode Celer-Plugin-Options header: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};

let header_str = match String::from_utf8(header_decoded) {
Ok(s) => s,
Err(e) => {
error!("Celer-Plugin-Options header is not valid UTF-8: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};

Ok(Some(header_str))
}

async fn compile_internal(
owner: &str,
repo: &str,
path: Option<&str>,
reference: &str,
plugin_options_json: Option<String>,
plugin_options_json: &str,
) -> CompileResponse {
let start_time = Instant::now();
let prep_ctx = match compiler::get_context(owner, repo, path, reference).await {
Ok(ctx) => ctx,
Err(e) => return CompileResponse::Failure(e.to_string()),
};

let plugin_options = match plugin_options_json {
None => None,
Some(s) => match compiler::parse_plugin_options(&s, &prep_ctx.project_res).await {
let plugin_options = if plugin_options_json.is_empty() {
None
} else {
match compiler::parse_plugin_options(plugin_options_json, &prep_ctx.project_res).await {
Ok(options) => Some(options),
Err(e) => return CompileResponse::Failure(e),
},
}
};

let expo_ctx = compiler::compile(&prep_ctx, Some(start_time), plugin_options).await;
let expo_ctx_json = match serde_json::to_value(expo_ctx) {
Ok(v) => v,
Err(e) => return CompileResponse::Failure(e.to_string()),
};

CompileResponse::Success(expo_ctx_json)
}
83 changes: 83 additions & 0 deletions server/src/api/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use axum::extract::Path;
use axum::http::HeaderMap;
use axum::routing;
use axum::{Json, Router};
use celerc::{ExpoDoc, ExportRequest};
use instant::Instant;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;

use crate::compiler;

use super::header;

pub fn init_api() -> Router {
Router::new()
.route(
"/:owner/:repo/:reference",
routing::get(export_owner_repo_ref),
)
.route(
"/:owner/:repo/:reference/*path",
routing::get(export_owner_repo_ref_path),
)
.layer(ServiceBuilder::new().layer(CompressionLayer::new()))
}

async fn export_owner_repo_ref(
Path((owner, repo, reference)): Path<(String, String, String)>,
headers: HeaderMap,
) -> Json<ExpoDoc> {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let req = match header::get_export_request(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let response = export_internal(&owner, &repo, None, &reference, &plugin_options, req).await;
Json(response)
}

async fn export_owner_repo_ref_path(
Path((owner, repo, reference, path)): Path<(String, String, String, String)>,
headers: HeaderMap,
) -> Json<ExpoDoc> {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let req = match header::get_export_request(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let response =
export_internal(&owner, &repo, Some(&path), &reference, &plugin_options, req).await;
Json(response)
}
async fn export_internal(
owner: &str,
repo: &str,
path: Option<&str>,
reference: &str,
plugin_options_json: &str,
req: ExportRequest,
) -> ExpoDoc {
let start_time = Instant::now();
let prep_ctx = match compiler::get_context(owner, repo, path, reference).await {
Ok(ctx) => ctx,
Err(e) => return ExpoDoc::Error(e.to_string()),
};

let plugin_options = if plugin_options_json.is_empty() {
None
} else {
match compiler::parse_plugin_options(plugin_options_json, &prep_ctx.project_res).await {
Ok(options) => Some(options),
Err(e) => return ExpoDoc::Error(e),
}
};

compiler::export(&prep_ctx, Some(start_time), plugin_options, req).await
}
Loading

0 comments on commit bdbf08b

Please sign in to comment.