A comprehensive guide for contributing to and extending the CV Generator
- Architecture Overview
- Development Setup
- Code Organization
- Performance Guidelines
- Contributing Guidelines
- Testing Strategy
- Release Process
The CV Generator follows functional programming principles with immutable data structures and pure functions wherever possible.
- Immutability: Use
imcrate collections (Vector,HashMap) for all data structures - Pure Functions: Minimize side effects, prefer functions that return new values
- Modular Design: Each module has a single responsibility
- Performance First: Sub-second builds with intelligent caching
- Type Safety: Leverage Rust's type system for correctness
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Data Layer │ │ Business Logic │ │ Presentation │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • cv_data.rs │───▶│ • github.rs │───▶│ • html_generator │
│ • site_config │ │ • performance │ │ • typst_generator│
│ • github_cache │ │ • css_generator │ │ • templates/ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
▲
│
┌─────────────────┐
│ Infrastructure │
├─────────────────┤
│ • github_cache │
│ • performance │
│ • asset_processor│
└─────────────────┘
# Install Rust (stable)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install GitHub CLI
# macOS: brew install gh
# Ubuntu: sudo apt install gh
# Windows: winget install GitHub.cli
# Install Typst for PDF generation
cargo install typst-cli
# Authenticate with GitHub
gh auth login# Clone and setup
git clone https://github.com/hakimjonas/cv.git
cd cv
# Build the project
cargo build
# Run with debug logging
RUST_LOG=debug cargo run --bin cv
# Run tests
cargo test
# Run with performance profiling
cargo run --bin cv 2>&1 | grep "📊"src/
├── lib.rs # Public API and documentation
├── main.rs # CLI entry point
├── cv_data.rs # Core data structures
├── github.rs # GitHub API integration
├── github_cache.rs # Caching system (77% perf improvement)
├── performance.rs # Profiling and optimization
├── css_generator.rs # Dynamic CSS generation
├── html_generator/ # HTML generation system
│ ├── mod.rs # Main coordination (264 lines)
│ ├── html_generators.rs # Core HTML pages (281 lines)
│ ├── config_generators.rs # Server configs (441 lines)
│ ├── asset_processor.rs # Asset handling (218 lines)
│ └── utils.rs # Utilities (156 lines)
├── typst_generator/ # PDF generation
├── blog_posts.rs # Blog system with tagging
├── markdown_pages.rs # Static page generation
└── site_config.rs # Configuration management
- Line Count: Keep modules under 500 lines (current max: 441 lines)
- Single Responsibility: Each module should have one clear purpose
- Documentation: Every public function needs doc comments with examples
- Error Handling: Use
anyhow::Resultfor error propagation - Testing: Include unit tests for all business logic
| Metric | Target | Current | Status |
|---|---|---|---|
| Build Time | <5s | 524ms | ✅ Excellent |
| GitHub API | <500ms | 0ms (cached) | ✅ Perfect |
| CSS Generation | <100ms | ~50ms | ✅ Excellent |
| HTML Generation | <1s | ~200ms | ✅ Excellent |
use crate::performance::BuildProfiler;
let mut profiler = BuildProfiler::new();
profiler.time_operation("operation_name", || {
// Your code here
});
profiler.print_summary(); // Always profile new features// Example: Configuration-based cache invalidation
fn needs_regeneration(config: &Config, output_path: &str) -> Result<bool> {
let config_hash = calculate_config_hash(config);
let existing_hash = read_hash_from_file(output_path)?;
Ok(config_hash != existing_hash)
}use im::Vector;
// ✅ Good: Immutable, efficient sharing
let mut projects = Vector::new();
projects.push_back(project);
// ❌ Avoid: Mutable Vec when sharing data
let mut projects = Vec::new();use rayon::prelude::*;
// Process multiple items in parallel
let results: Vec<_> = items
.par_iter()
.map(|item| process_item(item))
.collect();Always run performance tests when making changes:
# Build with profiling enabled
cargo run --bin cv
# Look for the performance summary
# Target: Total build time < 1000ms#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_functionality() {
let mut cache = GitHubCache::default();
cache.cache_projects("user", Vector::new());
assert!(cache.get_projects("user").is_some());
}
}# Run all tests
cargo test
# Run specific test module
cargo test github_cache
# Run with output
cargo test -- --nocapture#[test]
fn test_build_performance() {
let start = std::time::Instant::now();
// Run build process
generate_html(&cv, &config, "test_output.html").unwrap();
let duration = start.elapsed();
assert!(duration.as_millis() < 5000, "Build took too long: {}ms", duration.as_millis());
}- Formatting: Use
cargo fmtbefore committing - Linting: Run
cargo clippyand fix all warnings - Documentation: Document all public APIs with examples
- Testing: Add tests for new functionality
- Performance: Profile any changes that could affect build time
- Branch Naming: Use descriptive names (
feature/cache-optimization,fix/memory-leak) - Commit Messages: Follow conventional commits format
- Testing: Ensure all tests pass locally
- Performance: Include performance impact in PR description
- Documentation: Update relevant documentation
- Code follows project style guidelines
- All tests pass locally
- New code has appropriate test coverage
- Documentation updated for API changes
- Performance impact measured and documented
- No new compiler warnings
- Commit messages are clear and descriptive
We follow semantic versioning (SemVer):
- MAJOR: Breaking changes to public API
- MINOR: New features, backwards compatible
- PATCH: Bug fixes, performance improvements
- Update Version: Bump version in
Cargo.toml - Update Changelog: Document all changes
- Performance Check: Ensure build time still <1s
- Create Tag:
git tag v1.2.3 - Push Release: GitHub Actions handles deployment
Before each release, verify performance targets:
# Run multiple builds and average the times
for i in {1..5}; do
echo "Build $i:"
time cargo run --bin cv
doneTarget metrics:
- Average build time: <1000ms
- Cache hit rate: >90%
- Memory usage: <100MB peak
# Check auth status
gh auth status
# Login if needed
gh auth login# Clear cache
rm -rf cache/
# Rebuild from scratch
cargo run --bin cv# Enable debug logging
RUST_LOG=debug cargo run --bin cv
# Look for timing information
# Check for cache missesuse log::{debug, info, warn, error};
debug!("Processing {} projects", projects.len());
info!("✅ Build completed in {}ms", duration.as_millis());
warn!("⚠️ Cache miss for user: {}", username);
error!("❌ Failed to fetch data: {}", error);- Rust Book - Learn Rust fundamentals
- Askama Templates - Template engine documentation
- im crate - Immutable data structures
- anyhow - Error handling best practices
Our 10/10 performance targets:
| Metric | Target | Current Status |
|---|---|---|
| Build Time | <5s | ✅ 524ms (90% under target) |
| First Paint | <1s | ✅ ~200ms |
| GitHub Integration | <2s | ✅ 0ms (cached) |
| Memory Usage | <100MB | ✅ ~50MB |
| Bundle Size | <500KB | ✅ ~300KB |
Built with ❤️ in Rust following functional programming principles