From 34c112217d99567f6e830ca6f3e361773c312bb2 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Mon, 12 Aug 2024 14:38:48 +0800 Subject: [PATCH 01/12] Add docs in README.md --- zino-cli/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/zino-cli/README.md b/zino-cli/README.md index a9c86b82..7da7aa07 100644 --- a/zino-cli/README.md +++ b/zino-cli/README.md @@ -9,3 +9,32 @@ CLI tools for [`zino`]. [`zino`]: https://github.com/zino-rs/zino + +## Features +- **Project Initialization**: Quickly set up new `zino` projects. +- **Dependency Management**: Manage your project dependencies with ease. + +## Installation +```sh +cargo install zino-cli +``` + +## Usage + +### Create a new project +```sh +zli new +``` +options: +- `--template `: Use a custom template for the project. + +### Init project in current directory +```sh +zli init +``` +options: +- `--template `: Use a custom template for the project. +- `--project-name `: Name of the project. + +### Manage dependencies +run `zli serve` and access `http://localhost:6080/zino-config.html` in your browser. \ No newline at end of file From 1914291c204c174b8b3d839b44110e5da31e76fc Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Mon, 12 Aug 2024 14:48:16 +0800 Subject: [PATCH 02/12] Add details to docs in README.md --- zino-cli/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zino-cli/README.md b/zino-cli/README.md index 7da7aa07..1ea1dca8 100644 --- a/zino-cli/README.md +++ b/zino-cli/README.md @@ -34,7 +34,7 @@ zli init ``` options: - `--template `: Use a custom template for the project. -- `--project-name `: Name of the project. +- `--project-name `: Name of the project (current_dir by default). ### Manage dependencies -run `zli serve` and access `http://localhost:6080/zino-config.html` in your browser. \ No newline at end of file +run `zli serve` and access http://localhost:6080/zino-config.html in your browser. \ No newline at end of file From c63610497ec6059b96a5d37ac9c9659732a2af43 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Mon, 12 Aug 2024 15:42:03 +0800 Subject: [PATCH 03/12] Add package name validation checking Optimize error handling --- zino-cli/Cargo.toml | 1 + zino-cli/src/cli/init.rs | 26 ++++++++++---------------- zino-cli/src/cli/mod.rs | 12 +++++++++++- zino-cli/src/cli/new.rs | 23 ++++++++++------------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/zino-cli/Cargo.toml b/zino-cli/Cargo.toml index 11f1a282..e8866c1d 100644 --- a/zino-cli/Cargo.toml +++ b/zino-cli/Cargo.toml @@ -23,6 +23,7 @@ include_dir = "0.7.4" log = "0.4.22" taplo = "0.13.0" toml_edit = "0.22.20" +regex = "1.10.6" walkdir = "2.5.0" [dependencies.axum] diff --git a/zino-cli/src/cli/init.rs b/zino-cli/src/cli/init.rs index 55f6e172..60123ef0 100644 --- a/zino-cli/src/cli/init.rs +++ b/zino-cli/src/cli/init.rs @@ -1,5 +1,6 @@ use crate::cli::{ - clean_template_dir, process_template, DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, + clean_template_dir, check_package_name_validation, process_template, DEFAULT_TEMPLATE_URL, + TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; use std::{env, path::Path}; @@ -26,31 +27,24 @@ impl Init { let init_res = self.init_with_template(); // must clean the temporary template directory after the initialization clean_template_dir(TEMPORARY_TEMPLATE_PATH); - match init_res { - Ok(_) => { - log::info!("project initialized successfully"); - Ok(()) - } - Err(e) => Err(e), - } + init_res.map(|_| { + log::info!("project initialized successfully"); + }) } /// Initializes the project with the template. fn init_with_template(&self) -> Result<(), Error> { let current_dir = env::current_dir()? .file_name() - .expect("fail to get the current directory name") - .to_str() - .expect("fail to convert the directory name to string") - .to_string(); + .and_then(|name| name.to_str()) + .map(|s| s.to_string()) + .ok_or_else(|| Error::new("fail to get or convert the current directory name"))?; let project_name = match &self.project_name { Some(project_name) => project_name, None => ¤t_dir, }; - let template_url = match self.template { - Some(ref template) => template.as_ref(), - None => DEFAULT_TEMPLATE_URL, - }; + check_package_name_validation(project_name)?; + let template_url = self.template.as_deref().unwrap_or(DEFAULT_TEMPLATE_URL); process_template(template_url, "", project_name) } } diff --git a/zino-cli/src/cli/mod.rs b/zino-cli/src/cli/mod.rs index 7d82203d..b1681d70 100644 --- a/zino-cli/src/cli/mod.rs +++ b/zino-cli/src/cli/mod.rs @@ -2,6 +2,7 @@ use clap::Parser; use git2::Repository; +use regex::Regex; use std::fs; use std::fs::remove_dir_all; use std::path::Path; @@ -75,7 +76,7 @@ pub(crate) fn process_template( template_file_path .strip_prefix(TEMPORARY_TEMPLATE_PATH)? .to_str() - .unwrap() + .ok_or_else(|| Error::new("fail to convert the template file path to string"))? ); fs::create_dir_all(Path::new(&target_path).parent().unwrap())?; @@ -101,3 +102,12 @@ fn clean_template_dir(path: &str) { log::error!("fail to remove the temporary template directory: {}", err) } } + +/// Check name validity. +pub(crate) fn check_package_name_validation(name: &str) -> Result<(), Error> { + Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]*$") + .map_err(|e| Error::new(e.to_string()))? + .is_match(name) + .then(|| ()) + .ok_or_else(|| Error::new(format!("invalid package name: `{}`", name))) +} diff --git a/zino-cli/src/cli/new.rs b/zino-cli/src/cli/new.rs index 717b0d1b..2097e347 100644 --- a/zino-cli/src/cli/new.rs +++ b/zino-cli/src/cli/new.rs @@ -1,5 +1,6 @@ use crate::cli::{ - clean_template_dir, process_template, DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, + clean_template_dir, check_package_name_validation, process_template, DEFAULT_TEMPLATE_URL, + TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; use std::{fs, path::Path}; @@ -23,20 +24,18 @@ impl New { let new_res = self.new_with_template(); // must clean the temporary template directory after the initialization clean_template_dir(TEMPORARY_TEMPLATE_PATH); - match new_res { - Ok(_) => { + new_res + .map(|_| { log::info!("project `{}` created successfully", self.project_name); - Ok(()) - } - Err(err) => { + }) + .or_else(|err| { if !project_dir_already_exists { if let Err(err) = fs::remove_dir_all(&self.project_name) { log::warn!("fail to remove project directory: {err}"); } } Err(err) - } - } + }) } /// Checks if the project directory already exists and if it's empty. @@ -54,11 +53,9 @@ impl New { /// Creates a new project with the template. fn new_with_template(&self) -> Result<(), Error> { - let template_url = match self.template { - Some(ref template) => template.as_ref(), - None => DEFAULT_TEMPLATE_URL, - }; - let project_root = &format!("/{}", self.project_name); + let template_url = self.template.as_deref().unwrap_or(DEFAULT_TEMPLATE_URL); + check_package_name_validation(&self.project_name)?; + let project_root = &format!("/{}", &self.project_name); process_template(template_url, project_root, &self.project_name) } } From 3bb5a0b02631c480c916aeab37154e5a6a4f9a75 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Tue, 13 Aug 2024 16:38:32 +0800 Subject: [PATCH 04/12] 1. change controllers into zino style 2. add env_logger to print logs 3. optimize error handling --- zino-cli/Cargo.toml | 1 + zino-cli/public/zino-config.html | 8 +-- zino-cli/src/cli/init.rs | 4 +- zino-cli/src/cli/mod.rs | 12 ++-- zino-cli/src/cli/new.rs | 10 +-- zino-cli/src/cli/serve.rs | 103 +++++++++++++++---------------- zino-cli/src/main.rs | 4 ++ 7 files changed, 72 insertions(+), 70 deletions(-) diff --git a/zino-cli/Cargo.toml b/zino-cli/Cargo.toml index e8866c1d..b6829941 100644 --- a/zino-cli/Cargo.toml +++ b/zino-cli/Cargo.toml @@ -18,6 +18,7 @@ name = "zli" path = "src/main.rs" [dependencies] +env_logger = "0.11.5" git2 = "0.19.0" include_dir = "0.7.4" log = "0.4.22" diff --git a/zino-cli/public/zino-config.html b/zino-cli/public/zino-config.html index f80e1909..3ae1a059 100644 --- a/zino-cli/public/zino-config.html +++ b/zino-cli/public/zino-config.html @@ -385,9 +385,9 @@

