Skip to content

Utility Functions

This example demonstrates how to use the utility functions provided by the GitHub Action Parser library for type conversion and data processing.

Map Conversion Utilities

MapOfStringInterface

Convert various map types to map[string]interface{}:

go
package main

import (
    "fmt"
    "log"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // Example map with interface{} keys
    interfaceMap := map[interface{}]interface{}{
        "key1": "value1",
        "key2": 42,
        "key3": true,
        "key4": []string{"a", "b", "c"},
    }
    
    // Convert to map[string]interface{}
    stringMap, err := parser.MapOfStringInterface(interfaceMap)
    if err != nil {
        log.Fatalf("Failed to convert map: %v", err)
    }
    
    fmt.Println("Converted map:")
    for key, value := range stringMap {
        fmt.Printf("  %s: %v (type: %T)\n", key, value, value)
    }
}

MapOfStringString

Convert various map types to map[string]string:

go
// Example map with interface{} values
interfaceValueMap := map[string]interface{}{
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}

// Convert to map[string]string
stringStringMap, err := parser.MapOfStringString(interfaceValueMap)
if err != nil {
    log.Fatalf("Failed to convert map: %v", err)
}

fmt.Println("\nConverted string map:")
for key, value := range stringStringMap {
    fmt.Printf("  %s: %s\n", key, value)
}

// This will fail because not all values are strings
mixedMap := map[string]interface{}{
    "key1": "value1",
    "key2": 42,
}

_, err = parser.MapOfStringString(mixedMap)
if err != nil {
    fmt.Printf("\nExpected error: %v\n", err)
}

Working with Reusable Workflows

IsReusableWorkflow

Check if a workflow is reusable:

go
package main

import (
    "fmt"
    "log"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // Parse workflow file
    workflow, err := parser.ParseFile(".github/workflows/ci.yml")
    if err != nil {
        log.Fatalf("Failed to parse workflow: %v", err)
    }
    
    // Check if it's a reusable workflow
    if parser.IsReusableWorkflow(workflow) {
        fmt.Println("This is a reusable workflow")
    } else {
        fmt.Println("This is a regular workflow")
    }
}

ExtractInputsFromWorkflowCall

Extract input parameters from a reusable workflow:

go
// Check if workflow is reusable and extract inputs
if parser.IsReusableWorkflow(workflow) {
    inputs, err := parser.ExtractInputsFromWorkflowCall(workflow)
    if err != nil {
        log.Fatalf("Failed to extract inputs: %v", err)
    }
    
    fmt.Printf("Found %d inputs:\n", len(inputs))
    for name, input := range inputs {
        fmt.Printf("  %s:\n", name)
        fmt.Printf("    Description: %s\n", input.Description)
        fmt.Printf("    Required: %t\n", input.Required)
        if input.Default != "" {
            fmt.Printf("    Default: %s\n", input.Default)
        }
    }
}

ExtractOutputsFromWorkflowCall

Extract output parameters from a reusable workflow:

go
// Extract outputs from reusable workflow
if parser.IsReusableWorkflow(workflow) {
    outputs, err := parser.ExtractOutputsFromWorkflowCall(workflow)
    if err != nil {
        log.Fatalf("Failed to extract outputs: %v", err)
    }
    
    fmt.Printf("Found %d outputs:\n", len(outputs))
    for name, output := range outputs {
        fmt.Printf("  %s:\n", name)
        fmt.Printf("    Description: %s\n", output.Description)
        if output.Value != "" {
            fmt.Printf("    Value: %s\n", output.Value)
        }
    }
}

StringOrStringSlice Type

Working with the StringOrStringSlice type:

go
package main

