From c5fb22694e95f12c24b8d8af76999be7aea3fcec Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Mon, 12 Jan 2026 04:28:28 +0800 Subject: Reorganize crate structure and move documentation files --- docs/Cargo.toml | 6 + docs/build.rs | 196 +++++++++++++++++++++++ docs/src/docs.rs | 387 ++++++++++++++++++++++++++++++++++++++++++++++ docs/src/docs.rs.template | 26 ++++ docs/src/lib.rs | 1 + 5 files changed, 616 insertions(+) create mode 100644 docs/Cargo.toml create mode 100644 docs/build.rs create mode 100644 docs/src/docs.rs create mode 100644 docs/src/docs.rs.template create mode 100644 docs/src/lib.rs (limited to 'docs') diff --git a/docs/Cargo.toml b/docs/Cargo.toml new file mode 100644 index 0000000..285b83d --- /dev/null +++ b/docs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "vcs_docs" +edition = "2024" +version.workspace = true + +[dependencies] diff --git a/docs/build.rs b/docs/build.rs new file mode 100644 index 0000000..53679db --- /dev/null +++ b/docs/build.rs @@ -0,0 +1,196 @@ +use std::env; +use std::fs; +use std::io::{self, Write}; +use std::path::Path; + +// Template markers for code generation +const TEMPLATE_DOCUMENT_BEGIN: &str = "--- TEMPLATE DOCUMENT BEGIN ---"; +const TEMPLATE_DOCUMENT_END: &str = "--- TEMPLATE DOCUMENT END ---"; +const TEMPLATE_FUNC_BEGIN: &str = "--- TEMPLATE FUNC BEGIN ---"; +const TEMPLATE_FUNC_END: &str = "--- TEMPLATE FUNC END ---"; +const TEMPLATE_LIST_BEGIN: &str = "--- TEMPLATE LIST BEGIN ---"; +const TEMPLATE_LIST_END: &str = "--- TEMPLATE LIST END ---"; + +// Template parameter patterns for substitution +const PARAM_DOCUMENT_PATH: &str = "{{DOCUMENT_PATH}}"; +const PARAM_DOCUMENT_CONSTANT_NAME: &str = "{{DOCUMENT_CONSTANT_NAME}}"; +const PARAM_DOCUMENT_CONTENT: &str = "{{DOCUMENT_CONTENT}}"; +const PARAM_DOCUMENT_PATH_SNAKE_CASE: &str = "{{DOCUMENT_PATH_SNAKE_CASE}}"; + +fn main() -> io::Result<()> { + println!("cargo:rerun-if-changed=src/docs.rs.template"); + println!("cargo:rerun-if-changed=Documents"); + + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("docs.rs"); + + // Read all markdown files from docs directory recursively + let docs_dir = Path::new("./Documents"); + let mut documents = Vec::new(); + + if docs_dir.exists() { + collect_text_files(docs_dir, &mut documents)?; + } + + // Read template file + let template_path = Path::new("src/docs.rs.template"); + let template_content = fs::read_to_string(template_path)?; + + // Extract template sections preserving original indentation + let document_template = template_content + .split(TEMPLATE_DOCUMENT_BEGIN) + .nth(1) + .and_then(|s| s.split(TEMPLATE_DOCUMENT_END).next()) + .unwrap_or("") + .trim_start_matches('\n') + .trim_end_matches('\n'); + + let match_arm_template = template_content + .split(TEMPLATE_FUNC_BEGIN) + .nth(1) + .and_then(|s| s.split(TEMPLATE_FUNC_END).next()) + .unwrap_or("") + .trim_start_matches('\n') + .trim_end_matches('\n'); + + // Generate document blocks and match arms + let mut document_blocks = String::new(); + let mut match_arms = String::new(); + let mut list_items = String::new(); + + for (relative_path, content) in &documents { + // Calculate parameters for template substitution + let document_path = format!("./docs/Documents/{}", relative_path); + + // Generate constant name from relative path + let document_constant_name = relative_path + .replace(['/', '\\', '-'], "_") + .replace(".md", "") + .replace(".txt", "") + .replace(".toml", "") + .replace(".yaml", "") + .replace(".yml", "") + .replace(".json", "") + .replace(".rs", "") + .to_uppercase(); + + // Generate snake_case name for function matching + let document_path_snake_case = relative_path + .replace(['/', '\\', '-'], "_") + .replace(".md", "") + .replace(".txt", "") + .replace(".toml", "") + .replace(".yaml", "") + .replace(".yml", "") + .replace(".json", "") + .replace(".rs", "") + .to_lowercase(); + + // Escape double quotes in content + let escaped_content = content.trim().replace('\"', "\\\""); + + // Replace template parameters in document block preserving indentation + let document_block = document_template + .replace(PARAM_DOCUMENT_PATH, &document_path) + .replace(PARAM_DOCUMENT_CONSTANT_NAME, &document_constant_name) + .replace(PARAM_DOCUMENT_CONTENT, &escaped_content) + .replace("r#\"\"#", &format!("r#\"{}\"#", escaped_content)); + + document_blocks.push_str(&document_block); + document_blocks.push_str("\n\n"); + + // Replace template parameters in match arm preserving indentation + let match_arm = match_arm_template + .replace(PARAM_DOCUMENT_PATH_SNAKE_CASE, &document_path_snake_case) + .replace(PARAM_DOCUMENT_CONSTANT_NAME, &document_constant_name); + + match_arms.push_str(&match_arm); + match_arms.push('\n'); + + // Generate list item for documents() function + let list_item = format!(" \"{}\".to_string(),", document_path_snake_case); + list_items.push_str(&list_item); + list_items.push('\n'); + } + + // Remove trailing newline from the last list item + if !list_items.is_empty() { + list_items.pop(); + } + + // Build final output by replacing template sections + let mut output = String::new(); + + // Add header before document blocks + if let Some(header) = template_content.split(TEMPLATE_DOCUMENT_BEGIN).next() { + output.push_str(header.trim()); + output.push_str("\n\n"); + } + + // Add document blocks + output.push_str(&document_blocks); + + // Add function section + if let Some(func_section) = template_content.split(TEMPLATE_FUNC_BEGIN).next() + && let Some(rest) = func_section.split(TEMPLATE_DOCUMENT_END).nth(1) + { + output.push_str(rest.trim()); + output.push('\n'); + } + + // Add match arms + output.push_str(&match_arms); + + // Add list items for documents() function + if let Some(list_section) = template_content.split(TEMPLATE_LIST_BEGIN).next() + && let Some(rest) = list_section.split(TEMPLATE_FUNC_END).nth(1) + { + output.push_str(rest.trim()); + output.push('\n'); + } + output.push_str(&list_items); + + // Add footer + if let Some(footer) = template_content.split(TEMPLATE_LIST_END).nth(1) { + // Preserve original indentation in footer + output.push_str(footer); + } + + // Write generated file + let mut file = fs::File::create(&dest_path)?; + file.write_all(output.as_bytes())?; + + // Copy to src directory for development + let src_dest_path = Path::new("src/docs.rs"); + fs::write(src_dest_path, output)?; + + Ok(()) +} + +fn collect_text_files(dir: &Path, documents: &mut Vec<(String, String)>) -> io::Result<()> { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + collect_text_files(&path, documents)?; + } else if path.extension().is_some_and(|ext| { + ext == "md" + || ext == "txt" + || ext == "toml" + || ext == "yaml" + || ext == "yml" + || ext == "json" + || ext == "rs" + }) && let Ok(relative_path) = path.strip_prefix("./Documents") + && let Some(relative_path_str) = relative_path.to_str() + { + let content = fs::read_to_string(&path)?; + documents.push(( + relative_path_str.trim_start_matches('/').to_string(), + content, + )); + } + } + Ok(()) +} diff --git a/docs/src/docs.rs b/docs/src/docs.rs new file mode 100644 index 0000000..c12b737 --- /dev/null +++ b/docs/src/docs.rs @@ -0,0 +1,387 @@ +// Auto-generated code. + + +/// From ./docs/Documents/ASCII_YIZI.txt +pub const ASCII_YIZI: &str = "#BANNER START# + ████████ ████████ +██▒▒▒▒▒▒▒▒██ ██▒▒▒▒▒▒▒▒██ +██ ▒▒██ ██▒▒ ██ █████ ██ ██ ██████ █████ +██ ▒▒████████▒▒ ██ ▒▒▒██ ██ ██ ██████ ██████ +██ ▒▒▒▒▒▒▒▒ ██ ██ ██ ██ ███▒▒▒█ █▒▒▒▒█ +██ ██ ██ ██ ██ ███ ▒ ████ ▒ +██ ██ ██ ██ ██ ███ ▒████ +██ ████ ████ ██ ██ ▒██ ██▒ ███ ▒▒▒██ +██ ████ ████ ██ █ ██ ██ ██ ███ █ ██ ██ +██ ████ ████ ██ █ ██ ▒████▒ ▒██████ ██████ +██ ▒▒▒▒ ▒▒▒▒ █ ██ ▒████ ▒██▒ ██████ ▒████▒ +██ ██ ██ ▒▒▒▒ ▒▒ ▒▒▒▒▒▒ ▒▒▒▒ +██ ██████████ ██ +██ ██ {banner_line_1} + ████████████████████████████████ {banner_line_2} + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ {banner_line_3} +#BANNER END#"; + + + +/// From ./docs/Documents/docs\collaboration.txt +pub const DOCS_COLLABORATION: &str = "NO CONTENT YET :("; + + + +/// From ./docs/Documents/docs\get_started.txt +pub const DOCS_GET_STARTED: &str = "NO CONTENT YET :("; + + + +/// From ./docs/Documents/profiles\vault.toml +pub const PROFILES_VAULT: &str = "# +# +# ████████ ████████ +# ██▒▒▒▒▒▒▒▒██ ██▒▒▒▒▒▒▒▒██ +# ██ ▒▒██ ██▒▒ ██ █████ ██ ██ ██ ██ +# ██ ▒▒████████▒▒ ██ ▒▒▒██ ██ ██ ██ ██ +# ██ ▒▒▒▒▒▒▒▒ ██ ██ ██ ██ ██ ██ +# ██ ██ ██ ██ ██ ██ ██ +# ██ ██ ██ ██ ██ ██ ██ +# ██ ████ ████ ██ ██ ▒██ ██▒ ▒██ ██▒ +# ██ ████ ████ ██ █ ██ ██ ██ ██ ██ +# ██ ████ ████ ██ █ ██ ▒████▒ ▒████▒ +# ██ ▒▒▒▒ ▒▒▒▒ █ ██ ▒████ ▒██▒ ▒██▒ +# ██ ██ ██ ▒▒▒▒ ▒▒ ▒▒ +# ██ ██████████ ██ +# ██ ██ +# ████████████████████████████████ JustEnoughVCS Vault Profile +# ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ =========================== +# +# + +# Vault \"{vault_name}\" created by {user_name} ({date_format}) + +######################### +### Base Informations ### +######################### +name = \"{vault_name}\" +uuid = \"{vault_uuid}\" # Don't touch it! + +# Administrator list, indicating members who can switch identity to 'host' +hosts = [] + +[profile] + +#################### +### Connectivity ### +#################### + +# Bind address: 127.0.0.1 for localhost only, 0.0.0.0 to allow remote access +bind = \"127.0.0.1\" # (fallback: \"127.0.0.1\") + +# Bind port +port = 25331 # (fallback: 25331) + +# Enable LAN discovery: clients can discover this service on the local network +# TIP: Not yet supported +lan_discovery = \"disable\" # (enable, disable, fallback: disable) + +############# +### Debug ### +############# + +# Enable logger +logger = \"yes\" # (yes, no, fallback: no) + +# Logger output level +logger_level = \"info\" # (debug, trace, info, fallback: info) + +################################# +### Authentication & Security ### +################################# + +# Authentication mode +# TIP: Currently only \"key\" is supported +auth_mode = \"key\" # (key, password, noauth, fallback: password)"; + + + +/// From ./docs/Documents/README.md +pub const README: &str = "# JustEnoughVCS"; + + + +/// From ./docs/Documents/readmes\local_workspace_todolist.md +pub const READMES_LOCAL_WORKSPACE_TODOLIST: &str = "# Setup a Workspace + +You have created an empty local **Workspace** using `jv create` or `jv init`. + +At this point, the workspace exists only as a local structure. +It is **not yet associated with any identity or upstream Vault**. + +Follow the steps below to complete the initial setup. + +## Account Setup + +> [!TIP] +> If you have already registered an account on this machine, you can skip this section. + +An account represents **this machine acting on your behalf**. +You can create an account and generate a private key using the following commands. + +```bash +# Make sure OpenSSL is installed on your system. +# Create an account and automatically generate a private key using OpenSSL. +jv account add pan --keygen + +# If you already have a private key, you can associate it with an account. +jv account movekey pan ./your_private_key.pem +``` + +After creating the private key, generate the corresponding public key. +This also requires `OpenSSL` support. + +```bash +# Generate the public key in the current directory. +jv account genpub pan ./ +``` + +**Send the generated public key file to a Host of the upstream Vault.** +Only after the key is registered by the Vault can this workspace authenticate. + +## Login + +> In the following example, we assume: +> +> * the account name is `pan` +> * the upstream Vault address is `127.0.0.1` + +Navigate to the workspace directory (the directory containing this file), then run: + +```bash +# Log in as pan to the upstream Vault at 127.0.0.1 +# -C skips confirmation prompts +jv login pan 127.0.0.1 -C +``` + +This command performs the following steps internally: + +```bash +jv account as pan # Bind the workspace to account pan +jv direct 127.0.0.1 -C # Set the upstream Vault address +jv update # Fetch initial structure and metadata from upstream +rm SETUP.md # Remove this setup document +``` + +After login, the workspace becomes a **live participant** connected to the upstream Vault. + +## Completion + +At this point, the workspace is fully set up: + +* An account identity is bound locally +* An upstream Vault is configured +* Initial data has been synchronized + +Normally, `jv login` removes this file automatically. +If the file still exists due to a deletion failure, please remove it manually to keep the workspace clean. + +Once this file is gone, the workspace is considered **ready for daily use**. + +## Why does this file delete itself? + +A freshly created JVCS workspace is considered **clean** only when no sheets are in use and no unexplained files exist. + +During the initial setup phase, this `SETUP.md` file is the only allowed exception. +It exists solely to guide the workspace into a connected and explainable state. + +Once the workspace is connected to an upstream Vault and enters normal operation, +every file in the workspace is expected to be **explainable by structure**. + +If this setup file remains: + +* it becomes an unexplained local file +* `JustEnoughVCS` cannot determine which sheet it belongs to +* and any subsequent `jv use` operation would be ambiguous + +To prevent this ambiguity, JVCS enforces a strict rule: + +**A workspace with unexplained files is not allowed to enter active use.** + +Deleting `SETUP.md` marks the end of the setup phase and confirms that: + +* all remaining files are intentional +* their placement can be explained +* and the workspace is ready to participate in structure-driven operations + +This is why the setup document removes itself. +It is not cleanup — it is a boundary."; + + + +/// From ./docs/Documents/readmes\vault_readme.md +pub const READMES_VAULT_README: &str = "# Setup an Upstream Vault + +Thank you for using `JustEnoughVCS`. + +This document guides you through setting up an **Upstream Vault** — a shared source of structure and content that other workspaces may depend on. + +## Configuration File + +Before starting the Vault service with `jvv listen`, you need to configure `vault.toml`. + +This file defines the identity, connectivity, and responsibility boundaries of the Vault. + +### Adding Hosts + +Set the `hosts` parameter to specify which members are allowed to switch into the **Host** role for this Vault. + +```toml +# Pan and Alice are allowed to operate this Vault as hosts +hosts = [ \"pan\", \"alice\" ] +``` + +Members listed here can switch their identity as follows: + +```bash +jv as host/pan +``` + +> Becoming a Host means operating from a position that may affect other members. +> This role is not a permanent identity, but a responsibility-bearing context. + +### Configuring Connection + +Modify the `bind` parameter to `0.0.0.0` to allow access from other devices on the local network. + +```toml +bind = \"0.0.0.0\" +``` + +> [!TIP] +> `JustEnoughVCS` uses port **25331** by default. +> You can change the listening port by modifying the `port` parameter if needed. + +### Disabling Logger (Optional) + +If you prefer a cleaner console output, you can disable logging: + +```toml +logger = \"no\" +``` + +## Member Account Setup + +Run the following command in the root directory of the **Vault** to register a member: + +```bash +jvv member register pan +``` + +Registering a member only creates its identity record in the Vault. +It does **not** automatically make the member login-ready. + +An authentication method must be registered separately. + +> [!NOTE] +> Currently, `JustEnoughVCS` supports **key-based authentication** only. + +Place the public key file provided by the member (`name.pem`) into the Vault’s `./key` directory. + +For example, after registering a member named `pan`, the Vault directory structure should look like this: + +``` +. +├── key +│ └── pan.pem <- Public key file +├── members +│ ├── pan.bcfg <- Member registration info +│ └── host.bcfg +├── README.md +├── sheets +│ └── ref.bcfg +├── storage +└── vault.toml +``` + +## Completion + +At this point, the Vault is fully configured: + +* Member identities are registered +* Host roles are defined +* Authentication materials are in place + +You can now start the Vault service using: + +```bash +jvv listen +``` + +Other workspaces may connect to this Vault once it is listening."; + + + +/// From ./docs/Documents/web_docs\guide.md +pub const WEB_DOCS_GUIDE: &str = "# FIRST + +ok"; + + + +/// From ./docs/Documents/_navbar.md +pub const _NAVBAR: &str = "* [Home](/) +* [GitHub](https://github.com/JustEnoughVCS/) +* [Main Repo](https://github.com/JustEnoughVCS/VersionControl)"; + + + +/// From ./docs/Documents/_sidebar.md +pub const _SIDEBAR: &str = "* Getting Started + * [Introduction](README.md) + +* USER + * [Workspace Setup](web_docs/workspace_setup.md) + +* ADMIN + * [Vault Setup](web_docs/vault_setup.md)"; + + +// Get document content by name +pub fn document(name: impl AsRef) -> Option { + match name.as_ref() { + + "ascii_yizi" => Some(ASCII_YIZI.to_string()), + + "docs_collaboration" => Some(DOCS_COLLABORATION.to_string()), + + "docs_get_started" => Some(DOCS_GET_STARTED.to_string()), + + "profiles_vault" => Some(PROFILES_VAULT.to_string()), + + "readme" => Some(README.to_string()), + + "readmes_local_workspace_todolist" => Some(READMES_LOCAL_WORKSPACE_TODOLIST.to_string()), + + "readmes_vault_readme" => Some(READMES_VAULT_README.to_string()), + + "web_docs_guide" => Some(WEB_DOCS_GUIDE.to_string()), + + "_navbar" => Some(_NAVBAR.to_string()), + + "_sidebar" => Some(_SIDEBAR.to_string()), +_ => None, + } +} + +// Get list of all available document names +pub fn documents() -> Vec { + vec![ + "ascii_yizi".to_string(), + "docs_collaboration".to_string(), + "docs_get_started".to_string(), + "profiles_vault".to_string(), + "readme".to_string(), + "readmes_local_workspace_todolist".to_string(), + "readmes_vault_readme".to_string(), + "web_docs_guide".to_string(), + "_navbar".to_string(), + "_sidebar".to_string(), + ] +} diff --git a/docs/src/docs.rs.template b/docs/src/docs.rs.template new file mode 100644 index 0000000..c6787d9 --- /dev/null +++ b/docs/src/docs.rs.template @@ -0,0 +1,26 @@ +// Auto-generated code. + +--- TEMPLATE DOCUMENT BEGIN --- +/// From {{DOCUMENT_PATH}} +pub const {{DOCUMENT_CONSTANT_NAME}}: &str = "{{DOCUMENT_CONTENT}}"; + +--- TEMPLATE DOCUMENT END --- + +// Get document content by name +pub fn document(name: impl AsRef) -> Option { + match name.as_ref() { +--- TEMPLATE FUNC BEGIN --- + "{{DOCUMENT_PATH_SNAKE_CASE}}" => Some({{DOCUMENT_CONSTANT_NAME}}.to_string()), +--- TEMPLATE FUNC END --- + _ => None, + } +} + +// Get list of all available document names +pub fn documents() -> Vec { + vec![ +--- TEMPLATE LIST BEGIN --- + "{{DOCUMENT_PATH_SNAKE_CASE}}".to_string(), +--- TEMPLATE LIST END --- + ] +} diff --git a/docs/src/lib.rs b/docs/src/lib.rs new file mode 100644 index 0000000..ca422a9 --- /dev/null +++ b/docs/src/lib.rs @@ -0,0 +1 @@ +pub mod docs; -- cgit