current project: None

try { const response = await fetch('/current_dir'); if (!response.ok) { - throw new Error(await response.text()); + throw new Error((await response.json()).data); } - document.getElementById('currentDir').value = await response.text(); + document.getElementById('currentDir').value = (await response.json()).data; } catch (error) { console.error('Failed to fetch current directory:', error); } @@ -398,9 +398,9 @@

current project: None

try { const response = await fetch('/get_current_cargo_toml'); if (!response.ok) { - throw new Error(await response.text()); + throw new Error((await response.json()).data); } - const content = await response.text(); + const content = (await response.json()).data; document.getElementById('currentCargoTomlTextArea').value = content; const packageNameLine = content.split('\n').find(line => line.startsWith('name =')); diff --git a/zino-cli/src/cli/init.rs b/zino-cli/src/cli/init.rs index 60123ef0..41904e13 100644 --- a/zino-cli/src/cli/init.rs +++ b/zino-cli/src/cli/init.rs @@ -1,5 +1,5 @@ use crate::cli::{ - clean_template_dir, check_package_name_validation, process_template, DEFAULT_TEMPLATE_URL, + check_package_name_validation, clean_template_dir, clone_and_process_template, DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; @@ -45,6 +45,6 @@ impl Init { }; check_package_name_validation(project_name)?; let template_url = self.template.as_deref().unwrap_or(DEFAULT_TEMPLATE_URL); - process_template(template_url, "", project_name) + clone_and_process_template(template_url, "", project_name) } } diff --git a/zino-cli/src/cli/mod.rs b/zino-cli/src/cli/mod.rs index b1681d70..6239eb6d 100644 --- a/zino-cli/src/cli/mod.rs +++ b/zino-cli/src/cli/mod.rs @@ -56,7 +56,7 @@ pub(crate) static DEFAULT_TEMPLATE_URL: &str = "https://github.com/zino-rs/zino-template-default.git"; /// Clones the template repository, do replacements, and create the project. -pub(crate) fn process_template( +pub(crate) fn clone_and_process_template( template_url: &str, target_path_prefix: &str, project_name: &str, @@ -76,7 +76,9 @@ pub(crate) fn process_template( template_file_path .strip_prefix(TEMPORARY_TEMPLATE_PATH)? .to_str() - .ok_or_else(|| Error::new("fail to convert the template file path to string"))? + .ok_or_else(|| Error::new( + "fail to convert the template file path to string" + ))? ); fs::create_dir_all(Path::new(&target_path).parent().unwrap())?; @@ -98,9 +100,7 @@ fn is_ignored(entry: &DirEntry) -> bool { /// Clean the temporary template directory. fn clean_template_dir(path: &str) { - if let Err(err) = remove_dir_all(path) { - log::error!("fail to remove the temporary template directory: {}", err) - } + let _ = remove_dir_all(path); } /// Check name validity. @@ -108,6 +108,6 @@ pub(crate) fn check_package_name_validation(name: &str) -> Result<(), Error> { Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]*$") .map_err(|e| Error::new(e.to_string()))? .is_match(name) - .then(|| ()) + .then_some(()) .ok_or_else(|| Error::new(format!("invalid package name: `{}`", name))) } diff --git a/zino-cli/src/cli/new.rs b/zino-cli/src/cli/new.rs index 2097e347..ad1ac0b7 100644 --- a/zino-cli/src/cli/new.rs +++ b/zino-cli/src/cli/new.rs @@ -1,5 +1,5 @@ use crate::cli::{ - clean_template_dir, check_package_name_validation, process_template, DEFAULT_TEMPLATE_URL, + check_package_name_validation, clean_template_dir, clone_and_process_template, DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; @@ -28,13 +28,13 @@ impl New { .map(|_| { log::info!("project `{}` created successfully", self.project_name); }) - .or_else(|err| { + .map_err(|err| { if !project_dir_already_exists { if let Err(err) = fs::remove_dir_all(&self.project_name) { - log::warn!("fail to remove project directory: {err}"); + log::warn!("fail to remove project directory:{}, {err}", self.project_name); } } - Err(err) + err }) } @@ -56,6 +56,6 @@ impl New { let template_url = self.template.as_deref().unwrap_or(DEFAULT_TEMPLATE_URL); check_package_name_validation(&self.project_name)?; let project_root = &format!("/{}", &self.project_name); - process_template(template_url, project_root, &self.project_name) + clone_and_process_template(template_url, project_root, &self.project_name) } } diff --git a/zino-cli/src/cli/serve.rs b/zino-cli/src/cli/serve.rs index 5f6f9683..3671688f 100644 --- a/zino-cli/src/cli/serve.rs +++ b/zino-cli/src/cli/serve.rs @@ -1,6 +1,4 @@ use axum::{ - extract::Path, - response::{Html, IntoResponse}, routing::{get, post}, Router, }; @@ -43,78 +41,77 @@ impl Serve { } /// Returns the content of `Cargo.toml` file in the current directory. -async fn get_current_cargo_toml() -> impl IntoResponse { +async fn get_current_cargo_toml(req: zino::Request) -> zino::Result { + let mut res = zino::Response::default().context(&req); fs::read_to_string("./Cargo.toml") - .map(|content| content.into_response()) - .unwrap_or_else(|err| { - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - format!("fail to read 'Cargo.toml' file: {err}"), - ) - .into_response() + .map(|content| { + res.set_content_type("application/json"); + res.set_data(&content); + }) + .map_err(|err| { + res.set_code(axum::http::StatusCode::INTERNAL_SERVER_ERROR); + res.set_data(&err.to_string()); }) + .ok(); + Ok(res.into()) } /// Returns the HTML page. -async fn get_page(Path(file_name): Path) -> impl IntoResponse { - match RESOURCE.get_file(&file_name) { - Some(file) => { +async fn get_page(req: zino::Request) -> zino::Result { + let mut res = zino::Response::default(); + let file_name: String = req.parse_param("file_name").unwrap_or_default(); + RESOURCE + .get_file(&file_name) + .map(|file| { let content = file.contents_utf8().unwrap_or_default(); - Html(content).into_response() - } - None => RESOURCE - .get_file("404.html") - .map(|not_found_page| { - let not_found_page_content = not_found_page.contents_utf8().unwrap_or_default(); - ( - axum::http::StatusCode::NOT_FOUND, - Html(not_found_page_content), - ) - .into_response() + res.set_content_type("text/html"); + res.set_data(&content); + }) + .or_else(|| { + RESOURCE.get_file("404.html").map(|not_found_page| { + let not_found_page_content = not_found_page + .contents_utf8() + .unwrap_or("404.html not found, include_dir is corrupted. Try reinstall zli"); + res.set_content_type("text/html"); + res.set_data(¬_found_page_content); }) - .unwrap_or_else(|| { - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "404.html not found, include_dir is corrupted. Try reinstall zli", - ) - .into_response() - }), - } + }); + Ok(res.into()) } /// Returns the current directory. -async fn get_current_dir() -> impl IntoResponse { +async fn get_current_dir(req: zino::Request) -> zino::Result { + let mut res = zino::Response::default().context(&req); env::current_dir() .map(|current_dir| { - current_dir - .to_str() - .unwrap_or("fail to convert current_path to utf-8 string") - .to_string() - .into_response() + res.set_code(axum::http::StatusCode::OK); + res.set_data( + ¤t_dir + .to_str() + .unwrap_or("fail to convert current_path to utf-8 string"), + ); }) .unwrap_or_else(|err| { - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - format!("fail to get current_dir: {err}"), - ) - .into_response() - }) + res.set_code(axum::http::StatusCode::INTERNAL_SERVER_ERROR); + res.set_data(&format!("fail to get current_dir: {}", err)); + }); + Ok(res.into()) } /// Updates current directory. -async fn update_current_dir(Path(path): Path) -> impl IntoResponse { +async fn update_current_dir(req: zino::Request) -> zino::Result { + let mut res = zino::Response::default().context(&req); + let path: String = req.parse_param("path").unwrap_or_default(); env::set_current_dir(&path) .map(|_| { - log::info!("directory updated to: {}", path); - axum::http::StatusCode::OK.into_response() + res.set_code(axum::http::StatusCode::OK); + res.set_data(&format!("directory updated to: {}", path)); }) .unwrap_or_else(|err| { - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - format!("fail to update current_dir: {err}"), - ) - .into_response() - }) + res.set_code(axum::http::StatusCode::INTERNAL_SERVER_ERROR); + res.set_data(&format!("fail to update current_dir: {}", err)); + }); + Ok(res.into()) } /// Features struct. diff --git a/zino-cli/src/main.rs b/zino-cli/src/main.rs index fea84ea6..7a38a263 100644 --- a/zino-cli/src/main.rs +++ b/zino-cli/src/main.rs @@ -1,7 +1,11 @@ +use std::env; use clap::Parser; use zino_cli::{Cli, Subcommands::*}; fn main() { + env::set_var("RUST_LOG", "info"); + env_logger::init(); + let result = match Cli::parse().action() { Init(opts) => opts.run(), New(opts) => opts.run(), From 0b0b67d4e61825ce816b011ab0bb2c78ee0f76b6 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Tue, 13 Aug 2024 16:43:06 +0800 Subject: [PATCH 05/12] 1. new command will not warn when project_path not created --- zino-cli/src/cli/new.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zino-cli/src/cli/new.rs b/zino-cli/src/cli/new.rs index ad1ac0b7..3b6622e1 100644 --- a/zino-cli/src/cli/new.rs +++ b/zino-cli/src/cli/new.rs @@ -29,7 +29,7 @@ impl New { log::info!("project `{}` created successfully", self.project_name); }) .map_err(|err| { - if !project_dir_already_exists { + if !project_dir_already_exists && Path::new("./Cargo.toml").is_dir() { if let Err(err) = fs::remove_dir_all(&self.project_name) { log::warn!("fail to remove project directory:{}, {err}", self.project_name); } From adfe6e0482b75081f633dd81836436c285381fc9 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Wed, 14 Aug 2024 13:43:00 +0800 Subject: [PATCH 06/12] Fix log initialization conflict `env_logger` is only initialized once in `Init` and `New` commands --- zino-cli/src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/zino-cli/src/main.rs b/zino-cli/src/main.rs index 7a38a263..3c17f281 100644 --- a/zino-cli/src/main.rs +++ b/zino-cli/src/main.rs @@ -4,11 +4,17 @@ use zino_cli::{Cli, Subcommands::*}; fn main() { env::set_var("RUST_LOG", "info"); - env_logger::init(); + let result = match Cli::parse().action() { - Init(opts) => opts.run(), - New(opts) => opts.run(), + Init(opts) => { + env_logger::init(); + opts.run() + } + New(opts) => { + env_logger::init(); + opts.run() + } Serve(opts) => opts.run(), }; if let Err(err) = result { From bf1da4e9d703665f71aa6566a5cb6f23c95e45e9 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Wed, 14 Aug 2024 13:44:50 +0800 Subject: [PATCH 07/12] format code --- zino-cli/src/cli/init.rs | 4 ++-- zino-cli/src/cli/new.rs | 9 ++++++--- zino-cli/src/main.rs | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/zino-cli/src/cli/init.rs b/zino-cli/src/cli/init.rs index 41904e13..8f48b592 100644 --- a/zino-cli/src/cli/init.rs +++ b/zino-cli/src/cli/init.rs @@ -1,6 +1,6 @@ use crate::cli::{ - check_package_name_validation, clean_template_dir, clone_and_process_template, DEFAULT_TEMPLATE_URL, - TEMPORARY_TEMPLATE_PATH, + check_package_name_validation, clean_template_dir, clone_and_process_template, + DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; use std::{env, path::Path}; diff --git a/zino-cli/src/cli/new.rs b/zino-cli/src/cli/new.rs index 3b6622e1..7d137263 100644 --- a/zino-cli/src/cli/new.rs +++ b/zino-cli/src/cli/new.rs @@ -1,6 +1,6 @@ use crate::cli::{ - check_package_name_validation, clean_template_dir, clone_and_process_template, DEFAULT_TEMPLATE_URL, - TEMPORARY_TEMPLATE_PATH, + check_package_name_validation, clean_template_dir, clone_and_process_template, + DEFAULT_TEMPLATE_URL, TEMPORARY_TEMPLATE_PATH, }; use clap::Parser; use std::{fs, path::Path}; @@ -31,7 +31,10 @@ impl New { .map_err(|err| { if !project_dir_already_exists && Path::new("./Cargo.toml").is_dir() { if let Err(err) = fs::remove_dir_all(&self.project_name) { - log::warn!("fail to remove project directory:{}, {err}", self.project_name); + log::warn!( + "fail to remove project directory:{}, {err}", + self.project_name + ); } } err diff --git a/zino-cli/src/main.rs b/zino-cli/src/main.rs index 3c17f281..431d7f38 100644 --- a/zino-cli/src/main.rs +++ b/zino-cli/src/main.rs @@ -1,11 +1,10 @@ -use std::env; use clap::Parser; +use std::env; use zino_cli::{Cli, Subcommands::*}; fn main() { env::set_var("RUST_LOG", "info"); - let result = match Cli::parse().action() { Init(opts) => { env_logger::init(); From 7c9faa3f03b48b4c9943d1ebc0d8437eb8579597 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Fri, 16 Aug 2024 15:03:07 +0800 Subject: [PATCH 08/12] Split CSS and JS into separate files in the page Fixed a typo that caused the options in core-config-form to fail to update --- zino-cli/public/zino-config.css | 184 ++++++++++++++ zino-cli/public/zino-config.html | 416 +------------------------------ zino-cli/public/zino-config.js | 226 +++++++++++++++++ zino-cli/src/cli/serve.rs | 7 +- 4 files changed, 418 insertions(+), 415 deletions(-) create mode 100644 zino-cli/public/zino-config.css create mode 100644 zino-cli/public/zino-config.js diff --git a/zino-cli/public/zino-config.css b/zino-cli/public/zino-config.css new file mode 100644 index 00000000..d279e32a --- /dev/null +++ b/zino-cli/public/zino-config.css @@ -0,0 +1,184 @@ +body, html { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; +} + +.top-bar { + background-color: #007bff; + color: white; + padding: 20px; + text-align: center; + height: 18%; +} + +.top-bar h1 { + margin-top: 1%; + font-size: 32px; +} + +.main-content { + display: flex; + flex: 1; + overflow: hidden; +} + +input[type="text"] { + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 4px; + width: 61.8%; + box-sizing: border-box; + font-size: 18px; +} + +input[type="text"]:focus { + border-color: #007bff; + outline: none; +} + +.cargoTomlBlock { + display: flex; + flex-direction: column; + width: 30.9%; +} + +.cargoTomlDescription { + display: flex; + align-items: center; + justify-content: center; + height: 10%; + background-color: #f0f0f0; + color: #333; + font-size: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 5px; +} + +.textarea-container { + display: flex; + height: 100%; + width: 100%; + position: relative; + overflow: hidden; +} + +.textarea-container .lineNumber { + white-space: pre; + display: flex; + flex-direction: column; + position: absolute; + left: 0; + width: 20px; + height: 100%; + padding: 2px; + text-align: center; + color: #007bff; + font-family: monospace; + font-size: 14px; + line-height: 1.5; + pointer-events: none; +} + +.cargoTomlTextArea { + height: 100%; + width: 100%; + font-family: Consolas, monospace; + white-space: pre; + background-color: #f3f4f6; + color: #333; + padding-left: 30px; + line-height: 1.5; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border: none; + border-radius: 8px; + box-sizing: border-box; + overflow-x: auto; + overflow-y: scroll; + font-size: 14px; +} + +#config-form-block { + display: flex; + flex-direction: column; + padding: 20px; + width: 38.2%; + height: auto; + overflow-y: auto; +} + +.config-form { + display: flex; + flex-direction: column; + padding: 20px; + border-top: #007bff 2px solid; +} + +.config-form form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.config-form .form-description { + font-size: 32px; + color: #007bff; + margin-bottom: 20px; +} + +.config-form button:hover { + background-color: #0056b3; +} + +.closet { + margin-bottom: 10px; +} + +.closet .option-title { + font-size: 24px; + color: black; + margin-bottom: 8px; +} + +.option-group { + font-size: 24px; + color: #8d8f91; + border-color: #0056b3; + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-left: 15%; +} + +.option-group div { + cursor: pointer; + border-bottom: 2px solid grey; +} + +.option-group div.checked { + font-size: 25px; + color: #14161a; + border-color: #0056b3; + border-bottom: 2px solid #007bff; +} + +#save-config { + padding: 10px 15px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + font-size: 18px; + cursor: pointer; + width: 61.8%; + margin-left: 19.1%; +} + +.hidden { + display: none; +} \ No newline at end of file diff --git a/zino-cli/public/zino-config.html b/zino-cli/public/zino-config.html index 3ae1a059..b0c43768 100644 --- a/zino-cli/public/zino-config.html +++ b/zino-cli/public/zino-config.html @@ -3,192 +3,7 @@ new-project - +
@@ -356,233 +171,6 @@

