# SPDX-License-Identifier: LSL-1.0 # Copyright (c) 2026 Markus Maiwald # Stewardship: Self Sovereign Society Foundation # # This file is part of the Nexus Sovereign Core. # See legal/LICENSE_SOVEREIGN.md for license terms. ## variant_migration.nim ## Migration utilities for transitioning from legacy USE flags to variant domains ## Task 15: Legacy flag translation and migration warnings import std/[tables, strutils, os, strformat] import variant_domains import config type MigrationResult* = object ## Result of flag migration success*: bool translatedFlags*: Table[string, seq[string]] # domain -> flags warnings*: seq[string] errors*: seq[string] skippedFlags*: seq[string] # Flags that couldn't be migrated LegacyFlagInfo* = object ## Information about a legacy flag name*: string category*: string enabled*: bool suggestedDomain*: string suggestedFlag*: string # ############################################################################# # Legacy Flag Detection # ############################################################################# proc detectLegacyFlags*(flags: seq[UseFlag]): seq[LegacyFlagInfo] = ## Detect legacy USE flags and provide migration suggestions result = @[] for flag in flags: if isLegacyCategory(flag.category): let suggestedDomain = mapLegacyCategoryToDomain(flag.category) var info = LegacyFlagInfo( name: flag.name, category: flag.category, enabled: flag.enabled, suggestedDomain: suggestedDomain, suggestedFlag: flag.name ) result.add(info) proc isLegacyFlagString*(flagStr: string): bool = ## Check if a flag string uses legacy syntax ## Legacy: category/flag or just flag ## New: +domain=flag if flagStr.startsWith("+"): return false # New syntax if '/' in flagStr: return true # Old category/flag syntax # Could be either - assume legacy if no domain marker return true # ############################################################################# # Flag Translation # ############################################################################# proc translateLegacyFlag*( flagName: string, category: string ): tuple[domain: string, flag: string, success: bool] = ## Translate a single legacy flag to new domain syntax if not isLegacyCategory(category): return ("", "", false) let domain = mapLegacyCategoryToDomain(category) # Special cases that shouldn't be migrated (returns empty string) if domain.len == 0: return ("", "", false) return (domain, flagName, true) proc translateLegacyFlags*(flags: seq[UseFlag]): MigrationResult = ## Translate a list of legacy USE flags to domain-scoped flags result = MigrationResult( success: true, translatedFlags: initTable[string, seq[string]](), warnings: @[], errors: @[], skippedFlags: @[] ) for flag in flags: if not flag.enabled: continue # Skip disabled flags if not isLegacyCategory(flag.category): # Not a legacy flag - skip result.warnings.add(fmt"Skipping non-legacy flag: {flag.name} (category: {flag.category})") continue let (domain, translatedFlag, success) = translateLegacyFlag(flag.name, flag.category) if not success: result.skippedFlags.add(fmt"{flag.category}/{flag.name}") result.warnings.add(fmt"Cannot migrate {flag.category}/{flag.name} - special category") continue # Add to translated flags if not result.translatedFlags.hasKey(domain): result.translatedFlags[domain] = @[] if translatedFlag notin result.translatedFlags[domain]: result.translatedFlags[domain].add(translatedFlag) proc translateFlagString*(flagStr: string): string = ## Translate a single flag string from legacy to new syntax ## Examples: ## "gui/wayland" -> "+graphics=wayland" ## "optimization/lto" -> "+optimization=lto" if flagStr.startsWith("+"): return flagStr # Already new syntax if '/' in flagStr: let parts = flagStr.split('/', 1) if parts.len == 2: let category = parts[0] let flag = parts[1] if isLegacyCategory(category): let domain = mapLegacyCategoryToDomain(category) if domain notin ["profile", "build-mode"]: return fmt"+{domain}={flag}" # Couldn't translate - return as-is with warning marker return flagStr # ############################################################################# # Migration Warnings # ############################################################################# proc generateMigrationWarning*(flag: LegacyFlagInfo): string = ## Generate a deprecation warning for a legacy flag if flag.suggestedDomain in ["profile", "build-mode"]: return fmt"âš ī¸ Legacy flag '{flag.category}/{flag.name}' uses deprecated category. " & fmt"This should be handled as a {flag.suggestedDomain} instead." return fmt"âš ī¸ Legacy flag '{flag.category}/{flag.name}' is deprecated. " & fmt"Use: +{flag.suggestedDomain}={flag.suggestedFlag}" proc generateMigrationSummary*(migrationResult: MigrationResult): string = ## Generate a human-readable summary of migration results var lines: seq[string] = @[] lines.add("🔄 Legacy Flag Migration Summary") lines.add("") if migrationResult.translatedFlags.len > 0: lines.add("✅ Translated Flags:") for domain, flags in migrationResult.translatedFlags.pairs: let flagsStr = flags.join(", ") lines.add(fmt" {domain}: {flagsStr}") lines.add("") if migrationResult.skippedFlags.len > 0: lines.add("â­ī¸ Skipped Flags:") for flag in migrationResult.skippedFlags: lines.add(fmt" {flag}") lines.add("") if migrationResult.warnings.len > 0: lines.add("âš ī¸ Warnings:") for warning in migrationResult.warnings: lines.add(fmt" {warning}") lines.add("") if migrationResult.errors.len > 0: lines.add("❌ Errors:") for error in migrationResult.errors: lines.add(fmt" {error}") lines.add("") return lines.join("\n") # ############################################################################# # Config File Migration # ############################################################################# proc migrateConfigFile*( inputPath: string, outputPath: string = "" ): tuple[success: bool, message: string] = ## Migrate a config file from legacy USE flags to domain syntax ## If outputPath is empty, overwrites the input file let actualOutputPath = if outputPath.len > 0: outputPath else: inputPath if not fileExists(inputPath): return (false, fmt"Input file not found: {inputPath}") try: let content = readFile(inputPath) var newLines: seq[string] = @[] var migrationCount = 0 for line in content.splitLines(): let trimmed = line.strip() # Skip comments and empty lines if trimmed.len == 0 or trimmed.startsWith("#"): newLines.add(line) continue # Check if line contains legacy flag syntax if '/' in trimmed and not trimmed.startsWith("+"): # Try to translate let translated = translateFlagString(trimmed) if translated != trimmed: newLines.add(translated) migrationCount += 1 continue # Keep line as-is newLines.add(line) # Write output writeFile(actualOutputPath, newLines.join("\n")) if migrationCount > 0: return (true, fmt"✅ Migrated {migrationCount} flag(s) in {actualOutputPath}") else: return (true, fmt"â„šī¸ No legacy flags found in {inputPath}") except IOError as e: return (false, fmt"Failed to migrate config: {e.msg}") proc createMigrationBackup*(filePath: string): bool = ## Create a backup of a file before migration if not fileExists(filePath): return false let backupPath = filePath & ".backup" try: copyFile(filePath, backupPath) return true except: return false # ############################################################################# # CLI Helper Functions # ############################################################################# proc suggestDomainFlags*(legacyFlags: seq[string]): seq[string] = ## Suggest domain-scoped equivalents for legacy flags result = @[] for flagStr in legacyFlags: let translated = translateFlagString(flagStr) if translated != flagStr: result.add(translated) else: result.add(flagStr) # Keep as-is if can't translate proc printMigrationHelp*() = ## Print help for migration command echo """ 🔄 NIP Flag Migration Utility USAGE: nip migrate-flags [options] [file] OPTIONS: --dry-run Show what would be migrated without making changes --backup Create backup before migration (default: true) --output Write to different file instead of overwriting --help Show this help EXAMPLES: # Migrate config file nip migrate-flags ~/.nip/config # Dry run to see changes nip migrate-flags --dry-run ~/.nip/config # Migrate to new file nip migrate-flags --output new-config.conf old-config.conf LEGACY CATEGORIES → NEW DOMAINS: gui → graphics gaming → graphics container → integration virtualization → integration mesh → network ai-ml → runtime bindings → runtime features → runtime init → init (unchanged) audio → audio (unchanged) optimization → optimization (unchanged) security → security (unchanged) SYNTAX CHANGES: OLD: gui/wayland NEW: +graphics=wayland OLD: optimization/lto NEW: +optimization=lto """