278 lines
9.2 KiB
Text
278 lines
9.2 KiB
Text
# | Nickel to Nickel Migration Helper
|
|
# | Automates pattern detection and application
|
|
# | Follows: .claude/kcl_to_nickel_migration_framework.md
|
|
# | Author: Migration Framework
|
|
# | Date: 2025-12-15
|
|
|
|
# ============================================================
|
|
# Pattern Detection
|
|
# ============================================================
|
|
|
|
# Detect if Nickel file uses schema inheritance pattern
|
|
export def "detect-inheritance" [decl_file: path] -> bool {
|
|
let content = open $decl_file | into string
|
|
($content | str contains "schema ") and ($content | str contains "(")
|
|
}
|
|
|
|
# Detect if Nickel file exports global instances
|
|
export def "detect-exports" [decl_file: path] -> list {
|
|
let content = open $decl_file | into string
|
|
$content
|
|
| lines
|
|
| filter { |line| ($line | str contains ": ") and not ($line | str contains "schema") }
|
|
| filter { |line| ($line | str contains " = ") }
|
|
| map { |line| $line | str trim }
|
|
}
|
|
|
|
# Detect if Nickel file only defines schemas (no exports)
|
|
export def "is-schema-only" [decl_file: path] -> bool {
|
|
let exports = (detect-exports $decl_file)
|
|
($exports | length) == 0
|
|
}
|
|
|
|
# Get migration template type for Nickel file
|
|
export def "get-template-type" [decl_file: path] -> string {
|
|
let has_inheritance = (detect-inheritance $decl_file)
|
|
let is_empty_export = (is-schema-only $decl_file)
|
|
let exports = (detect-exports $decl_file)
|
|
let export_count = ($exports | length)
|
|
|
|
if $is_empty_export {
|
|
"template-1-schema-only"
|
|
} else if $has_inheritance {
|
|
"template-4-inheritance"
|
|
} else if $export_count == 1 {
|
|
"template-2-single-instance"
|
|
} else if $export_count > 1 {
|
|
"template-5-multiple-schemas"
|
|
} else {
|
|
"template-3-complex-nesting"
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# Value Conversion
|
|
# ============================================================
|
|
|
|
# Convert Nickel boolean to Nickel
|
|
export def "convert-boolean" [value: string] -> string {
|
|
match ($value | str trim) {
|
|
"True" => "true",
|
|
"False" => "false",
|
|
"true" => "true",
|
|
"false" => "false",
|
|
_ => $value,
|
|
}
|
|
}
|
|
|
|
# Convert Nickel None to Nickel null
|
|
export def "convert-none" [value: string] -> string {
|
|
match ($value | str trim) {
|
|
"None" => "null",
|
|
"null" => "null",
|
|
_ => $value,
|
|
}
|
|
}
|
|
|
|
# Convert Nickel value to Nickel value
|
|
export def "convert-value" [decl_value: string] -> string {
|
|
let trimmed = ($decl_value | str trim)
|
|
let bool_converted = (convert-boolean $trimmed)
|
|
(convert-none $bool_converted)
|
|
}
|
|
|
|
# ============================================================
|
|
# JSON Equivalence Validation
|
|
# ============================================================
|
|
|
|
# Export Nickel file to JSON for comparison
|
|
export def "nickel-to-json" [decl_file: path] {
|
|
if not ($decl_file | path exists) {
|
|
error make {msg: $"Nickel file not found: ($decl_file)"}
|
|
}
|
|
|
|
nickel export $decl_file --format json 2>&1
|
|
}
|
|
|
|
# Export Nickel file to JSON for comparison
|
|
export def "nickel-to-json" [nickel_file: path] {
|
|
if not ($nickel_file | path exists) {
|
|
error make {msg: $"Nickel file not found: ($nickel_file)"}
|
|
}
|
|
|
|
nickel export $nickel_file 2>&1 | from json | to json
|
|
}
|
|
|
|
# Compare Nickel and Nickel JSON outputs for equivalence
|
|
export def "compare-equivalence" [decl_file: path, nickel_file: path] -> bool {
|
|
let source_json = (nickel-to-json $decl_file | from json)
|
|
let nickel_json = (nickel-to-json $nickel_file | from json)
|
|
|
|
$source_json == $nickel_json
|
|
}
|
|
|
|
# Show detailed comparison between Nickel and Nickel
|
|
export def "show-comparison" [decl_file: path, nickel_file: path] {
|
|
print $"Comparing: ($decl_file) ⇄ ($nickel_file)\n"
|
|
|
|
let source_json = (nickel-to-json $decl_file)
|
|
let nickel_json = (nickel-to-json $nickel_file)
|
|
|
|
print "=== Source Output (JSON) ==="
|
|
print $source_json
|
|
print ""
|
|
print "=== Target Output (JSON) ==="
|
|
print $nickel_json
|
|
print ""
|
|
|
|
let equivalent = ($source_json == $nickel_json)
|
|
if $equivalent {
|
|
print "✅ Outputs are EQUIVALENT"
|
|
} else {
|
|
print "❌ Outputs DIFFER"
|
|
print "\nDifferences:"
|
|
diff <(print $source_json | jq -S .) <(print $nickel_json | jq -S .)
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# Migration Workflow
|
|
# ============================================================
|
|
|
|
# Analyze Nickel file and recommend migration approach
|
|
export def "analyze-nickel" [decl_file: path] {
|
|
if not ($decl_file | path exists) {
|
|
error make {msg: $"File not found: ($decl_file)"}
|
|
}
|
|
|
|
let template = (get-template-type $decl_file)
|
|
let has_inheritance = (detect-inheritance $decl_file)
|
|
let exports = (detect-exports $decl_file)
|
|
let is_empty = (is-schema-only $decl_file)
|
|
|
|
print $"File: ($decl_file)"
|
|
print $"Template Type: ($template)"
|
|
print $"Has Schema Inheritance: ($has_inheritance)"
|
|
print $"Is Schema-Only (no exports): ($is_empty)"
|
|
print $"Exported Instances: ($exports | length)"
|
|
|
|
if ($exports | length) > 0 {
|
|
print "\nExported instances:"
|
|
$exports | each { |exp| print $" - ($exp)" }
|
|
}
|
|
}
|
|
|
|
# Generate skeleton Nickel file from Nickel template
|
|
export def "generate-nickel-skeleton" [decl_file: path, output_file: path] {
|
|
let template = (get-template-type $decl_file)
|
|
let source_name = ($decl_file | path basename | str replace ".ncl" "")
|
|
|
|
let skeleton = match $template {
|
|
"template-1-schema-only" => {
|
|
$"# | Schema definitions migrated from ($source_name).ncl\n# | Migrated: 2025-12-15\n\n{{}}"
|
|
},
|
|
"template-2-single-instance" => {
|
|
let exports = (detect-exports $decl_file)
|
|
let instance = ($exports | get 0 | str split " " | get 0)
|
|
$"# | Configuration migrated from ($source_name).ncl\n\n{\n ($instance) = {\n # TODO: Fill in fields\n },\n}"
|
|
},
|
|
_ => {
|
|
$"# | Migrated from ($source_name).ncl\n# | Template: ($template)\n\n{\n # TODO: Implement\n}"
|
|
},
|
|
}
|
|
|
|
print $skeleton
|
|
print $"\nTo save: print output to ($output_file)"
|
|
}
|
|
|
|
# ============================================================
|
|
# Batch Migration
|
|
# ============================================================
|
|
|
|
# Migrate multiple Nickel files to Nickel using templates
|
|
export def "batch-migrate" [
|
|
source_dir: path,
|
|
nickel_dir: path,
|
|
--pattern: string = "*.ncl",
|
|
--dry-run: bool = false,
|
|
] {
|
|
let source_files = (glob $"($source_dir)/($pattern)")
|
|
|
|
print $"Found ($source_files | length) Nickel files matching pattern: ($pattern)"
|
|
print ""
|
|
|
|
$source_files | each { |source_file|
|
|
let relative_path = ($source_file | str replace $"($source_dir)/" "")
|
|
let nickel_file = $"($nickel_dir)/($relative_path | str replace ".ncl" ".ncl")"
|
|
|
|
print $"[$relative_path]"
|
|
let template = (get-template-type $source_file)
|
|
print $" Template: ($template)"
|
|
|
|
if not $dry_run {
|
|
if ($nickel_file | path exists) {
|
|
print $" ⚠️ Already exists: ($nickel_file)"
|
|
} else {
|
|
print $" → Would migrate to: ($nickel_file)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# Validation
|
|
# ============================================================
|
|
|
|
# Validate Nickel file syntax
|
|
export def "validate-nickel" [nickel_file: path] -> bool {
|
|
# Validate Nickel syntax (no try-catch)
|
|
let result = (do { nickel export $nickel_file | null } | complete)
|
|
($result.exit_code == 0)
|
|
}
|
|
|
|
# Full migration validation for a file pair
|
|
export def "validate-migration" [decl_file: path, nickel_file: path] -> record {
|
|
let source_exists = ($decl_file | path exists)
|
|
let nickel_exists = ($nickel_file | path exists)
|
|
let nickel_valid = if $nickel_exists { (validate-nickel $nickel_file) } else { false }
|
|
let equivalent = if ($source_exists and $nickel_valid) {
|
|
(compare-equivalence $decl_file $nickel_file)
|
|
} else {
|
|
false
|
|
}
|
|
|
|
{
|
|
source_exists: $source_exists,
|
|
nickel_exists: $nickel_exists,
|
|
nickel_valid: $nickel_valid,
|
|
outputs_equivalent: $equivalent,
|
|
status: if $equivalent { "✅ PASS" } else { "❌ FAIL" },
|
|
}
|
|
}
|
|
|
|
# Validation report for all migrated files
|
|
export def "validation-report" [source_dir: path, nickel_dir: path] {
|
|
let nickel_files = (glob $"($nickel_dir)/**/*.ncl")
|
|
|
|
print $"Validation Report: ($nickel_files | length) Nickel files\n"
|
|
|
|
let results = $nickel_files | map { |nickel_file|
|
|
let relative = ($nickel_file | str replace $"($nickel_dir)/" "")
|
|
let source_file = $"($source_dir)/($relative | str replace ".ncl" ".ncl")"
|
|
let validation = (validate-migration $source_file $nickel_file)
|
|
|
|
print $"($validation.status) $relative"
|
|
if not $validation.nickel_valid {
|
|
print " ⚠️ Nickel syntax error"
|
|
}
|
|
if not $validation.outputs_equivalent {
|
|
print " ⚠️ JSON outputs differ"
|
|
}
|
|
|
|
$validation
|
|
}
|
|
|
|
let passed = ($results | where {|r| $r.outputs_equivalent} | length)
|
|
let total = ($results | length)
|
|
print $"\nSummary: ($passed)/($total) files PASS equivalence check"
|
|
}
|