current project: None

- + \ No newline at end of file diff --git a/zino-cli/public/zino-config.js b/zino-cli/public/zino-config.js new file mode 100644 index 00000000..6ef3d46e --- /dev/null +++ b/zino-cli/public/zino-config.js @@ -0,0 +1,226 @@ +// fetch the current directory and Cargo.toml content +document.getElementById('currentDir').addEventListener('blur', async function () { + const currentDir = this.value; + + try { + const response = await fetch(`/update_current_dir/${encodeURIComponent(currentDir)}`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + }); + if (!response.ok) { + throw new Error(await response.text()); + } + } catch (err) { + console.error('Failed to update directory:', err); + await fetchCurrentDir(); + } + + await fetchCargoToml() + await fetchFeatures() +}); + +// ask the server to change the current directory +async function fetchCurrentDir() { + try { + const response = await fetch('/current_dir'); + if (!response.ok) { + throw new Error((await response.json()).data); + } + document.getElementById('currentDir').value = (await response.json()).data; + } catch (error) { + console.error('Failed to fetch current directory:', error); + } +} + +// get the content of current_dir/Cargo.toml +async function fetchCargoToml() { + try { + const response = await fetch('/get_current_cargo_toml'); + if (!response.ok) { + throw new Error((await response.json()).data); + } + const content = (await response.json()).data; + document.getElementById('currentCargoTomlTextArea').value = content; + + const packageNameLine = content.split('\n').find(line => line.startsWith('name =')); + const projectName = packageNameLine ? packageNameLine.split('=')[1].trim().replace(/"/g, '') : 'Not Found'; + document.getElementById("project_name").textContent = `current project: ${projectName}`; + updateLineNumbers(document.getElementById('currentCargoTomlTextArea')); + } catch (error) { + console.error('Failed to fetch Cargo.toml:', error); + document.getElementById('currentCargoTomlDescription').value = 'Failed to fetch Cargo.toml, make sure you entered a valid project directory'; + } +} + + +// init the options of each group +async function fetchFeatures() { + try { + const response = fetch('/get_current_features'); + let features = await (await response).json(); + document.querySelectorAll('.checked').forEach(option => { + option.classList.replace('checked', 'unchecked') + }) + for (let feature of features.data.zino_feature) { + Array.from(document.querySelectorAll('#zino-config-form [data-feature]')).filter(option => option.getAttribute('data-feature') === feature).forEach(option => { + option.click() + }) + } + for (let feature of features.data.core_feature) { + Array.from(document.querySelectorAll('#core-config-form [data-feature]')).filter(option => option.getAttribute('data-feature') === feature).forEach(option => { + option.click() + }) + } + } catch (error) { + console.error('Failed to init options:', error); + } +} + +function checkedOptions() { + let option_groups = {}; + document.querySelectorAll('.closet').forEach(closet => { + const groupName = closet.querySelector('.option-title').textContent; + option_groups[groupName] = []; + closet.querySelectorAll('.checked').forEach(option => { + if (!option.classList.contains('all-options')) { + option_groups[groupName].push(option.getAttribute('data-feature')); + } else { + let all_flag = option.getAttribute('data-feature') + if (all_flag != null) { + option_groups[groupName] = [option.getAttribute('data-feature')] + } + } + }); + }); + let option = { + zino_feature: option_groups['Framework'] + .concat(option_groups['zino-features']) + .sort(), + core_feature: option_groups['core-features'] + .concat(option_groups['Database']) + .concat(option_groups['Accessor']) + .concat(option_groups['Connector']) + .concat(option_groups['locale']) + .sort() + } + return option; +} + +async function generateCargoToml() { + const res = await fetch('/generate_cargo_toml', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(checkedOptions()) + }); + + + document.querySelector('#aimCargoTomlTextArea').value = (await res.json()).data; +} + + +function change_option_state() { + this.classList.toggle('unchecked'); + this.classList.toggle('checked'); + let self_checked = this.classList.contains('checked'); + + let group = this.parentElement; + + if (group.classList.contains('exclusive')) { + [...group.querySelectorAll('.checked')].filter(o => o !== this).forEach(option => { + option.classList.toggle('checked') + option.classList.toggle('unchecked') + }) + } + + if (this.classList.contains('all-options')) { + if (self_checked) { + group.querySelectorAll('.unchecked').forEach(option => { + option.classList.replace('unchecked', 'checked') + }) + } else { + group.querySelectorAll('.checked').forEach(option => { + option.classList.replace('checked', 'unchecked') + }) + } + } else { + if (self_checked) { + if (group.querySelectorAll('.unchecked').length === 1) { + group.querySelectorAll('.all-options').forEach(option => { + option.classList.replace('unchecked', 'checked') + }) + } + } else { + group.querySelectorAll('.all-options').forEach(option => { + option.classList.replace('checked', 'unchecked') + }) + } + } + + const ormOption = document.querySelector('#zino-config-form [data-feature="orm"]'); + const ormForm = document.getElementById('orm-form'); + if (ormOption && ormOption.classList.contains('checked')) { + ormForm.classList.remove('hidden'); + } else { + ormForm.classList.add('hidden'); + ormForm.querySelectorAll('.option-group div').forEach(option => { + option.classList.replace('checked', 'unchecked'); + }); + } + + generateCargoToml(); +} + +document.querySelectorAll('.unchecked').forEach(option => { + option.addEventListener('click', change_option_state); +}); + + +function updateLineNumbers(textArea) { + const lineCount = textArea.value.split('\n').length; + let lineNumberHtml = ''; + for (let i = 1; i <= lineCount; i++) { + lineNumberHtml += `${i}\n`; + } + + const lineNumberElement = textArea.previousElementSibling; + lineNumberElement.textContent = lineNumberHtml; +} + +document.querySelectorAll('.cargoTomlTextArea').forEach(textArea => { + textArea.addEventListener('input', function () { + updateLineNumbers(this); + }); + textArea.addEventListener('scroll', function () { + this.previousElementSibling.style.marginTop = `-${this.scrollTop}px`; + }); +}); + +// save the generated Cargo.toml +document.getElementById('save-config').addEventListener('click', async () => { + const aimCargoToml = document.getElementById('aimCargoTomlTextArea').value; + try { + const response = await fetch('/save_cargo_toml', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(aimCargoToml), + }); + if (!response.ok) { + throw new Error(await response.text()); + } + await fetchCargoToml(); + } catch (error) { + console.error('Failed to save Cargo.toml:', error); + } +}); + +window.onload = async () => { + await fetchCurrentDir(); + await fetchCargoToml(); + await fetchFeatures(); +}; \ No newline at end of file diff --git a/zino-cli/src/cli/serve.rs b/zino-cli/src/cli/serve.rs index 3671688f..ef4146f9 100644 --- a/zino-cli/src/cli/serve.rs +++ b/zino-cli/src/cli/serve.rs @@ -64,7 +64,12 @@ async fn get_page(req: zino::Request) -> zino::Result { .get_file(&file_name) .map(|file| { let content = file.contents_utf8().unwrap_or_default(); - res.set_content_type("text/html"); + res.set_content_type(match file_name.split('.').last().unwrap_or("html") { + "html" => "text/html", + "css" => "text/css", + "js" => "application/javascript", + _ => "text/plain", + }); res.set_data(&content); }) .or_else(|| { From 0ac0148362f373c83de425cbda81c66c56328575 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Sat, 17 Aug 2024 13:52:43 +0800 Subject: [PATCH 09/12] Refine error message details --- zino-cli/src/cli/init.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/zino-cli/src/cli/init.rs b/zino-cli/src/cli/init.rs index 8f48b592..94cdf4a4 100644 --- a/zino-cli/src/cli/init.rs +++ b/zino-cli/src/cli/init.rs @@ -40,10 +40,21 @@ impl Init { .map(|s| s.to_string()) .ok_or_else(|| Error::new("fail to get or convert the current directory name"))?; let project_name = match &self.project_name { - Some(project_name) => project_name, - None => ¤t_dir, + Some(project_name) => { + check_package_name_validation(project_name)?; + project_name + } + None => { + check_package_name_validation(¤t_dir).map_err(|_| { + Error::new(format!( + "current directory's name:{} is not a valid Rust package name,\ + try to specify the project name with `--project-name`", + ¤t_dir + )) + })?; + ¤t_dir + } }; - check_package_name_validation(project_name)?; let template_url = self.template.as_deref().unwrap_or(DEFAULT_TEMPLATE_URL); clone_and_process_template(template_url, "", project_name) } From 63af6eb37524b14aceb00e5af13a58dc0f90db1b Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Sun, 18 Aug 2024 13:25:09 +0800 Subject: [PATCH 10/12] "Improve form option layout: group validator and view-related options separately" --- zino-cli/public/zino-config.html | 26 ++++++++++++++++++++++++-- zino-cli/public/zino-config.js | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/zino-cli/public/zino-config.html b/zino-cli/public/zino-config.html index b0c43768..155b3018 100644 --- a/zino-cli/public/zino-config.html +++ b/zino-cli/public/zino-config.html @@ -133,6 +133,30 @@

