├── .gitignore ├── src ├── utils │ ├── mod.rs │ └── exec.rs ├── driver │ ├── apply_contacts.rs │ ├── apply_links.rs │ ├── apply_projects.rs │ ├── mod.rs │ ├── apply_github_bar.rs │ ├── apply_profile.rs │ ├── apply_app.rs │ ├── apply_static_resource.rs │ ├── common.rs │ ├── apply_footer.rs │ ├── html.rs │ └── apply_router.rs ├── zzhack.config.template.json ├── template.rs ├── config.rs └── main.rs ├── docs └── wiki_cover.png ├── .vscode └── settings.json ├── projects.config.json ├── Cargo.toml ├── LICENSE ├── README_ZH.md ├── links.config.json ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .zzhack -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod exec; 2 | -------------------------------------------------------------------------------- /src/driver/apply_contacts.rs: -------------------------------------------------------------------------------- 1 | pub fn apply_contacts() {} 2 | -------------------------------------------------------------------------------- /docs/wiki_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzhack-stack/zzhack-cli/HEAD/docs/wiki_cover.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.unsetTest": [ 3 | "tokio", 4 | "tokio-macros", 5 | "clap", 6 | "clap-macros" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/zzhack.config.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource_dir": "./kkk", 3 | "pages": [ 4 | { 5 | "name": "wiki", 6 | "template": "post", 7 | "source": "./wiki.md" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /src/driver/apply_links.rs: -------------------------------------------------------------------------------- 1 | use crate::driver::common::apply_config; 2 | 3 | pub fn apply_links_config(links_source: &String) { 4 | apply_config( 5 | links_source, 6 | ".zzhack/services/src/links_service/links.json", 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/driver/apply_projects.rs: -------------------------------------------------------------------------------- 1 | use crate::driver::common::apply_config; 2 | 3 | pub fn apply_projects_config(projects_source: &String) { 4 | apply_config( 5 | projects_source, 6 | ".zzhack/services/src/projects_service/projects.json", 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/driver/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod apply_app; 2 | pub mod apply_contacts; 3 | pub mod apply_footer; 4 | pub mod apply_github_bar; 5 | pub mod apply_links; 6 | pub mod apply_profile; 7 | pub mod apply_projects; 8 | pub mod apply_router; 9 | pub mod apply_static_resource; 10 | pub mod common; 11 | pub mod html; 12 | -------------------------------------------------------------------------------- /src/utils/exec.rs: -------------------------------------------------------------------------------- 1 | use indicatif::{ProgressBar, ProgressStyle}; 2 | 3 | pub fn exec_sync_with_spinner(stage_msg: &'static str, execor: F) 4 | where 5 | F: FnOnce() -> (), 6 | { 7 | let progress_bar = ProgressBar::new_spinner(); 8 | 9 | progress_bar.set_style(ProgressStyle::default_spinner()); 10 | progress_bar.enable_steady_tick(200); 11 | progress_bar.set_message(stage_msg); 12 | 13 | execor(); 14 | 15 | progress_bar.finish_with_message(format!("✅{}", stage_msg)) 16 | } 17 | -------------------------------------------------------------------------------- /projects.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ 3 | { 4 | "name": "zzhack", 5 | "addr": "https://github.com/zzhack-stack/zzhack", 6 | "desc": "⚙️ 🦀️ My personal blog site" 7 | }, 8 | { 9 | "name": "Archie", 10 | "addr": "https://github.com/wizardoc/archie", 11 | "desc": "GraphQL API server for @wizardoc/wizard, powered by @gin, Git-based document management, timely notification for document changes." 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zzhack" 3 | version = "0.1.5" 4 | edition = "2021" 5 | description = "ZZHACK WASM webapp build tool" 6 | keywords = ["WASM", "CLI", "ZZHACK", "webapp"] 7 | license = "MIT" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | clap = { version = "3.1.12", features = ["derive"] } 13 | convert_case = "0.5.0" 14 | fs_extra = "1.2.0" 15 | indicatif = "0.16.2" 16 | regex = "1.6.0" 17 | serde = { version = "1.0.140", features = ["derive"]} 18 | serde_json = "1.0.82" 19 | -------------------------------------------------------------------------------- /src/driver/apply_github_bar.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use crate::template; 4 | 5 | use super::common::APPLY_ZZHACK_FOLDER_ERROR_MESSAGE; 6 | 7 | const HEADER_CONFIG_PATH: &'static str = ".zzhack/ui/src/common/header/header_config.rs"; 8 | 9 | fn rewrite_github_bar(template: &str) { 10 | fs::write(HEADER_CONFIG_PATH, template).expect(APPLY_ZZHACK_FOLDER_ERROR_MESSAGE); 11 | } 12 | 13 | pub fn apply_github_bar(github_link: String) { 14 | rewrite_github_bar(&format!( 15 | " 16 | pub const IS_GITHUB_BAR_VISIBLE: bool = true; 17 | pub const GITHUB_BAR_LINK: &'static str = \"{}\"; 18 | ", 19 | github_link 20 | )); 21 | } 22 | 23 | pub fn reset_github_bar() { 24 | rewrite_github_bar( 25 | " 26 | pub const IS_GITHUB_BAR_VISIBLE: bool = false; 27 | pub const GITHUB_BAR_LINK: &'static str = \"\"; 28 | ", 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/template.rs: -------------------------------------------------------------------------------- 1 | pub const TEMPLATES: [&'static str; 4] = ["posts", "post", "projects", "links"]; 2 | 3 | pub enum Template { 4 | Posts, 5 | Post, 6 | Projects, 7 | Links, 8 | } 9 | 10 | impl From<&str> for Template { 11 | fn from(template: &str) -> Self { 12 | match template { 13 | "post" => Template::Post, 14 | "posts" => Template::Posts, 15 | "links" => Template::Links, 16 | "projects" => Template::Projects, 17 | _ => panic!( 18 | "Please make sure the template({}) is one of [{}]", 19 | template, 20 | TEMPLATES.join(", ") 21 | ), 22 | } 23 | } 24 | } 25 | 26 | impl Template { 27 | pub fn into_component_name(&self) -> &'static str { 28 | match &self { 29 | Template::Links => "Links", 30 | Template::Post => "Post", 31 | Template::Posts => "Home", 32 | Template::Projects => "Projects", 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zzhack-stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/driver/apply_profile.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use serde::Deserialize; 4 | 5 | use super::common::{with_static_resource, APPLY_ZZHACK_FOLDER_ERROR_MESSAGE}; 6 | 7 | #[derive(Deserialize, Clone, Debug, PartialEq)] 8 | pub struct ProfileConfig { 9 | pub name: String, 10 | pub avatar: String, 11 | } 12 | 13 | impl ProfileConfig { 14 | pub fn default() -> ProfileConfig { 15 | ProfileConfig { 16 | name: String::from("zzhack"), 17 | avatar: String::from("/images/zzhack_favicon.svg"), 18 | } 19 | } 20 | } 21 | 22 | pub fn apply_profile(config: Option, static_resource_path: &Option) { 23 | let config = match config { 24 | Some(config) => ProfileConfig { 25 | avatar: with_static_resource(static_resource_path, &config.avatar), 26 | ..config 27 | }, 28 | None => ProfileConfig::default(), 29 | }; 30 | 31 | let template = format!( 32 | " 33 | pub const AUTHOR_NAME: &'static str = \"{}\"; 34 | pub const AUTHOR_AVATAR: &'static str = \"{}\"; 35 | ", 36 | config.name, config.avatar 37 | ); 38 | 39 | fs::write(".zzhack/ui/src/post_card/author.rs", template) 40 | .expect(APPLY_ZZHACK_FOLDER_ERROR_MESSAGE); 41 | } 42 | -------------------------------------------------------------------------------- /src/driver/apply_app.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Clone, Debug, PartialEq)] 4 | pub struct AppInfo { 5 | pub name: String, 6 | pub description: Option, 7 | pub keywords: Option, 8 | pub logo: Option, 9 | } 10 | 11 | impl AppInfo { 12 | pub fn default() -> AppInfo { 13 | AppInfo { 14 | name: String::from("zzhack"), 15 | description: None, 16 | keywords: None, 17 | logo: Some(String::from("zzhack")), 18 | } 19 | } 20 | } 21 | 22 | pub fn apply_app(app_info_config: Option) -> String { 23 | let default_app_info = AppInfo::default(); 24 | let app_info = app_info_config.unwrap_or(default_app_info.clone()); 25 | let logo = app_info.logo.unwrap_or(default_app_info.logo.unwrap()); 26 | 27 | format!( 28 | " 29 | 30 | 31 | 32 | {} 33 | ", 34 | app_info.description.unwrap_or_default(), 35 | app_info.keywords.unwrap_or_default(), 36 | logo, 37 | logo, 38 | app_info.name, 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![](https://img.shields.io/crates/v/zzhack.svg?color=brightgreen&style=flat-square)](https://crates.io/crates/zzhack) 4 | ![GitHub](https://img.shields.io/github/license/zzhack-stack/zzhack-cli?color=green&style=flat-square) 5 | 6 | [English](https://github.com/zzhack-stack/zzhack-cli/blob/main/README.md) | 中文文档 7 | 8 | `zzhack-cli` 是一个可以帮助你快速生成 [WASM](https://webassembly.org/) WebApp 的命令行工具,不需要一行代码只需要简单的配置即可。值得一提的是 `zzhack-cli` 的 UI 模板来自于 [zzhack](https://github.com/zzhack-stack/zzhack),你可以直接访问 [Live Demo](https://www.zzhack.fun/) 来获得更真实的体验。 9 | 10 | ## 快速开始 11 | [zzhack](https://github.com/zzhack-stack/zzhack) 是用 Rust 编写的,因此你需要准备 Rust 的开发环境,以此来获得一些我们会用到的其他的命令行工具,比如 `trunk`, `zzhack` 等,你可以访问 [Rust Book](https://doc.rust-lang.org/cargo/getting-started/installation.html) 或者 [rust-lang.org](https://www.rust-lang.org/) 来了解更多。 12 | 13 | 在你拥有了 [Rust](https://www.rust-lang.org/) 的开发环境之后,你还需要一些命令行工具来帮助你构建。复制下面的命令到你的终端来安装它们。 14 | 15 | ```sh 16 | rustup target add wasm32-unknown-unknown 17 | cargo install trunk zzhack 18 | ``` 19 | 20 | 现在!让我们启动你的第一个 WASM WebApp 吧! 21 | 22 | ```sh 23 | # Create project workspace 24 | mkdir my-first-wasm-project 25 | 26 | cd my-first-wasm-project 27 | 28 | # Init zzhack project & serve 29 | zzhack init 30 | zzhack serve 31 | ``` 32 | 33 | ## 文档 34 | 你可以使用 `zzhack init` 来获取 zzhack 的默认配置来获取更多的文档细节。 35 | 36 | ## 许可 37 | MIT. 38 | -------------------------------------------------------------------------------- /src/driver/apply_static_resource.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use fs_extra::dir::{copy, CopyOptions}; 4 | 5 | use super::common::{rewrite_with_template, APPLY_ZZHACK_FOLDER_ERROR_MESSAGE}; 6 | 7 | fn apply_post_parser_resource_dir(resource_dir: &str) { 8 | let folder_name = Path::new(resource_dir) 9 | .file_name() 10 | .unwrap() 11 | .to_str() 12 | .unwrap(); 13 | 14 | rewrite_with_template( 15 | format!( 16 | "pub const SOURCES_FOLDER_NAME: &'static str = \"{}\"; 17 | ", 18 | folder_name 19 | ), 20 | "services/src/markdown_service/sources_config.rs", 21 | ) 22 | } 23 | 24 | fn move_static_dir(resource_dir: &str) { 25 | let mut options = CopyOptions::default(); 26 | 27 | options.overwrite = true; 28 | 29 | copy(resource_dir, ".zzhack/app/assets/", &options).expect(APPLY_ZZHACK_FOLDER_ERROR_MESSAGE); 30 | } 31 | 32 | pub fn apply_static_resource(resource_dir: String) -> String { 33 | apply_post_parser_resource_dir(&resource_dir); 34 | move_static_dir(&resource_dir); 35 | 36 | let dir_path = Path::new("../..").join(&resource_dir); 37 | 38 | if !Path::new(&resource_dir).exists() { 39 | panic!("Please make sure the resource_dir does exist"); 40 | } 41 | 42 | format!( 43 | "", 44 | dir_path.to_str().unwrap() 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /links.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": [ 3 | { 4 | "name": "Busyops博客", 5 | "addr": "https://busyops.com/", 6 | "desc": "Hello Moon", 7 | "logo": "https://busyops.com/images/avatar.jpg" 8 | }, 9 | { 10 | "name": "Clay 的技术博客", 11 | "addr": "https://www.techgrow.cn", 12 | "desc": "用进废退 | 艺不压身", 13 | "logo": "https://www.techgrow.cn/img/head.jpg" 14 | }, 15 | { 16 | "name": "Christine的博客", 17 | "desc": "虽然我不够优秀,但我从未放弃过努力。", 18 | "logo": "https://christine-only.github.io/blog/logo.png", 19 | "addr": "https://christine-only.github.io/blog/" 20 | }, 21 | { 22 | "name": "Forever丿顾北博客", 23 | "addr": "https://forevergubei.gitee.io/myblod/", 24 | "desc": "一个追寻大佬脚步的小白", 25 | "logo": "https://forevergubei.gitee.io/myblod/logo.png" 26 | }, 27 | { 28 | "name": "MrDJun's Blog", 29 | "addr": "https://mrdjun.gitee.io/", 30 | "desc": "Just an ordinary JAVA programmer", 31 | "logo": "https://mrdjun.gitee.io/images/avatar.png" 32 | }, 33 | { 34 | "name": "一物一世界的 blog", 35 | "addr": "https://www.flyfrag.cn/", 36 | "desc": "太阳强烈,水波温柔", 37 | "logo": "https://avatars.githubusercontent.com/u/53332638?v=4" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/driver/common.rs: -------------------------------------------------------------------------------- 1 | use fs_extra::dir::{copy, remove, CopyOptions}; 2 | use std::{fs, path::Path}; 3 | 4 | pub const APPLY_ZZHACK_FOLDER_ERROR_MESSAGE: &'static str = 5 | "Please make sure the .zzhack folder does exist"; 6 | 7 | pub fn apply_config(config_path: &String, target_config_path: &'static str) { 8 | let config = Path::new(&config_path); 9 | let config_content = 10 | fs::read_to_string(config).expect("Please make sure your source path is exist"); 11 | 12 | fs::write(target_config_path, config_content).expect(APPLY_ZZHACK_FOLDER_ERROR_MESSAGE); 13 | } 14 | 15 | pub fn with_static_resource(static_resource_path: &Option, source_name: &str) -> String { 16 | let static_resource_path = static_resource_path.clone().expect( 17 | "Please make sure you has already declare resource_dir field in your zzhack config", 18 | ); 19 | let static_resource_path = Path::new(&static_resource_path) 20 | .file_name() 21 | .unwrap() 22 | .to_str() 23 | .unwrap(); 24 | 25 | format!("/{}/{}", static_resource_path, source_name) 26 | } 27 | 28 | pub fn rewrite_with_template(template: String, rewrite_path: &'static str) { 29 | fs::write(format!(".zzhack/{}", rewrite_path), template) 30 | .expect(APPLY_ZZHACK_FOLDER_ERROR_MESSAGE) 31 | } 32 | 33 | pub fn copy_inside(from: &str, to: &str) { 34 | let cp_err_msg = format!("No directory such as {}", from); 35 | let mut options = CopyOptions::default(); 36 | 37 | options.copy_inside = true; 38 | options.content_only = true; 39 | 40 | match copy(fs::canonicalize(from).unwrap(), to, &options) { 41 | _ => (), 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![](https://img.shields.io/crates/v/zzhack.svg?color=brightgreen&style=flat-square)](https://crates.io/crates/zzhack) 4 | ![GitHub](https://img.shields.io/github/license/zzhack-stack/zzhack-cli?color=green&style=flat-square) 5 | 6 | English | [中文文档](https://github.com/zzhack-stack/zzhack-cli/blob/main/README_ZH.md) 7 | 8 | `zzhack-cli` is a Command Tool that can help you quickly generate a [WASM](https://webassembly.org/) WebApp with simple configuration and zero code. It's worth mention that UI template is from [zzhack](https://github.com/zzhack-stack/zzhack), you can navigate to [Live Demo](https://www.zzhack.fun/) for real experience. 9 | 10 | ## Quick start 11 | [zzhack](https://github.com/zzhack-stack/zzhack) was written by Rust, thus you need to prepare the development environment for Rust for get some CLI which we need, such as `trunk`, `zzhack` etc. You can visit [Rust Book](https://doc.rust-lang.org/cargo/getting-started/installation.html) or [rust-lang.org](https://www.rust-lang.org/) for more detail about Rust installation. 12 | 13 | After you have the [Rust](https://www.rust-lang.org/) development environment, you'll also need some Command Tools to help you building. Copy the following commands in your terminal to install them. 14 | 15 | ```sh 16 | rustup target add wasm32-unknown-unknown 17 | cargo install trunk zzhack 18 | ``` 19 | 20 | Now, let's launch your first WASM WebApp! 21 | ```sh 22 | # Create project workspace 23 | mkdir my-first-wasm-project 24 | 25 | cd my-first-wasm-project 26 | 27 | zzhack init 28 | zzhack serve 29 | ``` 30 | 31 | ## Docs 32 | You can use the zzhack default config by `zzhack init` for more detail of docs. 33 | 34 | ## License 35 | MIT. 36 | -------------------------------------------------------------------------------- /src/driver/apply_footer.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use super::common::{rewrite_with_template, with_static_resource}; 4 | 5 | #[derive(Deserialize, Clone, Debug, PartialEq)] 6 | pub struct FooterConfig { 7 | pub copyright: String, 8 | pub contacts: Vec, 9 | } 10 | 11 | #[derive(Deserialize, Clone, Debug, PartialEq)] 12 | pub struct Contact { 13 | pub icon: String, 14 | pub link: String, 15 | pub icon_size: Option, 16 | } 17 | 18 | impl FooterConfig { 19 | pub fn default() -> FooterConfig { 20 | FooterConfig { 21 | copyright: String::from(""), 22 | contacts: vec![], 23 | } 24 | } 25 | } 26 | 27 | pub fn apply_footer(config: Option, static_resource_path: &Option) { 28 | let config = config.unwrap_or(FooterConfig::default()); 29 | let contacts_template = config 30 | .contacts 31 | .iter() 32 | .map(|contact| { 33 | format!( 34 | "Contact {{ 35 | link: \"{}\", 36 | icon: \"{}\", 37 | icon_size: {}, 38 | }}", 39 | contact.link, 40 | with_static_resource(static_resource_path, &contact.icon), 41 | contact.icon_size.unwrap_or(30) 42 | ) 43 | }) 44 | .collect::>(); 45 | 46 | rewrite_with_template( 47 | format!( 48 | " 49 | #[derive(PartialEq, Clone)] 50 | pub struct Contact {{ 51 | pub link: &'static str, 52 | pub icon: &'static str, 53 | pub icon_size: i32, 54 | }} 55 | 56 | pub const CONTACTS: [Contact; {}] = [{}]; 57 | pub const FOOTER_TEXT: &'static str = \"{}\"; 58 | ", 59 | contacts_template.len(), 60 | contacts_template.join(",\n"), 61 | config.copyright 62 | ), 63 | "ui/src/common/footer_source.rs", 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use serde::Deserialize; 4 | 5 | use crate::driver::apply_app::{apply_app, AppInfo}; 6 | use crate::driver::apply_footer::{apply_footer, FooterConfig}; 7 | use crate::driver::apply_github_bar::{apply_github_bar, reset_github_bar}; 8 | use crate::driver::apply_profile::{apply_profile, ProfileConfig}; 9 | use crate::driver::apply_router::{apply_pages_config, PageConfig}; 10 | use crate::driver::apply_static_resource::apply_static_resource; 11 | use crate::driver::html::HtmlTemplate; 12 | 13 | const CONFIG_NAME: &'static str = "zzhack.config.json"; 14 | 15 | #[derive(Deserialize, Clone, Debug, PartialEq)] 16 | pub struct ContactConfig { 17 | pub kind: String, 18 | pub icon: Option, 19 | pub link: String, 20 | } 21 | 22 | #[derive(Deserialize, Clone, Debug, PartialEq)] 23 | pub struct ZZHACKConfig { 24 | pub pages: Vec, 25 | pub resource_dir: Option, 26 | pub contacts: Option>, 27 | pub github_bar: Option, 28 | pub profile: Option, 29 | pub app: Option, 30 | pub footer: Option, 31 | } 32 | 33 | fn optional(config: Option, cb: F) -> String 34 | where 35 | F: Fn(T) -> String, 36 | { 37 | match config { 38 | Some(value) => cb(value), 39 | None => String::from(""), 40 | } 41 | } 42 | 43 | pub fn apply_config() { 44 | let config = fs::read_to_string(CONFIG_NAME) 45 | .expect("Cannot find zzhack.config.json, use zzhack init to create a template config."); 46 | let config: ZZHACKConfig = serde_json::from_str(&config) 47 | .expect("Cannot parse the config of zzhack, please make sure you are suit the bounds of zzhack config"); 48 | 49 | apply_pages_config(config.pages, &config.resource_dir); 50 | apply_profile(config.profile, &config.resource_dir); 51 | apply_footer(config.footer, &config.resource_dir); 52 | 53 | HtmlTemplate::from(optional(config.resource_dir, |resource_dir_path| { 54 | apply_static_resource(resource_dir_path) 55 | })) 56 | .append(apply_app(config.app)) 57 | .write(); 58 | 59 | match config.github_bar { 60 | Some(github_bar_config) => apply_github_bar(github_bar_config), 61 | None => reset_github_bar(), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use config::apply_config; 3 | use fs_extra::dir::remove; 4 | use std::fs::{self, create_dir}; 5 | use std::io::ErrorKind; 6 | use std::path::Path; 7 | use std::process::Command; 8 | use utils::exec::exec_sync_with_spinner; 9 | 10 | use crate::driver::common::copy_inside; 11 | 12 | mod config; 13 | mod driver; 14 | mod template; 15 | mod utils; 16 | 17 | #[derive(Parser, Debug)] 18 | struct CLI { 19 | #[clap(subcommand)] 20 | action: Action, 21 | } 22 | 23 | #[derive(Subcommand, Debug)] 24 | enum Action { 25 | Init, 26 | Serve, 27 | Build, 28 | } 29 | 30 | const TEMPLATE_DIR: &'static str = ".zzhack"; 31 | const TEMPLATE_REMOTE_ADDR: &'static str = "https://github.com/zzhack-stack/zzhack"; 32 | const CLI_CONFIG_TEMPLATE_REMOTE_ADDR: &'static str = 33 | "https://github.com/zzhack-stack/zzhack-init-template-zh"; 34 | 35 | pub fn main() { 36 | let args = CLI::parse(); 37 | 38 | match args.action { 39 | Action::Init => { 40 | exec_sync_with_spinner("Create template from network", || { 41 | match create_dir(".zzhack") { 42 | Err(err) => match err.kind() { 43 | ErrorKind::AlreadyExists => (), 44 | _ => panic!("{}", err), 45 | }, 46 | _ => (), 47 | } 48 | Command::new("git") 49 | .arg("clone") 50 | .arg("-b") 51 | .arg("feature/cli") 52 | .arg(TEMPLATE_REMOTE_ADDR) 53 | .arg(TEMPLATE_DIR) 54 | .output() 55 | .unwrap(); 56 | }); 57 | 58 | // TODO: disgusting language 59 | 60 | if !Path::new("./zzhack.config.json").exists() { 61 | exec_sync_with_spinner("Sync zzhack zh template", || { 62 | Command::new("git") 63 | .arg("clone") 64 | .arg(CLI_CONFIG_TEMPLATE_REMOTE_ADDR) 65 | .arg(".template") 66 | .output() 67 | .unwrap(); 68 | }); 69 | 70 | copy_inside(".template", "."); 71 | 72 | if Path::new("./.template").exists() { 73 | remove(".template").unwrap(); 74 | } 75 | } 76 | } 77 | Action::Serve => { 78 | exec_sync_with_spinner("Apply config", || { 79 | apply_config(); 80 | }); 81 | 82 | Command::new("trunk") 83 | .arg("serve") 84 | .current_dir(".zzhack/app") 85 | .spawn() 86 | .unwrap() 87 | .wait() 88 | .unwrap(); 89 | } 90 | Action::Build => { 91 | Command::new("trunk") 92 | .arg("build") 93 | .arg("-d") 94 | .arg("../../dist") 95 | .current_dir(".zzhack/app") 96 | .spawn() 97 | .unwrap() 98 | .wait() 99 | .unwrap(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/driver/html.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | pub struct HtmlTemplate { 4 | header_labels: Vec, 5 | } 6 | 7 | impl HtmlTemplate { 8 | pub fn from(label: String) -> HtmlTemplate { 9 | let header_labels: Vec = vec![label]; 10 | 11 | HtmlTemplate { header_labels } 12 | } 13 | 14 | pub fn append(&mut self, label: String) -> &HtmlTemplate { 15 | self.header_labels.push(label); 16 | 17 | self 18 | } 19 | 20 | pub fn write(&self) { 21 | rewrite_html_template(&self.header_labels.join("\n")) 22 | } 23 | } 24 | 25 | pub fn rewrite_html_template(header_label_template: &String) { 26 | let html_template = format!(" 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {} 44 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | ", header_label_template); 98 | 99 | fs::write(".zzhack/app/index.html", html_template).unwrap(); 100 | } 101 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "clap" 39 | version = "3.2.12" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "ab8b79fe3946ceb4a0b1c080b4018992b8d27e9ff363644c1c9b6387c854614d" 42 | dependencies = [ 43 | "atty", 44 | "bitflags", 45 | "clap_derive", 46 | "clap_lex", 47 | "indexmap", 48 | "once_cell", 49 | "strsim", 50 | "termcolor", 51 | "textwrap", 52 | ] 53 | 54 | [[package]] 55 | name = "clap_derive" 56 | version = "3.2.7" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 59 | dependencies = [ 60 | "heck", 61 | "proc-macro-error", 62 | "proc-macro2", 63 | "quote", 64 | "syn", 65 | ] 66 | 67 | [[package]] 68 | name = "clap_lex" 69 | version = "0.2.4" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 72 | dependencies = [ 73 | "os_str_bytes", 74 | ] 75 | 76 | [[package]] 77 | name = "console" 78 | version = "0.15.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 81 | dependencies = [ 82 | "encode_unicode", 83 | "libc", 84 | "once_cell", 85 | "terminal_size", 86 | "winapi", 87 | ] 88 | 89 | [[package]] 90 | name = "convert_case" 91 | version = "0.5.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" 94 | 95 | [[package]] 96 | name = "encode_unicode" 97 | version = "0.3.6" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 100 | 101 | [[package]] 102 | name = "fs_extra" 103 | version = "1.2.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" 106 | 107 | [[package]] 108 | name = "hashbrown" 109 | version = "0.12.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 112 | 113 | [[package]] 114 | name = "heck" 115 | version = "0.4.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 118 | 119 | [[package]] 120 | name = "hermit-abi" 121 | version = "0.1.19" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 124 | dependencies = [ 125 | "libc", 126 | ] 127 | 128 | [[package]] 129 | name = "indexmap" 130 | version = "1.9.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 133 | dependencies = [ 134 | "autocfg", 135 | "hashbrown", 136 | ] 137 | 138 | [[package]] 139 | name = "indicatif" 140 | version = "0.16.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" 143 | dependencies = [ 144 | "console", 145 | "lazy_static", 146 | "number_prefix", 147 | "regex", 148 | ] 149 | 150 | [[package]] 151 | name = "itoa" 152 | version = "1.0.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 155 | 156 | [[package]] 157 | name = "lazy_static" 158 | version = "1.4.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 161 | 162 | [[package]] 163 | name = "libc" 164 | version = "0.2.126" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 167 | 168 | [[package]] 169 | name = "memchr" 170 | version = "2.5.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 173 | 174 | [[package]] 175 | name = "number_prefix" 176 | version = "0.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 179 | 180 | [[package]] 181 | name = "once_cell" 182 | version = "1.13.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 185 | 186 | [[package]] 187 | name = "os_str_bytes" 188 | version = "6.2.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" 191 | 192 | [[package]] 193 | name = "proc-macro-error" 194 | version = "1.0.4" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 197 | dependencies = [ 198 | "proc-macro-error-attr", 199 | "proc-macro2", 200 | "quote", 201 | "syn", 202 | "version_check", 203 | ] 204 | 205 | [[package]] 206 | name = "proc-macro-error-attr" 207 | version = "1.0.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "version_check", 214 | ] 215 | 216 | [[package]] 217 | name = "proc-macro2" 218 | version = "1.0.40" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 221 | dependencies = [ 222 | "unicode-ident", 223 | ] 224 | 225 | [[package]] 226 | name = "quote" 227 | version = "1.0.20" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 230 | dependencies = [ 231 | "proc-macro2", 232 | ] 233 | 234 | [[package]] 235 | name = "regex" 236 | version = "1.6.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 239 | dependencies = [ 240 | "aho-corasick", 241 | "memchr", 242 | "regex-syntax", 243 | ] 244 | 245 | [[package]] 246 | name = "regex-syntax" 247 | version = "0.6.27" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 250 | 251 | [[package]] 252 | name = "ryu" 253 | version = "1.0.10" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 256 | 257 | [[package]] 258 | name = "serde" 259 | version = "1.0.140" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" 262 | dependencies = [ 263 | "serde_derive", 264 | ] 265 | 266 | [[package]] 267 | name = "serde_derive" 268 | version = "1.0.140" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" 271 | dependencies = [ 272 | "proc-macro2", 273 | "quote", 274 | "syn", 275 | ] 276 | 277 | [[package]] 278 | name = "serde_json" 279 | version = "1.0.82" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" 282 | dependencies = [ 283 | "itoa", 284 | "ryu", 285 | "serde", 286 | ] 287 | 288 | [[package]] 289 | name = "strsim" 290 | version = "0.10.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 293 | 294 | [[package]] 295 | name = "syn" 296 | version = "1.0.98" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 299 | dependencies = [ 300 | "proc-macro2", 301 | "quote", 302 | "unicode-ident", 303 | ] 304 | 305 | [[package]] 306 | name = "termcolor" 307 | version = "1.1.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 310 | dependencies = [ 311 | "winapi-util", 312 | ] 313 | 314 | [[package]] 315 | name = "terminal_size" 316 | version = "0.1.17" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 319 | dependencies = [ 320 | "libc", 321 | "winapi", 322 | ] 323 | 324 | [[package]] 325 | name = "textwrap" 326 | version = "0.15.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 329 | 330 | [[package]] 331 | name = "unicode-ident" 332 | version = "1.0.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" 335 | 336 | [[package]] 337 | name = "version_check" 338 | version = "0.9.4" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 341 | 342 | [[package]] 343 | name = "winapi" 344 | version = "0.3.9" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 347 | dependencies = [ 348 | "winapi-i686-pc-windows-gnu", 349 | "winapi-x86_64-pc-windows-gnu", 350 | ] 351 | 352 | [[package]] 353 | name = "winapi-i686-pc-windows-gnu" 354 | version = "0.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 357 | 358 | [[package]] 359 | name = "winapi-util" 360 | version = "0.1.5" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 363 | dependencies = [ 364 | "winapi", 365 | ] 366 | 367 | [[package]] 368 | name = "winapi-x86_64-pc-windows-gnu" 369 | version = "0.4.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 372 | 373 | [[package]] 374 | name = "zzhack" 375 | version = "0.1.5" 376 | dependencies = [ 377 | "clap", 378 | "convert_case", 379 | "fs_extra", 380 | "indicatif", 381 | "regex", 382 | "serde", 383 | "serde_json", 384 | ] 385 | -------------------------------------------------------------------------------- /src/driver/apply_router.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::{fs, time::UNIX_EPOCH}; 3 | 4 | use convert_case::{Case, Casing}; 5 | use serde::Deserialize; 6 | 7 | use crate::template::{Template, TEMPLATES}; 8 | 9 | use super::apply_links::apply_links_config; 10 | use super::apply_projects::apply_projects_config; 11 | use super::common::{rewrite_with_template, with_static_resource}; 12 | 13 | const BUILD_IN_ROUTE: [&'static str; 3] = ["post", "not_found", "root"]; 14 | const FIRST_PAGE_ROUTE_ENUM_NAME: &'static str = "Home"; 15 | const POST_SOURCE_SUFFIX: &'static str = "PostsSource"; 16 | 17 | #[derive(Deserialize, Clone, Debug, PartialEq)] 18 | pub struct PageConfig { 19 | pub name: String, 20 | pub route: Option, 21 | pub template: String, 22 | pub source: String, 23 | // Posts optional config 24 | pub banner: Option, 25 | // projects & links optional config 26 | pub banner_link: Option, 27 | pub banner_text: Option, 28 | } 29 | 30 | pub fn parse_page_route(page: &PageConfig) -> String { 31 | match &page.route { 32 | Some(route) => route, 33 | None => &page.name, 34 | } 35 | .clone() 36 | } 37 | 38 | pub fn get_route_enum_name(idx: usize, page: &PageConfig) -> String { 39 | if idx == 0 { 40 | FIRST_PAGE_ROUTE_ENUM_NAME.to_string() 41 | } else { 42 | parse_page_route(page).to_case(Case::Pascal) 43 | } 44 | } 45 | 46 | pub fn verify_pages_name_and_route(pages: &Vec) { 47 | let mut route_vec: Vec = vec![]; 48 | 49 | for page in pages { 50 | let route = parse_page_route(page); 51 | 52 | if BUILD_IN_ROUTE.contains(&route.as_str()) { 53 | panic!("The {} has already declare", route); 54 | } 55 | 56 | if route_vec.contains(&route) { 57 | panic!("Cannot redeclare {}", route); 58 | } 59 | 60 | route_vec.push(route) 61 | } 62 | } 63 | 64 | pub fn verify_template(template: &str) { 65 | let is_legal_template = TEMPLATES.contains(&template); 66 | 67 | if !is_legal_template { 68 | panic!( 69 | "Please make sure the template({}) is one of [{}]", 70 | template, 71 | TEMPLATES.join(", ") 72 | ); 73 | } 74 | } 75 | 76 | pub fn apply_router(pages: &Vec) { 77 | let routes = pages 78 | .iter() 79 | .enumerate() 80 | .map(|(idx, page)| { 81 | verify_template(&page.template); 82 | 83 | let route = parse_page_route(&page); 84 | let route_enum_name = get_route_enum_name(idx, page); 85 | 86 | format!("#[at(\"/{}\")]\n{}", route, route_enum_name) 87 | }) 88 | .collect::>(); 89 | let routes_config = format!( 90 | r#" 91 | use yew_router::prelude::*; 92 | 93 | #[derive(Clone, Routable, PartialEq, Debug)] 94 | pub enum RootRoutes {{ 95 | {}, 96 | #[at("/")] 97 | Root, 98 | #[at("/posts/:filename")] 99 | Post {{ filename: String }}, 100 | #[not_found] 101 | #[at("/404")] 102 | NotFound, 103 | }} 104 | "#, 105 | routes.join(",\n") 106 | ); 107 | 108 | // Write back to routes 109 | fs::write(".zzhack/router/src/lib.rs", routes_config) 110 | .expect("Please make sure the .zzhack folder does exist."); 111 | } 112 | 113 | pub fn get_post_file_from_path(path: &PathBuf) -> (String, u128, String) { 114 | let stemname = path.file_stem().unwrap().to_str().unwrap().to_string(); 115 | let metadata = path.metadata().unwrap(); 116 | let modified_time = metadata 117 | .modified() 118 | .unwrap() 119 | .duration_since(UNIX_EPOCH) 120 | .unwrap() 121 | .as_millis(); 122 | let filepath = fs::canonicalize(&path) 123 | .unwrap() 124 | .to_str() 125 | .unwrap() 126 | .to_string(); 127 | 128 | (filepath, modified_time, stemname) 129 | } 130 | 131 | pub fn apply_routes_switch(pages: &Vec, static_resource_path: &Option) { 132 | let mut post_file_template: Vec = vec![]; 133 | let switch_cases = pages 134 | .iter() 135 | .enumerate() 136 | .map(|(idx, page)| { 137 | let template = page.template.as_str(); 138 | let template: Template = template.into(); 139 | let route_enum_name = get_route_enum_name(idx, page); 140 | let posts_source_key_name = format!("{}{}", route_enum_name, POST_SOURCE_SUFFIX); 141 | let properties = match template { 142 | Template::Post => format!( 143 | "filename=\"{}\"", 144 | Path::new(&page.source) 145 | .file_stem() 146 | .unwrap() 147 | .to_str() 148 | .unwrap() 149 | ), 150 | Template::Posts => format!("posts_key=\"{}\"", posts_source_key_name), 151 | _ => String::from(""), 152 | }; 153 | 154 | match template { 155 | Template::Links => { 156 | let banner_text = page 157 | .banner_text 158 | .clone() 159 | .expect("Missing the banner_text field in links page"); 160 | 161 | rewrite_with_template( 162 | format!( 163 | " 164 | pub const LINKS_BANNER_TEXT: &'static str = \"{}\"; 165 | ", 166 | banner_text 167 | ), 168 | "pages/links/src/links_config.rs", 169 | ); 170 | 171 | apply_links_config(&page.source) 172 | } 173 | Template::Post => { 174 | let (filepath, modified_time, filename) = 175 | get_post_file_from_path(&Path::new(&page.source).to_path_buf()); 176 | 177 | post_file_template.push(format!( 178 | " 179 | (String::from(\"{}\"), vec![PostFile {{ 180 | content: include_str!(\"{}\"), 181 | modified_time: {}, 182 | filename: \"{}\" 183 | }}]) 184 | ", 185 | posts_source_key_name, filepath, modified_time, filename 186 | )); 187 | } 188 | Template::Projects => { 189 | let banner_link = page.banner_link.clone().unwrap_or(String::from("#")); 190 | let banner_text = page.banner_text.clone().unwrap_or(String::from("")); 191 | 192 | rewrite_with_template( 193 | format!( 194 | " 195 | pub const PROJECTS_BANNER_TEXT: &'static str = \"{}\"; 196 | pub const PROJECTS_BANNER_LINK: &'static str = \"{}\"; 197 | ", 198 | banner_text, banner_link 199 | ), 200 | "pages/projects/src/projects_config.rs", 201 | ); 202 | 203 | apply_projects_config(&page.source); 204 | } 205 | Template::Posts => { 206 | let banner = page.banner.clone().unwrap_or(String::from("")); 207 | let banner_source = with_static_resource(static_resource_path, &banner); 208 | // let banner Text 209 | rewrite_with_template( 210 | format!( 211 | " 212 | pub const BANNER_LINK: &'static str = \"{}\"; 213 | ", 214 | banner_source 215 | ), 216 | "pages/home/src/posts_config.rs", 217 | ); 218 | 219 | // Construct routes 220 | let dir = fs::read_dir(&page.source).expect( 221 | format!("Please make sure the {} does exits.", &page.source).as_str(), 222 | ); 223 | let posts_source = dir 224 | .map(|entry| { 225 | let entry = entry.unwrap(); 226 | let path = entry.path(); 227 | let (filepath, modified_time, filename) = 228 | get_post_file_from_path(&Path::new(&path).to_path_buf()); 229 | 230 | format!( 231 | " 232 | PostFile {{ 233 | content: include_str!(\"{}\"), 234 | modified_time: {}, 235 | filename: \"{}\" 236 | }}", 237 | filepath, modified_time, filename 238 | ) 239 | }) 240 | .collect::>() 241 | .join(",\n"); 242 | 243 | post_file_template.push(format!( 244 | " 245 | (String::from(\"{}\"), vec![{}]) 246 | ", 247 | posts_source_key_name, posts_source 248 | )); 249 | } 250 | _ => (), 251 | }; 252 | 253 | format!( 254 | "RootRoutes::{} => html! {{ <{} {} />}}", 255 | route_enum_name, 256 | template.into_component_name(), 257 | properties 258 | ) 259 | }) 260 | .collect::>(); 261 | 262 | let posts_source_template = format!( 263 | " 264 | use std::collections::HashMap; 265 | 266 | #[derive(Clone)] 267 | pub struct PostFile {{ 268 | pub content: &'static str, 269 | pub modified_time: u128, 270 | pub filename: &'static str, 271 | }} 272 | 273 | pub fn get_posts() -> HashMap> {{ 274 | HashMap::from([{}]) 275 | }} 276 | ", 277 | post_file_template.join(",\n") 278 | ); 279 | 280 | let switch_fn_template = format!( 281 | r#" 282 | use router::RootRoutes; 283 | use yew::prelude::*; 284 | use yew_router::prelude::*; 285 | 286 | use home::Home; 287 | use links::Links; 288 | use projects::Projects; 289 | use post::Post; 290 | use not_found::NotFound; 291 | 292 | pub fn switch(routes: &RootRoutes) -> Html {{ 293 | match routes {{ 294 | {}, 295 | RootRoutes::Root => html! {{ to={{RootRoutes::Home}}/>}}, 296 | RootRoutes::Post {{ filename }} => html! {{}}, 297 | RootRoutes::NotFound => html! {{ }}, 298 | }} 299 | }} 300 | "#, 301 | switch_cases.join(",\n") 302 | ); 303 | 304 | fs::write(".zzhack/app/src/routes_switch.rs", switch_fn_template) 305 | .expect("Please make sure the .zzhack folder does exist."); 306 | fs::write(".zzhack/services/src/posts.rs", posts_source_template).unwrap(); 307 | } 308 | 309 | pub fn apply_navigator(pages: &Vec) { 310 | let navigator_pages = pages 311 | .iter() 312 | .enumerate() 313 | .map(|(idx, page)| { 314 | format!( 315 | "Page {{ 316 | route: RootRoutes::{}, 317 | name: \"{}\" 318 | }}", 319 | get_route_enum_name(idx, page), 320 | page.name 321 | ) 322 | }) 323 | .collect::>(); 324 | 325 | let navigator_pages = format!( 326 | " 327 | use router::RootRoutes; 328 | 329 | pub struct Page {{ 330 | pub route: RootRoutes, 331 | pub name: &'static str, 332 | }} 333 | 334 | pub const PAGES: [Page; {}] = [ 335 | {} 336 | ]; 337 | ", 338 | pages.len(), 339 | navigator_pages.join(",\n"), 340 | ); 341 | 342 | println!("{}", navigator_pages); 343 | 344 | fs::write(".zzhack/ui/src/common/header/pages.rs", navigator_pages) 345 | .expect("Please make sure the .zzhack folder does exist."); 346 | } 347 | 348 | pub fn apply_pages_config(pages: Vec, static_resource_path: &Option) { 349 | verify_pages_name_and_route(&pages); 350 | apply_router(&pages); 351 | apply_routes_switch(&pages, static_resource_path); 352 | apply_navigator(&pages); 353 | } 354 | --------------------------------------------------------------------------------