Sybren A. Stüvel 7ffbd72dce Manager: normalise slashes when expanding two-way variables
When expanding the `{shared}` variable in a path like
`{shared}\shot\file.blend`, all slashes will be normalised to the target
platform, so for example result in:

- Windows: `Y:\shared\flamenco\shot\file.blend`
- Linux  : `/shared/flamenco/shot/file.blend`

Due to this normalisation, the same paths will be served to these
platforms when the path was `{shared}/shot/file.blend`.
2024-06-25 11:24:27 +02:00

163 lines
5.4 KiB
Go

package config
import (
"fmt"
"strings"
"github.com/rs/zerolog/log"
"projects.blender.org/studio/flamenco/pkg/crosspath"
)
type ValueToVariableReplacer struct {
twoWayVars map[string]string // Mapping from variable name to value.
}
// VariableExpander expands variables and applies two-way variable replacement to the values.
type VariableExpander struct {
oneWayVars map[string]string // Mapping from variable name to value.
managerTwoWayVars map[string]string // Mapping from variable name to value for the Manager platform.
targetTwoWayVars map[string]string // Mapping from variable name to value for the target platform.
targetPlatform VariablePlatform
}
// NewVariableToValueConverter returns a ValueToVariableReplacer for the given audience & platform.
func (c *Conf) NewVariableToValueConverter(audience VariableAudience, platform VariablePlatform) *ValueToVariableReplacer {
// Get the variables for the given audience & platform.
twoWayVars := c.GetTwoWayVariables(audience, platform)
if len(twoWayVars) == 0 {
log.Debug().
Str("audience", string(audience)).
Str("platform", string(platform)).
Msg("no two-way variables defined for this platform given this audience")
}
return &ValueToVariableReplacer{
twoWayVars: twoWayVars,
}
}
// NewVariableExpander returns a new VariableExpander for the given audience & platform.
func (c *Conf) NewVariableExpander(audience VariableAudience, platform VariablePlatform) *VariableExpander {
return &VariableExpander{
oneWayVars: c.GetOneWayVariables(audience, platform),
managerTwoWayVars: c.GetTwoWayVariables(audience, c.currentGOOS),
targetTwoWayVars: c.GetTwoWayVariables(audience, platform),
targetPlatform: platform,
}
}
// ValueToVariableReplacer replaces any variable values it recognises in
// valueToConvert to the actual variable. For example, `/path/to/file.blend` can
// be changed to `{my_storage}/file.blend`.
func (vvc *ValueToVariableReplacer) Replace(valueToConvert string) string {
result := valueToConvert
for varName, varValue := range vvc.twoWayVars {
if !isValueMatch(result, varValue) {
continue
}
result = vvc.join(varName, result[len(varValue):])
}
log.Debug().
Str("from", valueToConvert).
Str("to", result).
Msg("first step of two-way variable replacement")
return result
}
func (vvc *ValueToVariableReplacer) join(varName, value string) string {
return fmt.Sprintf("{%s}%s", varName, value)
}
// isValueMatch returns whether `valueToMatch` starts with `variableValue`.
// When `variableValue` is a Windows path (with backslash separators), it is
// also tested with forward slashes against `valueToMatch`.
func isValueMatch(valueToMatch, variableValue string) bool {
if strings.HasPrefix(valueToMatch, variableValue) {
return true
}
// If any of the values have backslashes, assume it is a Windows path.
// Convert it to slash notation just to see if that would provide a
// match.
if strings.ContainsRune(variableValue, '\\') {
variableValue = crosspath.ToSlash(variableValue)
}
if strings.ContainsRune(valueToMatch, '\\') {
valueToMatch = crosspath.ToSlash(valueToMatch)
}
return strings.HasPrefix(valueToMatch, variableValue)
}
// Replace converts "{variable name}" to the value that belongs to the audience and platform.
func (ve *VariableExpander) Expand(valueToExpand string) string {
expanded := valueToExpand
// Expand variables from {varname} to their value for the target platform.
for varname, varvalue := range ve.oneWayVars {
placeholder := fmt.Sprintf("{%s}", varname)
expanded = strings.Replace(expanded, placeholder, varvalue, -1)
}
// Go through the two-way variables for the target platform.
isPathValue := false
for varname, varvalue := range ve.targetTwoWayVars {
placeholder := fmt.Sprintf("{%s}", varname)
expanded = strings.Replace(expanded, placeholder, varvalue, -1)
// Since two-way variables are meant for path replacement, we know this
// should be a path.
isPathValue = true
}
// Go through the two-way variables, to make sure that the result of
// expanding variables gets the two-way variables applied as well. This is
// necessary to make implicitly-defined variable, which are only defined for
// the Manager's platform, usable for the target platform.
//
// Practically, this replaces "value for the Manager platform" with "value
// for the target platform".
for varname, managerValue := range ve.managerTwoWayVars {
targetValue, ok := ve.targetTwoWayVars[varname]
if !ok {
continue
}
if !isValueMatch(expanded, managerValue) {
continue
}
expanded = ve.join(targetValue, expanded[len(managerValue):], ve.targetPlatform)
// Since two-way variables are meant for path replacement, we know this
// should be a path.
isPathValue = true
}
if isPathValue {
expanded = crosspath.ToPlatform(expanded, string(ve.targetPlatform))
}
log.Trace().
Str("valueToExpand", valueToExpand).
Str("expanded", expanded).
Bool("isPathValue", isPathValue).
Msg("expanded variable")
return expanded
}
func (ve *VariableExpander) join(valueFromVariable, suffix string, platform VariablePlatform) string {
result := valueFromVariable + suffix
if platform == VariablePlatformWindows {
// 'result' may now be of the form `F:some\path\to\file`, where `F:` comes
// from `valueFromVariable` and the rest is the suffix. This is not an
// absolute path, and needs a separator between the drive letter and the
// rest of the path.
return crosspath.EnsureDriveAbsolute(result)
}
return result
}