current project: None

+
+
validator
+ +
+
validator
+
validator-credit-card
+
validator-email
+
validator-phone-number
+
validator-regex
+
all
+
+
+ +
+
view
+ +
+
view
+
view-minijinja
+
view-tera
+
all
+
+
+
core-features
@@ -147,10 +171,8 @@

current project: None

sentry
tls-native
tls-rustls
-
validator
tracing-log
http02
-
view
all
diff --git a/zino-cli/public/zino-config.js b/zino-cli/public/zino-config.js index 6ef3d46e..1a8d5724 100644 --- a/zino-cli/public/zino-config.js +++ b/zino-cli/public/zino-config.js @@ -103,6 +103,8 @@ function checkedOptions() { .concat(option_groups['Accessor']) .concat(option_groups['Connector']) .concat(option_groups['locale']) + .concat(option_groups['validator']) + .concat(option_groups['view']) .sort() } return option; From 9d646ddae705244ea044ca3970a4aefffd02aa5c Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Sun, 18 Aug 2024 14:12:50 +0800 Subject: [PATCH 11/12] Improved the prompt information for users. Refined the 404 interface. --- zino-cli/public/404.html | 17 +++++++++++++++-- zino-cli/src/cli/mod.rs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/zino-cli/public/404.html b/zino-cli/public/404.html index ac294fe8..6bbc8fa0 100644 --- a/zino-cli/public/404.html +++ b/zino-cli/public/404.html @@ -8,22 +8,35 @@ font-family: Arial, sans-serif; text-align: center; padding-top: 50px; + background-color: #f8f9fa; + color: #343a40; + } + h1 { + font-size: 3em; + margin-bottom: 20px; } .message { margin: 20px; + font-size: 1.2em; } a { color: #007BFF; text-decoration: none; + padding: 10px 20px; + border: 1px solid #007BFF; + border-radius: 5px; + transition: background-color 0.3s, color 0.3s; } a:hover { - text-decoration: underline; + background-color: #007BFF; + color: #fff; + text-decoration: none; }

