From 4c52e661e7d0eae44984c1bbca20ce9473258213 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 14 Mar 2025 03:24:24 +0000 Subject: [PATCH 01/11] add minijinja package Signed-off-by: luojiyin --- Cargo.lock | 24 ++++++++++++++++++++++++ aiscript-runtime/Cargo.toml | 1 + 2 files changed, 25 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ba003df..7ba7dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,7 @@ dependencies = [ "hyper-util", "indexmap", "jsonwebtoken", + "minijinja", "notify", "oas3", "redis", @@ -1551,6 +1552,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + [[package]] name = "mime" version = "0.3.17" @@ -1567,6 +1574,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "minijinja" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e877d961d4f96ce13615862322df7c0b6d169d40cab71a7ef3f9b9e594451e" +dependencies = [ + "memo-map", + "self_cell", + "serde", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2456,6 +2474,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" + [[package]] name = "semver" version = "1.0.24" diff --git a/aiscript-runtime/Cargo.toml b/aiscript-runtime/Cargo.toml index 8b85e39..e84f8c2 100644 --- a/aiscript-runtime/Cargo.toml +++ b/aiscript-runtime/Cargo.toml @@ -35,3 +35,4 @@ redis.workspace = true toml = "0.8" oas3 = "0.15" reqwest.workspace = true +minijinja = { version = "1.0", features = ["loader"] } From 2212b99917a4fa99a0a9e58143adc3d7752dbbde Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 14 Mar 2025 04:29:51 +0000 Subject: [PATCH 02/11] add template engine to lib.rs Signed-off-by: luojiyin --- aiscript-runtime/src/lib.rs | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/aiscript-runtime/src/lib.rs b/aiscript-runtime/src/lib.rs index 0c188db..89e1408 100644 --- a/aiscript-runtime/src/lib.rs +++ b/aiscript-runtime/src/lib.rs @@ -300,3 +300,55 @@ async fn run_server( } } } + +fn generate_openapi_json(routes: &[ast::Route]) -> serde_json::Value { + let mut openapi = serde_json::json!({ + "openapi": "3.0.0", + "info": { + "title": "AIScript API", + "version": "1.0.0", + "description": "API documentation for AIScript" + }, + "paths": {}, + }); + + //Add paths from routes + let paths = openapi["paths"].as_object_mut().unwrap(); + + for route in routes { + for endpoint in &route.endpoints { + for path_spec in &endpoint.path_specs { + let path = if route.prefix == "/" { + path_spec.path.clone() + } else { + format!("{}{}", route.prefix, path_spec.path) + }; + + let method = match path_spec.method { + ast::HttpMethod::Get => "get", + ast::HttpMethod::Post => "post", + ast::HttpMethod::Put => "put", + ast::HttpMethod::Delete => "delete", + }; + + //For each method, add the path and method to the paths object + if !paths.contains_key(&path) { + paths.insert(path.clone(), serde_json::json!({})); + } + + //Add the method to the path + let path_obj = paths.get_mut(&path).unwrap(); + path_obj[method] = serde_json::json!({ + "summary": format!("{} {}", method.to_uppercase(), path), + "responses": { + "200": { + "description": "Successful response" + } + } + }); + } + } + } + + openapi +} From 7cf78545dea76d7f1ea701f0e51c7eefd1215121 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 14 Mar 2025 04:32:58 +0000 Subject: [PATCH 03/11] add comment Signed-off-by: luojiyin --- aiscript-runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/aiscript-runtime/src/lib.rs b/aiscript-runtime/src/lib.rs index 89e1408..40edaf1 100644 --- a/aiscript-runtime/src/lib.rs +++ b/aiscript-runtime/src/lib.rs @@ -301,6 +301,7 @@ async fn run_server( } } +// Generate OpenAPI JSON from routes fn generate_openapi_json(routes: &[ast::Route]) -> serde_json::Value { let mut openapi = serde_json::json!({ "openapi": "3.0.0", From ab33589a5bf5604f5dc268870d35d5582d7759ca Mon Sep 17 00:00:00 2001 From: luojiyin Date: Tue, 18 Mar 2025 03:29:22 +0000 Subject: [PATCH 04/11] set template and render --- aiscript-runtime/src/template.rs | 57 ++++++++++++++++++++++++++++++++ aiscript-vm/src/stdlib/web.rs | 22 ++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 aiscript-runtime/src/template.rs create mode 100644 aiscript-vm/src/stdlib/web.rs diff --git a/aiscript-runtime/src/template.rs b/aiscript-runtime/src/template.rs new file mode 100644 index 0000000..c5f2d8e --- /dev/null +++ b/aiscript-runtime/src/template.rs @@ -0,0 +1,57 @@ +use minijinja::{Environment, Source}; +use std::path::Path; +use std::sync::Arc; +use std::sync::RwLock; + + +/// Template engine for AIScript +pub struct TemplateEngine { + env: RwLock>, +} + +impl TemplateEngine { + /// Create a new template engine + pub fn new() -> Self { + let mut env = Environment::new(); + + //Set the source to the templates directory + let source = Source::from_path("templates"); + env.set_source(source); + + Self { + env: RwLock::new(env), + } + } + + /// Render a template with the given context + pub fn render(&self, template_name: &str, context: serde_json::Value) -> Result { + let env = self.env.read().unwrap(); + + //get template + let template = env.get_template(template_name).map_err(|e| format!("Failed to get template: {}", e))?; + + //render template + let rendered = template.render(&context).map_err(|e| format!("Failed to render template: {}", e))?; + } + + /// Reload the templates + pub fn reload(&self) -> Result<(), String> { + let mut env = self.env.write().unwrap(); + + //reload templates + let source = Source::from_path("templates"); + env.set_source(source); + + Ok(()) + } +} + +//Create a global instance of the template engine +lazy_static::lazy_static! { + static ref TEMPLATE_ENGINE: TemplateEngine = TemplateEngine::new(); +} + +//Get the template engine instance +pub fn get_template_engine() -> &'static TemplateEngine { + &TEMPLATE_ENGINE +} \ No newline at end of file diff --git a/aiscript-vm/src/stdlib/web.rs b/aiscript-vm/src/stdlib/web.rs new file mode 100644 index 0000000..2f7fed8 --- /dev/null +++ b/aiscript-vm/src/stdlib/web.rs @@ -0,0 +1,22 @@ +use crate::value::Value; +use aiscript_runtime::template::get_template_engine; + +/// Render a template with the given context +pub fn render(args: &[Value]) -> Result { + if args.len() != 2 { + return Err("render expects 2 arguments: template name and context".to_string()); + } + + //get the template name + let template_name = args[0].as_str().ok_or_else(|| "First argument must be a string (template name)".to_string())?; + + //get the context + let context = serde_json::to_value(&args[1]) + .map_err(|e| format!("Failed to convert context to JSON: {}", e))?; + + //render the template + let result = get_template_engine().render(template_name, context)?; + + OK(Value::String(result.into())) + +} \ No newline at end of file From 68b361c1587eb37e826cdd317d51621dd2064199 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 24 Mar 2025 03:15:58 +0000 Subject: [PATCH 05/11] add render --- aiscript-runtime/src/endpoint.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aiscript-runtime/src/endpoint.rs b/aiscript-runtime/src/endpoint.rs index 42163ea..e64dfed 100644 --- a/aiscript-runtime/src/endpoint.rs +++ b/aiscript-runtime/src/endpoint.rs @@ -602,3 +602,8 @@ pub(crate) fn convert_field(field: ast::Field) -> Field { validators: Arc::from(field.validators), } } + +pub fn render(template: &str, context: serde_json::Value) -> Result { + let engine = template::get_template_engine(); + engine.render(template, &context) +} \ No newline at end of file From aea0693c7d3546e9654ff68f54cee3e68c6871ed Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 24 Mar 2025 03:20:22 +0000 Subject: [PATCH 06/11] mod template --- aiscript-runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/aiscript-runtime/src/lib.rs b/aiscript-runtime/src/lib.rs index 40edaf1..9648c06 100644 --- a/aiscript-runtime/src/lib.rs +++ b/aiscript-runtime/src/lib.rs @@ -24,6 +24,7 @@ mod error; mod openapi; mod parser; mod utils; +mod template; use aiscript_lexer as lexer; From 5ee51dd8239bd96309e43bd77c111e657efe79b4 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 24 Mar 2025 03:30:42 +0000 Subject: [PATCH 07/11] add test template and test route --- routes/template-test.ai | 7 +++++++ templates/hello-page.jinja | 5 +++++ 2 files changed, 12 insertions(+) create mode 100644 routes/template-test.ai create mode 100644 templates/hello-page.jinja diff --git a/routes/template-test.ai b/routes/template-test.ai new file mode 100644 index 0000000..2ff7501 --- /dev/null +++ b/routes/template-test.ai @@ -0,0 +1,7 @@ +route /template { + get /hello { + name: str, + is_admin: bool = false, + } + return render("hello-page.jinja", query) +} \ No newline at end of file diff --git a/templates/hello-page.jinja b/templates/hello-page.jinja new file mode 100644 index 0000000..7df5c26 --- /dev/null +++ b/templates/hello-page.jinja @@ -0,0 +1,5 @@ +{% if query.is_admin %} +
Hello, {{ query.name }} (Admin)
+{% else %} +
Hello, {{ query.name }}
+{% endif %} \ No newline at end of file From dad64f77db7def3b2a41598ef5de6cc406fde9d2 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 24 Mar 2025 04:04:01 +0000 Subject: [PATCH 08/11] add lazy_static --- Cargo.lock | 1 + aiscript-runtime/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7ba7dd8..1a8c3c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,7 @@ dependencies = [ "hyper-util", "indexmap", "jsonwebtoken", + "lazy_static", "minijinja", "notify", "oas3", diff --git a/aiscript-runtime/Cargo.toml b/aiscript-runtime/Cargo.toml index e84f8c2..0792eeb 100644 --- a/aiscript-runtime/Cargo.toml +++ b/aiscript-runtime/Cargo.toml @@ -36,3 +36,4 @@ toml = "0.8" oas3 = "0.15" reqwest.workspace = true minijinja = { version = "1.0", features = ["loader"] } +lazy_static = "1.4" From 69093aaf94875b2d29b9943852f451bb154a6ad2 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 24 Mar 2025 04:04:37 +0000 Subject: [PATCH 09/11] fix build error --- aiscript-runtime/src/endpoint.rs | 1 + aiscript-runtime/src/template.rs | 36 +++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/aiscript-runtime/src/endpoint.rs b/aiscript-runtime/src/endpoint.rs index e64dfed..dd08e35 100644 --- a/aiscript-runtime/src/endpoint.rs +++ b/aiscript-runtime/src/endpoint.rs @@ -1,3 +1,4 @@ +use crate::template; use aiscript_directive::{Validator, route::RouteAnnotation}; use aiscript_vm::{ReturnValue, Vm, VmError}; use axum::{ diff --git a/aiscript-runtime/src/template.rs b/aiscript-runtime/src/template.rs index c5f2d8e..e372dc7 100644 --- a/aiscript-runtime/src/template.rs +++ b/aiscript-runtime/src/template.rs @@ -1,4 +1,4 @@ -use minijinja::{Environment, Source}; +use minijinja::Environment; use std::path::Path; use std::sync::Arc; use std::sync::RwLock; @@ -15,8 +15,13 @@ impl TemplateEngine { let mut env = Environment::new(); //Set the source to the templates directory - let source = Source::from_path("templates"); - env.set_source(source); + env.set_loader(|name| -> Result, minijinja::Error> { + let path = std::path::Path::new("templates").join(name); + match std::fs::read_to_string(path) { + Ok(content) => Ok(Some(content)), + Err(_) => Ok(None) + } + }); Self { env: RwLock::new(env), @@ -24,14 +29,16 @@ impl TemplateEngine { } /// Render a template with the given context - pub fn render(&self, template_name: &str, context: serde_json::Value) -> Result { + pub fn render(&self, template_name: &str, context: &serde_json::Value) -> Result { let env = self.env.read().unwrap(); - - //get template - let template = env.get_template(template_name).map_err(|e| format!("Failed to get template: {}", e))?; - - //render template - let rendered = template.render(&context).map_err(|e| format!("Failed to render template: {}", e))?; + + // 获取模板 + let template = env.get_template(template_name) + .map_err(|e| format!("Failed to load template '{}': {}", template_name, e))?; + + // 渲染模板并返回结果 + template.render(context) + .map_err(|e| format!("Failed to render template '{}': {}", template_name, e)) } /// Reload the templates @@ -39,8 +46,13 @@ impl TemplateEngine { let mut env = self.env.write().unwrap(); //reload templates - let source = Source::from_path("templates"); - env.set_source(source); + env.set_loader(|name| -> Result, minijinja::Error> { + let path = std::path::Path::new("templates").join(name); + match std::fs::read_to_string(path) { + Ok(content) => Ok(Some(content)), + Err(_) => Ok(None) + } + }); Ok(()) } From e040db9df3a370516eddb0641bdbd6ef544b52a3 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Tue, 25 Mar 2025 02:21:36 +0000 Subject: [PATCH 10/11] fix fmt error --- aiscript-runtime/src/endpoint.rs | 2 +- aiscript-runtime/src/lib.rs | 2 +- aiscript-runtime/src/template.rs | 35 ++++++++++++++++++-------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/aiscript-runtime/src/endpoint.rs b/aiscript-runtime/src/endpoint.rs index dd08e35..28704ce 100644 --- a/aiscript-runtime/src/endpoint.rs +++ b/aiscript-runtime/src/endpoint.rs @@ -607,4 +607,4 @@ pub(crate) fn convert_field(field: ast::Field) -> Field { pub fn render(template: &str, context: serde_json::Value) -> Result { let engine = template::get_template_engine(); engine.render(template, &context) -} \ No newline at end of file +} diff --git a/aiscript-runtime/src/lib.rs b/aiscript-runtime/src/lib.rs index 9648c06..554d49f 100644 --- a/aiscript-runtime/src/lib.rs +++ b/aiscript-runtime/src/lib.rs @@ -23,8 +23,8 @@ mod endpoint; mod error; mod openapi; mod parser; -mod utils; mod template; +mod utils; use aiscript_lexer as lexer; diff --git a/aiscript-runtime/src/template.rs b/aiscript-runtime/src/template.rs index e372dc7..8a8c9f9 100644 --- a/aiscript-runtime/src/template.rs +++ b/aiscript-runtime/src/template.rs @@ -3,7 +3,6 @@ use std::path::Path; use std::sync::Arc; use std::sync::RwLock; - /// Template engine for AIScript pub struct TemplateEngine { env: RwLock>, @@ -19,7 +18,7 @@ impl TemplateEngine { let path = std::path::Path::new("templates").join(name); match std::fs::read_to_string(path) { Ok(content) => Ok(Some(content)), - Err(_) => Ok(None) + Err(_) => Ok(None), } }); @@ -29,28 +28,34 @@ impl TemplateEngine { } /// Render a template with the given context - pub fn render(&self, template_name: &str, context: &serde_json::Value) -> Result { + pub fn render( + &self, + template_name: &str, + context: &serde_json::Value, + ) -> Result { let env = self.env.read().unwrap(); - - // 获取模板 - let template = env.get_template(template_name) + + // get the template + let template = env + .get_template(template_name) .map_err(|e| format!("Failed to load template '{}': {}", template_name, e))?; - - // 渲染模板并返回结果 - template.render(context) + + // render the template and return the result + template + .render(context) .map_err(|e| format!("Failed to render template '{}': {}", template_name, e)) } /// Reload the templates pub fn reload(&self) -> Result<(), String> { - let mut env = self.env.write().unwrap(); + let mut env = self.env.write().unwrap(); - //reload templates + //reload templates env.set_loader(|name| -> Result, minijinja::Error> { let path = std::path::Path::new("templates").join(name); match std::fs::read_to_string(path) { Ok(content) => Ok(Some(content)), - Err(_) => Ok(None) + Err(_) => Ok(None), } }); @@ -58,12 +63,12 @@ impl TemplateEngine { } } -//Create a global instance of the template engine +//Create a global instance of the template engine lazy_static::lazy_static! { static ref TEMPLATE_ENGINE: TemplateEngine = TemplateEngine::new(); } -//Get the template engine instance +//Get the template engine instance pub fn get_template_engine() -> &'static TemplateEngine { &TEMPLATE_ENGINE -} \ No newline at end of file +} From f22d36c0346b8af6eccaaeb607a58e6bb9040762 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Tue, 25 Mar 2025 02:31:33 +0000 Subject: [PATCH 11/11] del no use import --- aiscript-runtime/src/template.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/aiscript-runtime/src/template.rs b/aiscript-runtime/src/template.rs index 8a8c9f9..1a5f077 100644 --- a/aiscript-runtime/src/template.rs +++ b/aiscript-runtime/src/template.rs @@ -1,6 +1,4 @@ use minijinja::Environment; -use std::path::Path; -use std::sync::Arc; use std::sync::RwLock; /// Template engine for AIScript