322 lines
9.5 KiB
Nim
322 lines
9.5 KiB
Nim
# 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 <file> 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
|
||
"""
|