404 Page Not Found

Sorry, the page you are looking for is not found.

-

Access this page to set the configuration for your project.

+

Access zino-config page to set the configuration for your project.

\ No newline at end of file diff --git a/zino-cli/src/cli/mod.rs b/zino-cli/src/cli/mod.rs index 6239eb6d..7f6bce7c 100644 --- a/zino-cli/src/cli/mod.rs +++ b/zino-cli/src/cli/mod.rs @@ -44,7 +44,7 @@ pub enum Subcommands { Init(init::Init), /// Create a new project. New(new::New), - /// Start the server. + /// Start the server at localhost:6080/zino-config.html. Serve(serve::Serve), } From 6733f0e8141d6269c336d94f5665d59a3c8c0a12 Mon Sep 17 00:00:00 2001 From: qiumiaomiao <2268428576@qq.com> Date: Mon, 19 Aug 2024 13:42:07 +0800 Subject: [PATCH 12/12] currentDirInput updates automatically when the enter key is pressed. --- zino-cli/public/zino-config.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/zino-cli/public/zino-config.js b/zino-cli/public/zino-config.js index 1a8d5724..ea63630c 100644 --- a/zino-cli/public/zino-config.js +++ b/zino-cli/public/zino-config.js @@ -1,6 +1,17 @@ // fetch the current directory and Cargo.toml content -document.getElementById('currentDir').addEventListener('blur', async function () { - const currentDir = this.value; +const currentDirInput = document.getElementById('currentDir'); + +currentDirInput.addEventListener('blur', updateCurrentDir); + +currentDirInput.addEventListener('keyup', async function (event) { + if (event.key === 'Enter') { + event.preventDefault(); // 防止默认的回车行为(如提交表单) + await updateCurrentDir(); + } +}); + +async function updateCurrentDir() { + const currentDir = currentDirInput.value; try { const response = await fetch(`/update_current_dir/${encodeURIComponent(currentDir)}`, { @@ -19,7 +30,7 @@ document.getElementById('currentDir').addEventListener('blur', async function () await fetchCargoToml() await fetchFeatures() -}); +} // ask the server to change the current directory async function fetchCurrentDir() {