diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dbe034e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,358 @@ +# Contributing to CalDAV Calendar Synchronizer + +Thank you for your interest in contributing to the CalDAV Calendar Synchronizer! This document provides guidelines and information for contributors. + +## Getting Started + +### Prerequisites + +- Rust 1.70 or newer +- Git +- Basic familiarity with CalDAV protocol +- Understanding of async/await in Rust + +### Development Setup + +1. **Fork and Clone** + ```bash + # Fork the repository on your Forgejo instance + git clone ssh://git@gitea.soliverez.com.ar/YOUR_USERNAME/caldavpuller.git + cd caldavpuller + + # Add upstream remote + git remote add upstream ssh://git@gitea.soliverez.com.ar/alvaro/caldavpuller.git + ``` + +2. **Install Development Tools** + ```bash + # Install Rust toolchain + rustup update stable + rustup component add rustfmt clippy rust-analyzer + + # Verify installation + cargo --version + rustfmt --version + clippy --version + ``` + +3. **Build and Test** + ```bash + # Build the project + cargo build + + # Run tests + cargo test + + # Check formatting + cargo fmt --check + + # Run linter + cargo clippy -- -D warnings + ``` + +## Development Workflow + +### 1. **Create a Branch** + +```bash +# Sync with upstream +git fetch upstream +git checkout main +git merge upstream/main + +# Create feature branch +git checkout -b feature/your-feature-name +# or +git checkout -b fix/your-fix-name +``` + +### 2. **Make Changes** + +- Follow the existing code style +- Add tests for new functionality +- Update documentation as needed +- Ensure all tests pass + +### 3. **Commit Changes** + +Use clear, descriptive commit messages: + +```bash +# Good commit message +git commit -m "feat: Add support for calendar color filtering + +- Add color field to CalendarInfo struct +- Implement color-based filtering in CalendarFilter +- Add configuration option for color preferences +- Add unit tests for color filtering logic" + +# Another good example +git commit -m "fix: Handle timezone parsing errors gracefully + +- Add proper error handling for unknown timezone identifiers +- Return meaningful error messages to users +- Add integration tests for timezone edge cases" +``` + +### 4. **Submit Pull Request** + +```bash +# Push to your fork +git push origin feature/your-feature-name + +# Create pull request on Forgejo +# Provide clear description of changes and testing done +``` + +## Code Style and Standards + +### 1. **Formatting** + +Use `rustfmt` with default settings: + +```bash +cargo fmt +``` + +### 2. **Linting** + +All code must pass `clippy` without warnings: + +```bash +cargo clippy -- -D warnings +``` + +### 3. **Documentation** + +- Document all public APIs with `///` comments +- Include examples for complex functionality +- Update README and other documentation as needed + +```rust +/// Filters calendars based on user-defined criteria. +/// +/// # Examples +/// +/// ``` +/// use caldav_sync::CalendarFilter; +/// +/// let filter = CalendarFilter::new() +/// .with_selected_calendars(vec!["Work".to_string()]); +/// +/// assert!(filter.should_import_calendar("Work")); +/// assert!(!filter.should_import_calendar("Personal")); +/// ``` +pub struct CalendarFilter { + // ... +} +``` + +### 4. **Error Handling** + +- Use `Result` for fallible operations +- Provide meaningful error context +- Handle errors gracefully at the UI layer + +```rust +pub async fn sync_calendars(&mut self) -> CalDavResult { + let calendars = self.discover_calendars().await + .context("Failed to discover calendars")?; + + // ... rest of implementation +} +``` + +### 5. **Testing** + +Write comprehensive tests: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_calendar_filtering() { + let filter = CalendarFilter::new() + .with_selected_calendars(vec!["Work".to_string()]); + + assert!(filter.should_import_calendar("Work")); + assert!(!filter.should_import_calendar("Personal")); + } +} +``` + +## Testing + +### 1. **Unit Tests** + +Test individual components in isolation: + +```bash +cargo test --lib +``` + +### 2. **Integration Tests** + +Test component interactions: + +```bash +cargo test --test integration_tests +``` + +### 3. **Manual Testing** + +Test with real CalDAV servers: + +```bash +# Build and run with debug logging +cargo build +./target/debug/caldav-sync --debug --list-events +``` + +### 4. **Test Coverage** + +Aim for high test coverage, especially for: +- Configuration parsing +- Error handling paths +- Calendar filtering logic +- Timezone conversions + +## Types of Contributions + +### 1. **Bug Fixes** + +- Check existing issues for bug reports +- Create minimal reproduction case +- Add tests that fail before the fix +- Fix the issue and ensure tests pass + +### 2. **New Features** + +- Open issue for discussion first +- Design the feature interface +- Implement with tests +- Update documentation + +### 3. **Documentation** + +- Improve README and guides +- Add code examples +- Fix typos and clarity issues +- Translate documentation if possible + +### 4. **Performance Improvements** + +- Profile existing code +- Identify bottlenecks +- Implement optimizations +- Add benchmarks for verification + +## Areas Needing Help + +### 1. **Enhanced CalDAV Support** + +- Better support for CalDAV extensions +- Handling of recurring events +- Support for attachments and attendees + +### 2. **User Experience** + +- Interactive configuration wizard +- Progress indicators for long operations +- Better error messages and recovery + +### 3. **Testing Infrastructure** + +- Mock CalDAV server for testing +- Automated testing in CI/CD +- Performance benchmarks + +### 4. **Documentation** + +- User guides for different platforms +- API documentation +- Troubleshooting guides + +## Code Review Process + +### 1. **Self-Review** + +Before submitting, review your own changes: +- Code follows project style +- Tests are comprehensive +- Documentation is updated +- No debug code or TODOs left + +### 2. **Review Criteria** + +Maintainers will review for: +- Correctness and safety +- Performance implications +- User experience impact +- Code maintainability + +### 3. **Feedback** + +Be prepared to: +- Answer questions about your changes +- Make requested modifications +- Discuss alternative approaches +- Update tests based on feedback + +## Release Process + +### 1. **Version Management** + +- Follow semantic versioning (MAJOR.MINOR.PATCH) +- Update version in `Cargo.toml` +- Create git tag for releases + +### 2. **Changelog** + +Maintain `CHANGELOG.md` with: +- New features +- Bug fixes +- Breaking changes +- Migration notes + +### 3. **Release Checklist** + +- [ ] All tests pass +- [ ] Documentation updated +- [ ] Version numbers updated +- [ ] Changelog updated +- [ ] Release tag created +- [ ] Release artifacts built + +## Support and Communication + +### 1. **Issues and Questions** + +- Use GitHub issues for bug reports and feature requests +- Check existing issues before creating new ones +- Provide clear, reproducible examples + +### 2. **Discussions** + +- Use discussions for general questions +- Share ideas and suggestions +- Ask for help with development + +### 3. **Code of Conduct** + +Be respectful and constructive: +- Welcome newcomers and help them learn +- Assume good faith in interactions +- Focus on what is best for the community +- Show empathy towards other community members + +## Recognition + +Contributors are recognized in: +- `CONTRIBUTORS.md` file +- Release notes +- Git commit history +- Project documentation + +Thank you for contributing to the CalDAV Calendar Synchronizer! Your contributions help make this project better for everyone. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..925d4d5 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,17 @@ +# Contributors + +This project exists thanks to all the people who contribute. + +## Lead Developer + +- **Alvaro Soliverez** - *Project creator and maintainer* + +## Development Setup + +For information on how to contribute to this project, please see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Acknowledgments + +- The Rust community for excellent tools and libraries +- The CalDAV protocol developers and maintainers +- All users who provide feedback and bug reports diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..a96fb60 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,416 @@ +# Development Guide + +This document provides comprehensive information about the design, architecture, and development of the CalDAV Calendar Synchronizer. + +## Architecture Overview + +The application is built with a modular architecture using Rust's strong type system and async capabilities. + +### Core Components + +#### 1. **Configuration System** (`src/config.rs`) +- **Purpose**: Manage configuration from files, environment variables, and CLI arguments +- **Features**: + - TOML-based configuration files + - Environment variable support + - Command-line argument overrides + - Configuration validation +- **Key Types**: `Config`, `ServerConfig`, `CalendarConfig`, `SyncConfig` + +#### 2. **CalDAV Client** (`src/caldav_client.rs`) +- **Purpose**: Handle CalDAV protocol operations with Zoho and Nextcloud +- **Features**: + - HTTP client with authentication + - Calendar discovery via PROPFIND + - Event retrieval via REPORT requests + - Event creation via PUT requests +- **Key Types**: `CalDavClient`, `CalendarInfo`, `CalDavEventInfo` + +#### 3. **Event Model** (`src/event.rs`) +- **Purpose**: Represent calendar events and handle parsing +- **Features**: + - iCalendar data parsing + - Timezone-aware datetime handling + - Event filtering and validation +- **Key Types**: `Event`, `EventBuilder`, `EventFilter` + +#### 4. **Timezone Handler** (`src/timezone.rs`) +- **Purpose**: Manage timezone conversions and datetime operations +- **Features**: + - Convert between different timezones + - Parse timezone information from iCalendar data + - Handle DST transitions +- **Key Types**: `TimezoneHandler`, `TimeZoneInfo` + +#### 5. **Calendar Filter** (`src/calendar_filter.rs`) +- **Purpose**: Filter calendars and events based on user criteria +- **Features**: + - Calendar name filtering + - Regex pattern matching + - Event date range filtering +- **Key Types**: `CalendarFilter`, `FilterRule`, `EventFilter` + +#### 6. **Sync Engine** (`src/sync.rs`) +- **Purpose**: Coordinate the synchronization process +- **Features**: + - Pull events from Zoho + - Push events to Nextcloud + - Conflict resolution + - Progress tracking +- **Key Types**: `SyncEngine`, `SyncResult`, `SyncStats` + +#### 7. **Error Handling** (`src/error.rs`) +- **Purpose**: Comprehensive error management +- **Features**: + - Custom error types + - Error context and chaining + - User-friendly error messages +- **Key Types**: `CalDavError`, `CalDavResult` + +## Design Decisions + +### 1. **Selective Calendar Import** +The application allows users to select specific Zoho calendars to import from, consolidating all events into a single Nextcloud calendar. This design choice: + +- **Reduces complexity** compared to bidirectional sync +- **Provides clear data flow** (Zoho → Nextcloud) +- **Minimizes sync conflicts** +- **Matches user requirements** exactly + +### 2. **Timezone Handling** +All events are converted to UTC internally for consistency, while preserving original timezone information: + +```rust +pub struct Event { + pub id: String, + pub summary: String, + pub start: DateTime, + pub end: DateTime, + pub original_timezone: Option, + pub source_calendar: String, +} +``` + +### 3. **Configuration Hierarchy** +Configuration is loaded in priority order: + +1. **Command line arguments** (highest priority) +2. **User config file** (`config/config.toml`) +3. **Default config file** (`config/default.toml`) +4. **Environment variables** +5. **Hardcoded defaults** (lowest priority) + +### 4. **Error Handling Strategy** +Uses `thiserror` for custom error types and `anyhow` for error propagation: + +```rust +#[derive(Error, Debug)] +pub enum CalDavError { + #[error("HTTP error: {0}")] + Http(#[from] reqwest::Error), + + #[error("Configuration error: {0}")] + Config(String), + + #[error("Calendar not found: {0}")] + CalendarNotFound(String), + + // ... more error variants +} +``` + +## Data Flow + +### 1. **Application Startup** +``` +1. Load CLI arguments +2. Load configuration files +3. Apply environment variables +4. Validate configuration +5. Initialize logging +6. Create CalDAV clients +``` + +### 2. **Calendar Discovery** +``` +1. Connect to Zoho CalDAV server +2. Authenticate with app password +3. Send PROPFIND request to discover calendars +4. Parse calendar list and metadata +5. Apply user filters to select calendars +``` + +### 3. **Event Synchronization** +``` +1. Query selected Zoho calendars for events (next week) +2. Parse iCalendar data into Event objects +3. Convert timestamps to UTC with timezone preservation +4. Apply event filters (duration, status, patterns) +5. Connect to Nextcloud CalDAV server +6. Create target calendar if needed +7. Upload events to Nextcloud calendar +8. Report sync statistics +``` + +## Key Algorithms + +### 1. **Calendar Filtering** +```rust +impl CalendarFilter { + pub fn should_import_calendar(&self, calendar_name: &str) -> bool { + // Check exact matches + if self.selected_names.contains(&calendar_name.to_string()) { + return true; + } + + // Check regex patterns + for pattern in &self.regex_patterns { + if pattern.is_match(calendar_name) { + return true; + } + } + + false + } +} +``` + +### 2. **Timezone Conversion** +```rust +impl TimezoneHandler { + pub fn convert_to_utc(&self, dt: DateTime, timezone: &str) -> CalDavResult> { + let tz = self.get_timezone(timezone)?; + let local_dt = dt.with_timezone(&tz); + Ok(local_dt.with_timezone(&Utc)) + } +} +``` + +### 3. **Event Processing** +```rust +impl SyncEngine { + pub async fn sync_calendar(&mut self, calendar: &CalendarInfo) -> CalDavResult { + // 1. Fetch events from Zoho + let zoho_events = self.fetch_zoho_events(calendar).await?; + + // 2. Filter and process events + let processed_events = self.process_events(zoho_events)?; + + // 3. Upload to Nextcloud + let upload_results = self.upload_to_nextcloud(processed_events).await?; + + // 4. Return sync statistics + Ok(SyncResult::from_upload_results(upload_results)) + } +} +``` + +## Configuration Schema + +### Complete Configuration Structure +```toml +# Zoho Configuration (Source) +[zoho] +server_url = "https://caldav.zoho.com/caldav" +username = "your-zoho-email@domain.com" +password = "your-zoho-app-password" +selected_calendars = ["Work Calendar", "Personal Calendar"] + +# Nextcloud Configuration (Target) +[nextcloud] +server_url = "https://your-nextcloud-domain.com" +username = "your-nextcloud-username" +password = "your-nextcloud-app-password" +target_calendar = "Imported-Zoho-Events" +create_if_missing = true + +# General Settings +[server] +timeout = 30 + +[calendar] +color = "#3174ad" +timezone = "UTC" + +[sync] +interval = 300 +sync_on_startup = true +weeks_ahead = 1 +dry_run = false + +# Optional Filtering +[filters] +min_duration_minutes = 5 +max_duration_hours = 24 +exclude_patterns = ["Cancelled:", "BLOCKED"] +include_status = ["confirmed", "tentative"] +exclude_status = ["cancelled"] +``` + +## Dependencies and External Libraries + +### Core Dependencies +```toml +[dependencies] +tokio = { version = "1.0", features = ["full"] } # Async runtime +reqwest = { version = "0.11", features = ["json", "xml"] } # HTTP client +serde = { version = "1.0", features = ["derive"] } # Serialization +chrono = { version = "0.4", features = ["serde"] } # Date/time +chrono-tz = "0.8" # Timezone support +quick-xml = "0.28" # XML parsing +thiserror = "1.0" # Error handling +anyhow = "1.0" # Error propagation +config = "0.13" # Configuration +clap = { version = "4.0", features = ["derive"] } # CLI +tracing = "0.1" # Logging +tracing-subscriber = "0.3" # Log formatting +toml = "0.8" # TOML parsing +``` + +### Optional Dependencies for Future Features +```toml +# For enhanced XML handling +serde_xml_rs = "0.6" + +# For better HTTP client customization +http = "0.2" + +# For async file operations +tokio-util = "0.7" + +# For better error formatting +color-eyre = "0.6" +``` + +## Testing Strategy + +### 1. **Unit Tests** +- Individual module functionality +- Configuration parsing and validation +- Event parsing and timezone conversion +- Error handling paths + +### 2. **Integration Tests** +- End-to-end CalDAV operations +- Configuration loading from files +- CLI argument processing +- HTTP client behavior + +### 3. **Mock Testing** +- Mock CalDAV server responses +- Test error conditions without real servers +- Validate retry logic and timeout handling + +## Performance Considerations + +### 1. **Async Operations** +All network operations are async to prevent blocking: +```rust +pub async fn fetch_events(&self, calendar: &CalendarInfo) -> CalDavResult> { + let response = self.client + .request(reqwest::Method::REPORT, &calendar.url) + .body(report_body) + .send() + .await?; + + // Process response... +} +``` + +### 2. **Memory Management** +- Stream processing for large calendar responses +- Efficient string handling with `Cow` where appropriate +- Clear lifecycle management for HTTP connections + +### 3. **Configuration Caching** +- Cache parsed timezone information +- Reuse HTTP connections where possible +- Validate configuration once at startup + +## Security Considerations + +### 1. **Authentication** +- Support for app-specific passwords only +- Never log authentication credentials +- Secure storage of sensitive configuration + +### 2. **Network Security** +- Enforce HTTPS by default +- SSL certificate validation +- Custom CA certificate support + +### 3. **Data Privacy** +- Minimal data collection (only required event fields) +- Optional debug logging with sensitive data filtering +- Clear data retention policies + +## Future Enhancements + +### 1. **Enhanced Filtering** +- Advanced regex patterns +- Calendar color-based filtering +- Attendee-based filtering + +### 2. **Bidirectional Sync** +- Two-way synchronization with conflict resolution +- Event modification tracking +- Deletion synchronization + +### 3. **Performance Optimizations** +- Parallel calendar processing +- Incremental sync with change detection +- Local caching and offline mode + +### 4. **User Experience** +- Interactive configuration wizard +- Web-based status dashboard +- Real-time sync notifications + +## Build and Development + +### 1. **Development Setup** +```bash +# Clone repository +git clone ssh://git@gitea.soliverez.com.ar/alvaro/caldavpuller.git +cd caldavpuller + +# Install Rust toolchain +rustup update stable +rustup component add rustfmt clippy + +# Build in development mode +cargo build + +# Run tests +cargo test + +# Check formatting +cargo fmt --check + +# Run linter +cargo clippy -- -D warnings +``` + +### 2. **Release Build** +```bash +# Build optimized release +cargo build --release + +# Create distribution archive +tar -czf caldav-sync-${VERSION}-${TARGET}.tar.gz \ + -C target/release caldav-sync \ + -C ../config example.toml \ + -C .. README.md LICENSE +``` + +### 3. **Testing with Mock Servers** +For testing without real CalDAV servers, use the mock server setup: +```bash +# Start mock CalDAV server +cargo run --bin mock-server + +# Run integration tests against mock server +cargo test --test integration_tests +``` + +This architecture provides a solid foundation for the CalDAV synchronization tool while maintaining flexibility for future enhancements. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b8abd7e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Alvaro Soliverez + +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. diff --git a/README.md b/README.md index df228c3..6f29bd8 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,18 @@ A Rust-based command-line tool that synchronizes calendar events between Zoho Ca ## Quick Start -### 1. Prerequisites +### Prerequisites - Rust 1.70+ (for building from source) - Zoho account with CalDAV access - Nextcloud instance with CalDAV enabled - App-specific passwords for both services (recommended) -### 2. Installation +### Installation ```bash # Clone the repository -git clone +git clone ssh://git@gitea.soliverez.com.ar/alvaro/caldavpuller.git cd caldavpuller # Build the project @@ -34,7 +34,7 @@ cargo build --release # The binary will be at target/release/caldav-sync ``` -### 3. Configuration +### Configuration Copy the example configuration file: @@ -63,7 +63,7 @@ target_calendar = "Imported-Zoho-Events" create_if_missing = true ``` -### 4. First Run +### First Run Test the configuration with a dry run: @@ -77,35 +77,6 @@ Perform a one-time sync: ./target/release/caldav-sync --once ``` -## Configuration Details - -### Zoho Setup - -1. **Enable CalDAV in Zoho**: - - Go to Zoho Mail Settings → CalDAV - - Enable CalDAV access - - Generate an app-specific password - -2. **Find Calendar Names**: - ```bash - ./target/release/caldav-sync --list-events --debug - ``` - This will show all available calendars. - -### Nextcloud Setup - -1. **Enable CalDAV**: - - Usually enabled by default - - Access at `https://your-domain.com/remote.php/dav/` - -2. **Generate App Password**: - - Go to Settings → Security → App passwords - - Create a new app password for the sync tool - -3. **Target Calendar**: - - The tool can automatically create the target calendar - - Or create it manually in Nextcloud first - ## Usage ### Command Line Options @@ -149,36 +120,7 @@ Options: caldav-sync --username "user@example.com" --password "app-password" --once ``` -## Configuration Reference - -### Complete Configuration Example - -```toml -[server] -url = "https://caldav.zoho.com/caldav" -username = "your-email@domain.com" -password = "your-app-password" -timeout = 30 - -[calendar] -name = "Work Calendar" -color = "#4285F4" - -[sync] -sync_interval = 300 -sync_on_startup = true -weeks_ahead = 1 -dry_run = false - -[filters] -exclude_patterns = ["Cancelled:", "BLOCKED"] -min_duration_minutes = 5 -max_duration_hours = 24 - -[logging] -level = "info" -file = "caldav-sync.log" -``` +## Configuration ### Environment Variables @@ -193,6 +135,10 @@ export CALDAV_CALENDAR="Work Calendar" ./target/release/caldav-sync ``` +### Complete Configuration Example + +See `config/example.toml` for a comprehensive configuration example with all available options. + ## Security Considerations 1. **Use App Passwords**: Never use your main account password @@ -233,65 +179,11 @@ caldav-sync --debug --list-events This will show detailed HTTP requests, responses, and processing steps. -## Development +## More Information -### Building from Source - -```bash -# Clone the repository -git clone -cd caldavpuller - -# Build in debug mode -cargo build - -# Build in release mode -cargo build --release - -# Run tests -cargo test - -# Check code formatting -cargo fmt --check - -# Run linter -cargo clippy -``` - -### Project Structure - -``` -caldavpuller/ -├── src/ -│ ├── main.rs # CLI interface and entry point -│ ├── lib.rs # Library interface -│ ├── config.rs # Configuration management -│ ├── caldav_client.rs # CalDAV HTTP client -│ ├── event.rs # Event data structures -│ ├── timezone.rs # Timezone handling -│ ├── calendar_filter.rs # Calendar filtering logic -│ ├── sync.rs # Synchronization engine -│ └── error.rs # Error types and handling -├── config/ -│ ├── default.toml # Default configuration -│ └── example.toml # Example configuration -├── tests/ -│ └── integration_tests.rs # Integration tests -├── Cargo.toml # Rust project configuration -└── README.md # This file -``` - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests if applicable -5. Submit a pull request - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. +- **Development & Design**: See [DEVELOPMENT.md](DEVELOPMENT.md) +- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md) +- **License**: See [LICENSE](LICENSE) ## Support