Skip to content

高级用法

本指南涵盖了 Erlang Rebar 配置解析器的高级使用模式和最佳实践,包括性能优化、错误处理策略和复杂用例。

性能优化

内存管理

处理大型配置或处理多个文件时,考虑内存使用:

go
func processLargeConfigs(configPaths []string) error {
    for _, path := range configPaths {
        // 一次处理一个配置以限制内存使用
        config, err := parser.ParseFile(path)
        if err != nil {
            log.Printf("解析 %s 失败: %v", path, err)
            continue
        }
        
        // 处理配置
        processConfig(config)
        
        // 允许垃圾回收
        config = nil
        runtime.GC()
    }
    
    return nil
}

并发处理

并发处理多个配置:

go
func processConcurrently(configPaths []string, maxWorkers int) {
    jobs := make(chan string, len(configPaths))
    results := make(chan ConfigResult, len(configPaths))
    
    // 启动工作器
    for w := 0; w < maxWorkers; w++ {
        go configWorker(jobs, results)
    }
    
    // 发送作业
    for _, path := range configPaths {
        jobs <- path
    }
    close(jobs)
    
    // 收集结果
    for i := 0; i < len(configPaths); i++ {
        result := <-results
        if result.Error != nil {
            log.Printf("处理 %s 出错: %v", result.Path, result.Error)
        } else {
            log.Printf("成功处理 %s", result.Path)
        }
    }
}

type ConfigResult struct {
    Path   string
    Config *parser.RebarConfig
    Error  error
}

func configWorker(jobs <-chan string, results chan<- ConfigResult) {
    for path := range jobs {
        config, err := parser.ParseFile(path)
        results <- ConfigResult{
            Path:   path,
            Config: config,
            Error:  err,
        }
    }
}

高级错误处理

自定义错误类型

创建自定义错误类型以便更好地处理错误:

go
type ConfigError struct {
    Type    string
    Path    string
    Line    int
    Column  int
    Message string
    Cause   error
}

func (e *ConfigError) Error() string {
    if e.Path != "" {
        return fmt.Sprintf("%s 错误在 %s: %s", e.Type, e.Path, e.Message)
    }
    return fmt.Sprintf("%s 错误: %s", e.Type, e.Message)
}

func (e *ConfigError) Unwrap() error {
    return e.Cause
}

func parseWithDetailedErrors(path string) (*parser.RebarConfig, error) {
    config, err := parser.ParseFile(path)
    if err != nil {
        // 使用上下文增强错误
        configErr := &ConfigError{
            Type:    "解析",
            Path:    path,
            Message: err.Error(),
            Cause:   err,
        }
        
        // 如果可用,尝试提取行/列信息
        if strings.Contains(err.Error(), "line") {
            // 从错误消息解析行信息
            // 这是特定于实现的
        }
        
        return nil, configErr
    }
    
    return config, nil
}

错误恢复策略

实现从部分解析失败中恢复的策略:

go
func parseWithRecovery(content string) (*parser.RebarConfig, []error) {
    var errors []error
    
    // 首先尝试解析整个内容
    config, err := parser.Parse(content)
    if err == nil {
        return config, nil
    }
    
    errors = append(errors, err)
    
    // 如果解析失败,尝试解析单个术语
    lines := strings.Split(content, "\n")
    var validTerms []parser.Term
    
    for i, line := range lines {
        line = strings.TrimSpace(line)
        if line == "" || strings.HasPrefix(line, "%") {
            continue // 跳过空行和注释
        }
        
        // 尝试将单行解析为术语
        if strings.HasSuffix(line, ".") {
            termConfig, err := parser.Parse(line)
            if err != nil {
                errors = append(errors, fmt.Errorf("第 %d 行: %w", i+1, err))
            } else if len(termConfig.Terms) > 0 {
                validTerms = append(validTerms, termConfig.Terms...)
            }
        }
    }
    
    if len(validTerms) > 0 {
        // 返回部分配置
        return &parser.RebarConfig{
            Terms: validTerms,
            Raw:   content,
        }, errors
    }
    
    return nil, errors
}

配置转换

配置迁移

在不同格式或版本之间迁移配置:

go
type ConfigMigrator struct {
    migrations []Migration
}

type Migration interface {
    Name() string
    Description() string
    Apply(config *parser.RebarConfig) (*parser.RebarConfig, error)
    CanApply(config *parser.RebarConfig) bool
}

// 示例迁移:将旧式依赖项转换为新格式
type DependencyFormatMigration struct{}

func (m DependencyFormatMigration) Name() string {
    return "dependency-format-v2"
}

