| package template |
| |
| import ( |
| "fmt" |
| "regexp" |
| "strings" |
| ) |
| |
| var delimiter = "\\$" |
| var substitution = "[_a-z][_a-z0-9]*(?::?-[^}]+)?" |
| |
| var patternString = fmt.Sprintf( |
| "%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))", |
| delimiter, delimiter, substitution, substitution, |
| ) |
| |
| var pattern = regexp.MustCompile(patternString) |
| |
| // InvalidTemplateError is returned when a variable template is not in a valid |
| // format |
| type InvalidTemplateError struct { |
| Template string |
| } |
| |
| func (e InvalidTemplateError) Error() string { |
| return fmt.Sprintf("Invalid template: %#v", e.Template) |
| } |
| |
| // Mapping is a user-supplied function which maps from variable names to values. |
| // Returns the value as a string and a bool indicating whether |
| // the value is present, to distinguish between an empty string |
| // and the absence of a value. |
| type Mapping func(string) (string, bool) |
| |
| // Substitute variables in the string with their values |
| func Substitute(template string, mapping Mapping) (result string, err *InvalidTemplateError) { |
| result = pattern.ReplaceAllStringFunc(template, func(substring string) string { |
| matches := pattern.FindStringSubmatch(substring) |
| groups := make(map[string]string) |
| for i, name := range pattern.SubexpNames() { |
| if i != 0 { |
| groups[name] = matches[i] |
| } |
| } |
| |
| substitution := groups["named"] |
| if substitution == "" { |
| substitution = groups["braced"] |
| } |
| if substitution != "" { |
| // Soft default (fall back if unset or empty) |
| if strings.Contains(substitution, ":-") { |
| name, defaultValue := partition(substitution, ":-") |
| value, ok := mapping(name) |
| if !ok || value == "" { |
| return defaultValue |
| } |
| return value |
| } |
| |
| // Hard default (fall back if-and-only-if empty) |
| if strings.Contains(substitution, "-") { |
| name, defaultValue := partition(substitution, "-") |
| value, ok := mapping(name) |
| if !ok { |
| return defaultValue |
| } |
| return value |
| } |
| |
| // No default (fall back to empty string) |
| value, ok := mapping(substitution) |
| if !ok { |
| return "" |
| } |
| return value |
| } |
| |
| if escaped := groups["escaped"]; escaped != "" { |
| return escaped |
| } |
| |
| err = &InvalidTemplateError{Template: template} |
| return "" |
| }) |
| |
| return result, err |
| } |
| |
| // Split the string at the first occurrence of sep, and return the part before the separator, |
| // and the part after the separator. |
| // |
| // If the separator is not found, return the string itself, followed by an empty string. |
| func partition(s, sep string) (string, string) { |
| if strings.Contains(s, sep) { |
| parts := strings.SplitN(s, sep, 2) |
| return parts[0], parts[1] |
| } |
| return s, "" |
| } |