import (
    "fmt"
    "log"
    "gopkg.in/yaml.v3"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // Example YAML with string or string array fields
    yamlData := `
singleString: value1
stringArray:
  - item1
  - item2
  - item3
`
    
    // Parse YAML
    var data struct {
        SingleString parser.StringOrStringSlice `yaml:"singleString"`
        StringArray  parser.StringOrStringSlice `yaml:"stringArray"`
    }
    
    err := yaml.Unmarshal([]byte(yamlData), &data)
    if err != nil {
        log.Fatalf("Failed to parse YAML: %v", err)
    }
    
    // Access single string
    fmt.Printf("SingleString.Value: %s\n", data.SingleString.Value)
    fmt.Printf("SingleString.Values: %v\n", data.SingleString.Values)
    
    // Access string array
    fmt.Printf("StringArray.Value: %s\n", data.StringArray.Value)
    fmt.Printf("StringArray.Values: %v\n", data.StringArray.Values)
    
    // Check if contains a value
    if data.StringArray.Contains("item2") {
        fmt.Println("StringArray contains 'item2'")
    }
    
    // String representation
    fmt.Printf("StringArray as string: %s\n", data.StringArray.String())
}

Error Handling Patterns

go
package main

import (
    "fmt"
    "log"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // Example of safe map conversion with error handling
    interfaceMap := map[interface{}]interface{}{
        "key1": "value1",
        "key2": 42,
        "key3": true,
    }
    
    // Try to convert to string-string map (will fail)
    stringMap, err := safeMapConversion(interfaceMap)
    if err != nil {
        fmt.Printf("Warning: %v\n", err)
        fmt.Printf("Fallback map: %v\n", stringMap)
    } else {
        fmt.Printf("Converted map: %v\n", stringMap)
    }
}

// Safe conversion with fallback
func safeMapConversion(input interface{}) (map[string]string, error) {
    // Try direct conversion first
    result, err := parser.MapOfStringString(input)
    if err == nil {
        return result, nil
    }
    
    // If that fails, try to convert to map[string]interface{} first
    interfaceMap, err := parser.MapOfStringInterface(input)
    if err != nil {
        return nil, fmt.Errorf("failed to convert map: %w", err)
    }
    
    // Then manually convert values to strings
    result = make(map[string]string)
    var conversionErrors []string
    
    for key, value := range interfaceMap {
        switch v := value.(type) {
        case string:
            result[key] = v
        case int, int64, float64, bool:
            result[key] = fmt.Sprintf("%v", v)
        default:
            conversionErrors = append(conversionErrors, 
                fmt.Sprintf("cannot convert %s: %v (%T) to string", key, v, v))
        }
    }
    
    if len(conversionErrors) > 0 {
        return result, fmt.Errorf("partial conversion with %d errors", len(conversionErrors))
    }
    
    return result, nil
}

Batch Processing with Utilities

go
package main

import (
    "fmt"
    "log"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // Parse all workflows
    workflows, err := parser.ParseDir(".github/workflows")
    if err != nil {
        log.Fatalf("Failed to parse workflows: %v", err)
    }
    
    // Process each workflow
    for path, workflow := range workflows {
        fmt.Printf("=== %s ===\n", path)
        
        // Process workflow triggers
        processTriggers(workflow.On)
        
        // Process environment variables
        processEnvVars(workflow.Env)
        
        // Process jobs
        for jobID, job := range workflow.Jobs {
            fmt.Printf("Job: %s\n", jobID)
            
            // Process job environment variables
            processEnvVars(job.Env)
            
            // Process steps
            for _, step := range job.Steps {
                if step.With != nil {
                    processStepInputs(step.With)
                }
            }
        }
        
        fmt.Println()
    }
}

func processTriggers(on interface{}) {
    fmt.Println("Triggers:")
    
    switch v := on.(type) {
    case string:
        fmt.Printf("  %s\n", v)
    case []interface{}:
        for _, trigger := range v {
            fmt.Printf("  %v\n", trigger)
        }
    case map[string]interface{}:
        stringMap, err := parser.MapOfStringInterface(v)
        if err != nil {
            fmt.Printf("  Error converting triggers: %v\n", err)
            return
        }
        
        for event, config := range stringMap {
            fmt.Printf("  %s: %v\n", event, config)
        }
    default:
        fmt.Printf("  Unknown trigger type: %T\n", on)
    }
}

func processEnvVars(env interface{}) {
    if env == nil {
        return
    }
    
    fmt.Println("Environment Variables:")
    
    envVars, err := parser.MapOfStringString(env)
    if err != nil {
        fmt.Printf("  Error converting env vars: %v\n", err)
        return
    }
    
    for key, value := range envVars {
        fmt.Printf("  %s: %s\n", key, value)
    }
}

