-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
180 lines (161 loc) · 4.9 KB
/
main.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
package main
import (
"bufio"
"crypto/sha1"
"flag"
"fmt"
"golang.org/x/net/html"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func getHeadNode(doc *html.Node) *html.Node {
var f func(*html.Node) *html.Node
f = func(n *html.Node) *html.Node {
if n.Type == html.ElementNode && n.Data == "head" {
return n
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
h := f(c)
if h != nil {
return h
}
}
return nil
}
return f(doc)
}
func getScriptNodes(doc *html.Node) []*html.Node {
// Collect the head node's script nodes
scriptNodes := make([]*html.Node, 0, 100)
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "script" && n.Parent.Data == "head" {
scriptNodes = append(scriptNodes, n)
} else {
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
}
f(doc)
return scriptNodes
}
func jsSource(scriptUrl string, htmlDir string, rootDir string) string {
p := filepath.Join(htmlDir, scriptUrl)
dat, err := ioutil.ReadFile(p)
check(err)
return string(dat)
}
func createScriptNodeWithSrc(s string) *html.Node {
doc, _ := html.Parse(strings.NewReader("<script src=\"" + s + "\"></script>"))
nodes := getScriptNodes(doc)
scriptNode := nodes[0]
scriptNode.Parent.RemoveChild(scriptNode)
return scriptNode
}
func createScriptNodeWithInlineCode(s string) *html.Node {
doc, _ := html.Parse(strings.NewReader("<script>\n" + s + "\n</script>"))
nodes := getScriptNodes(doc)
scriptNode := nodes[0]
scriptNode.Parent.RemoveChild(scriptNode)
return scriptNode
}
func processHtml(srcRoot string, inline bool, destRoot string, htmlPath string) {
// Parse the HTML nodes.
htmlFile, err := os.Open(htmlPath)
check(err)
doc, err := html.Parse(htmlFile)
check(err)
headNode := getHeadNode(doc)
scriptNodes := getScriptNodes(headNode)
jsSources := make([]string, len(scriptNodes))
htmlDir := filepath.Dir(htmlPath)
for _, n := range scriptNodes {
for _, attr := range n.Attr {
if attr.Key == "src" {
scriptPath := attr.Val
jsSources = append(jsSources, jsSource(scriptPath, htmlDir, srcRoot))
}
}
// Remove the script node, and trailing EOLNs
n2 := n.NextSibling
if n2.Type == html.TextNode {
n2.Data = strings.Replace(n2.Data, "\n", "", -1)
}
n.Parent.RemoveChild(n)
}
// New JS source
catSource := strings.Join(jsSources, "\n\n\n")
// Calculate the destination dir for the HTML and JS, and create it if needed
destDir, err := filepath.Rel(srcRoot, filepath.Dir(htmlPath))
check(err)
destDir = filepath.Join(destRoot, destDir)
check(os.MkdirAll(destDir, 0777))
if inline {
headNode.AppendChild(createScriptNodeWithInlineCode(catSource))
} else {
// Get the jsBytes and their SHA-1 fingerprint.
jsBytes := []byte(catSource)
fingerprint := fmt.Sprintf("%x", sha1.Sum(jsBytes))
// Insert the new script tag, with the new JS file name
jsFileName := fingerprint + ".js"
headNode.AppendChild(createScriptNodeWithSrc(jsFileName))
// Write the JS file.
jsDestPath := filepath.Join(destDir, jsFileName)
fmt.Println("writing JS: ", jsDestPath)
err = ioutil.WriteFile(jsDestPath, jsBytes, 0644)
check(err)
}
// Write the HTML file.
htmlDestPath := filepath.Join(destDir, filepath.Base(htmlPath))
fmt.Println("writing HTML:", htmlDestPath)
f, err := os.Create(htmlDestPath)
check(err)
defer f.Close()
w := bufio.NewWriter(f)
err = html.Render(w, doc)
w.Flush()
}
// for each node
// if it is a <script src="foo.js"></script> tag in the <head> section:
// Open the JS file using the srcroot and the script tag's src attrib.
// Append that JS file's contents to the in-mem catted script.
// Remove the script tag's nodes.
// Get the SHA1 fingerprint (like abc123) of the final catted script.
// Insert a <head> child <script src="adb123.js"></script>
// Write the script content to abc123.js in the dest tree.
// Write the HTML file to the dest tree
func main() {
// htmlPathFlag := flag.String("htmlpath", "", "The file path to the HTML file with script tags")
srcRootFlag := flag.String("srcroot", "ERROR", "The root of the source file tree")
destRootFlag := flag.String("destroot", "ERROR", "The root of the re-written file tree")
inlineJsFlag := flag.Bool("inline_js", false, "Whether to inline the JS, or write it to a fingerprinted source file.")
flag.Parse()
if *srcRootFlag == "ERROR" {
panic("Forgot srcroot flag?")
}
if *destRootFlag == "ERROR" {
panic("Forgot destroot flag?")
}
var inline = *inlineJsFlag
// Make all paths absolute
srcRoot, err := filepath.Abs(*srcRootFlag)
check(err)
destRoot, err := filepath.Abs(*destRootFlag)
check(err)
htmlPaths := os.Args[(flag.NFlag() + 1):]
for _, htmlPath := range htmlPaths {
htmlPath, err := filepath.Abs(htmlPath)
fmt.Println("reading", htmlPath)
check(err)
processHtml(srcRoot, inline, destRoot, htmlPath)
fmt.Println()
}
}