
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`.
163 lines
5.4 KiB
Go
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
|
|
}
|