func processStepInputs(with interface{}) {
    fmt.Println("  Step Inputs:")
    
    inputs, err := parser.MapOfStringInterface(with)
    if err != nil {
        fmt.Printf("    Error converting inputs: %v\n", err)
        return
    }
    
    for key, value := range inputs {
        fmt.Printf("    %s: %v\n", key, value)
    }
}

Complete Utility Example

go
package main

import (
    "fmt"
    "log"
    "os"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    if len(os.Args) < 2 {
        log.Fatal("Usage: go run main.go <workflow.yml>")
    }
    
    workflowFile := os.Args[1]
    
    // Parse workflow file
    workflow, err := parser.ParseFile(workflowFile)
    if err != nil {
        log.Fatalf("Failed to parse %s: %v", workflowFile, err)
    }
    
    // Comprehensive workflow analysis using utility functions
    fmt.Printf("=== Analyzing %s ===\n\n", workflowFile)
    
    // Basic info
    fmt.Printf("Name: %s\n", workflow.Name)
    
    // Process triggers with type conversion
    fmt.Println("\n=== Triggers ===")
    processTriggers(workflow.On)
    
    // Process environment variables
    if workflow.Env != nil {
        fmt.Println("\n=== Environment Variables ===")
        envVars, err := parser.MapOfStringString(workflow.Env)
        if err != nil {
            fmt.Printf("Error: %v\n", err)
        } else {
            for key, value := range envVars {
                fmt.Printf("%s: %s\n", key, value)
            }
        }
    }
    
    // Check if reusable
    if parser.IsReusableWorkflow(workflow) {
        fmt.Println("\n=== Reusable Workflow ===")
        
        // Extract inputs
        inputs, err := parser.ExtractInputsFromWorkflowCall(workflow)
        if err != nil {
            fmt.Printf("Failed to extract inputs: %v\n", err)
        } else {
            fmt.Printf("Inputs: %d\n", len(inputs))
            for name, input := range inputs {
                fmt.Printf("  %s: %s (required: %t)\n", 
                    name, input.Description, input.Required)
            }
        }
        
        // Extract outputs
        outputs, err := parser.ExtractOutputsFromWorkflowCall(workflow)
        if err != nil {
            fmt.Printf("Failed to extract outputs: %v\n", err)
        } else {
            fmt.Printf("Outputs: %d\n", len(outputs))
            for name, output := range outputs {
                fmt.Printf("  %s: %s\n", name, output.Description)
            }
        }
    }
    
    // Process jobs
    fmt.Printf("\n=== Jobs (%d) ===\n", len(workflow.Jobs))
    for jobID, job := range workflow.Jobs {
        fmt.Printf("\nJob: %s\n", jobID)
        
        // Process runs-on
        if job.RunsOn != nil {
            fmt.Printf("Runs on: ")
            switch runsOn := job.RunsOn.(type) {
            case string:
                fmt.Printf("%s\n", runsOn)
            case []interface{}:
                fmt.Printf("%v\n", runsOn)
            default:
                fmt.Printf("%v (type: %T)\n", runsOn, runsOn)
            }
        }
        
        // Process steps
        fmt.Printf("Steps: %d\n", len(job.Steps))
    }
}

func processTriggers(on interface{}) {
    switch v := on.(type) {
    case string:
        fmt.Printf("Single event: %s\n", v)
    case []interface{}:
        fmt.Println("Multiple events:")
        for _, event := range v {
            fmt.Printf("  - %v\n", event)
        }
    case map[interface{}]interface{}:
        fmt.Println("Complex events:")
        events, err := parser.MapOfStringInterface(v)
        if err != nil {
            fmt.Printf("Error converting events: %v\n", err)
            return
        }
        
        for event, config := range events {
            fmt.Printf("  %s: %v\n", event, config)
        }
    case map[string]interface{}:
        fmt.Println("Complex events:")
        for event, config := range v {
            fmt.Printf("  %s: %v\n", event, config)
        }
    default:
        fmt.Printf("Unknown trigger type: %T\n", on)
    }
}

Next Steps

Released under the MIT License.