forked from tdewolff/minify
/
minify.go
134 lines (115 loc) · 5.06 KB
/
minify.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
// Package minify relates MIME type to minifiers. Several minifiers are provided in the subpackages.
package minify // import "github.com/tdewolff/minify"
import (
"errors"
"io"
"os/exec"
"regexp"
"github.com/tdewolff/buffer"
)
// ErrNotExist is returned when no minifier exists for a given mediatype.
var ErrNotExist = errors.New("minify function does not exist for mediatype")
////////////////////////////////////////////////////////////////
// Func is the function interface for minifiers.
// The Minifier parameter is used for embedded resources, such as JS within HTML.
// The mediatype string is for wildcard minifiers so they know what they minify and for parameter passing (charset for example).
type Func func(m Minifier, mediatype string, w io.Writer, r io.Reader) error
// Minifier is the interface which all minifier functions accept as first parameter.
// It's used to extract parameter values of the mediatype and to recursively call other minifier functions.
type Minifier interface {
Minify(mediatype string, w io.Writer, r io.Reader) error
}
////////////////////////////////////////////////////////////////
type regexpFunc struct {
re *regexp.Regexp
Func
}
func cmdFunc(origCmd *exec.Cmd) func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
return func(_ Minifier, _ string, w io.Writer, r io.Reader) error {
cmd := &exec.Cmd{}
*cmd = *origCmd // concurrency safety
cmd.Stdout = w
cmd.Stdin = r
return cmd.Run()
}
}
////////////////////////////////////////////////////////////////
// Minify holds a map of mediatype => function to allow recursive minifier calls of the minifier functions.
type Minify struct {
literal map[string]Func
regexp []regexpFunc
}
// New returns a new Minify.
func New() *Minify {
return &Minify{
map[string]Func{},
[]regexpFunc{},
}
}
// AddFunc adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype.
func (m *Minify) AddFunc(mediatype string, minifyFunc Func) {
m.literal[mediatype] = minifyFunc
}
// AddFuncRegexp adds a minify function to the mediatype => function map (unsafe for concurrent use).
// It allows one to implement a custom minifier for a specific mediatype regular expression.
func (m *Minify) AddFuncRegexp(mediatype *regexp.Regexp, minifyFunc Func) {
m.regexp = append(m.regexp, regexpFunc{mediatype, minifyFunc})
}
// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmd(mediatype string, cmd *exec.Cmd) {
m.literal[mediatype] = cmdFunc(cmd)
}
// AddCmd adds a minify function to the mediatype => function map (unsafe for concurrent use) that executes a command to process the minification.
// It allows the use of external tools like ClosureCompiler, UglifyCSS, etc. for a specific mediatype regular expression.
// Be aware that running external tools will slow down minification a lot!
func (m *Minify) AddCmdRegexp(mediatype *regexp.Regexp, cmd *exec.Cmd) {
m.regexp = append(m.regexp, regexpFunc{mediatype, cmdFunc(cmd)})
}
// Minify minifies the content of a Reader and writes it to a Writer (safe for concurrent use).
// An error is returned when no such mediatype exists (ErrNotExist) or when an error occurred in the minifier function.
// Mediatype may take the form of 'text/plain', 'text/*', '*/*' or 'text/plain; charset=UTF-8; version=2.0'.
func (m Minify) Minify(mediatype string, w io.Writer, r io.Reader) error {
mimetype := mediatype
for i, c := range mediatype {
if c == ';' {
mimetype = mediatype[:i]
break
}
}
if minifyFunc, ok := m.literal[mimetype]; ok {
if err := minifyFunc(m, mediatype, w, r); err != nil {
return err
}
return nil
}
for _, minifyRegexp := range m.regexp {
if minifyRegexp.re.MatchString(mimetype) {
if err := minifyRegexp.Func(m, mediatype, w, r); err != nil {
return err
}
return nil
}
}
return ErrNotExist
}
// Bytes minifies an array of bytes (safe for concurrent use). When an error occurs it return the original array and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func Bytes(m Minifier, mediatype string, v []byte) ([]byte, error) {
out := buffer.NewWriter(make([]byte, 0, len(v)))
if err := m.Minify(mediatype, out, buffer.NewReader(v)); err != nil {
return v, err
}
return out.Bytes(), nil
}
// String minifies a string (safe for concurrent use). When an error occurs it return the original string and the error.
// It returns an error when no such mediatype exists (ErrNotExist) or any error occurred in the minifier function.
func String(m Minifier, mediatype string, v string) (string, error) {
out := buffer.NewWriter(make([]byte, 0, len(v)))
if err := m.Minify(mediatype, out, buffer.NewReader([]byte(v))); err != nil {
return v, err
}
return string(out.Bytes()), nil
}