summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/Cargo.toml6
-rw-r--r--docs/build.rs196
-rw-r--r--docs/src/docs.rs387
-rw-r--r--docs/src/docs.rs.template26
-rw-r--r--docs/src/lib.rs1
5 files changed, 616 insertions, 0 deletions
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<str>) -> Option<String> {
+ 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<String> {
+ 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<str>) -> Option<String> {
+ 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<String> {
+ 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;