This repository was archived by the owner on Apr 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpaths.go
181 lines (167 loc) · 6.36 KB
/
paths.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package ortfomk
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/antonmedv/expr"
exprAST "github.com/antonmedv/expr/ast"
exprParser "github.com/antonmedv/expr/parser"
exprVM "github.com/antonmedv/expr/vm"
)
// PreprocessDynamicPathExpression expands some custom syntax added on top of regular antonmedv expressions. Output is a valid antonmedv expression.
// The expansions are the following:
//
// … is … -> 1 == 2 ? 1 : ""
// … except … -> 1 != 2 ? 1 : ""
func PreprocessDynamicPathExpression(expression string) string {
// Expand "is" assertions syntax
if matches := regexp.MustCompile(`^(\w+)\s+is\s+(.+)$`).FindStringSubmatch(expression); matches != nil {
expression = fmt.Sprintf(`%s == %s ? %s : ""`, matches[1], matches[2], matches[2])
}
// Expand "except" assertions syntax
if matches := regexp.MustCompile(`^(\w+)\s+except\s+(.+)$`).FindStringSubmatch(expression); matches != nil {
expression = fmt.Sprintf(`%s != %s ? %s : ""`, matches[1], matches[2], matches[1])
}
return expression
}
// EvaluateDynamicPathExpression evaluates a path expression (that does not contain the leading ":" or the surrounding "[" and "]"
// and returns the evaluated expression, as a boolean (second return value) if the result is a boolean or as a string (first return value) if
// the result is anything else (stringifying the type with "%s"). If the result is an empty string, it becomes indistinguishable from a false boolean result.
// This is within expectations: an empty string, as well as a false boolean, means that this hydration with this path should not be rendered.
func EvaluateDynamicPathExpression(h *Hydration, expression string) (stringResult string, boolResult bool, err error) {
var compiledExpr *exprVM.Program
expression = PreprocessDynamicPathExpression(expression)
if cached, ok := DynamicPathExpressionsCache[expression]; ok {
compiledExpr = cached
} else {
LogDebug("Compiling dynamic path expression %q", expression)
compiledExpr, err = expr.Compile(expression)
DynamicPathExpressionsCache[expression] = compiledExpr
}
if err != nil {
return "", false, fmt.Errorf("invalid dynamic path expression %q: %w", expression, err)
}
value, err := expr.Run(compiledExpr, map[string]interface{}{
"work": h.work,
"tag": h.tag,
"tech": h.tech,
"technology": h.tech,
"site": h.site,
"language": h.language,
"collection": h.collection,
"lang": h.language,
})
if err != nil {
return "", false, fmt.Errorf("couldn't evaluate expression %q: %w", expression, err)
}
switch coerced := value.(type) {
case bool:
boolResult = coerced
default:
stringResult = fmt.Sprintf("%s", coerced)
}
return
}
// ExtractDynamicPathExpression extracts the path expression from a path.
// If the path is not a path expression, it returns an empty string.
// The extension argument is used to strip a potential extension from the path, to not let it be part of the expression when using the ":expression" syntax.
func ExtractDynamicPathExpression(path string, extension string) string {
if strings.HasPrefix(path, ":") {
return strings.TrimSuffix(path[1:], extension)
} else if strings.HasPrefix(path, "[") && strings.HasSuffix(path, "]") {
return path[1 : len(path)-1]
} else {
return ""
}
}
func VariablesOfExpression(expression string) ([]string, error) {
tree, err := exprParser.Parse(PreprocessDynamicPathExpression(expression))
if err != nil {
return []string{}, fmt.Errorf("while parsing expression: %w", err)
}
visitor := exprVariablesExtractor{}
exprAST.Walk(&tree.Node, &visitor)
return visitor.collected, nil
}
type exprVariablesExtractor struct {
collected []string
}
func (e *exprVariablesExtractor) Enter(node *exprAST.Node) {
if ident, ok := (*node).(*exprAST.IdentifierNode); ok {
e.collected = append(e.collected, ident.Value)
}
}
func (e *exprVariablesExtractor) Exit(node *exprAST.Node) {
// nothing
}
// DynamicPathExpressions returns a list of all dynamic path expressions if the given path.
func DynamicPathExpressions(path string) (expressions []string) {
parts := strings.Split(path, string(filepath.Separator))
for i, part := range parts {
extension := ""
if i == len(parts)-1 {
extension = filepath.Ext(part)
}
if expression := ExtractDynamicPathExpression(part, extension); expression != "" {
expressions = append(expressions, expression)
}
}
return
}
// EvaluateDynamicPathExpression evaluates a path that mau contain parts that are dynamic path expressions.
func EvaluateDynamicPath(h *Hydration, path string) (string, error) {
evaluatedParts := make([]string, 0)
parts := strings.Split(path, string(filepath.Separator))
leadingSlash := ""
if strings.HasPrefix(path, string(filepath.Separator)) {
leadingSlash = string(filepath.Separator)
}
for i, part := range parts {
var evaluatedPart string
extension := ""
if i == len(parts)-1 {
extension = filepath.Ext(part)
}
if expression := ExtractDynamicPathExpression(part, extension); expression != "" {
stringResult, boolResult, err := EvaluateDynamicPathExpression(h, expression)
if err != nil {
return "", fmt.Errorf("invalid dynamic path part %q: %w", part, err)
}
if stringResult != "" {
evaluatedPart = stringResult
} else if boolResult {
evaluatedPart = part
} else {
return "", nil
}
if extension != "" {
evaluatedPart += extension
}
} else {
evaluatedPart = part
}
evaluatedParts = append(evaluatedParts, evaluatedPart)
}
return leadingSlash + filepath.Join(evaluatedParts...), nil
}
// GetDistFilepath evaluates dynamic paths and replaces src/ with dist/.
// An empty return value means the path shouldn't be rendered with this hydration.
func (h *Hydration) GetDistFilepath(srcFilepath string) (string, error) {
// Turn into a dist/ path
outPath := filepath.Join("dist", GetPathRelativeToSrcDir(srcFilepath))
outPath, err := EvaluateDynamicPath(h, outPath)
LogDebug("after evaluation, path is %q", outPath)
if err != nil {
return "", fmt.Errorf("couldn't evaluate dynamic path %q: %w", outPath, err)
}
if strings.HasSuffix(outPath, ".pug") {
outPath = strings.TrimSuffix(outPath, ".pug") + ".html"
}
// If it's a future .pdf file, remove .html/.pug to keep .pdf alone
if regexp.MustCompile(`\.pdf\.(html|pug)$`).MatchString(outPath) {
outPath = strings.TrimSuffix(outPath, ".pug")
outPath = strings.TrimSuffix(outPath, ".html")
}
return outPath, nil
}