Skip to content

Term Comparison

This example demonstrates how to compare Erlang terms and configurations using the library's comparison functionality.

Overview

The library provides comparison capabilities through the Compare() method implemented by all term types. This allows you to check equality between different terms and configurations.

Basic Term Comparison

Simple Comparisons

go
package main

import (
    "fmt"
    
    "github.com/scagogogo/erlang-rebar-config-parser/pkg/parser"
)

func main() {
    // Create some terms for comparison
    atom1 := parser.Atom{Value: "debug_info", IsQuoted: false}
    atom2 := parser.Atom{Value: "debug_info", IsQuoted: true}  // Different quoting
    atom3 := parser.Atom{Value: "warnings_as_errors", IsQuoted: false}
    
    str1 := parser.String{Value: "hello"}
    str2 := parser.String{Value: "hello"}
    str3 := parser.String{Value: "world"}
    
    int1 := parser.Integer{Value: 42}
    int2 := parser.Integer{Value: 42}
    int3 := parser.Integer{Value: 24}
    
    // Atom comparisons (ignores IsQuoted)
    fmt.Printf("atom1.Compare(atom2): %t\n", atom1.Compare(atom2)) // true
    fmt.Printf("atom1.Compare(atom3): %t\n", atom1.Compare(atom3)) // false
    
    // String comparisons
    fmt.Printf("str1.Compare(str2): %t\n", str1.Compare(str2)) // true
    fmt.Printf("str1.Compare(str3): %t\n", str1.Compare(str3)) // false
    
    // Integer comparisons
    fmt.Printf("int1.Compare(int2): %t\n", int1.Compare(int2)) // true
    fmt.Printf("int1.Compare(int3): %t\n", int1.Compare(int3)) // false
    
    // Cross-type comparisons (always false)
    fmt.Printf("atom1.Compare(str1): %t\n", atom1.Compare(str1)) // false
    fmt.Printf("str1.Compare(int1): %t\n", str1.Compare(int1))   // false
}

Complex Structure Comparisons

go
func demonstrateComplexComparisons() {
    // Create identical tuples
    tuple1 := parser.Tuple{
        Elements: []parser.Term{
            parser.Atom{Value: "cowboy"},
            parser.String{Value: "2.9.0"},
        },
    }
    
    tuple2 := parser.Tuple{
        Elements: []parser.Term{
            parser.Atom{Value: "cowboy"},
            parser.String{Value: "2.9.0"},
        },
    }
    
    tuple3 := parser.Tuple{
        Elements: []parser.Term{
            parser.Atom{Value: "cowboy"},
            parser.String{Value: "2.8.0"}, // Different version
        },
    }
    
    fmt.Printf("tuple1.Compare(tuple2): %t\n", tuple1.Compare(tuple2)) // true
    fmt.Printf("tuple1.Compare(tuple3): %t\n", tuple1.Compare(tuple3)) // false
    
    // Create identical lists
    list1 := parser.List{
        Elements: []parser.Term{
            parser.Atom{Value: "debug_info"},
            parser.Atom{Value: "warnings_as_errors"},
        },
    }
    
    list2 := parser.List{
        Elements: []parser.Term{
            parser.Atom{Value: "debug_info"},
            parser.Atom{Value: "warnings_as_errors"},
        },
    }
    
    list3 := parser.List{
        Elements: []parser.Term{
            parser.Atom{Value: "debug_info"},
            // Missing second element
        },
    }
    
    fmt.Printf("list1.Compare(list2): %t\n", list1.Compare(list2)) // true
    fmt.Printf("list1.Compare(list3): %t\n", list1.Compare(list3)) // false
}

Configuration Comparison

Comparing Entire Configurations

go
func compareConfigurations(config1Str, config2Str string) {
    config1, err := parser.Parse(config1Str)
    if err != nil {
        fmt.Printf("Failed to parse config1: %v\n", err)
        return
    }
    
    config2, err := parser.Parse(config2Str)
    if err != nil {
        fmt.Printf("Failed to parse config2: %v\n", err)
        return
    }
    
    fmt.Println("=== Configuration Comparison ===")
    
    // Compare number of terms
    if len(config1.Terms) != len(config2.Terms) {
        fmt.Printf("Different number of terms: %d vs %d\n", 
            len(config1.Terms), len(config2.Terms))
        return
    }
    
    // Compare each term
    allEqual := true
    for i, term1 := range config1.Terms {
        term2 := config2.Terms[i]
        if !term1.Compare(term2) {
            fmt.Printf("Term %d differs:\n", i+1)
            fmt.Printf("  Config1: %s\n", term1.String())
            fmt.Printf("  Config2: %s\n", term2.String())
            allEqual = false
        }
    }
    
    if allEqual {
        fmt.Println("✓ Configurations are identical")
    } else {
        fmt.Println("✗ Configurations differ")
    }
}

// Usage example
func main() {
    config1 := `{erl_opts, [debug_info]}.{deps, [{cowboy, "2.9.0"}]}.`
    config2 := `{erl_opts, [debug_info]}.{deps, [{cowboy, "2.9.0"}]}.`
    config3 := `{erl_opts, [debug_info]}.{deps, [{cowboy, "2.8.0"}]}.`
    
    compareConfigurations(config1, config2) // Should be identical
    compareConfigurations(config1, config3) // Should differ
}

Comparing Specific Sections

go
func compareSpecificSections(config1, config2 *parser.RebarConfig) {
    fmt.Println("=== Section-by-Section Comparison ===")
    
    // Compare dependencies
    deps1, ok1 := config1.GetDeps()
    deps2, ok2 := config2.GetDeps()
    
    if ok1 && ok2 {
        if len(deps1) > 0 && len(deps2) > 0 {
            if deps1[0].Compare(deps2[0]) {
                fmt.Println("✓ Dependencies are identical")
            } else {
                fmt.Println("✗ Dependencies differ")
                fmt.Printf("  Config1 deps: %s\n", deps1[0].String())
                fmt.Printf("  Config2 deps: %s\n", deps2[0].String())
            }
        }
    } else if ok1 != ok2 {
        fmt.Println("✗ One config has dependencies, the other doesn't")
    } else {
        fmt.Println("- Neither config has dependencies")
    }
    
    // Compare Erlang options
    opts1, ok1 := config1.GetErlOpts()
    opts2, ok2 := config2.GetErlOpts()
    
    if ok1 && ok2 {
        if len(opts1) > 0 && len(opts2) > 0 {
            if opts1[0].Compare(opts2[0]) {
                fmt.Println("✓ Erlang options are identical")
            } else {
                fmt.Println("✗ Erlang options differ")
                fmt.Printf("  Config1 erl_opts: %s\n", opts1[0].String())
                fmt.Printf("  Config2 erl_opts: %s\n", opts2[0].String())
            }
        }
    } else if ok1 != ok2 {
        fmt.Println("✗ One config has erl_opts, the other doesn't")
    } else {
        fmt.Println("- Neither config has erl_opts")
    }
    
    // Compare application names
    app1, ok1 := config1.GetAppName()
    app2, ok2 := config2.GetAppName()
    
    if ok1 && ok2 {
        if app1 == app2 {
            fmt.Printf("✓ Application names are identical: %s\n", app1)
        } else {
            fmt.Printf("✗ Application names differ: %s vs %s\n", app1, app2)
        }
    } else if ok1 != ok2 {
        fmt.Println("✗ One config has app_name, the other doesn't")
    } else {
        fmt.Println("- Neither config has app_name")
    }
}

Advanced Comparison Utilities

Dependency Comparison

go
func compareDependencies(config1, config2 *parser.RebarConfig) {
    fmt.Println("=== Dependency Analysis ===")
    
    deps1 := extractDependencyNames(config1)
    deps2 := extractDependencyNames(config2)
    
    // Find common dependencies
    common := findCommonDeps(deps1, deps2)
    if len(common) > 0 {
        fmt.Printf("Common dependencies (%d): %v\n", len(common), common)
    }
    
    // Find unique dependencies
    unique1 := findUniqueDeps(deps1, deps2)
    if len(unique1) > 0 {
        fmt.Printf("Only in config1 (%d): %v\n", len(unique1), unique1)
    }
    
    unique2 := findUniqueDeps(deps2, deps1)
    if len(unique2) > 0 {
        fmt.Printf("Only in config2 (%d): %v\n", len(unique2), unique2)
    }
    
    // Compare versions for common dependencies
    compareVersions(config1, config2, common)
}

func extractDependencyNames(config *parser.RebarConfig) []string {
    var names []string
    
    if deps, ok := config.GetDeps(); ok && len(deps) > 0 {
        if depsList, ok := deps[0].(parser.List); ok {
            for _, dep := range depsList.Elements {
                if tuple, ok := dep.(parser.Tuple); ok && len(tuple.Elements) >= 1 {
                    if atom, ok := tuple.Elements[0].(parser.Atom); ok {
                        names = append(names, atom.Value)
                    }
                }
            }
        }
    }
    
    return names
}

func findCommonDeps(deps1, deps2 []string) []string {
    var common []string
    for _, dep1 := range deps1 {
        for _, dep2 := range deps2 {
            if dep1 == dep2 {
                common = append(common, dep1)
                break
            }
        }
    }
    return common
}

func findUniqueDeps(deps1, deps2 []string) []string {
    var unique []string
    for _, dep1 := range deps1 {
        found := false
        for _, dep2 := range deps2 {
            if dep1 == dep2 {
                found = true
                break
            }
        }
        if !found {
            unique = append(unique, dep1)
        }
    }
    return unique
}

func compareVersions(config1, config2 *parser.RebarConfig, commonDeps []string) {
    if len(commonDeps) == 0 {
        return
    }
    
    fmt.Println("\n--- Version Comparison ---")
    
    for _, depName := range commonDeps {
        version1 := getDependencyVersion(config1, depName)
        version2 := getDependencyVersion(config2, depName)
        
        if version1 == version2 {
            fmt.Printf("✓ %s: %s (same)\n", depName, version1)
        } else {
            fmt.Printf("✗ %s: %s vs %s\n", depName, version1, version2)
        }
    }
}

func getDependencyVersion(config *parser.RebarConfig, depName string) string {
    if deps, ok := config.GetDeps(); ok && len(deps) > 0 {
        if depsList, ok := deps[0].(parser.List); ok {
            for _, dep := range depsList.Elements {
                if tuple, ok := dep.(parser.Tuple); ok && len(tuple.Elements) >= 2 {
                    if atom, ok := tuple.Elements[0].(parser.Atom); ok && atom.Value == depName {
                        if str, ok := tuple.Elements[1].(parser.String); ok {
                            return str.Value
                        }
                        return tuple.Elements[1].String()
                    }
                }
            }
        }
    }
    return "unknown"
}

Configuration Diff Tool

go
func generateConfigDiff(config1, config2 *parser.RebarConfig) {
    fmt.Println("=== Configuration Diff ===")
    
    // Compare basic structure
    fmt.Printf("Config1 terms: %d\n", len(config1.Terms))
    fmt.Printf("Config2 terms: %d\n", len(config2.Terms))
    
    // Create maps for easier comparison
    terms1 := createTermMap(config1)
    terms2 := createTermMap(config2)
    
    // Find all unique keys
    allKeys := make(map[string]bool)
    for key := range terms1 {
        allKeys[key] = true
    }
    for key := range terms2 {
        allKeys[key] = true
    }
    
    // Compare each section
    for key := range allKeys {
        term1, exists1 := terms1[key]
        term2, exists2 := terms2[key]
        
        if !exists1 {
            fmt.Printf("+ %s: %s (only in config2)\n", key, term2.String())
        } else if !exists2 {
            fmt.Printf("- %s: %s (only in config1)\n", key, term1.String())
        } else if !term1.Compare(term2) {
            fmt.Printf("~ %s:\n", key)
            fmt.Printf("  - %s\n", term1.String())
            fmt.Printf("  + %s\n", term2.String())
        } else {
            fmt.Printf("= %s: identical\n", key)
        }
    }
}

func createTermMap(config *parser.RebarConfig) map[string]parser.Term {
    termMap := make(map[string]parser.Term)
    
    for _, term := range config.Terms {
        if tuple, ok := term.(parser.Tuple); ok && len(tuple.Elements) > 0 {
            if atom, ok := tuple.Elements[0].(parser.Atom); ok {
                termMap[atom.Value] = term
            }
        }
    }
    
    return termMap
}

Complete Comparison Example

go
package main

import (
    "fmt"
    "log"
    
    "github.com/scagogogo/erlang-rebar-config-parser/pkg/parser"
)

func main() {
    // Sample configurations for comparison
    config1Str := `
    {app_name, my_app}.
    {erl_opts, [debug_info, warnings_as_errors]}.
    {deps, [
        {cowboy, "2.9.0"},
        {jsx, "3.1.0"}
    ]}.
    `
    
    config2Str := `
    {app_name, my_app}.
    {erl_opts, [debug_info, warnings_as_errors]}.
    {deps, [
        {cowboy, "2.8.0"},
        {jsx, "3.1.0"},
        {lager, "3.9.2"}
    ]}.
    `
    
    config1, err := parser.Parse(config1Str)
    if err != nil {
        log.Fatalf("Failed to parse config1: %v", err)
    }
    
    config2, err := parser.Parse(config2Str)
    if err != nil {
        log.Fatalf("Failed to parse config2: %v", err)
    }
    
    fmt.Println("=== Comprehensive Configuration Comparison ===")
    
    // 1. Basic comparison
    compareConfigurations(config1Str, config2Str)
    
    fmt.Println()
    
    // 2. Section-by-section comparison
    compareSpecificSections(config1, config2)
    
    fmt.Println()
    
    // 3. Dependency analysis
    compareDependencies(config1, config2)
    
    fmt.Println()
    
    // 4. Generate diff
    generateConfigDiff(config1, config2)
    
    // 5. Demonstrate term-level comparisons
    fmt.Println("\n=== Term-Level Comparisons ===")
    demonstrateTermComparisons()
}

func demonstrateTermComparisons() {
    // Create various terms for comparison
    terms := []parser.Term{
        parser.Atom{Value: "debug_info"},
        parser.Atom{Value: "debug_info", IsQuoted: true},
        parser.String{Value: "2.9.0"},
        parser.Integer{Value: 42},
        parser.Float{Value: 3.14},
        parser.Tuple{
            Elements: []parser.Term{
                parser.Atom{Value: "cowboy"},
                parser.String{Value: "2.9.0"},
            },
        },
        parser.List{
            Elements: []parser.Term{
                parser.Atom{Value: "debug_info"},
                parser.Atom{Value: "warnings_as_errors"},
            },
        },
    }
    
    // Compare each term with itself and others
    for i, term1 := range terms {
        for j, term2 := range terms {
            result := term1.Compare(term2)
            if i == j {
                fmt.Printf("✓ Term %d == Term %d: %t (self-comparison)\n", i+1, j+1, result)
            } else if result {
                fmt.Printf("✓ Term %d == Term %d: %t\n", i+1, j+1, result)
                fmt.Printf("  %s == %s\n", term1.String(), term2.String())
            }
        }
    }
}

This comprehensive example demonstrates all aspects of term and configuration comparison, from simple equality checks to complex diff generation and dependency analysis.

Released under the MIT License.