func (m DependencyFormatMigration) Description() string {
    return "将旧式依赖项格式转换为新格式"
}

func (m DependencyFormatMigration) CanApply(config *parser.RebarConfig) bool {
    // 检查配置是否使用旧格式
    if deps, ok := config.GetDeps(); ok && len(deps) > 0 {
        if depsList, ok := deps[0].(parser.List); ok {
            for _, dep := range depsList.Elements {
                // 检查旧格式模式
                if atom, ok := dep.(parser.Atom); ok {
                    // 旧格式:只有原子名称
                    _ = atom
                    return true
                }
            }
        }
    }
    return false
}

func (m DependencyFormatMigration) Apply(config *parser.RebarConfig) (*parser.RebarConfig, error) {
    newTerms := make([]parser.Term, len(config.Terms))
    copy(newTerms, config.Terms)
    
    // 查找并转换依赖项术语
    for i, term := range newTerms {
        if tuple, ok := term.(parser.Tuple); ok && len(tuple.Elements) >= 2 {
            if atom, ok := tuple.Elements[0].(parser.Atom); ok && atom.Value == "deps" {
                newDeps, err := m.transformDependencies(tuple.Elements[1])
                if err != nil {
                    return nil, err
                }
                
                newTerms[i] = parser.Tuple{
                    Elements: []parser.Term{
                        tuple.Elements[0],
                        newDeps,
                    },
                }
            }
        }
    }
    
    return &parser.RebarConfig{
        Terms: newTerms,
        Raw:   "", // 格式化时将重新生成原始内容
    }, nil
}

func (m DependencyFormatMigration) transformDependencies(deps parser.Term) (parser.Term, error) {
    if depsList, ok := deps.(parser.List); ok {
        var newElements []parser.Term
        
        for _, dep := range depsList.Elements {
            if atom, ok := dep.(parser.Atom); ok {
                // 将原子转换为 {atom, "latest"} 元组
                newDep := parser.Tuple{
                    Elements: []parser.Term{
                        atom,
                        parser.String{Value: "latest"},
                    },
                }
                newElements = append(newElements, newDep)
            } else {
                // 保持现有格式
                newElements = append(newElements, dep)
            }
        }
        
        return parser.List{Elements: newElements}, nil
    }
    
    return deps, nil
}

func (cm *ConfigMigrator) Migrate(config *parser.RebarConfig) (*parser.RebarConfig, error) {
    current := config
    
    for _, migration := range cm.migrations {
        if migration.CanApply(current) {
            log.Printf("应用迁移: %s", migration.Name())
            
            migrated, err := migration.Apply(current)
            if err != nil {
                return nil, fmt.Errorf("迁移 %s 失败: %w", migration.Name(), err)
            }
            
            current = migrated
        }
    }
    
    return current, nil
}

配置合并

智能合并多个配置:

go
type ConfigMerger struct {
    strategy MergeStrategy
}

type MergeStrategy interface {
    Merge(base, overlay *parser.RebarConfig) (*parser.RebarConfig, error)
}

// 策略:覆盖 - 覆盖层完全替换基础部分
type OverrideStrategy struct{}

func (s OverrideStrategy) Merge(base, overlay *parser.RebarConfig) (*parser.RebarConfig, error) {
    baseTerms := createTermMap(base)
    overlayTerms := createTermMap(overlay)
    
    // 从基础术语开始
    result := make(map[string]parser.Term)
    for key, term := range baseTerms {
        result[key] = term
    }
    
    // 用覆盖层术语覆盖
    for key, term := range overlayTerms {
        result[key] = term
    }
    
    // 转换回切片
    var terms []parser.Term
    for _, term := range result {
        terms = append(terms, term)
    }
    
    return &parser.RebarConfig{Terms: terms}, nil
}

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
}

最佳实践总结

1. 内存管理

  • 一次处理大文件
  • 使用 goroutine 进行并发处理
  • 在操作之间允许垃圾回收

2. 错误处理

  • 为更好的上下文创建自定义错误类型
  • 实现部分失败的恢复策略
  • 提供带有位置信息的详细错误消息

3. 配置管理

  • 对格式更改使用迁移模式
  • 实现智能合并策略
  • 在处理前验证配置

4. 可扩展性

  • 为自定义分析设计插件系统
  • 使用接口进行灵活实现
  • 提供全面的报告机制

5. 性能

  • 可能时缓存解析的配置
  • 对非常大的文件使用流式处理
  • 在生产中分析内存使用

本高级指南为使用 Erlang Rebar 配置解析器构建强大的、生产就绪的应用程序提供了基础。

Released under the MIT License.