github.com/amplia-iiot/yutil@v1.0.1-0.20231229120411-5d96a4c5a136/internal/replace/replace.go (about)

     1  /*
     2  Copyright (c) 2023 Adrian Haasler GarcĂ­a <dev@ahaasler.com>
     3  
     4  Permission is hereby granted, free of charge, to any person obtaining a copy
     5  of this software and associated documentation files (the "Software"), to deal
     6  in the Software without restriction, including without limitation the rights
     7  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  copies of the Software, and to permit persons to whom the Software is
     9  furnished to do so, subject to the following conditions:
    10  
    11  The above copyright notice and this permission notice shall be included in all
    12  copies or substantial portions of the Software.
    13  
    14  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20  SOFTWARE.
    21  */
    22  
    23  package replace
    24  
    25  import (
    26  	"errors"
    27  	"fmt"
    28  	"path/filepath"
    29  
    30  	iio "github.com/amplia-iiot/yutil/internal/io"
    31  )
    32  
    33  type EngineType int
    34  
    35  const (
    36  	Golang EngineType = iota
    37  	Jinja2
    38  )
    39  
    40  func (e EngineType) String() string {
    41  	return [...]string{"Golang", "Jinja2"}[e]
    42  }
    43  
    44  type FileNameRenamer func(string) string
    45  
    46  type Options struct {
    47  	Engine          EngineType
    48  	Directory       string
    49  	Include         []string
    50  	Exclude         []string
    51  	Replacements    map[string]interface{}
    52  	FileNameRenamer FileNameRenamer
    53  }
    54  
    55  func (o *Options) sanitize() {
    56  	if o.Directory == "" {
    57  		o.Directory = "."
    58  	}
    59  	if o.Include == nil {
    60  		o.Include = []string{}
    61  	}
    62  	if o.Exclude == nil {
    63  		o.Exclude = []string{}
    64  	}
    65  	if o.Replacements == nil {
    66  		o.Replacements = map[string]interface{}{}
    67  	} else {
    68  		o.Replacements = sanitizeReplacements(&o.Replacements)
    69  	}
    70  	if o.FileNameRenamer == nil {
    71  		o.FileNameRenamer = func(s string) string {
    72  			return s
    73  		}
    74  	}
    75  }
    76  
    77  func sanitizeReplacements(node *map[string]any) map[string]interface{} {
    78  	reps := map[string]interface{}{}
    79  	for name, v := range *node {
    80  		if n, ok := v.(map[any]any); ok {
    81  			reps[name] = sanitizeNode(n)
    82  		} else {
    83  			reps[name] = v
    84  		}
    85  	}
    86  	return reps
    87  }
    88  
    89  func sanitizeNode(node any) any {
    90  	if n, ok := (node).(map[any]any); ok {
    91  		m := map[string]interface{}{}
    92  		for k, v := range n {
    93  			if name, ok := k.(string); ok {
    94  				m[name] = sanitizeNode(v)
    95  			}
    96  		}
    97  		return m
    98  	} else if n, ok := (node).([]any); ok {
    99  		s := []interface{}{}
   100  		for _, v := range n {
   101  			s = append(s, sanitizeNode(v))
   102  		}
   103  		return s
   104  	}
   105  	return node
   106  }
   107  
   108  func (o *Options) engine() (Engine, error) {
   109  	switch o.Engine {
   110  	case Golang:
   111  		return golang, nil
   112  	case Jinja2:
   113  		return jinja2, nil
   114  	}
   115  	return nil, fmt.Errorf("unsupported engine: %s", o.Engine)
   116  }
   117  
   118  func Replace(opts Options) (err error) {
   119  	opts.sanitize()
   120  	engine, err := opts.engine()
   121  	if err != nil {
   122  		return
   123  	}
   124  	files, err := iio.ListFiles(opts.Directory, opts.Include, opts.Exclude)
   125  	if err != nil {
   126  		return
   127  	}
   128  	errs := []error{}
   129  	for _, file := range files {
   130  		content, err := iio.ReadAsString(file)
   131  		if err != nil {
   132  			errs = append(errs, err)
   133  			continue
   134  		}
   135  		replaced, err := engine.Replace(content, opts.Replacements)
   136  		if err != nil {
   137  			errs = append(errs, fmt.Errorf("error on file %s: %w", file, err))
   138  			continue
   139  		}
   140  		path := filepath.Dir(file)
   141  		name := filepath.Base(file)
   142  		renamed := opts.FileNameRenamer(name)
   143  		err = iio.WriteToFile(filepath.Join(path, renamed), replaced)
   144  		if err != nil {
   145  			errs = append(errs, err)
   146  			continue
   147  		}
   148  	}
   149  	return errors.Join(errs...)
   150  }