diff --git a/script-config.json b/script-config.json new file mode 100644 index 0000000..2cd36d5 --- /dev/null +++ b/script-config.json @@ -0,0 +1,6 @@ +{ + "file_ext": "dart", + "simple_script_url": "https://git.hatter.ink/hatter/dart-scripts/raw/branch/main/single-scripts/$NAME", + "project_script_url": "https://git.hatter.ink/hatter/dart-scripts/raw/branch/main/$NAME/main.dart", + "skip_dirs": [] +} diff --git a/script-meta-v2.json b/script-meta-v2.json new file mode 100644 index 0000000..847a5f4 --- /dev/null +++ b/script-meta-v2.json @@ -0,0 +1,8 @@ +{ + "helloworld.dart": { + "script_name": "helloworld.dart", + "script_length": 74, + "script_sha256": "a4ec48f29f109da6bddedd6044362310b15451cd467bfc79594e34e5179393dd", + "script_full_url": "https://git.hatter.ink/hatter/dart-scripts/raw/branch/main/helloworld-dart/main.dart" + } +} \ No newline at end of file diff --git a/update-meta.rs b/update-meta-v1.rs similarity index 100% rename from update-meta.rs rename to update-meta-v1.rs diff --git a/update-meta-v2.rs b/update-meta-v2.rs new file mode 100755 index 0000000..f14586c --- /dev/null +++ b/update-meta-v2.rs @@ -0,0 +1,208 @@ +#!/usr/bin/env runrs + +//! ```cargo +//! [dependencies] +//! serde = { version = "1.0", features = ["derive"] } +//! serde_json = "1.0" +//! sha256 = "1.5" +//! rust_util = { version = "0.6" } +//! ``` + +use rust_util::{ + debugging, failure_and_exit, iff, opt_result, opt_value_result, success, warning, XResult, +}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::fs; +use std::option::Option; +use std::path::PathBuf; + +const SCRIPT_META_FILE: &str = "script-meta-v2.json"; +const SCRIPT_CONFIG_FILE: &str = "script-config.json"; +const SINGLE_SCRIPTS_DIR: &str = "single-scripts"; + +#[derive(Debug, Deserialize)] +struct ScriptConfig { + file_ext: String, + simple_script_url: String, + project_script_url: String, + script_sub_dir: Option, + skip_dirs: Vec, +} + +impl ScriptConfig { + fn skip_dir(&self, dir: &str) -> bool { + for skip_dir in &self.skip_dirs { + if skip_dir == dir { + return true; + } + } + dir == "update-meta-rs" || dir == SINGLE_SCRIPTS_DIR || dir.starts_with(".") + } +} + +#[derive(Serialize)] +struct ScriptMeta { + script_name: String, + script_length: u64, + script_sha256: String, + script_full_url: String, + #[serde(skip_serializing_if = "Option::is_none")] + single_script_file: Option, +} + +fn main() -> XResult<()> { + if fs::metadata(SCRIPT_CONFIG_FILE).is_err() { + failure_and_exit!("Script config file {} not found.", SCRIPT_CONFIG_FILE); + } + if fs::metadata(SCRIPT_META_FILE).is_err() { + failure_and_exit!("Script meta file {} not found.", SCRIPT_META_FILE); + } + + let script_config_content = opt_result!( + fs::read_to_string(SCRIPT_CONFIG_FILE), + "Read {}, failed: {}", + SCRIPT_CONFIG_FILE + ); + let script_config: ScriptConfig = opt_result!( + serde_json::from_str(&script_config_content), + "Parse {}, failed: {}", + SCRIPT_CONFIG_FILE + ); + debugging!("Script config: {:#?}", script_config); + + let mut script_meta_map = BTreeMap::new(); + + let current_read_dir = opt_result!(fs::read_dir("."), "Read dir '.' failed: {}"); + for dir in current_read_dir { + let dir_entry = opt_result!(dir, "Get dir failed: {}"); + let dir_file_type = opt_result!(dir_entry.file_type(), "Get dir type failed: {}"); + let file_name_os_string = dir_entry.file_name(); + let script_dir = opt_value_result!(file_name_os_string.to_str(), "Cannot get file name."); + if !dir_file_type.is_dir() { + debugging!("Skip none dir: {}", script_dir); + continue; + } + if script_config.skip_dir(&script_dir) { + debugging!("Skip update skip dirs: {}", script_dir); + continue; + } + let abs_dir_entry = std::path::absolute(&dir_entry.path())?; + let mut main_script = abs_dir_entry; + if let Some(sub_dir) = &script_config.script_sub_dir { + main_script = main_script.join(sub_dir); + } + main_script = main_script.join(format!("main.{}", script_config.file_ext)); + + if let Some(script_file_name) = + translate_script_dir_to_script_name(script_dir, &script_config) + { + script_meta_map.insert( + script_file_name.clone(), + read_script_meta( + script_dir, + script_file_name.clone(), + &main_script, + false, + &script_config, + )?, + ); + } + } + + if let Ok(single_script_meta) = fs::metadata(SINGLE_SCRIPTS_DIR) { + if single_script_meta.is_dir() { + let single_scripts_read_dir = + opt_result!(fs::read_dir(SINGLE_SCRIPTS_DIR), "Read dir '.' failed: {}"); + for file in single_scripts_read_dir { + let file_entry = opt_result!(file, "Get dir failed: {}"); + let dir_file_type = opt_result!(file_entry.file_type(), "Get dir type failed: {}"); + let file_name_os_string = file_entry.file_name(); + let script_file = + opt_value_result!(file_name_os_string.to_str(), "Cannot get file name."); + if !dir_file_type.is_file() { + debugging!("Skip none file: {}", script_file); + continue; + } + let abs_file_entry = std::path::absolute(&file_entry.path())?; + let script_file_ext = format!(".{}", script_config.file_ext); + if !script_file.ends_with(&script_file_ext) { + continue; + } + let script_file_name = script_file.to_string(); + if script_meta_map.contains_key(&script_file_name) { + warning!("Script: {script_file_name} exists."); + continue; + } + script_meta_map.insert( + script_file_name.clone(), + read_script_meta( + "", + script_file_name.clone(), + &abs_file_entry, + true, + &script_config, + )?, + ); + } + } + } + + let script_meta_json = serde_json::to_string_pretty(&script_meta_map)?; + + fs::write(SCRIPT_META_FILE, script_meta_json.as_bytes())?; + + success!("Update file: {} succeed.", SCRIPT_META_FILE); + + Ok(()) +} + +// translate filename-ext to filename.ext +fn translate_script_dir_to_script_name( + script_dir: &str, + script_config: &ScriptConfig, +) -> Option { + let script_dir_ext = format!("-{}", script_config.file_ext); + if script_dir.ends_with(&script_dir_ext) { + let remove_ext_dir_name = script_dir + .chars() + .take(script_dir.len() - script_dir_ext.len()) + .collect::(); + Some(format!("{remove_ext_dir_name}.{}", script_config.file_ext)) + } else { + None + } +} + +fn read_script_meta( + script_dir: &str, + script_name: String, + script_path: &PathBuf, + is_simple_script: bool, + script_config: &ScriptConfig, +) -> XResult { + let script_meta = opt_result!( + script_path.metadata(), + "Read file: {:?} meta failed: {}", + script_path + ); + let script_path_content = fs::read(script_path)?; + let script_sha256 = sha256::digest(&script_path_content); + let script_full_url = if is_simple_script { + script_config + .simple_script_url + .replace("$NAME", &script_name) + } else { + script_config + .project_script_url + .replace("$NAME", script_dir) + }; + let single_script_file = iff!(is_simple_script, Some(true), None); + Ok(ScriptMeta { + script_name: script_name.clone(), + script_length: script_meta.len(), + script_sha256, + script_full_url, + single_script_file, + }) +}