This repository has been 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 pathhydrate.go
204 lines (185 loc) · 6.4 KB
/
hydrate.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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package ortfomk
import (
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
chromaQuick "github.com/alecthomas/chroma/quick"
"github.com/joho/godotenv"
"github.com/yosssi/gohtml"
"golang.org/x/net/html"
v8 "rogchap.com/v8go"
)
// Hydration represents a Tag, Technology or Work
type Hydration struct {
language string
tag Tag
tech Technology
work Work
site ExternalSite
collection Collection
}
// IsWork returns true if the current hydration contains a Work
func (h *Hydration) IsWork() bool {
return h.work.ID != ""
}
// IsTag returns true if the current hydration contains a Tag
func (h *Hydration) IsTag() bool {
return h.tag.URLName() != ""
}
// IsTech returns true if the current hydration contains a Tech
func (h *Hydration) IsTech() bool {
return h.tech.URLName != ""
}
func (h *Hydration) IsSite() bool {
return h.site.URL != ""
}
func (h *Hydration) IsCollection() bool {
return h.collection.ID != ""
}
// Name returns the identifier of the object in the hydration,
// and defaults to the empty string if the current hydration is empty
func (h *Hydration) Name() string {
if h.IsWork() {
return h.work.ID + "@" + h.language
}
if h.IsCollection() {
return h.collection.ID + "@" + h.language
}
if h.IsTag() {
return h.tag.URLName() + "@" + h.language
}
if h.IsTech() {
return h.tech.URLName + "@" + h.language
}
return h.language
}
// BuildingForProduction returns true if the environment file declares ENVIRONMENT to not "dev"
func BuildingForProduction() bool {
err := godotenv.Load(".env")
if err != nil {
panic("Could not load the .env file")
}
return os.Getenv("ENVIRONMENT") != "dev"
}
// PrintTemplateErrorMessage prints a nice error message with a preview of the code where the error occured
func PrintTemplateErrorMessage(whileDoing string, templateName string, templateContent string, err error, templateLanguage string) {
// TODO when error occurs in a subtemplate, show code snippet from the innermost subtemplate instead of the outermost
lineIndexPattern := regexp.MustCompile(`:(\d+)`)
listIndices := lineIndexPattern.FindStringSubmatch(err.Error())
if listIndices == nil {
LogError("While %s %s: %s", whileDoing, templateName, err.Error())
return
}
lineIndex64, _ := strconv.ParseInt(listIndices[1], 10, 64)
lineIndex := int(lineIndex64)
message := fmt.Sprintf("While %s %s:%d: %s\n", whileDoing, templateName, lineIndex, strings.SplitN(err.Error(), listIndices[1], 2)[1])
lineIndex-- // Lines start at 1, arrays of line are indexed from 0
highlightedWriter := bytes.NewBufferString("")
chromaQuick.Highlight(highlightedWriter, gohtml.Format(templateContent), templateLanguage, "terminal16m", "pygments")
lines := strings.Split(highlightedWriter.String(), "\n")
var lineIndexOffset int
if len(lines) >= lineIndex+5+1 {
if lineIndex >= 5 {
lines = lines[lineIndex-5 : lineIndex+5]
lineIndexOffset = lineIndex - 5
} else {
lines = lines[0 : lineIndex+5]
}
}
for i, line := range lines {
if i+lineIndexOffset == lineIndex {
message += "→ "
} else {
message += " "
}
message += fmt.Sprintf("%d %s\n", lineIndexOffset+i+1, line)
}
LogError(message)
}
// CompileTemplate compiles a pug template using the CLI tool pug.
func CompileTemplate(templateName string, templateContent []byte) ([]byte, error) {
command := exec.Command("pug", "--client", "--path", templateName)
LogDebug("compiling template: running %s", command)
command.Stdin = bytes.NewReader(templateContent)
command.Stderr = os.Stderr
return command.Output()
}
// RunTemplate parses a given (HTML) template.
func RunTemplate(javascriptRuntime *v8.Isolate, hydration *Hydration, templateName string, compiledTemplate []byte) (string, error) {
compiledJSFile, err := GenerateJSFile(hydration, templateName, string(compiledTemplate))
if os.Getenv("DEBUG") == "1" {
os.WriteFile(templateName+"."+hydration.Name()+".js", []byte(compiledJSFile), 0644)
}
if err != nil {
return "", fmt.Errorf("while generating template: %w", err)
}
LogDebug("executing template")
ctx := v8.NewContext(javascriptRuntime)
jsValue, err := ctx.RunScript(compiledJSFile, templateName+".js")
LogDebug("finished executing")
if err, ok := err.(*v8.JSError); ok {
line, column := lineAndColumn(err)
codeSpinnet := codeSpinnetAround(compiledJSFile, line, column)
return "", fmt.Errorf("while running template: %w\n%s\nStack trace:\n%s", err, codeSpinnet, err.StackTrace)
}
return jsValue.String(), nil
}
func lineAndColumn(err *v8.JSError) (line uint64, column uint64) {
parts := strings.Split(err.Location, ":")
for i, part := range parts {
var parseErr error
if i == len(parts)-1 {
line, parseErr = strconv.ParseUint(part, 10, 64)
} else if i == len(parts)-2 {
column, parseErr = strconv.ParseUint(part, 10, 64)
}
if parseErr != nil {
panic(fmt.Sprintf("while parsing positive integer %s: %s", part, parseErr))
}
}
return
}
// TranslateHydrated translates an hydrated HTML page, removing i18n tags and attributes
// and replacing translatable content with their translations
func (t TranslationsOneLang) TranslateHydrated(content string) string {
content = t.TranslateTranslationStrings(content)
parsedContent, err := html.Parse(strings.NewReader(content))
if err != nil {
LogError("An error occured while parsing the hydrated HTML for translation: %s", err)
return ""
}
return Translate(t.language, parsedContent)
}
// NameOfTemplate returns the name given to a template that is applied to multiple objects, e.g. :work.pug<portfolio>.
// Falls back to template.Name() if hydration is empty
func NameOfTemplate(name string, hydration Hydration) string {
if hydration.Name() != "" {
return fmt.Sprintf("%s<%s>", name, hydration.Name())
}
return name
}
// WriteDistFile writes the given content to the dist/ equivalent of the given fileName and returns that equivalent's path
func (h *Hydration) WriteDistFile(fileName string, content string, language string) string {
distFilePath, err := h.GetDistFilepath(fileName)
if err != nil {
LogError("Invalid path: %s", err)
return ""
}
distFile, err := os.Create(distFilePath)
if err != nil {
LogError("Could not create the destination file %s: %s ", distFilePath, err)
return ""
}
defer distFile.Close()
_, err = distFile.WriteString(content)
if err != nil {
LogError("Could not write to the destination file %s: %s", distFilePath, err)
return ""
}
fmt.Printf("\r\033[KWritten %s", distFilePath)
return distFilePath
}