Welcome to the first chapter of our journey to build a robust, production-grade Mermaid code analyzer and fixer in Rust! In this chapter, we’ll lay the essential groundwork for our project, which we’ll affectionately call mermtool. Just as a sturdy foundation is crucial for any building, a well-structured and properly configured project is vital for a reliable and maintainable software tool.
This chapter focuses on setting up your development environment, initializing the Rust project, defining its initial structure, and configuring core aspects like dependency management, basic CLI argument parsing, logging, and build optimizations. By the end of this chapter, you’ll have a runnable Rust CLI application that can parse basic command-line arguments and is ready for the exciting work of parsing Mermaid diagrams. We’ll emphasize production-ready practices from the outset, including robust error handling, structured logging, and release build optimizations, ensuring our mermtool is reliable and performant from day one.
Planning & Design
Before we dive into code, let’s outline the initial project structure and the high-level steps for setting up our environment. Our goal is to create a modular and scalable codebase that adheres to Rust’s idiomatic practices.
Project Architecture Overview (Initial Setup)
At this stage, our mermtool is primarily concerned with establishing the development environment and a basic command-line interface. The following diagram illustrates the initial components and their interactions:
Initial File Structure
We’ll start with a standard Rust project layout and incrementally add modules as we build out features.
mermtool/
├── .cargo/
│ └── config.toml # Cargo build configurations (e.g., LTO)
├── src/
│ ├── main.rs # Main entry point, orchestrates CLI
│ ├── lib.rs # Main library crate (eventually holds core logic)
│ ├── cli.rs # CLI argument parsing logic
│ └── error.rs # Custom error types and handling
├── build.rs # Build script for embedding version info
├── Cargo.toml # Project dependencies and metadata
├── README.md # Project description and usage
├── CONTRIBUTING.md # Contribution guidelines
├── LICENSE # Project license
└── .gitignore # Git ignore rules
This structure provides a clean separation of concerns: main.rs for orchestration, cli.rs for argument parsing, error.rs for centralized error management, and lib.rs as the home for our core logic. The .cargo/config.toml and build.rs files are crucial for production-grade builds and debugging.
Step-by-Step Implementation
Let’s get our hands dirty and start building mermtool.
1. Install Rust Toolchain
If you don’t have Rust installed, rustup is the recommended way to manage Rust toolchains.
Action: Open your terminal and run the following command:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Follow the on-screen instructions. It’s usually safe to proceed with the default installation.
Explanation: rustup is the official Rust toolchain installer. It allows you to easily install, manage, and update different Rust versions and components (like cargo, rustc, rustfmt, clippy). Installing it ensures you have the latest stable Rust compiler and cargo (Rust’s build system and package manager).
Verification: After installation, restart your terminal or source your shell’s environment file (e.g., source $HOME/.cargo/env) and verify the installation:
rustc --version
cargo --version
You should see output similar to:
rustc 1.XX.X (YYYY-MM-DD)
cargo 1.XX.X (YYYY-MM-DD)
(The exact versions will vary but should be recent as of 2026-03-17)
2. Create a New Rust Project
Now, let’s create our mermtool project.
Action: Navigate to your development directory and run:
cargo new mermtool --bin
cd mermtool
Explanation:
cargo new mermtool: This command initializes a new Rust project namedmermtool.--bin: Specifies that this is an executable application (binary crate), not a library. This creates asrc/main.rsfile. If omitted,cargo newdefaults to a library.cd mermtool: Changes your current directory into the newly created project folder.
Verification: You should now have a mermtool directory with a Cargo.toml file and a src/main.rs file containing a “Hello, world!” program. You can run it:
cargo run
Expected output: Hello, world!
3. Configure Cargo.toml and Add Initial Dependencies
Cargo.toml is the manifest file for our Rust project. It defines metadata, dependencies, and build configurations. We’ll add essential crates for robust CLI development: clap for argument parsing, anyhow for simplified error handling, and env_logger for flexible logging.
Action: Open Cargo.toml and replace its content with the following:
# mermtool/Cargo.toml
[package]
name = "mermtool"
version = "0.1.0"
authors = ["Your Name <[email protected]>"]
edition = "2021"
description = "A strict, production-grade Mermaid code analyzer and fixer."
license = "MIT"
repository = "https://github.com/your-username/mermtool" # Replace with your repository
keywords = ["mermaid", "cli", "linter", "formatter", "compiler", "rust"]
categories = ["command-line-utilities", "development-tools", "parsing"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.5", features = ["derive", "env", "cargo"] } # CLI argument parsing
anyhow = "1.0" # Flexible error handling
env_logger = "0.11" # Environment-configurable logging
log = "0.4" # Logging facade
# Placeholder for future dependencies
# regex = "1.0"
# ariadne = "0.3" # For rich diagnostics
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] } # Benchmarking
tempfile = "3.10" # Temporary file creation for tests
[[bench]]
name = "my_benchmark"
harness = false
Explanation:
[package]: Contains metadata about our crate. Updateauthorsandrepository.description,license,keywords, andcategoriesare crucial for discoverability and documentation.[dependencies]:clap = { version = "4.5", features = ["derive", "env", "cargo"] }:clapis the de-facto standard for building robust command-line interfaces in Rust.deriveenables procedural macros for easy argument definition,envallows arguments to be configured via environment variables, andcargois useful for integrating withcargosubcommands (though we won’t use that immediately).anyhow = "1.0": A fantastic crate for ergonomic error handling. It allows returningResult<T, anyhow::Error>without defining specific error enums for every possible failure, simplifying error propagation across functions. This is ideal for application-level errors.env_logger = "0.11": Provides flexible logging configured via environment variables (e.g.,RUST_LOG=info).log = "0.4": This is the logging facade crate.env_loggeris an implementation of this facade. By depending onlog, our code remains decoupled from the specific logging backend.
[dev-dependencies]: Dependencies only used during development or testing, likecriterionfor benchmarking andtempfilefor creating temporary files in tests.[[bench]]: Configures benchmarking withcriterion. We’ll explore this in later chapters.
4. Implement Basic CLI Structure with clap
Now, let’s set up the core CLI logic. We’ll extract argument parsing into its own module (src/cli.rs) and use anyhow for top-level error handling in main.rs.
Action:
First, create src/cli.rs:
// mermtool/src/cli.rs
use clap::{Parser, Subcommand};
use std::path::PathBuf;
/// A strict, production-grade Mermaid code analyzer and fixer.
///
/// This tool provides linting, formatting, and validation for Mermaid diagrams,
/// ensuring strict compliance with syntax specifications.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct MermtoolArgs {
/// Increase logging verbosity (e.g., -v, -vv, -vvv)
#[arg(short, long, action = clap::ArgAction::Count)]
pub verbose: u8,
/// Subcommand to execute
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Validates Mermaid code for syntax and semantic errors.
Validate {
/// Path to the Mermaid file to validate.
/// If not provided, reads from stdin.
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
/// Exit with a non-zero status code on warnings.
#[arg(long)]
warnings_as_errors: bool,
},
/// Formats Mermaid code according to strict rules.
Format {
/// Path to the Mermaid file to format.
/// If not provided, reads from stdin.
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
/// Overwrite the input file with the formatted content.
#[arg(short, long)]
in_place: bool,
},
/// Lints Mermaid code and reports potential issues.
Lint {
/// Path to the Mermaid file to lint.
/// If not provided, reads from stdin.
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
/// Apply automatic fixes where possible (implies --in-place if file is provided).
#[arg(short, long)]
fix: bool,
},
}
/// Initializes logging based on verbose level.
pub fn setup_logging(verbose: u8) {
let log_level = match verbose {
0 => "warn",
1 => "info",
2 => "debug",
_ => "trace",
};
// Initialize env_logger. If RUST_LOG is set, it takes precedence.
// Otherwise, use our verbose level.
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level))
.format_timestamp_millis()
.init();
log::debug!("Logging initialized at level: {}", log_level);
}
Next, create src/error.rs for initial error handling structure:
// mermtool/src/error.rs
use thiserror::Error; // We'll introduce thiserror in a later chapter for structured errors
/// Custom error type for `mermtool`.
/// For now, we'll primarily rely on `anyhow::Error` for application-level errors,
/// but this placeholder demonstrates where specific errors would live.
#[derive(Debug, Error)]
pub enum MermtoolError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("File not found: {0}")]
FileNotFound(PathBuf),
#[error("Mermaid parsing error: {0}")]
ParseError(String),
#[error("Validation error: {0}")]
ValidationError(String),
// Add more specific error types as needed
}
use std::path::PathBuf;
/// Helper function to create a FileNotFound error.
pub fn file_not_found_error(path: &PathBuf) -> MermtoolError {
MermtoolError::FileNotFound(path.clone())
}
Now, modify src/main.rs:
// mermtool/src/main.rs
mod cli;
mod error;
use anyhow::{Context, Result};
use clap::Parser;
use log::{debug, error, info, warn};
use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;
/// Main entry point for the mermtool CLI application.
#[tokio::main] // We'll add tokio later for async operations, but keep it here for future-proofing
async fn main() -> Result<()> {
// Parse command-line arguments
let args = cli::MermtoolArgs::parse();
// Setup logging based on verbosity level
cli::setup_logging(args.verbose);
info!("mermtool started (version: {})", env!("CARGO_PKG_VERSION"));
debug!("Parsed arguments: {:?}", args);
// Execute the appropriate subcommand
match &args.command {
cli::Commands::Validate { file, warnings_as_errors } => {
debug!("Executing 'validate' command.");
let mermaid_code = read_input(file).context("Failed to read Mermaid input")?;
validate_mermaid_code(&mermaid_code, *warnings_as_errors)?;
}
cli::Commands::Format { file, in_place } => {
debug!("Executing 'format' command.");
let mermaid_code = read_input(file).context("Failed to read Mermaid input")?;
format_mermaid_code(&mermaid_code, file.as_ref(), *in_place)?;
}
cli::Commands::Lint { file, fix } => {
debug!("Executing 'lint' command.");
let mermaid_code = read_input(file).context("Failed to read Mermaid input")?;
lint_mermaid_code(&mermaid_code, file.as_ref(), *fix)?;
}
}
info!("mermtool finished successfully.");
Ok(())
}
/// Reads Mermaid code from a file or stdin.
fn read_input(file_path: &Option<PathBuf>) -> Result<String> {
match file_path {
Some(path) => {
debug!("Reading Mermaid code from file: {:?}", path);
fs::read_to_string(path).with_context(|| format!("Could not read file: {:?}", path))
}
None => {
debug!("Reading Mermaid code from stdin.");
let mut buffer = String::new();
io::stdin()
.read_to_string(&mut buffer)
.context("Could not read from stdin")?;
Ok(buffer)
}
}
}
/// Placeholder for the validation logic.
fn validate_mermaid_code(code: &str, warnings_as_errors: bool) -> Result<()> {
info!("Validating Mermaid code (warnings_as_errors: {}).", warnings_as_errors);
debug!("Mermaid code to validate:\n{}", code);
// TODO: Implement actual lexing, parsing, and validation here
warn!("Validation logic not yet implemented. This is a placeholder.");
// Simulate a potential validation error for demonstration
if code.contains("syntax error") {
error!("Detected simulated syntax error in code.");
return Err(anyhow::anyhow!("Mermaid syntax error detected (simulated)").into());
}
Ok(())
}
/// Placeholder for the formatting logic.
fn format_mermaid_code(code: &str, file_path: Option<&PathBuf>, in_place: bool) -> Result<()> {
info!("Formatting Mermaid code (in_place: {}).", in_place);
debug!("Mermaid code to format:\n{}", code);
// TODO: Implement actual formatting here
warn!("Formatting logic not yet implemented. This is a placeholder.");
let formatted_code = format!("-- Formatted by mermtool --\n{}", code);
if in_place {
if let Some(path) = file_path {
info!("Writing formatted code back to file: {:?}", path);
fs::write(path, formatted_code)
.with_context(|| format!("Failed to write formatted code to file: {:?}", path))?;
} else {
warn!("--in-place flag ignored when reading from stdin.");
println!("{}", formatted_code);
}
} else {
println!("{}", formatted_code);
}
Ok(())
}
/// Placeholder for the linting logic.
fn lint_mermaid_code(code: &str, file_path: Option<&PathBuf>, fix: bool) -> Result<()> {
info!("Linting Mermaid code (fix: {}).", fix);
debug!("Mermaid code to lint:\n{}", code);
// TODO: Implement actual linting and fixing here
warn!("Linting logic not yet implemented. This is a placeholder.");
if code.contains("bad practice") && fix {
info!("Applying simulated fix.");
let fixed_code = code.replace("bad practice", "good practice");
if let Some(path) = file_path {
info!("Writing fixed code back to file: {:?}", path);
fs::write(path, fixed_code)
.with_context(|| format!("Failed to write fixed code to file: {:?}", path))?;
} else {
println!("-- Fixed content --\n{}", fixed_code);
}
} else {
warn!("No issues found or fix not requested.");
}
Ok(())
}
Note: We’ve added #[tokio::main] and async to main as a forward-looking step. While not strictly necessary for this chapter, it anticipates potential asynchronous operations (e.g., network requests for external Mermaid definitions, or advanced I/O) in a production-grade tool. For now, it won’t affect synchronous code.
Explanation:
src/cli.rs:MermtoolArgs: This struct defines our top-level CLI arguments usingclap’sderivemacro. It includes averboseflag for logging level control and acommandfield for subcommands.Commandsenum: Defines our main subcommands (validate,format,lint), each with its own arguments. This makes our CLI structured and user-friendly.setup_logging: A utility function to initializeenv_loggerbased on theverboseflag. It allows users to control log output from the CLI or via theRUST_LOGenvironment variable.
src/error.rs:MermtoolErrorenum: A placeholder for our custom error types. Whileanyhowis great for simplifying error propagation, for specific, recoverable, or user-facing errors, defining custom error types withthiserror(which we’ll introduce fully later) provides more context and structure. For now, it shows where these would live.
src/main.rs:mod cli; mod error;: Declares our new modules.use anyhow::{Context, Result};: Importsanyhow’sResulttype, which isResult<T, anyhow::Error>. TheContexttrait allows us to add descriptive messages to errors as they propagate.use clap::Parser;: Imports theParsertrait forMermtoolArgs.use log::{debug, error, info, warn};: Imports logging macros.mainfunction:- Parses arguments using
MermtoolArgs::parse(). - Initializes logging.
- Uses a
matchstatement to dispatch to different functions based on the chosen subcommand. read_input: A helper to abstract reading from a file orstdin, a common CLI pattern.validate_mermaid_code,format_mermaid_code,lint_mermaid_code: These are placeholder functions for now. They demonstrate how the CLI will interact with the core logic. They include basic logging and simulated error handling to show theanyhowflow.
- Parses arguments using
5. Implement build.rs for Version Information
For production-ready tools, it’s invaluable to embed build-time information (like version, git commit hash, build date) directly into the executable. This helps tremendously with debugging deployed binaries.
Action: Create a new file build.rs in the project root (mermtool/build.rs):
// mermtool/build.rs
fn main() {
// Tell Cargo that if the "src/main.rs" file changes, rerun this build script.
println!("cargo:rerun-if-changed=src/main.rs");
// Embed the git commit hash if available
if let Ok(output) = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
{
let git_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !git_hash.is_empty() {
println!("cargo:rustc-env=MERMTOOL_GIT_HASH={}", git_hash);
eprintln!("Build: Embedded Git Hash: {}", git_hash);
} else {
eprintln!("Build: Git hash not available.");
}
} else {
eprintln!("Build: Git command not found or failed. Git hash not embedded.");
}
// Embed the build date and time
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string();
println!("cargo:rustc-env=MERMTOOL_BUILD_DATE={}", now);
eprintln!("Build: Embedded Build Date: {}", now);
}
Explanation:
build.rsis a special Rust script that Cargo runs before compiling your main crate.println!("cargo:rerun-if-changed=src/main.rs");: This tells Cargo to re-runbuild.rsifsrc/main.rschanges.println!("cargo:rustc-env=KEY=VALUE");: This mechanism allowsbuild.rsto set environment variables that are available at compile-time to your main crate.- We attempt to get the current Git commit hash using
git rev-parse HEAD. - We embed the current UTC date and time.
- These embedded values can then be accessed in
main.rsusingenv!("MERMTOOL_GIT_HASH")andenv!("MERMTOOL_BUILD_DATE"). - We’ll need to add
chronoas abuild-dependencyfor thebuild.rsscript.
Action: Update Cargo.toml to add chrono as a build-dependency:
# mermtool/Cargo.toml
# ... (previous content) ...
[build-dependencies]
chrono = "0.4"
Explanation: chrono is a powerful date and time library for Rust. We add it as a build-dependency because it’s only needed by build.rs, not by the main mermtool application itself.
6. Configure Build Optimizations
For production deployments, we want our binary to be as small and fast as possible. Cargo’s release profile provides some optimizations by default, but we can enhance them.
Action: Create a new directory .cargo in your project root and a file config.toml inside it (mermtool/.cargo/config.toml):
# mermtool/.cargo/config.toml
[profile.release]
lto = "fat" # Enable Link Time Optimizations for maximum performance
codegen-units = 1 # Reduce codegen units for better optimization, potentially slower compile
panic = "abort" # Abort on panic in release builds for smaller binaries and security
strip = true # Strip debug symbols from the binary
Explanation:
[profile.release]: This section applies configurations specifically when building with--release.lto = "fat": Link Time Optimizations. This allows the linker to perform whole-program optimizations, often resulting in smaller and faster binaries.fatis the most aggressive option.codegen-units = 1: Reduces the number of code generation units to one. This allows the compiler to perform more extensive inter-procedural optimizations across your entire codebase, improving runtime performance and reducing binary size, but at the cost of longer compile times.panic = "abort": Changes the panic strategy from unwinding (which can be slow and increase binary size) to aborting the process immediately. For CLI tools, this is often acceptable in release builds, as an unhandled panic typically indicates a fatal error. This helps reduce binary size and can prevent certain security vulnerabilities related to unwinding.strip = true: Removes debug symbols from the compiled binary. This significantly reduces the size of the executable, which is desirable for deployment.
7. Add Essential Project Files
Good projects include standard files for documentation, licensing, and version control.
Action:
mermtool/README.md: Create or update with a basic description.# mermtool: Mermaid Code Analyzer and Fixer `mermtool` is a strict, production-grade command-line tool written in Rust for validating, linting, and formatting Mermaid diagrams. It aims to provide a compiler-like experience, ensuring full compliance with official Mermaid syntax specifications. ## Features (Planned) - **Lexer**: Tokenizes Mermaid input. - **Parser**: Builds a strongly typed Abstract Syntax Tree (AST). - **Validator**: Detects syntax and semantic errors with rich diagnostics. - **Rule Engine**: Applies deterministic rules for linting and fixing. - **Formatter**: Ensures consistent Mermaid code style. ## Usage ```bash mermtool --help mermtool validate <file.mmd> mermtool format <file.mmd> --in-place mermtool lint <file.mmd> --fixDevelopment
See
CONTRIBUTING.mdfor development guidelines.License
This project is licensed under the MIT License. See
LICENSEfor details.mermtool/CONTRIBUTING.md: Create with initial guidelines.# Contributing to mermtool We welcome contributions to `mermtool`! Please read this document to understand our development process and guidelines. ## How to Contribute 1. **Fork the repository.** 2. **Clone your fork:** `git clone https://github.com/your-username/mermtool.git` 3. **Create a new branch:** `git checkout -b feature/your-feature-name` or `bugfix/issue-description` 4. **Make your changes:** * Follow Rust best practices and idiomatic patterns. * Write clear, concise, and well-documented code. * Add unit and integration tests for new features and bug fixes. * Ensure all tests pass (`cargo test`). * Format your code (`cargo fmt`). * Lint your code (`cargo clippy`). 5. **Commit your changes:** Write clear and descriptive commit messages. 6. **Push your branch:** `git push origin feature/your-feature-name` 7. **Open a Pull Request (PR):** * Provide a clear description of your changes. * Reference any relevant issues. * Ensure your PR passes all CI checks. ## Code Style We use `rustfmt` for code formatting and `clippy` for linting. Please ensure your code passes these checks before submitting a PR: ```bash cargo fmt --check cargo clippy -- -D warningsTesting
All new features and bug fixes should be accompanied by appropriate tests.
Reporting Issues
Please report any bugs, feature requests, or questions on the GitHub Issues page.
mermtool/LICENSE: Create with the MIT License content.MIT License Copyright (c) [YEAR] [Your Name] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.(Remember to replace
[YEAR]and[Your Name])mermtool/.gitignore: Create or update.# mermtool/.gitignore # Generated by Cargo # # Compiled Object files *.o *.rlib *.so *.dylib *.dll # Executables *.exe *.exp *.lib *.pdb # Build artifacts target/ # Cargo files Cargo.lock Cargo.toml.orig # Editor backup files *~ *.bak *.swp .DS_Store # OS generated files .vscode/ .idea/ .env .env.*
Explanation: These files are standard practice for open-source and professional projects. They provide essential information for users, contributors, and maintainers, promoting clarity and good project hygiene.
Testing This Component
Let’s verify our setup.
Build the project:
cargo buildYou should see output indicating compilation of
clap,anyhow,env_logger,log,chrono(forbuild.rs), and finallymermtool. Thebuild.rsscript will print messages about embedding the Git hash and build date.Run the CLI with help:
cargo run -- --helpYou should see the help message generated by
clap, listing thevalidate,format, andlintsubcommands.A strict, production-grade Mermaid code analyzer and fixer. This tool provides linting, formatting, and validation for Mermaid diagrams, ensuring strict compliance with syntax specifications. Usage: mermtool [OPTIONS] <COMMAND> Commands: validate Validates Mermaid code for syntax and semantic errors. format Formats Mermaid code according to strict rules. lint Lints Mermaid code and reports potential issues. help Print this message or the help of the given subcommand(s) Options: -v, --verbose Increase logging verbosity (e.g., -v, -vv, -vvv) -h, --help Print help -V, --version Print versionTest subcommands (placeholders):
# Test validate with a dummy file echo "graph TD; A-->B; syntax error" > temp.mmd cargo run -- validate temp.mmd # Expected: "ERROR mermtool: Detected simulated syntax error in code." and an error message. # Test format (output to stdout) echo "graph TD; A-->B;" | cargo run -- format # Expected: "-- Formatted by mermtool --" and your input. # Test lint with fix echo "graph TD; A-->B; bad practice" > temp.mmd cargo run -- lint temp.mmd --fix # Expected: "INFO mermtool: Applying simulated fix." and temp.mmd updated. cat temp.mmd # Should show "good practice" rm temp.mmd # Clean upTest logging levels:
# Default (warn level) cargo run -- validate dummy.mmd # Info level cargo run -- -v validate dummy.mmd # Debug level cargo run -- -vv validate dummy.mmd # Trace level (most verbose) cargo run -- -vvv validate dummy.mmd # Using RUST_LOG environment variable RUST_LOG=debug cargo run -- validate dummy.mmdObserve how the amount of log output changes with the verbosity flags or
RUST_LOG.
Production Considerations
- Error Handling: By using
anyhow::Result<()>,maincan gracefully handle errors propagated from any function. The.context()method adds useful information to error messages, making debugging easier in production. For more structured, user-facing errors, we’ll build uponsrc/error.rswiththiserrorin future chapters. - Logging:
env_loggerprovides a flexible and standard way to log messages. In a production environment, you can control the logging level via theRUST_LOGenvironment variable without recompiling the binary, which is essential for troubleshooting. - Configuration:
clapmakes CLI argument parsing robust and self-documenting. It automatically generates--helpmessages, ensuring users can easily understand how to interact withmermtool. - Security: Rust’s memory safety guarantees significantly reduce a class of common vulnerabilities. By configuring
panic = "abort"in release builds, we prevent potential information leaks that could occur during stack unwinding on panic, leading to smaller, more predictable binaries. We also started a.gitignoreto prevent sensitive files from being committed. - Performance: The
lto = "fat",codegen-units = 1, andstrip = truesettings in.cargo/config.tomlare crucial for optimizing the binary for size and speed. When you build withcargo build --release, these optimizations will be applied, producing a highly optimized executable suitable for deployment.
Code Review Checkpoint
At this point, you should have the following files and modifications:
mermtool/Cargo.toml: Updated withclap,anyhow,env_logger,logas dependencies,chronoas abuild-dependency, andcriterion,tempfileasdev-dependencies. Also includes[package]metadata and[[bench]]configuration.mermtool/src/main.rs: The main entry point, usingclapfor argument parsing,env_loggerfor logging, andanyhowfor error handling. It dispatches to placeholder functions forvalidate,format, andlint.mermtool/src/cli.rs: Defines theMermtoolArgsstruct andCommandsenum for CLI argument structure, and thesetup_loggingfunction.mermtool/src/error.rs: A placeholder for custom error types.mermtool/build.rs: A script to embed Git hash and build date into the binary.mermtool/.cargo/config.toml: Configures release build optimizations.mermtool/README.md: Basic project description.mermtool/CONTRIBUTING.md: Initial guidelines for contributors.mermtool/LICENSE: The MIT License file.mermtool/.gitignore: Standard Git ignore rules.
The project is now a well-structured Rust CLI application, ready to be extended with our core logic.
Common Issues & Solutions
rustup: command not foundorcargo: command not found:- Issue: Rust toolchain is not properly installed or its
bindirectory is not in your system’sPATH. - Solution: Re-run the
rustupinstallation command. After installation, ensure you restart your terminal or explicitly source the environment file:source $HOME/.cargo/env. - Prevention: Always follow
rustup’s instructions carefully and restart your shell.
- Issue: Rust toolchain is not properly installed or its
error: failed to run custom build command forchrono v0.4.31``:- Issue: This typically happens if
chronocannot compile, often due to missing C/C++ build tools on your system, aschronomight rely on system libraries for time-related functions. - Solution: Ensure you have a C compiler (like
gccorclang) and its associated build tools installed. On Ubuntu/Debian,sudo apt install build-essential. On macOS, install Xcode Command Line Tools:xcode-select --install. On Windows, installBuild Tools for Visual Studiowith the “Desktop development with C++” workload. - Prevention: Always ensure your development environment has common build prerequisites installed.
- Issue: This typically happens if
error: unknown fieldlong_aboutin structMermtoolArgs``:- Issue: This indicates a version mismatch with
clap. Thelong_aboutattribute was introduced in a specific version ofclap(or renamed). - Solution: Double-check your
Cargo.tomlto ensureclap = { version = "4.5", ... }matches the version used in the guide. If you’re on an older Rust toolchain orclapversion, you might encounter such issues. Runningcargo updatemight help if the lock file is outdated. - Prevention: Always ensure your
Cargo.tomldependencies match the guide’s specified versions and keep your Rust toolchain updated withrustup update.
- Issue: This indicates a version mismatch with
Testing & Verification
To verify that everything is correctly set up for this chapter:
- Run
cargo build --release: This will build the optimized release binary. It should complete without errors and print messages frombuild.rs. - Check binary size: Compare the size of
target/debug/mermtoolwithtarget/release/mermtool. The release binary should be significantly smaller due tostrip = trueand LTO. - Run the release binary:This should output the package version from
./target/release/mermtool --versionCargo.toml.This should display the help message../target/release/mermtool --helpThis should run the validate command with debug logging enabled (both via CLIRUST_LOG=debug ./target/release/mermtool -vv validate-vvandRUST_LOG).
If all these steps pass, your mermtool project is correctly set up, configured for production-grade builds, and ready for the next phase of development.
Summary & Next Steps
In this chapter, we successfully laid the foundation for our mermtool project. We installed the Rust toolchain, created a new project, configured its Cargo.toml with essential dependencies, established a modular file structure, and implemented a basic CLI using clap. We also incorporated production-ready considerations like embedded build information, optimized release profiles, robust error handling with anyhow, and flexible logging with env_logger.
With this solid base, we are now perfectly positioned to dive into the core logic of mermtool. In the next chapter, we will begin the exciting task of building our Lexer. This component will be responsible for taking raw Mermaid code as input and breaking it down into a stream of meaningful tokens, the first crucial step in our compiler-like pipeline.