Content-Length: 52136 | pFad | http://github.com/postgresml/postgresml/pull/966.diff

thub.com diff --git a/pgml-apps/cargo-pgml-components/.gitignore b/pgml-apps/cargo-pgml-components/.gitignore new file mode 100644 index 000000000..608af9905 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/.gitignore @@ -0,0 +1 @@ +/static diff --git a/pgml-apps/cargo-pgml-components/Cargo.lock b/pgml-apps/cargo-pgml-components/Cargo.lock index 3c5ee69e9..fa54e9722 100644 --- a/pgml-apps/cargo-pgml-components/Cargo.lock +++ b/pgml-apps/cargo-pgml-components/Cargo.lock @@ -59,6 +59,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" @@ -67,14 +79,17 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cargo-pgml-components" -version = "0.1.5" +version = "0.1.7" dependencies = [ + "anyhow", "clap", "convert_case", "env_logger", "glob", "log", "md5", + "owo-colors", + "sailfish", ] [[package]] @@ -86,6 +101,12 @@ dependencies = [ "libc", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.4.1" @@ -155,6 +176,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.3" @@ -176,12 +203,30 @@ dependencies = [ "libc", ] +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -194,12 +239,31 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -211,6 +275,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itoap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" + [[package]] name = "libc" version = "0.2.147" @@ -247,6 +317,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -265,6 +341,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.9.4" @@ -300,13 +385,86 @@ version = "0.38.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sailfish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7519b7521780097b0183bb4b0c7c2165b924f5f1d44c3ef765bde8c2f8008fd1" +dependencies = [ + "itoap", + "ryu", + "sailfish-macros", + "version_check", +] + +[[package]] +name = "sailfish-compiler" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535500faca492ee8054fbffdfca6447ca97fa495e0ede9f28fa473e1a44f9d5c" +dependencies = [ + "filetime", + "home", + "memchr", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", +] + +[[package]] +name = "sailfish-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06a95a6b8a0f59bf66f430a4ed37ece23fcefcd26898399573043e56fb202be2" +dependencies = [ + "proc-macro2", + "sailfish-compiler", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -333,6 +491,40 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -351,6 +543,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "winapi" version = "0.3.9" @@ -447,3 +645,12 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] diff --git a/pgml-apps/cargo-pgml-components/Cargo.toml b/pgml-apps/cargo-pgml-components/Cargo.toml index dcb4cdd23..84be9f5b5 100644 --- a/pgml-apps/cargo-pgml-components/Cargo.toml +++ b/pgml-apps/cargo-pgml-components/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-pgml-components" -version = "0.1.6" +version = "0.1.7" edition = "2021" authors = ["PostgresML "] license = "MIT" @@ -15,3 +15,6 @@ clap = { version = "4", features = ["derive"] } md5 = "0.7" log = "0.4" env_logger = "0.10" +anyhow = "1" +owo-colors = "3" +sailfish = "0.8" diff --git a/pgml-apps/cargo-pgml-components/sailfish.toml b/pgml-apps/cargo-pgml-components/sailfish.toml new file mode 100644 index 000000000..2d804e09e --- /dev/null +++ b/pgml-apps/cargo-pgml-components/sailfish.toml @@ -0,0 +1 @@ +template_dirs = ["src"] diff --git a/pgml-apps/cargo-pgml-components/src/frontend/components.rs b/pgml-apps/cargo-pgml-components/src/frontend/components.rs new file mode 100644 index 000000000..e4bf2b6b0 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/components.rs @@ -0,0 +1,134 @@ +use convert_case::{Case, Casing}; +use sailfish::TemplateOnce; +use std::fs::{create_dir_all, read_dir}; +use std::path::Path; +use std::process::exit; + +use crate::frontend::templates; +use crate::util::{error, info, unwrap_or_exit, write_to_file}; + +static COMPONENT_DIRECTORY: &'static str = "src/components"; +static COMPONENT_MOD: &'static str = "src/components/mod.rs"; + +pub struct Component { + name: String, +} + +impl Component { + pub fn new(name: &str) -> Component { + Component { + name: name.to_string(), + } + } + + pub fn path(&self) -> String { + self.name.to_case(Case::Snake).to_string() + } + + pub fn name(&self) -> String { + self.name.to_case(Case::UpperCamel).to_string() + } + + pub fn full_path(&self) -> String { + Path::new(COMPONENT_DIRECTORY) + .join(&self.path()) + .display() + .to_string() + } + + pub fn controller_name(&self) -> String { + self.path().replace("_", "-") + } + + #[allow(dead_code)] + pub fn controller_path(&self) -> String { + format!("{}_controller.js", self.path()) + } + + pub fn rust_module(&self) -> String { + let full_path = self.full_path(); + let path = Path::new(&full_path); + let components = path.components(); + + components + .skip(2) // skip src/components + .map(|c| c.as_os_str().to_str().unwrap()) + .collect::>() + .join("::") + .to_string() + } +} + +impl From<&Path> for Component { + fn from(path: &Path) -> Self { + assert!(path.is_dir()); + + let components = path.components(); + let name = components + .clone() + .last() + .unwrap() + .as_os_str() + .to_str() + .unwrap(); + Component::new(name) + } +} + +//github.com/ Add a new component. +pub fn add(name: &str, overwrite: bool) { + let component = Component::new(name); + let path = Path::new(COMPONENT_DIRECTORY).join(component.path()); + + if path.exists() && !overwrite { + error(&format!("component {} already exists", component.path())); + exit(1); + } else { + unwrap_or_exit!(create_dir_all(&path)); + info(&format!("created directory {}", path.display())); + } + + let rust = unwrap_or_exit!(templates::Component::new(&component).render_once()); + let stimulus = unwrap_or_exit!(templates::Stimulus::new(&component).render_once()); + let html = unwrap_or_exit!(templates::Html::new(&component).render_once()); + let scss = String::new(); + + let html_path = path.join("template.html"); + unwrap_or_exit!(write_to_file(&html_path, &html)); + info(&format!("written {}", html_path.display())); + + let stimulus_path = path.join(&format!("{}_controller.js", component.path())); + unwrap_or_exit!(write_to_file(&stimulus_path, &stimulus)); + info(&format!("written {}", stimulus_path.display())); + + let rust_path = path.join("mod.rs"); + unwrap_or_exit!(write_to_file(&rust_path, &rust)); + info(&format!("written {}", rust_path.display())); + + let scss_path = path.join(&format!("{}.scss", component.path())); + unwrap_or_exit!(write_to_file(&scss_path, &scss)); + info(&format!("written {}", scss_path.display())); + + update_modules(); +} + +//github.com/ Update `mod.rs` with all the components in `src/components`. +pub fn update_modules() { + let mut modules = Vec::new(); + + for path in unwrap_or_exit!(read_dir(COMPONENT_DIRECTORY)) { + let path = unwrap_or_exit!(path).path(); + + if path.is_file() { + continue; + } + + let component = Component::from(Path::new(&path)); + modules.push(component); + } + + let modules = unwrap_or_exit!(templates::Mod { modules }.render_once()); + + unwrap_or_exit!(write_to_file(&Path::new(COMPONENT_MOD), &modules)); + info(&format!("written {}", COMPONENT_MOD)); +} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs new file mode 100644 index 000000000..90b733122 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs @@ -0,0 +1,137 @@ +//! Javascript bundling. + +use glob::glob; +use std::fs::{copy, read_to_string, remove_file, File}; +use std::io::Write; +use std::process::Command; + +use convert_case::{Case, Casing}; + +use crate::util::{execute_command, info, unwrap_or_exit, warn}; + +//github.com/ The name of the JS file that imports all other JS files +//github.com/ created in the modules. +static MODULES_FILE: &'static str = "static/js/modules.js"; + +//github.com/ The JS bundle. +static JS_FILE: &'static str = "static/js/bundle.js"; +static JS_FILE_HASHED: &'static str = "static/js/bundle.{}.js"; +static JS_HASH_FILE: &'static str = "static/js/.pgml-bundle"; + +//github.com/ Finds all the JS files we have generated or the user has created. +static MODULES_GLOB: &'static str = "src/components/**/*.js"; +static STATIC_JS_GLOB: &'static str = "static/js/*.js"; + +//github.com/ Finds old JS bundles we created. +static OLD_BUNLDES_GLOB: &'static str = "static/js/*.*.js"; + +//github.com/ JS compiler +static JS_COMPILER: &'static str = "rollup"; + +//github.com/ Delete old bundles we may have created. +fn cleanup_old_bundles() { + // Clean up old bundles + for file in unwrap_or_exit!(glob(OLD_BUNLDES_GLOB)) { + let file = unwrap_or_exit!(file); + debug!("removing {}", file.display()); + unwrap_or_exit!(remove_file(file.clone())); + warn(&format!("deleted {}", file.display())); + } +} + +fn assemble_modules() { + let js = unwrap_or_exit!(glob(MODULES_GLOB)); + let js = js.chain(unwrap_or_exit!(glob(STATIC_JS_GLOB))); + + // Don't bundle artifacts we produce. + let js = js.filter(|path| { + let path = path.as_ref().unwrap(); + let path = path.display().to_string(); + + !path.contains("main.js") && !path.contains("bundle.js") && !path.contains("modules.js") + }); + + let mut modules = unwrap_or_exit!(File::create(MODULES_FILE)); + + unwrap_or_exit!(writeln!(&mut modules, "// Build with --bin components")); + unwrap_or_exit!(writeln!( + &mut modules, + "import {{ Application }} from '@hotwired/stimulus'" + )); + unwrap_or_exit!(writeln!( + &mut modules, + "const application = Application.start()" + )); + + for source in js { + let source = unwrap_or_exit!(source); + + let full_path = source.display(); + let stem = source.file_stem().unwrap().to_str().unwrap(); + let upper_camel = stem.to_case(Case::UpperCamel); + + let mut controller_name = stem.split("_").collect::>(); + + if stem.contains("controller") { + let _ = controller_name.pop().unwrap(); + } + + let controller_name = controller_name.join("-"); + + unwrap_or_exit!(writeln!( + &mut modules, + "import {{ default as {} }} from '../../{}'", + upper_camel, full_path + )); + + unwrap_or_exit!(writeln!( + &mut modules, + "application.register('{}', {})", + controller_name, upper_camel + )); + } + + info(&format!("written {}", MODULES_FILE)); +} + +pub fn bundle() { + cleanup_old_bundles(); + assemble_modules(); + + // Bundle JavaScript. + unwrap_or_exit!(execute_command( + Command::new(JS_COMPILER) + .arg(MODULES_FILE) + .arg("--file") + .arg(JS_FILE) + .arg("--format") + .arg("es"), + )); + + info(&format!("written {}", JS_FILE)); + + // Hash the bundle. + let bundle = unwrap_or_exit!(read_to_string(JS_FILE)); + let hash = format!("{:x}", md5::compute(bundle)) + .chars() + .take(8) + .collect::(); + + unwrap_or_exit!(copy(JS_FILE, &JS_FILE_HASHED.replace("{}", &hash))); + info(&format!("written {}", JS_FILE_HASHED.replace("{}", &hash))); + + // Legacy, remove code from main.js into respective modules. + unwrap_or_exit!(copy( + "static/js/main.js", + &format!("static/js/main.{}.js", &hash) + )); + info(&format!( + "written {}", + format!("static/js/main.{}.js", &hash) + )); + + let mut hash_file = unwrap_or_exit!(File::create(JS_HASH_FILE)); + unwrap_or_exit!(writeln!(&mut hash_file, "{}", hash)); + + info(&format!("written {}", JS_HASH_FILE)); +} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/mod.rs b/pgml-apps/cargo-pgml-components/src/frontend/mod.rs new file mode 100644 index 000000000..55790107f --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/mod.rs @@ -0,0 +1,5 @@ +pub mod components; +pub mod javascript; +pub mod sass; +pub mod templates; +pub mod tools; diff --git a/pgml-apps/cargo-pgml-components/src/frontend/sass.rs b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs new file mode 100644 index 000000000..dc74cd5d2 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs @@ -0,0 +1,102 @@ +//! Collect and compile SASS files to produce CSS stylesheets. + +use glob::glob; +use std::fs::{copy, read_to_string, remove_file, File}; +use std::io::Write; +use std::process::Command; + +use crate::util::{execute_command, info, unwrap_or_exit, warn}; + +//github.com/ The name of the SASS file that imports all other SASS files +//github.com/ created in the modules. +static MODULES_FILE: &'static str = "static/css/modules.scss"; + +//github.com/ The SASS file assembling all other files. +static SASS_FILE: &'static str = "static/css/bootstrap-theme.scss"; + +//github.com/ The CSS bundle. +static CSS_FILE: &'static str = "static/css/style.css"; +static CSS_FILE_HASHED: &'static str = "static/css/style.{}.css"; +static CSS_HASH_FILE: &'static str = "static/css/.pgml-bundle"; + +//github.com/ Finds all the SASS files we have generated or the user has created. +static MODULES_GLOB: &'static str = "src/components/**/*.scss"; + +//github.com/ Finds old CSS bundles we created. +static OLD_BUNLDES_GLOB: &'static str = "static/css/style.*.css"; + +//github.com/ Sass compiler +static SASS_COMPILER: &'static str = "sass"; + +//github.com/ Find Sass files and register them with modules.scss. +fn assemble_modules() { + // Assemble SCSS. + let scss = unwrap_or_exit!(glob(MODULES_GLOB)); + + let mut modules = unwrap_or_exit!(File::create(MODULES_FILE)); + + unwrap_or_exit!(writeln!( + &mut modules, + "// This file is automatically generated." + )); + unwrap_or_exit!(writeln!( + &mut modules, + "// There is no need to edit it manually." + )); + unwrap_or_exit!(writeln!(&mut modules, "")); + + for stylesheet in scss { + let stylesheet = unwrap_or_exit!(stylesheet); + + debug!("Adding '{}' to SCSS bundle", stylesheet.display()); + + let line = format!(r#"@import "../../{}";"#, stylesheet.display()); + + unwrap_or_exit!(writeln!(&mut modules, "{}", line)); + } + + info(&format!("written {}", MODULES_FILE)); +} + +//github.com/ Delete old bundles we may have created. +fn cleanup_old_bundles() { + // Clean up old bundles + for file in unwrap_or_exit!(glob(OLD_BUNLDES_GLOB)) { + let file = unwrap_or_exit!(file); + debug!("removing {}", file.display()); + unwrap_or_exit!(remove_file(file.clone())); + warn(&format!("deleted {}", file.display())); + } +} + +//github.com/ Entrypoint. +pub fn bundle() { + crate::frontend::tools::install(); + + assemble_modules(); + cleanup_old_bundles(); + + // Build Sass. + unwrap_or_exit!(execute_command( + Command::new(SASS_COMPILER).arg(SASS_FILE).arg(CSS_FILE), + )); + + info(&format!("written {}", CSS_FILE)); + + // Hash the bundle to bust all caches. + let bundle = read_to_string(CSS_FILE).expect("failed to read bundle.css"); + let hash = format!("{:x}", md5::compute(bundle)) + .chars() + .take(8) + .collect::(); + + let hash_file = CSS_FILE_HASHED.replace("{}", &hash); + + unwrap_or_exit!(copy(CSS_FILE, &hash_file)); + info(&format!("written {}", hash_file)); + + let mut hash_file = unwrap_or_exit!(File::create(CSS_HASH_FILE)); + unwrap_or_exit!(writeln!(&mut hash_file, "{}", hash)); + + info(&format!("written {}", CSS_HASH_FILE)); +} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl new file mode 100644 index 000000000..37c5c346c --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl @@ -0,0 +1,7 @@ +import { Application } from '@hotwired/stimulus' +const application = Application.start() + +<% for component in components { +import { default as <%= component.name() %> } from '../../<%= component.controller_path() %>'" +application.register('<%= component.controller_name() %>') +<% } %> diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl new file mode 100644 index 000000000..483ccea1d --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl @@ -0,0 +1,16 @@ +use sailfish::TemplateOnce; +use crate::components::component; + +#[derive(TemplateOnce, Default)] +#[template(path = "<%= component_path %>/template.html")] +pub struct <%= component_name %> { + value: String, +} + +impl <%= component_name %> { + pub fn new() -> <%= component_name %> { + <%= component_name %>::default() + } +} + +component!(<%= component_name %>); diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs new file mode 100644 index 000000000..2b78f9f64 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs @@ -0,0 +1,53 @@ +use sailfish::TemplateOnce; + +use crate::frontend::components::Component as ComponentModel; + +#[derive(TemplateOnce)] +#[template(path = "frontend/templates/component.rs.tpl")] +pub struct Component { + pub component_name: String, + pub component_path: String, +} + +impl Component { + pub fn new(component: &ComponentModel) -> Self { + Self { + component_name: component.name(), + component_path: component.path(), + } + } +} + +#[derive(TemplateOnce)] +#[template(path = "frontend/templates/template.html.tpl")] +pub struct Html { + pub controller_name: String, +} + +impl Html { + pub fn new(component: &ComponentModel) -> Self { + Self { + controller_name: component.path().replace("_", "-"), + } + } +} + +#[derive(TemplateOnce)] +#[template(path = "frontend/templates/stimulus.js.tpl")] +pub struct Stimulus { + pub controller_name: String, +} + +impl Stimulus { + pub fn new(component: &ComponentModel) -> Self { + Self { + controller_name: component.controller_name(), + } + } +} + +#[derive(TemplateOnce)] +#[template(path = "frontend/templates/mod.rs.tpl")] +pub struct Mod { + pub modules: Vec, +} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl new file mode 100644 index 000000000..2458898bd --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl @@ -0,0 +1,11 @@ +// This file is automatically generated. +// You shouldn't modify it manually. + +mod component; +pub(crate) use component::{component, Component}; + +<% for component in modules.iter() { %> +// <%= component.full_path() %> +pub mod <%= component.path() %>; +pub use <%= component.rust_module() %>::<%= component.name() %>; +<% } %> diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl new file mode 100644 index 000000000..ea0564b98 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = [] + static outlets = [] + + initialize() { + console.log('Initialized <%= controller_name %>') + } + + connect() {} + + disconnect() {} +} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl new file mode 100644 index 000000000..a5cf6b8ea --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl @@ -0,0 +1,3 @@ +
+ <%%= value %> +
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/tools.rs b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs new file mode 100644 index 000000000..b6b2e785c --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs @@ -0,0 +1,29 @@ +//! Tools required by us to build stuff. + +use crate::util::{error, execute_command, unwrap_or_exit, warn}; +use std::process::{exit, Command}; + +//github.com/ Required tools. +static TOOLS: &[&str] = &["sass", "rollup"]; + +//github.com/ Install any missing tools. +pub fn install() { + if let Err(err) = execute_command(Command::new("node").arg("--version")) { + error("Node is not installed. Install it with nvm or your system package manager."); + debug!("{}", err); + exit(1); + } + + for tool in TOOLS { + match execute_command(Command::new(tool).arg("--version")) { + Ok(_) => (), + Err(err) => { + debug!("{}", err); + warn(&format!("installing {}", tool)); + unwrap_or_exit!(execute_command( + Command::new("npm").arg("install").arg("-g").arg(tool) + )); + } + } + } +} diff --git a/pgml-apps/cargo-pgml-components/src/main.rs b/pgml-apps/cargo-pgml-components/src/main.rs index 4ed3305d8..6f2a57a6d 100644 --- a/pgml-apps/cargo-pgml-components/src/main.rs +++ b/pgml-apps/cargo-pgml-components/src/main.rs @@ -1,65 +1,20 @@ //! A tool to assemble and bundle our frontend components. use clap::{Args, Parser, Subcommand}; -use convert_case::{Case, Casing}; -use glob::glob; use std::env::{current_dir, set_current_dir}; -use std::fs::{create_dir_all, read_to_string, remove_file, File, read_dir}; -use std::io::Write; +use std::fs::{create_dir_all}; use std::path::Path; -use std::process::{exit, Command}; #[macro_use] extern crate log; +mod frontend; +mod util; +use util::{info, unwrap_or_exit}; + //github.com/ These paths are exepcted to exist in the project directory. static PROJECT_PATHS: &[&str] = &["src", "static/js", "static/css"]; -//github.com// These executables are required to be installed globally. -static REQUIRED_EXECUTABLES: &[&str] = &["sass", "rollup"]; - -static COMPONENT_TEMPLATE_RS: &'static str = r#" -use sailfish::TemplateOnce; -use crate::components::component; - -#[derive(TemplateOnce, Default)] -#[template(path = "{component_path}/template.html")] -pub struct {component_name} { - value: String, -} - -impl {component_name} { - pub fn new() -> {component_name} { - {component_name}::default() - } -} - -component!({component_name}); -"#; - -static COMPONENT_STIMULUS_JS: &'static str = r#" -import { Controller } from '@hotwired/stimulus' - -export default class extends Controller { - static targets = [] - static outlets = [] - - initialize() { - console.log('Initialized {controller_name}') - } - - connect() {} - - disconnect() {} -} -"#; - -static COMPONENT_HTML: &'static str = r#" -
- <%= value %> -
-"#; - #[derive(Parser, Debug)] #[command(author, version, about, long_about = None, propagate_version = true, bin_name = "cargo", name = "cargo")] struct Cli { @@ -77,8 +32,13 @@ struct PgmlCommands { #[command(subcommand)] command: Commands, + //github.com/ Specify project path (default: current directory) #[arg(short, long)] project_path: Option, + + //github.com/ Overwrite existing files (default: false) + #[arg(short, long, default_value = "false")] + overwrite: bool, } #[derive(Subcommand, Debug)] @@ -86,15 +46,15 @@ enum Commands { //github.com/ Bundle SASS and JavaScript into neat bundle files. Bundle {}, - //github.com/ Add a new component. - AddComponent { - name: String, - - #[arg(short, long, default_value = "false")] - overwrite: bool, - }, + //github.com/ Add new elements to the project. + #[command(subcommand)] + Add(AddCommands), +} - UpdateComponents {}, +#[derive(Subcommand, Debug)] +enum AddCommands { + //github.com/ Add a new component. + Component { name: String }, } fn main() { @@ -104,68 +64,15 @@ fn main() { match cli.subcomand { CargoSubcommands::PgmlComponents(pgml_commands) => match pgml_commands.command { Commands::Bundle {} => bundle(pgml_commands.project_path), - Commands::AddComponent { name, overwrite } => add_component(name, overwrite), - Commands::UpdateComponents {} => update_components(), - + Commands::Add(command) => match command { + AddCommands::Component { name } => crate::frontend::components::add(&name, pgml_commands.overwrite), + }, }, } } -fn execute_command(command: &mut Command) -> std::io::Result { - let output = match command.output() { - Ok(output) => output, - Err(err) => { - return Err(err); - } - }; - - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let stdout = String::from_utf8_lossy(&output.stderr).to_string(); - - if !output.status.success() { - error!( - "{} failed: {}", - command.get_program().to_str().unwrap(), - String::from_utf8_lossy(&output.stderr).to_string(), - ); - exit(1); - } - - if !stderr.is_empty() { - warn!("{}", stderr); - } - - if !stdout.is_empty() { - info!("{}", stdout); - } - - Ok(stdout) -} - -fn check_executables() { - for executable in REQUIRED_EXECUTABLES { - match execute_command(Command::new(executable).arg("--version")) { - Ok(_) => (), - Err(err) => { - error!( - "'{}' is not installed. Install it with 'npm install -g {}'", - executable, executable - ); - debug!( - "Failed to execute '{} --version': {}", - executable, - err.to_string() - ); - exit(1); - } - } - } -} - //github.com/ Bundle SASS and JavaScript into neat bundle files. fn bundle(project_path: Option) { - check_executables(); - // Validate that the required project paths exist. let cwd = if let Some(project_path) = project_path { project_path @@ -179,250 +86,15 @@ fn bundle(project_path: Option) { let check = path.join(project_path); if !check.exists() { - error!( - "Project path '{}/{}' does not exist but is required", - path.display(), - project_path - ); - exit(1); + unwrap_or_exit!(create_dir_all(&check)); + info(&format!("created {} directory", check.display())); } } - set_current_dir(path).expect("failed to change paths"); - - // Assemble SCSS. - let scss = glob("src/components/**/*.scss").expect("failed to glob scss files"); - - let mut modules = - File::create("static/css/modules.scss").expect("failed to create modules.scss"); - - for stylesheet in scss { - let stylesheet = stylesheet.expect("failed to glob stylesheet"); - - debug!("Adding '{}' to SCSS bundle", stylesheet.display()); - - let line = format!(r#"@import "../../{}";"#, stylesheet.display()); - - writeln!(&mut modules, "{}", line).expect("failed to write line to modules.scss"); - } - - drop(modules); + unwrap_or_exit!(set_current_dir(path)); + frontend::sass::bundle(); + frontend::javascript::bundle(); + frontend::components::update_modules(); - // Clean up old bundles - for file in glob("static/css/style.*.css").expect("failed to glob") { - let file = file.expect("failed to glob file"); - debug!("Removing '{}'", file.display()); - let _ = remove_file(file); - } - - // Bundle SCSS. - // Build Bootstrap - execute_command( - Command::new("sass") - .arg("static/css/bootstrap-theme.scss") - .arg("static/css/style.css"), - ) - .unwrap(); - - // Hash the bundle. - let bundle = read_to_string("static/css/style.css").expect("failed to read bundle.css"); - let hash = format!("{:x}", md5::compute(bundle)) - .chars() - .take(8) - .collect::(); - - execute_command( - Command::new("cp") - .arg("static/css/style.css") - .arg(format!("static/css/style.{}.css", hash)), - ) - .unwrap(); - - let mut hash_file = - File::create("static/css/.pgml-bundle").expect("failed to create .pgml-bundle"); - writeln!(&mut hash_file, "{}", hash).expect("failed to write hash to .pgml-bundle"); - drop(hash_file); - - debug!("Created css .pgml-bundle with hash {}", hash); - - // Assemble JavaScript. - - // Remove prebuilt files. - for file in glob::glob("static/js/*.*.js").expect("failed to glob") { - let _ = remove_file(file.expect("failed to glob file")); - } - - let js = glob("src/components/**/*.js").expect("failed to glob js files"); - let js = js.chain(glob("static/js/*.js").expect("failed to glob static/js/*.js")); - let js = js.filter(|path| { - let path = path.as_ref().unwrap(); - let path = path.display().to_string(); - - !path.contains("main.js") && !path.contains("bundle.js") && !path.contains("modules.js") - }); - - let mut modules = File::create("static/js/modules.js").expect("failed to create modules.js"); - - writeln!(&mut modules, "// Build with --bin components").unwrap(); - writeln!( - &mut modules, - "import {{ Application }} from '@hotwired/stimulus'" - ) - .expect("failed to write to modules.js"); - writeln!(&mut modules, "const application = Application.start()") - .expect("failed to write to modules.js"); - - for source in js { - let source = source.expect("failed to glob js file"); - - let full_path = source.display(); - let stem = source.file_stem().unwrap().to_str().unwrap(); - let upper_camel = stem.to_case(Case::UpperCamel); - - let mut controller_name = stem.split("_").collect::>(); - - if stem.contains("controller") { - let _ = controller_name.pop().unwrap(); - } - - let controller_name = controller_name.join("-"); - - writeln!( - &mut modules, - "import {{ default as {} }} from '../../{}'", - upper_camel, full_path - ) - .unwrap(); - writeln!( - &mut modules, - "application.register('{}', {})", - controller_name, upper_camel - ) - .unwrap(); - } - - drop(modules); - - // Bundle JavaScript. - execute_command( - Command::new("rollup") - .arg("static/js/modules.js") - .arg("--file") - .arg("static/js/bundle.js") - .arg("--format") - .arg("es"), - ) - .unwrap(); - - // Hash the bundle. - let bundle = read_to_string("static/js/bundle.js").expect("failed to read bundle.js"); - let hash = format!("{:x}", md5::compute(bundle)) - .chars() - .take(8) - .collect::(); - - execute_command( - Command::new("cp") - .arg("static/js/bundle.js") - .arg(format!("static/js/bundle.{}.js", hash)), - ) - .unwrap(); - - let mut hash_file = - File::create("static/js/.pgml-bundle").expect("failed to create .pgml-bundle"); - writeln!(&mut hash_file, "{}", hash).expect("failed to write hash to .pgml-bundle"); - drop(hash_file); - - println!("Finished bundling CSS and JavaScript successfully"); -} - -fn add_component(name: String, overwrite: bool) { - let component_name = name.as_str().to_case(Case::UpperCamel); - let component_path = name.as_str().to_case(Case::Snake); - let folder = Path::new("src/components").join(&component_path); - - if !folder.exists() { - match create_dir_all(folder.clone()) { - Ok(_) => (), - Err(err) => { - error!( - "Failed to create path '{}' for component '{}': {}", - folder.display(), - name, - err - ); - exit(1); - } - } - } else if !overwrite { - error!("Component '{}' already exists", folder.display()); - exit(1); - } - - // Create mod.rs - let mod_file = format!( - "{}", - COMPONENT_TEMPLATE_RS - .replace("{component_name}", &component_name) - .replace("{component_path}", &component_path) - ); - - let mod_path = folder.join("mod.rs"); - - let mut mod_file_fd = File::create(mod_path).expect("failed to create mod.rs"); - writeln!(&mut mod_file_fd, "{}", mod_file.trim()).expect("failed to write mod.rs"); - drop(mod_file_fd); - - // Create template.html - let template_path = folder.join("template.html"); - let mut template_file = File::create(template_path).expect("failed to create template.html"); - let template_source = - COMPONENT_HTML.replace("{controller_name}", &component_path.replace("_", "-")); - writeln!(&mut template_file, "{}", template_source.trim(),) - .expect("failed to write template.html"); - drop(template_file); - - // Create Stimulus controller - let stimulus_path = folder.join(&format!("{}_controller.js", component_path)); - let mut template_file = - File::create(stimulus_path).expect("failed to create stimulus controller"); - let controller_source = - COMPONENT_STIMULUS_JS.replace("{controller_name}", &component_path.replace("_", "-")); - writeln!(&mut template_file, "{}", controller_source.trim()) - .expect("failed to write stimulus controller"); - drop(template_file); - - // Create SASS file - let sass_path = folder.join(&format!("{}.scss", component_path)); - let sass_file = File::create(sass_path).expect("failed to create sass file"); - drop(sass_file); - - println!("Component '{}' created successfully", folder.display()); - update_components(); -} - -fn update_components() { - let mut file = File::create("src/components/mod.rs").expect("failed to create mod.rs"); - - writeln!(&mut file, "// This file is automatically generated by cargo-pgml-components.").expect("failed to write to mod.rs"); - writeln!(&mut file, "// Do not modify it directly.").expect("failed to write to mod.rs"); - writeln!(&mut file, "mod component;").expect("failed to write to mod.rs"); - writeln!(&mut file, "pub(crate) use component::{{component, Component}};").expect("failed to write to mod.rs"); - - for component in read_dir("src/components").expect("failed to read components directory") { - let path = component.expect("dir entry").path(); - - if path.is_file() { - continue; - } - - let components = path.components(); - let component_name = components.clone().last().expect("component_name").as_os_str().to_str().unwrap(); - let module = components.skip(2).map(|c| c.as_os_str().to_str().unwrap()).collect::>().join("::"); - // let module = format!("crate::{}", module); - let component_name = component_name.to_case(Case::UpperCamel); - - writeln!(&mut file, "pub mod {};", module).expect("failed to write to mod.rs"); - writeln!(&mut file, "pub use {}::{};", module, component_name).expect("failed to write to mod.rs"); - } + info("Bundle complete"); } diff --git a/pgml-apps/cargo-pgml-components/src/util.rs b/pgml-apps/cargo-pgml-components/src/util.rs new file mode 100644 index 000000000..7205c4a9b --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/util.rs @@ -0,0 +1,71 @@ +use owo_colors::OwoColorize; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::process::{exit, Command}; + +macro_rules! unwrap_or_exit { + ($i:expr) => { + match $i { + Ok(v) => v, + Err(e) => { + error!("{}:{}:{} {e}", file!(), line!(), column!()); + + std::process::exit(1); + } + } + }; +} + +pub(crate) use unwrap_or_exit; + +pub fn info(value: &str) { + println!("{}", value.green()); +} + +pub fn error(value: &str) { + println!("{}", value.red()); +} + +pub fn warn(value: &str) { + println!("{}", value.yellow()); +} + +pub fn execute_command(command: &mut Command) -> std::io::Result { + let output = match command.output() { + Ok(output) => output, + Err(err) => { + return Err(err); + } + }; + + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stdout = String::from_utf8_lossy(&output.stderr).to_string(); + + if !output.status.success() { + error!( + "{} failed: {}", + command.get_program().to_str().unwrap(), + String::from_utf8_lossy(&output.stderr).to_string(), + ); + exit(1); + } + + if !stderr.is_empty() { + warn!("{}", stderr); + } + + if !stdout.is_empty() { + info!("{}", stdout); + } + + Ok(stdout) +} + +pub fn write_to_file(path: &Path, content: &str) -> std::io::Result<()> { + let mut file = File::create(path)?; + + file.write_all(content.as_bytes())?; + + Ok(()) +} diff --git a/pgml-dashboard/src/components/mod.rs b/pgml-dashboard/src/components/mod.rs index 1c5737be0..64af15f8a 100644 --- a/pgml-dashboard/src/components/mod.rs +++ b/pgml-dashboard/src/components/mod.rs @@ -1,32 +1,62 @@ -// This file is automatically generated by cargo-pgml-components. -// Do not modify it directly. +// This file is automatically generated. +// You shouldn't modify it manually. + mod component; pub(crate) use component::{component, Component}; + + +// src/components/navbar_web_app pub mod navbar_web_app; pub use navbar_web_app::NavbarWebApp; + +// src/components/navbar pub mod navbar; pub use navbar::Navbar; + +// src/components/postgres_logo pub mod postgres_logo; pub use postgres_logo::PostgresLogo; + +// src/components/static_nav_link pub mod static_nav_link; pub use static_nav_link::StaticNavLink; + +// src/components/modal pub mod modal; pub use modal::Modal; + +// src/components/static_nav pub mod static_nav; pub use static_nav::StaticNav; + +// src/components/test_component pub mod test_component; pub use test_component::TestComponent; + +// src/components/nav pub mod nav; pub use nav::Nav; + +// src/components/left_nav_web_app pub mod left_nav_web_app; pub use left_nav_web_app::LeftNavWebApp; + +// src/components/github_icon pub mod github_icon; pub use github_icon::GithubIcon; + +// src/components/confirm_modal pub mod confirm_modal; pub use confirm_modal::ConfirmModal; + +// src/components/left_nav_menu pub mod left_nav_menu; pub use left_nav_menu::LeftNavMenu; + +// src/components/nav_link pub mod nav_link; pub use nav_link::NavLink; + +// src/components/breadcrumbs pub mod breadcrumbs; pub use breadcrumbs::Breadcrumbs; diff --git a/pgml-dashboard/static/css/modules.scss b/pgml-dashboard/static/css/modules.scss index e15d16010..c038c5029 100644 --- a/pgml-dashboard/static/css/modules.scss +++ b/pgml-dashboard/static/css/modules.scss @@ -1,3 +1,6 @@ +// This file is automatically generated. +// There is no need to edit it manually. + @import "../../src/components/left_nav_menu/left_nav_menu.scss"; @import "../../src/components/left_nav_web_app/left_nav_web_app.scss"; @import "../../src/components/modal/modal.scss"; diff --git a/pgml-dashboard/templates/content/dashboard/panels/model.html b/pgml-dashboard/templates/content/dashboard/panels/model.html index dc1b392d0..fbe188d2e 100644 --- a/pgml-dashboard/templates/content/dashboard/panels/model.html +++ b/pgml-dashboard/templates/content/dashboard/panels/model.html @@ -59,7 +59,7 @@

<%= param %>

+ @@ -46,7 +56,7 @@ - + @@ -60,17 +70,7 @@ - - - + <% if config::dev_mode() { %>








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/postgresml/postgresml/pull/966.diff

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy