diff --git a/README.md b/README.md index 409b666..0c596f1 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Affected services: app1, app2 Suggestion: Use different host ports, e.g., 8080, 8081 ``` -**[πŸ“– Learn more about Enhanced Error Handling β†’](docs/ERROR_HANDLING.md)** +**[Learn more about Enhanced Error Handling β†’](docs/ERROR_HANDLING.md)** ## Quick Start @@ -142,7 +142,7 @@ athena init go my-service --framework gin --with-mongodb ## Key Features -### 🚨 Enhanced Error Handling System (New!) +### Enhanced Error Handling System (New!) - **Line & Column Precision** => Exact error locations with visual context - **Intelligent Suggestions** => Automatic recommendations for common fixes - **Advanced Validation** => Port conflicts, service references, circular dependencies @@ -173,10 +173,17 @@ athena init go my-service --framework gin --with-mongodb - Flask + PostgreSQL => Modern Python web development - Docker ready => Multi-stage builds, Nginx reverse proxy included +### Syntax Highlighting (New!) +- **Beautiful DSL highlighting** for `.ath` files with customizable colors +- **Zed editor extension** ready to install in `syntax-highlighting/` +- **Smart color coding** for keywords, directives, template variables, and more +- **Easy customization** via `colors.json` make it your own! + ## Documentation ### Core Documentation - [Enhanced Error Handling (**New**)](docs/ERROR_HANDLING.md) - Complete guide to Athena's advanced error system. +- [Syntax Highlighting (**New**)](syntax-highlighting/README.md) - Beautiful colors for `.ath` files in Zed editor. - [Installation Guide](docs/INSTALLATION.md) - [Docker Compose Generator Usage](docs/DSL_REFERENCE.md) - [Boilerplate Project Generator](docs/BOILERPLATE.md) @@ -209,50 +216,6 @@ athena init go my-service --framework echo --with-postgresql # Flask projects athena init flask my-app --with-postgresql ``` - -## Complete Example: Modern Web Application - -The `presentation.ath` file demonstrates **all Athena features** in a production-ready web application: - -```athena -DEPLOYMENT-ID MODERN_WEB_APP -VERSION-ID 1.0.0 - -ENVIRONMENT SECTION -NETWORK-NAME modern_app_network - -SERVICES SECTION - -SERVICE nginx_proxy -IMAGE-ID "nginx:alpine" -PORT-MAPPING 80 TO 80 -DEPENDS-ON backend -END SERVICE - -SERVICE backend -PORT-MAPPING 3000 TO 3000 -ENV-VARIABLE {{NODE_ENV}} -DEPENDS-ON mongodb -END SERVICE - -SERVICE mongodb -IMAGE-ID "mongo:7.0" -PORT-MAPPING 27017 TO 27017 -RESTART-POLICY always -END SERVICE -``` - -**Generated Configuration Highlights:** - -- **Automatic Dockerfile Detection**: Backend service gets `build.dockerfile: Dockerfile` -- **Service Type Detection**: MongoDB β†’ Database type with optimized settings -- **Custom Network**: All services connected to `modern_app_network` -- **Smart Labels**: Project tracking and metadata automatically added -- **Dependency Ordering**: Services sorted automatically (mongodb β†’ backend β†’ nginx) -- **Health Checks**: Type-specific intervals and commands - -This **65-line configuration** generates a **220+ line** production-ready Docker Compose file with all best practices included! - ## What Athena Adds Automatically - Smart service detection (Database, Cache, WebApp, Proxy) @@ -265,10 +228,6 @@ This **65-line configuration** generates a **220+ line** production-ready Docker - Dockerfile integration when no image specified - Dependency ordering with topological sort -## License - -This project is licensed under the MIT License see the [LICENSE](LICENSE) file for details. - ## Acknowledgments - **Pest** for powerful parsing capabilities @@ -276,6 +235,10 @@ This project is licensed under the MIT License see the [LICENSE](LICENSE) file f - **Docker Community** for container standards - **Rust Community** for the amazing ecosystem +## License + +This project is licensed under the MIT License see the [LICENSE](LICENSE) file for details. + --- Built with ❀️ using Rust | Production-ready DevOps made simple diff --git a/docker-compose.yml b/docker-compose.yml index 0fb05db..21130a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,26 @@ # Generated by Athena v0.1.0 from test_no_conflicts deployment -# Developed by UNFAIR Team -# Generated: 2025-09-25 15:40:51 UTC +# Developed by UNFAIR Team: https://github.com/Jeck0v/Athena +# Generated: 2025-10-05 20:37:19 UTC # Features: Intelligent defaults, optimized networking, enhanced health checks # Services: 3 configured with intelligent defaults services: + app3: + image: apache:latest + container_name: test-no-conflicts-app3 + ports: + - 9000:80 + restart: always + networks: + - test_no_conflicts_network + pull_policy: missing + labels: + athena.project: test_no_conflicts + athena.service: app3 + athena.generated: 2025-10-05 + athena.type: proxy + app1: image: nginx:alpine container_name: test-no-conflicts-app1 @@ -16,10 +31,10 @@ services: - test_no_conflicts_network pull_policy: missing labels: - athena.generated: 2025-09-25 - athena.type: proxy athena.project: test_no_conflicts athena.service: app1 + athena.generated: 2025-10-05 + athena.type: proxy app2: image: httpd:alpine @@ -31,25 +46,10 @@ services: - test_no_conflicts_network pull_policy: missing labels: - athena.type: generic athena.service: app2 + athena.type: generic athena.project: test_no_conflicts - athena.generated: 2025-09-25 - - app3: - image: apache:latest - container_name: test-no-conflicts-app3 - ports: - - 9000:80 - restart: always - networks: - - test_no_conflicts_network - pull_policy: missing - labels: - athena.generated: 2025-09-25 - athena.service: app3 - athena.type: proxy - athena.project: test_no_conflicts + athena.generated: 2025-10-05 networks: test_no_conflicts_network: driver: bridge diff --git a/docs/PRESENTATION_GUIDE.md b/docs/PRESENTATION_GUIDE.md new file mode 100644 index 0000000..164e039 --- /dev/null +++ b/docs/PRESENTATION_GUIDE.md @@ -0,0 +1,93 @@ +# Athena Presentation Files Guide + +This directory contains two presentation configurations demonstrating Athena's capabilities: + +## Files Overview + +### `presentation.ath` - Production Version +**Use for:** Demonstrating production-ready security practices + +**Features:** +- βœ… **Secure**: Only nginx exposed (ports 80/443) +- βœ… **Internal services**: No external ports for databases/APIs +- βœ… **Production-ready**: Docker Swarm compatible +- βœ… **Comments**: Comprehensive documentation + +**Exposed Ports:** +- `80, 443` - nginx_reverse_proxy (only entry point) +- `9090` - monitoring (Prometheus UI) + +### `light_presentation.ath` - Demo/Testing Version +**Use for:** Live demonstrations, API testing, development + +**Features:** +- βœ… **Testing-friendly**: All services accessible +- βœ… **Development mode**: NODE_ENV=development +- βœ… **Direct access**: Can test APIs individually +- βœ… **Database tools**: Direct database connections + +**Exposed Ports:** +- `80, 443` - nginx_reverse_proxy (web entry) +- `3000` - api_gateway (REST API) +- `3001` - product_service (Catalog API) +- `3002` - auth_service (Authentication API) +- `5432` - database (PostgreSQL) +- `6379` - cache (Redis) +- `9090` - monitoring (Prometheus) + +## Usage Examples + +### Quick Demo +```bash +# Start demo environment +athena build light_presentation.ath +docker-compose up -d + +# Test APIs directly +curl http://localhost:3000/health # API Gateway +curl http://localhost:3001/health # Product Service +curl http://localhost:3002/health # Auth Service + +# Access monitoring +open http://localhost:9090 # Prometheus + +# Database access +psql -h localhost -p 5432 -U postgres +redis-cli -h localhost -p 6379 +``` + +### Production Demo +```bash +# Show production security +athena build presentation.ath +docker-compose up -d + +# Only nginx accessible externally +curl http://localhost # βœ… Works +curl http://localhost:3000 # ❌ Blocked (secure) +``` + +## Presentation Tips + +1. **Start with `light_presentation.ath`** to show functionality +2. **Switch to `presentation.ath`** to demonstrate security +3. **Compare generated YAML** to highlight Athena's intelligence +4. **Show comment features** and intelligent defaults + +## Key Differences + +| Aspect | light_presentation.ath | presentation.ath | +|--------|----------------------|------------------| +| **Security** | Demo-friendly | Production-ready | +| **Port Exposure** | All services | Nginx only | +| **Testing** | Direct API access | Proxy-only access | +| **Environment** | Development | Production | +| **Use Case** | Demos, Testing | Production deployment | + +## Generated Files + +- `light_presentation.ath` β†’ `docker-compose.yml` (with all ports) +- `presentation.ath` β†’ `docker-compose.yml` (secure, minimal ports) + +The presentation files will evolve as the project progresses (We will soon be working on more advanced support for swarm). +Both demonstrate Athena's intelligent defaults, healthchecks, and Docker Swarm readiness! diff --git a/examples/light_presentation.ath b/examples/light_presentation.ath new file mode 100644 index 0000000..61aec45 --- /dev/null +++ b/examples/light_presentation.ath @@ -0,0 +1,94 @@ +// Modern E-commerce Platform - Demo/Testing Version with Exposed Ports +DEPLOYMENT-ID MODERN_ECOMMERCE_DEMO +VERSION-ID 2.0.0 + +ENVIRONMENT SECTION +NETWORK-NAME ecommerce_network +// Define persistent volumes for testing +VOLUME postgres_data +VOLUME redis_data + +SERVICES SECTION + +// Reverse proxy with SSL termination - Main entry point +SERVICE nginx_reverse_proxy +IMAGE-ID "nginx:alpine" +PORT-MAPPING 80 TO 80 +PORT-MAPPING 443 TO 443 +VOLUME-MAPPING "./nginx/conf.d" TO "/etc/nginx/conf.d" (ro) +RESOURCE-LIMITS CPU "0.2" MEMORY "256M" +DEPENDS-ON api_gateway +HEALTH-CHECK "curl -f http://localhost:80/health || exit 1" +// Athena auto-applies: restart=always for proxy type +END SERVICE + +// API Gateway - Accessible for testing +SERVICE api_gateway +BUILD-ARGS NODE_ENV="development" API_VERSION="v2.1" JWT_EXPIRY="24h" +PORT-MAPPING 3000 TO 3000 +ENV-VARIABLE {{JWT_SECRET}} +ENV-VARIABLE {{DATABASE_URL}} +ENV-VARIABLE {{REDIS_URL}} +RESOURCE-LIMITS CPU "0.5" MEMORY "512M" +DEPENDS-ON database +DEPENDS-ON cache +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +// Athena auto-applies: restart=unless-stopped for webapp type +END SERVICE + +// Product catalog service - Exposed for direct testing +SERVICE product_service +BUILD-ARGS CATALOG_VERSION="v1.5" SEARCH_ENGINE="elasticsearch" +PORT-MAPPING 3001 TO 3000 +ENV-VARIABLE {{DATABASE_URL}} +RESOURCE-LIMITS CPU "0.3" MEMORY "256M" +DEPENDS-ON database +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +END SERVICE + +// User authentication service - Exposed for API testing +SERVICE auth_service +BUILD-ARGS AUTH_PROVIDER="oauth2" SESSION_TIMEOUT="1h" +PORT-MAPPING 3002 TO 3000 +ENV-VARIABLE {{JWT_SECRET}} +ENV-VARIABLE {{DATABASE_URL}} +RESOURCE-LIMITS CPU "0.2" MEMORY "256M" +DEPENDS-ON database +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +END SERVICE + +// Main database - Exposed for database tools/debugging +SERVICE database +IMAGE-ID "postgres:15" +PORT-MAPPING 5432 TO 5432 +ENV-VARIABLE {{POSTGRES_USER}} +ENV-VARIABLE {{POSTGRES_PASSWORD}} +ENV-VARIABLE {{POSTGRES_DB}} +// Using named volume for persistence +VOLUME-MAPPING "postgres_data" TO "/var/lib/postgresql/data" +RESOURCE-LIMITS CPU "1.0" MEMORY "1024M" +HEALTH-CHECK "pg_isready -U ${POSTGRES_USER} || exit 1" +// Athena auto-applies: restart=always for database type +END SERVICE + +// Redis cache - Exposed for Redis tools/monitoring +SERVICE cache +IMAGE-ID "redis:7-alpine" +PORT-MAPPING 6379 TO 6379 +// Using named volume for persistence +VOLUME-MAPPING "redis_data" TO "/data" +RESOURCE-LIMITS CPU "0.3" MEMORY "512M" +HEALTH-CHECK "redis-cli ping || exit 1" +// Athena auto-applies: restart=always for cache type +END SERVICE + +// Monitoring service - Prometheus for observability +SERVICE monitoring +IMAGE-ID "prom/prometheus:latest" +PORT-MAPPING 9090 TO 9090 +VOLUME-MAPPING "./prometheus/prometheus.yml" TO "/etc/prometheus/prometheus.yml" (ro) +DEPENDS-ON api_gateway +DEPENDS-ON product_service +DEPENDS-ON auth_service +RESOURCE-LIMITS CPU "0.3" MEMORY "512M" +END SERVICE \ No newline at end of file diff --git a/presentation.ath b/presentation.ath index a2f0e8a..a519203 100644 --- a/presentation.ath +++ b/presentation.ath @@ -1,73 +1,106 @@ -DEPLOYMENT-ID MODERN_WEB_APP -VERSION-ID 1.0.0 +// Modern E-commerce Platform - Production-ready with enhanced features +DEPLOYMENT-ID MODERN_ECOMMERCE +VERSION-ID 2.0.0 ENVIRONMENT SECTION -NETWORK-NAME modern_app_network +NETWORK-NAME ecommerce_network +// TODO: In Swarm mode, network will use overlay driver automatically +// For now: bridge driver (compose), overlay driver (swarm deploy) +// Define persistent volumes for production +VOLUME postgres_data +VOLUME redis_data +// Secrets management for production +SECRET jwt_secret "use Docker secrets in production" +SECRET postgres_password "use Docker secrets in production" SERVICES SECTION -SERVICE nginx_proxy +// Reverse proxy with SSL termination - Only exposed service in production +SERVICE nginx_reverse_proxy IMAGE-ID "nginx:alpine" PORT-MAPPING 80 TO 80 PORT-MAPPING 443 TO 443 VOLUME-MAPPING "./nginx/conf.d" TO "/etc/nginx/conf.d" (ro) -VOLUME-MAPPING "./nginx/ssl" TO "/etc/nginx/ssl" (ro) -DEPENDS-ON backend -RESTART-POLICY always +RESOURCE-LIMITS CPU "0.2" MEMORY "256M" +DEPENDS-ON api_gateway HEALTH-CHECK "curl -f http://localhost:80/health || exit 1" +// Athena auto-applies: restart=always for proxy type END SERVICE -SERVICE backend -PORT-MAPPING 3000 TO 3000 -ENV-VARIABLE {{NODE_ENV}} +// API Gateway - Internal only, stateless and scalable +SERVICE api_gateway +BUILD-ARGS NODE_ENV="production" API_VERSION="v2.1" JWT_EXPIRY="24h" +// No PORT-MAPPING for production - only internal network access ENV-VARIABLE {{JWT_SECRET}} -ENV-VARIABLE {{MONGODB_URI}} -ENV-VARIABLE {{API_PORT}} -COMMAND "npm start" -DEPENDS-ON mongodb -RESTART-POLICY unless-stopped +ENV-VARIABLE {{DATABASE_URL}} +ENV-VARIABLE {{REDIS_URL}} RESOURCE-LIMITS CPU "0.5" MEMORY "512M" -HEALTH-CHECK "curl -f http://localhost:3000/api/health || exit 1" +DEPENDS-ON database +DEPENDS-ON cache +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +// Athena auto-applies: restart=unless-stopped for webapp type +// TODO: Add replicas: 3 for load balancing when Athena supports it END SERVICE -SERVICE mongodb -IMAGE-ID "mongo:7.0" -PORT-MAPPING 27017 TO 27017 -ENV-VARIABLE {{MONGO_INITDB_ROOT_USERNAME}} -ENV-VARIABLE {{MONGO_INITDB_ROOT_PASSWORD}} -ENV-VARIABLE {{MONGO_INITDB_DATABASE}} -VOLUME-MAPPING "./data/mongodb" TO "/data/db" -VOLUME-MAPPING "./mongo-init" TO "/docker-entrypoint-initdb.d" (ro) -RESTART-POLICY always -RESOURCE-LIMITS CPU "0.7" MEMORY "1024M" -HEALTH-CHECK "mongosh --eval 'db.adminCommand(ping)' || exit 1" +// Product catalog service - Stateless, horizontally scalable +SERVICE product_service +BUILD-ARGS CATALOG_VERSION="v1.5" SEARCH_ENGINE="elasticsearch" +// No PORT-MAPPING for production - internal access only +ENV-VARIABLE {{DATABASE_URL}} +RESOURCE-LIMITS CPU "0.3" MEMORY "256M" +DEPENDS-ON database +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +// TODO: Add replicas: 2 for high availability when Athena supports it +END SERVICE + +// User authentication service - Stateless, scalable microservice +SERVICE auth_service +BUILD-ARGS AUTH_PROVIDER="oauth2" SESSION_TIMEOUT="1h" +// No PORT-MAPPING for production - internal access only +ENV-VARIABLE {{JWT_SECRET}} +ENV-VARIABLE {{DATABASE_URL}} +RESOURCE-LIMITS CPU "0.2" MEMORY "256M" +DEPENDS-ON database +HEALTH-CHECK "curl -f http://localhost:3000/health || exit 1" +// TODO: Add replicas: 2 for authentication redundancy when Athena supports it +END SERVICE + +// Main database - PRODUCTION: No external ports, internal network only +SERVICE database +IMAGE-ID "postgres:15" +// No PORT-MAPPING in production - internal network communication only +// Use 'docker exec' or port forwarding for debugging if needed +ENV-VARIABLE {{POSTGRES_USER}} +ENV-VARIABLE {{POSTGRES_PASSWORD}} +ENV-VARIABLE {{POSTGRES_DB}} +// Using named volume for production persistence +VOLUME-MAPPING "postgres_data" TO "/var/lib/postgresql/data" +RESOURCE-LIMITS CPU "1.0" MEMORY "1024M" +HEALTH-CHECK "pg_isready -U ${POSTGRES_USER} || exit 1" +// Athena auto-applies: restart=always for database type +// TODO: Add replicas: 1 for database (single master) when Athena supports it END SERVICE -SERVICE redis_cache +// Redis cache - PRODUCTION: No external ports, internal network only +SERVICE cache IMAGE-ID "redis:7-alpine" -PORT-MAPPING 6379 TO 6379 -VOLUME-MAPPING "./data/redis" TO "/data" -COMMAND "redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru" -RESTART-POLICY always -RESOURCE-LIMITS CPU "0.3" MEMORY "256M" +/* No PORT-MAPPING in production - internal network communication only +Use 'docker exec' or port forwarding for debugging if needed +Using named volume for production persistence */ +VOLUME-MAPPING "redis_data" TO "/data" +RESOURCE-LIMITS CPU "0.3" MEMORY "512M" HEALTH-CHECK "redis-cli ping || exit 1" +// Athena auto-applies: restart=always for cache type +// TODO: Add replicas: 1 for cache (single instance) when Athena supports it END SERVICE +// Monitoring service - Independent scraping configuration SERVICE monitoring IMAGE-ID "prom/prometheus:latest" PORT-MAPPING 9090 TO 9090 -VOLUME-MAPPING "./monitoring/prometheus.yml" TO "/etc/prometheus/prometheus.yml" (ro) -VOLUME-MAPPING "./data/prometheus" TO "/prometheus" -DEPENDS-ON backend -RESTART-POLICY always +VOLUME-MAPPING "./prometheus/prometheus.yml" TO "/etc/prometheus/prometheus.yml" (ro) +// No DEPENDS-ON - Prometheus handles service discovery and scraping independently +// Configure prometheus.yml to scrape targets via service names RESOURCE-LIMITS CPU "0.3" MEMORY "512M" -END SERVICE - -SERVICE logs_collector -IMAGE-ID "fluent/fluent-bit:latest" -VOLUME-MAPPING "./fluent-bit/fluent-bit.conf" TO "/fluent-bit/etc/fluent-bit.conf" (ro) -VOLUME-MAPPING "/var/log" TO "/var/log" (ro) -DEPENDS-ON backend -RESTART-POLICY unless-stopped -RESOURCE-LIMITS CPU "0.2" MEMORY "128M" +// TODO: Add replicas: 2 for HA monitoring when Athena supports it END SERVICE diff --git a/src/athena/generator/compose.rs b/src/athena/generator/compose.rs index 1471b8c..f26910f 100644 --- a/src/athena/generator/compose.rs +++ b/src/athena/generator/compose.rs @@ -2,11 +2,11 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use super::defaults::{DefaultsEngine, EnhancedDockerService}; +use crate::athena::dockerfile::{analyze_dockerfile, validate_build_args_against_dockerfile}; use crate::athena::error::{ AthenaError, AthenaResult, EnhancedValidationError, ValidationErrorType, }; use crate::athena::parser::ast::*; -use crate::athena::dockerfile::{analyze_dockerfile, validate_build_args_against_dockerfile}; #[derive(Debug, Serialize, Deserialize)] pub struct DockerCompose { @@ -127,7 +127,10 @@ fn create_optimized_volumes(volume_defs: &[VolumeDefinition]) -> HashMap AthenaResult<()> { +fn validate_compose_enhanced( + compose: &DockerCompose, + athena_file: &AthenaFile, +) -> AthenaResult<()> { // Pre-allocate for better performance let service_names: std::collections::HashSet = compose.services.keys().cloned().collect(); @@ -352,7 +355,7 @@ fn validate_dockerfile_build_args(athena_file: &AthenaFile) -> AthenaResult<()> if let Some(build_args) = &service.build_args { // Try to find and analyze the Dockerfile let dockerfile_path = "Dockerfile"; // Default path - + match analyze_dockerfile(dockerfile_path) { Ok(dockerfile_analysis) => { // Validate BUILD-ARGS against Dockerfile ARGs @@ -379,7 +382,10 @@ fn validate_dockerfile_build_args(athena_file: &AthenaFile) -> AthenaResult<()> } Err(e) => { // Validation process failed, but we don't want to block builds - eprintln!("Warning: Could not validate BUILD-ARGS for service '{}': {}", service.name, e); + eprintln!( + "Warning: Could not validate BUILD-ARGS for service '{}': {}", + service.name, e + ); } } } @@ -392,7 +398,7 @@ fn validate_dockerfile_build_args(athena_file: &AthenaFile) -> AthenaResult<()> } } } - + Ok(()) } @@ -446,7 +452,7 @@ fn add_enhanced_yaml_comments(yaml: String, athena_file: &AthenaFile) -> String env!("CARGO_PKG_VERSION"), athena_file.get_project_name() )); - result.push_str("# Developed by UNFAIR Team\n"); + result.push_str("# Developed by UNFAIR Team: https://github.com/Jeck0v/Athena \n"); if let Some(deployment) = &athena_file.deployment { if let Some(version) = &deployment.version_id { @@ -459,7 +465,9 @@ fn add_enhanced_yaml_comments(yaml: String, athena_file: &AthenaFile) -> String chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC") )); - result.push_str("# Features: Intelligent defaults, optimized networking, enhanced health checks\n\n"); + result.push_str( + "# Features: Intelligent defaults, optimized networking, enhanced health checks\n\n", + ); // Add service count and optimization info let service_count = athena_file.services.services.len(); diff --git a/src/athena/parser/grammar.pest b/src/athena/parser/grammar.pest index 099b0b6..0c019be 100644 --- a/src/athena/parser/grammar.pest +++ b/src/athena/parser/grammar.pest @@ -1,5 +1,9 @@ WHITESPACE = _{ " " | "\t" | "\r" | "\n" } -COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } +COMMENT = _{ + line_comment | block_comment +} +line_comment = _{ "//" ~ (!"\n" ~ ANY)* } +block_comment = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" } athena_file = { SOI ~ deployment_section? ~ environment_section? ~ services_section ~ EOI } diff --git a/src/athena/parser/parser.rs b/src/athena/parser/parser.rs index 852fe8e..bb38d1e 100644 --- a/src/athena/parser/parser.rs +++ b/src/athena/parser/parser.rs @@ -461,8 +461,15 @@ fn create_enhanced_parse_error( Some("Use BUILD-ARGS KEY=\"value\" KEY2=\"value2\" format, e.g., BUILD-ARGS NODE_VERSION=\"20\" BUILD_ENV=\"production\"".to_string()) ) } else { + // Check for unclosed comment errors + if file_content.contains("/*") && file_content.matches("/*").count() != file_content.matches("*/").count() { + ( + "Unclosed multi-line comment".to_string(), + Some("Multi-line comments must be closed with '*/'. Each '/*' must have a matching '*/'".to_string()) + ) + } // Check for common missing END SERVICE error - if base_message.contains("end of input") || base_message.contains("EOI") { + else if base_message.contains("end of input") || base_message.contains("EOI") { ( "Missing 'END SERVICE' statement".to_string(), Some("Each SERVICE block must be closed with 'END SERVICE'".to_string()) diff --git a/tests/fixtures/comments_test.ath b/tests/fixtures/comments_test.ath new file mode 100644 index 0000000..153fe82 --- /dev/null +++ b/tests/fixtures/comments_test.ath @@ -0,0 +1,36 @@ +// Single-line comment at the start +DEPLOYMENT-ID COMMENTS_TEST +VERSION-ID 1.0.0 + +/* + * Multi-line comment block + * describing the project setup + */ + +ENVIRONMENT SECTION +NETWORK-NAME comments_network // Inline comment + +SERVICES SECTION + +// Web server service +SERVICE web +IMAGE-ID "nginx:alpine" // Using Alpine for smaller size +PORT-MAPPING 80 TO 80 // HTTP port +/* + * This service serves static content + * and acts as a reverse proxy + */ +DEPENDS-ON api +RESTART-POLICY unless-stopped // Restart policy +END SERVICE + +SERVICE api +// API service configuration +IMAGE-ID "python:3.11" +PORT-MAPPING 8000 TO 8000 +ENV-VARIABLE {{API_KEY}} // API key environment variable +ENV-VARIABLE {{DATABASE_URL}} // Database connection +RESTART-POLICY always /* Always restart API */ +END SERVICE + +// End of configuration \ No newline at end of file diff --git a/tests/integration/structural/comments.rs b/tests/integration/structural/comments.rs new file mode 100644 index 0000000..4e3d2a2 --- /dev/null +++ b/tests/integration/structural/comments.rs @@ -0,0 +1,188 @@ +use super::*; +use std::fs; +use tempfile::TempDir; + +#[test] +fn test_single_line_comments() { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let content = r#" +// This is a single-line comment +DEPLOYMENT-ID COMMENT_TEST + +SERVICES SECTION + +// Comment before service +SERVICE test_service +IMAGE-ID "nginx:alpine" // Inline comment +PORT-MAPPING 80 TO 80 // Port comment +END SERVICE +// Comment after service +"#; + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", content); + let yaml = run_athena_build_and_parse(&ath_file).expect("Failed to parse YAML"); + + // Verify the structure is correct despite comments + assert!(yaml["services"].is_mapping()); + assert!(yaml["services"]["test_service"].is_mapping()); + assert_eq!(yaml["services"]["test_service"]["image"].as_str().unwrap(), "nginx:alpine"); + assert_eq!(yaml["services"]["test_service"]["ports"][0].as_str().unwrap(), "80:80"); +} + +#[test] +fn test_multi_line_comments() { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let content = r#" +/* + * Multi-line comment at the top + * describing the configuration + */ +DEPLOYMENT-ID MULTILINE_TEST + +SERVICES SECTION + +SERVICE api +/* + * This is the API service + * that handles all requests + */ +IMAGE-ID "python:3.11" +PORT-MAPPING 8000 TO 8000 +/* + * Environment variables for the API + */ +ENV-VARIABLE {{API_KEY}} +END SERVICE +"#; + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", content); + let yaml = run_athena_build_and_parse(&ath_file).expect("Failed to parse YAML"); + + // Verify the structure is correct despite multi-line comments + assert!(yaml["services"]["api"].is_mapping()); + assert_eq!(yaml["services"]["api"]["image"].as_str().unwrap(), "python:3.11"); + assert_eq!(yaml["services"]["api"]["ports"][0].as_str().unwrap(), "8000:8000"); + assert_eq!(yaml["services"]["api"]["environment"][0].as_str().unwrap(), "API_KEY=${API_KEY}"); +} + +#[test] +fn test_mixed_comments() { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let content = r#" +// Single line comment +DEPLOYMENT-ID MIXED_COMMENTS +/* Block comment */ + +ENVIRONMENT SECTION +NETWORK-NAME test_network // Inline comment + +SERVICES SECTION + +SERVICE web +IMAGE-ID "nginx:alpine" /* inline block comment */ +PORT-MAPPING 80 TO 80 +/* + * Dependencies section + */ +DEPENDS-ON api // dependency comment +END SERVICE + +// API service with mixed comments +SERVICE api +IMAGE-ID "python:3.11" +/* Multi-line + comment inside + service block */ +PORT-MAPPING 8000 TO 8000 +ENV-VARIABLE {{DATABASE_URL}} // Database URL +RESTART-POLICY always /* always restart */ +END SERVICE +"#; + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", content); + let yaml = run_athena_build_and_parse(&ath_file).expect("Failed to parse YAML"); + + // Verify both services are present and configured correctly + assert!(yaml["services"]["web"].is_mapping()); + assert!(yaml["services"]["api"].is_mapping()); + assert_eq!(yaml["services"]["web"]["depends_on"][0].as_str().unwrap(), "api"); + assert_eq!(yaml["services"]["api"]["restart"].as_str().unwrap(), "always"); + assert_eq!(yaml["networks"]["test_network"]["driver"].as_str().unwrap(), "bridge"); +} + +#[test] +fn test_comments_with_complex_content() { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + + // Use the fixture file we created + let fixture_content = fs::read_to_string("tests/fixtures/comments_test.ath") + .expect("Failed to read comments test fixture"); + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", &fixture_content); + let yaml = run_athena_build_and_parse(&ath_file).expect("Failed to parse YAML"); + + // Verify complex structure with comments + assert!(yaml["services"]["web"].is_mapping()); + assert!(yaml["services"]["api"].is_mapping()); + assert_eq!(yaml["services"]["web"]["depends_on"][0].as_str().unwrap(), "api"); + assert_eq!(yaml["services"]["web"]["restart"].as_str().unwrap(), "unless-stopped"); + assert_eq!(yaml["services"]["api"]["restart"].as_str().unwrap(), "always"); + assert_eq!(yaml["networks"]["comments_network"]["driver"].as_str().unwrap(), "bridge"); +} + +#[test] +fn test_comment_edge_cases() { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let content = r#" +// Comment with special characters: !@#$%^&*() +DEPLOYMENT-ID EDGE_CASE_TEST + +SERVICES SECTION + +SERVICE test +// Comment with quotes: "hello" and 'world' +IMAGE-ID "nginx:alpine" +/* Comment with slashes and other symbols */ +PORT-MAPPING 80 TO 80 +END SERVICE + +// Final comment with unicode: cafΓ©, rΓ©sumΓ©, naΓ―ve +"#; + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", content); + let yaml = run_athena_build_and_parse(&ath_file).expect("Failed to parse YAML"); + + // Verify parsing works with edge case comments + assert!(yaml["services"]["test"].is_mapping()); + assert_eq!(yaml["services"]["test"]["image"].as_str().unwrap(), "nginx:alpine"); +} + +#[test] +fn test_unclosed_comment_error() { + use assert_cmd::Command; + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let content = r#" +DEPLOYMENT-ID BROKEN_TEST + +SERVICES SECTION + +SERVICE test +/* Unclosed comment +IMAGE-ID "nginx:alpine" +END SERVICE +"#; + + let ath_file = create_test_ath_file(&temp_dir, "test.ath", content); + + let mut cmd = Command::cargo_bin("athena").expect("Failed to find athena binary"); + let result = cmd.arg("validate") + .arg(&ath_file) + .output() + .expect("Failed to execute command"); + + // Should fail with specific comment error + assert!(!result.status.success()); + let stderr = String::from_utf8_lossy(&result.stderr); + assert!(stderr.contains("Unclosed multi-line comment")); + assert!(stderr.contains("Multi-line comments must be closed with '*/'")); +} \ No newline at end of file diff --git a/tests/integration/structural/mod.rs b/tests/integration/structural/mod.rs index f989ff9..e14fa48 100644 --- a/tests/integration/structural/mod.rs +++ b/tests/integration/structural/mod.rs @@ -10,6 +10,7 @@ pub mod networking; pub mod policies; pub mod formatting; pub mod complex_scenarios; +pub mod comments; /// Create a test .ath file with given content pub fn create_test_ath_file(temp_dir: &TempDir, filename: &str, content: &str) -> String {