grim/convey

Merge the redux branch into default.

2021-12-20, Gary Kramlich
6b1dbda836d9
Merge the redux branch into default.

This officials abandons the 0.14.0 release and moves right on to 0.15.0 which
includes replacing docker with podman and simplifying many things.
// Convey
// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package environment
import (
"fmt"
"os"
"strings"
)
type envMapper struct {
env []string
}
const (
maxExpandWidth = 100
)
// mapVariable replaces instances of the given name.
func (e *envMapper) mapVariable(name string) string {
// We might have something like $(pwd), in which case we don't want
// to wrap the name in `{}`, which would cause it to be ${}(pwd) and
// cannot be evaluated by a run task.
if name == "" {
return "$"
}
for _, item := range e.env {
if parts := strings.SplitN(item, "=", 2); parts[0] == name {
if len(parts) == 2 {
return parts[1]
}
return os.Getenv(name)
}
}
return fmt.Sprintf("${%s}", name)
}
// Map will return the value matching a KEY=VAL pair in the given environment.
func Map(key string, env []string) string {
val, _ := Mapper(key, env)
return val
}
// Mapper will replace ${TEMPLATE} patterns in the string with the KEY=VAL pairs
// in the given environment. This function will replace patterns recursively, so
// if VAL has the form ${TEMPLATE}, it will be replaced again.
func Mapper(str string, env []string) (string, error) {
mapper := envMapper{env}
orig := str
last := str
next := os.Expand(last, mapper.mapVariable)
prev := map[string]struct{}{}
for i := 0; i < maxExpandWidth; i++ {
if last == next {
return next, nil
}
if _, ok := prev[next]; ok {
break
}
last = next
next = os.Expand(last, mapper.mapVariable)
prev[last] = struct{}{}
}
return "", fmt.Errorf("infinite environment mapping loop while expanding '%s'", orig)
}
// SliceMapper calls Mapper for each item in a slice and returns a new slice.
func SliceMapper(slice, env []string) ([]string, error) {
mapped := []string{}
for _, template := range slice {
expanded, err := Mapper(template, env)
if err != nil {
return nil, err
}
mapped = append(mapped, expanded)
}
return mapped, nil
}