## Validatrix - Universal Source Code Validation Framework ## ## A comprehensive, extensible validation framework for all common ## programming languages and file formats. Provides tokenizer-based ## analysis, auto-detection of file flavor, JSON diagnostics, and ## consistent error reporting. ## ## Usage: ## import validatrix ## let r = validateSource("print('hello')", flavor=lfPython) ## let jsonResult = result.toJson() ## ## Or with auto-detection: ## let r = validateFile("script.py") ## ## See README.md for full documentation. import std/[json, strutils, os, strformat, sequtils, algorithm] # Core types and base classes — imported and re-exported for public API consumers import validatrix/core/types import validatrix/core/tokenizerbase import validatrix/core/validatorbase import validatrix/core/detector import validatrix/core/debug import validatrix/reporting/errors export types export tokenizerbase export validatorbase export errors # Import all language validators (they register themselves on import) {.warning[UnusedImport]:off.} import validatrix/languages/nim_validator import validatrix/languages/bash_validator import validatrix/languages/python_validator import validatrix/languages/javascript_validator import validatrix/languages/php_validator import validatrix/languages/html_validator import validatrix/languages/jinja_validator import validatrix/languages/config_validators {.warning[UnusedImport]:on.} # --------------------------------------------------------------------------- # Public API - Validate from source string # --------------------------------------------------------------------------- proc validateSource*( source: string, flavor: LanguageFlavor = lfUnknown, options: ValidationOptions = newValidationOptions(), sourcePath: string = "" ): ValidationResult = ## Validate source code from a string. ## ## If flavor is lfUnknown (default), auto-detection is performed. ## Returns a ValidationResult with errors, diagnostics, and metadata. ## ## Example: ## let r = validateSource("proc hello() = echo 'hi'", flavor=lfNim) ## let r = validateSource("var x = 1", flavor=lfUnknown) # auto-detect when defined(validatrixDebug): if options.debugMode and not debugEnabled: enableDebug() debugEnter("VALIDATRIX", "validateSource called") try: var opts = options if flavor != lfUnknown: opts.flavor = flavor # Detect flavor if not explicitly set var detectedFlavor = flavor var detectionMethod = fdmExplicit if detectedFlavor == lfUnknown: let (df, dm, _) = detectFlavor(source, sourcePath, lfUnknown) detectedFlavor = df detectionMethod = dm opts.flavor = detectedFlavor if detectedFlavor == lfUnknown: # Cannot determine language result = newValidationResult() result.valid = false result.flavor = lfUnknown result.detectionMethod = fdmUnknown result.errors.add(newValidationError( esError, "Could not detect language flavor from source content", ErrInvalidSyntax, newSourcePosition(1, 1, 0), newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)), hint = "Specify the flavor explicitly, or add a shebang/extension" )) result.durationMs = 0.0 return result # Create validator and run validation let validator = createValidator(detectedFlavor, source, opts, sourcePath) result = validator.validate() result.flavor = detectedFlavor result.detectionMethod = detectionMethod # Ensure module info is populated result.moduleInfo.flavor = detectedFlavor result.moduleInfo.detectionMethod = detectionMethod except Exception as e: result = newValidationResult() result.valid = false result.errors.add(newValidationError( esCritical, &"Validation framework error: {e.msg}", "E9999", newSourcePosition(0, 0, 0), newSourceRange(newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0)) )) when defined(validatrixDebug): if options.debugMode: debugLeave("VALIDATRIX", &"Validation complete: valid={result.valid}, errors={result.errors.len}") result.debugOutput = getDebugOutput() # --------------------------------------------------------------------------- # Public API - Validate from file # --------------------------------------------------------------------------- proc validateFile*( filePath: string, flavor: LanguageFlavor = lfUnknown, options: ValidationOptions = newValidationOptions() ): ValidationResult = ## Validate a source file from disk. ## ## Auto-detects flavor from file extension (and content if needed). ## Returns ValidationResult with errors, diagnostics, and metadata. ## ## Example: ## let r = validateFile("src/main.nim") ## let r = validateFile("template.html.jinja", flavor=lfJinja) when defined(validatrixDebug): if options.debugMode: debugEnter("VALIDATRIX", &"validateFile: {filePath}") try: if not fileExists(filePath): result = newValidationResult() result.valid = false result.errors.add(newValidationError( esError, &"File not found: {filePath}", "E0011", newSourcePosition(0, 0, 0), newSourceRange(newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0)) )) return result let source = readFile(filePath) # If flavor is not set, detect from extension var detectedFlavor = flavor if detectedFlavor == lfUnknown: let ext {.used.} = splitFile(filePath).ext.toLowerAscii() let (df, _, _) = detectFlavor(source, filePath, lfUnknown) detectedFlavor = df result = validateSource(source, detectedFlavor, options, filePath) when defined(validatrixDebug): if options.debugMode: debugLeave("VALIDATRIX", &"File validation complete: {result.errors.len} errors") except Exception as e: result = newValidationResult() result.valid = false result.errors.add(newValidationError( esCritical, &"File validation error: {e.msg}", "E9999", newSourcePosition(0, 0, 0), newSourceRange(newSourcePosition(0, 0, 0), newSourcePosition(0, 0, 0)) )) # --------------------------------------------------------------------------- # Public API - Inspection / Diagnostics # --------------------------------------------------------------------------- proc inspectSource*( source: string, flavor: LanguageFlavor = lfUnknown, options: ValidationOptions = newValidationOptions(), sourcePath: string = "" ): JsonNode = ## Inspect source code and return structural diagnostics as JSON. ## ## Returns a JSON object with all variables, functions, classes, ## imports, and other structural information found in the source. ## ## Example: ## let diag = inspectSource("class Foo: pass", lfPython) ## echo $diag # JSON with class/function info let r = validateSource(source, flavor, options, sourcePath) if r.valid or r.errors.len > 0: r.moduleInfo.toJson() else: %*{"error": "Validation failed", "details": r.errors.mapIt(it.toJson())} proc inspectFile*( filePath: string, flavor: LanguageFlavor = lfUnknown, options: ValidationOptions = newValidationOptions() ): JsonNode = ## Inspect a source file and return structural diagnostics as JSON. let r = validateFile(filePath, flavor, options) if r.valid or r.errors.len > 0: r.moduleInfo.toJson() else: %*{"error": "Validation failed", "details": r.errors.mapIt(it.toJson())} # --------------------------------------------------------------------------- # Public API - Additional helpers # --------------------------------------------------------------------------- proc validateSourceJson*(source: string, flavor: LanguageFlavor = lfUnknown): string = ## Validate source and return result as JSON string. validateSource(source, flavor).toJson().pretty() proc validateFileJson*(filePath: string, flavor: LanguageFlavor = lfUnknown): string = ## Validate file and return result as JSON string. validateFile(filePath, flavor).toJson().pretty() proc reportSource*(source: string, flavor: LanguageFlavor = lfUnknown): string = ## Get a human-readable validation report for source code. let r = validateSource(source, flavor) formatReport(r, source) proc reportFile*(filePath: string, flavor: LanguageFlavor = lfUnknown): string = ## Get a human-readable validation report for a file. let source = if fileExists(filePath): readFile(filePath) else: "" let r = validateFile(filePath, flavor) formatReport(r, source) proc detectFlavor*(source: string, filePath: string = ""): LanguageFlavor = ## Detect language flavor of source code. ## Convenience wrapper around core detector. let (flavor, _, _) = detector.detectFlavor(source, filePath) return flavor # --------------------------------------------------------------------------- # Version information # --------------------------------------------------------------------------- const validatrixVersion* = "0.1.0" validatrixDescription* = "Universal Source Code Validation Framework" proc version*(): string = ## Get the Validatrix version string. validatrixVersion proc supportedFlavors*(): seq[string] = ## Get list of all supported language flavors. result = @[] for key in validatorRegistry.keys: result.add(key) result.sort(system.cmp[string]) # --------------------------------------------------------------------------- # Debug API # --------------------------------------------------------------------------- proc enableDebug*() = ## Enable debug mode globally. debug.enableDebug() proc disableDebug*() = ## Disable debug mode globally. debug.disableDebug() # --------------------------------------------------------------------------- # When compiled as standalone executable # --------------------------------------------------------------------------- when isMainModule: proc printUsage() = echo &"Validatrix v{validatrixVersion} - {validatrixDescription}" echo "" echo "Usage:" echo " validatrix validate [--file= | --source=] [--flavor=]" echo " validatrix inspect [--file= | --source=] [--flavor=]" echo " validatrix detect [--file= | --source=]" echo " validatrix help" echo "" echo "Options:" echo " --file= Source file to process" echo " --source= Source code string to process" echo " --flavor= Language flavor (auto-detect if omitted)" echo " --debug Enable debug mode" echo " --json Output as JSON (default for inspect)" echo "" echo "Supported flavors:" for f in supportedFlavors(): echo &" - {f}" proc main() = let args = commandLineParams() if args.len == 0: printUsage() return let cmd = args[0].toLowerAscii() case cmd of "validate", "val": var filePath, source, flavor = "" var debugMode = false var i = 1 while i < args.len: let a = args[i] if a.startsWith("--file="): filePath = a[7..^1] elif a.startsWith("--source="): source = a[9..^1] elif a.startsWith("--flavor="): flavor = a[9..^1] elif a == "--debug": debugMode = true i += 1 var opts = newValidationOptions() if debugMode: opts.debugMode = true var fl = lfUnknown if flavor.len > 0: try: fl = parseEnum[LanguageFlavor](flavor) except: discard var r: ValidationResult if filePath.len > 0: r = validateFile(filePath, fl, opts) elif source.len > 0: r = validateSource(source, fl, opts, "stdin") else: # Read from stdin let input = readAll(stdin).strip() r = validateSource(input, fl, opts, "stdin") echo formatReport(r, r.moduleInfo.toJson().pretty()) of "inspect", "ins": var filePath, source, flavor = "" var debugMode = false var i = 1 while i < args.len: let a = args[i] if a.startsWith("--file="): filePath = a[7..^1] elif a.startsWith("--source="): source = a[9..^1] elif a.startsWith("--flavor="): flavor = a[9..^1] elif a == "--debug": debugMode = true i += 1 var opts = newValidationOptions() if debugMode: opts.debugMode = true var fl = lfUnknown if flavor.len > 0: try: fl = parseEnum[LanguageFlavor](flavor) except: discard var jsonResult: JsonNode if filePath.len > 0: jsonResult = inspectFile(filePath, fl, opts) elif source.len > 0: jsonResult = inspectSource(source, fl, opts, "stdin") else: let input = readAll(stdin).strip() jsonResult = inspectSource(input, fl, opts, "stdin") echo jsonResult.pretty() of "detect": var filePath, source = "" var i = 1 while i < args.len: let a = args[i] if a.startsWith("--file="): filePath = a[7..^1] elif a.startsWith("--source="): source = a[9..^1] i += 1 var src = source if filePath.len > 0 and fileExists(filePath): src = readFile(filePath) let (dflavor, dmethod, dconfidence) = detector.detectFlavor(src, filePath) echo &"Detected flavor: {dflavor}" echo &" Method: {dmethod}" echo &" Confidence: {dconfidence * 100:.2f}%" of "version", "--version", "-v": echo &"Validatrix v{validatrixVersion}" of "help", "--help", "-h": printUsage() else: echo &"Unknown command: {cmd}" printUsage() main()