aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/tdewolff/minify/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tdewolff/minify/cmd')
-rw-r--r--vendor/github.com/tdewolff/minify/cmd/minify/README.md149
-rw-r--r--vendor/github.com/tdewolff/minify/cmd/minify/main.go648
-rw-r--r--vendor/github.com/tdewolff/minify/cmd/minify/util.go46
-rw-r--r--vendor/github.com/tdewolff/minify/cmd/minify/watch.go106
4 files changed, 949 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/cmd/minify/README.md b/vendor/github.com/tdewolff/minify/cmd/minify/README.md
new file mode 100644
index 0000000..662ca2d
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/cmd/minify/README.md
@@ -0,0 +1,149 @@
+# Minify [![Join the chat at https://gitter.im/tdewolff/minify](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tdewolff/minify?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+**[Download binaries](https://github.com/tdewolff/minify/releases) for Windows, Linux and macOS**
+
+Minify is a CLI implementation of the minify [library package](https://github.com/tdewolff/minify).
+
+## Installation
+Make sure you have [Go](http://golang.org/) and [Git](http://git-scm.com/) installed.
+
+Run the following command
+
+ go get github.com/tdewolff/minify/cmd/minify
+
+and the `minify` command will be in your `$GOPATH/bin`.
+
+## Usage
+
+ Usage: minify [options] [input]
+
+ Options:
+ -a, --all
+ Minify all files, including hidden files and files in hidden directories
+ -l, --list
+ List all accepted filetypes
+ --match string
+ Filename pattern matching using regular expressions, see https://github.com/google/re2/wiki/Syntax
+ --mime string
+ Mimetype (text/css, application/javascript, ...), optional for input filenames, has precedence over -type
+ -o, --output string
+ Output file or directory (must have trailing slash), leave blank to use stdout
+ -r, --recursive
+ Recursively minify directories
+ --type string
+ Filetype (css, html, js, ...), optional for input filenames
+ -u, --update
+ Update binary
+ --url string
+ URL of file to enable URL minification
+ -v, --verbose
+ Verbose
+ -w, --watch
+ Watch files and minify upon changes
+
+ --css-decimals
+ Number of decimals to preserve in numbers, -1 is all
+ --html-keep-conditional-comments
+ Preserve all IE conditional comments
+ --html-keep-default-attrvals
+ Preserve default attribute values
+ --html-keep-document-tags
+ Preserve html, head and body tags
+ --html-keep-end-tags
+ Preserve all end tags
+ --html-keep-whitespace
+ Preserve whitespace characters but still collapse multiple into one
+ --svg-decimals
+ Number of decimals to preserve in numbers, -1 is all
+ --xml-keep-whitespace
+ Preserve whitespace characters but still collapse multiple into one
+
+ Input:
+ Files or directories, leave blank to use stdin
+
+### Types
+
+ css text/css
+ htm text/html
+ html text/html
+ js text/javascript
+ json application/json
+ svg image/svg+xml
+ xml text/xml
+
+## Examples
+Minify **index.html** to **index-min.html**:
+```sh
+$ minify -o index-min.html index.html
+```
+
+Minify **index.html** to standard output (leave `-o` blank):
+```sh
+$ minify index.html
+```
+
+Normally the mimetype is inferred from the extension, to set the mimetype explicitly:
+```sh
+$ minify --type=html -o index-min.tpl index.tpl
+```
+
+You need to set the type or the mimetype option when using standard input:
+```sh
+$ minify --mime=text/javascript < script.js > script-min.js
+
+$ cat script.js | minify --type=js > script-min.js
+```
+
+### Directories
+You can also give directories as input, and these directories can be minified recursively.
+
+Minify files in the current working directory to **out/** (no subdirectories):
+```sh
+$ minify -o out/ .
+```
+
+Minify files recursively in **src/**:
+```sh
+$ minify -r -o out/ src
+```
+
+Minify only javascript files in **src/**:
+```sh
+$ minify -r -o out/ --match=\.js src
+```
+
+### Concatenate
+When multiple inputs are given and either standard output or a single output file, it will concatenate the files together.
+
+Concatenate **one.css** and **two.css** into **style.css**:
+```sh
+$ minify -o style.css one.css two.css
+```
+
+Concatenate all files in **styles/** into **style.css**:
+```sh
+$ minify -o style.css styles
+```
+
+You can also use `cat` as standard input to concatenate files and use gzip for example:
+```sh
+$ cat one.css two.css three.css | minify --type=css | gzip -9 -c > style.css.gz
+```
+
+### Watching
+To watch file changes and automatically re-minify you can use the `-w` or `--watch` option.
+
+Minify **style.css** to itself and watch changes:
+```sh
+$ minify -w -o style.css style.css
+```
+
+Minify and concatenate **one.css** and **two.css** to **style.css** and watch changes:
+```sh
+$ minify -w -o style.css one.css two.css
+```
+
+Minify files in **src/** and subdirectories to **out/** and watch changes:
+```sh
+$ minify -w -r -o out/ src
+```
diff --git a/vendor/github.com/tdewolff/minify/cmd/minify/main.go b/vendor/github.com/tdewolff/minify/cmd/minify/main.go
new file mode 100644
index 0000000..62263ba
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/cmd/minify/main.go
@@ -0,0 +1,648 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/url"
+ "os"
+ "os/signal"
+ "path"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "sort"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ humanize "github.com/dustin/go-humanize"
+ "github.com/matryer/try"
+ flag "github.com/spf13/pflag"
+ min "github.com/tdewolff/minify"
+ "github.com/tdewolff/minify/css"
+ "github.com/tdewolff/minify/html"
+ "github.com/tdewolff/minify/js"
+ "github.com/tdewolff/minify/json"
+ "github.com/tdewolff/minify/svg"
+ "github.com/tdewolff/minify/xml"
+)
+
+var Version = "master"
+var Commit = ""
+var Date = ""
+
+var filetypeMime = map[string]string{
+ "css": "text/css",
+ "htm": "text/html",
+ "html": "text/html",
+ "js": "text/javascript",
+ "json": "application/json",
+ "svg": "image/svg+xml",
+ "xml": "text/xml",
+}
+
+var (
+ hidden bool
+ list bool
+ m *min.M
+ pattern *regexp.Regexp
+ recursive bool
+ verbose bool
+ version bool
+ watch bool
+)
+
+type task struct {
+ srcs []string
+ srcDir string
+ dst string
+}
+
+var (
+ Error *log.Logger
+ Info *log.Logger
+)
+
+func main() {
+ output := ""
+ mimetype := ""
+ filetype := ""
+ match := ""
+ siteurl := ""
+
+ cssMinifier := &css.Minifier{}
+ htmlMinifier := &html.Minifier{}
+ jsMinifier := &js.Minifier{}
+ jsonMinifier := &json.Minifier{}
+ svgMinifier := &svg.Minifier{}
+ xmlMinifier := &xml.Minifier{}
+
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %s [options] [input]\n\nOptions:\n", os.Args[0])
+ flag.PrintDefaults()
+ fmt.Fprintf(os.Stderr, "\nInput:\n Files or directories, leave blank to use stdin\n")
+ }
+ flag.StringVarP(&output, "output", "o", "", "Output file or directory (must have trailing slash), leave blank to use stdout")
+ flag.StringVar(&mimetype, "mime", "", "Mimetype (text/css, application/javascript, ...), optional for input filenames, has precedence over -type")
+ flag.StringVar(&filetype, "type", "", "Filetype (css, html, js, ...), optional for input filenames")
+ flag.StringVar(&match, "match", "", "Filename pattern matching using regular expressions, see https://github.com/google/re2/wiki/Syntax")
+ flag.BoolVarP(&recursive, "recursive", "r", false, "Recursively minify directories")
+ flag.BoolVarP(&hidden, "all", "a", false, "Minify all files, including hidden files and files in hidden directories")
+ flag.BoolVarP(&list, "list", "l", false, "List all accepted filetypes")
+ flag.BoolVarP(&verbose, "verbose", "v", false, "Verbose")
+ flag.BoolVarP(&watch, "watch", "w", false, "Watch files and minify upon changes")
+ flag.BoolVarP(&version, "version", "", false, "Version")
+
+ flag.StringVar(&siteurl, "url", "", "URL of file to enable URL minification")
+ flag.IntVar(&cssMinifier.Decimals, "css-decimals", -1, "Number of decimals to preserve in numbers, -1 is all")
+ flag.BoolVar(&htmlMinifier.KeepConditionalComments, "html-keep-conditional-comments", false, "Preserve all IE conditional comments")
+ flag.BoolVar(&htmlMinifier.KeepDefaultAttrVals, "html-keep-default-attrvals", false, "Preserve default attribute values")
+ flag.BoolVar(&htmlMinifier.KeepDocumentTags, "html-keep-document-tags", false, "Preserve html, head and body tags")
+ flag.BoolVar(&htmlMinifier.KeepEndTags, "html-keep-end-tags", false, "Preserve all end tags")
+ flag.BoolVar(&htmlMinifier.KeepWhitespace, "html-keep-whitespace", false, "Preserve whitespace characters but still collapse multiple into one")
+ flag.IntVar(&svgMinifier.Decimals, "svg-decimals", -1, "Number of decimals to preserve in numbers, -1 is all")
+ flag.BoolVar(&xmlMinifier.KeepWhitespace, "xml-keep-whitespace", false, "Preserve whitespace characters but still collapse multiple into one")
+ flag.Parse()
+ rawInputs := flag.Args()
+
+ Error = log.New(os.Stderr, "ERROR: ", 0)
+ if verbose {
+ Info = log.New(os.Stderr, "INFO: ", 0)
+ } else {
+ Info = log.New(ioutil.Discard, "INFO: ", 0)
+ }
+
+ if version {
+ if Version == "devel" {
+ fmt.Printf("minify version devel+%.7s %s\n", Commit, Date)
+ } else {
+ fmt.Printf("minify version %s\n", Version)
+ }
+ return
+ }
+
+ if list {
+ var keys []string
+ for k := range filetypeMime {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ fmt.Println(k + "\t" + filetypeMime[k])
+ }
+ return
+ }
+
+ useStdin := len(rawInputs) == 0
+ mimetype = getMimetype(mimetype, filetype, useStdin)
+
+ var err error
+ if match != "" {
+ pattern, err = regexp.Compile(match)
+ if err != nil {
+ Error.Fatalln(err)
+ }
+ }
+
+ if watch && (useStdin || output == "") {
+ Error.Fatalln("watch doesn't work with stdin or stdout")
+ }
+
+ ////////////////
+
+ dirDst := false
+ if output != "" {
+ output = sanitizePath(output)
+ if output[len(output)-1] == '/' {
+ dirDst = true
+ if err := os.MkdirAll(output, 0777); err != nil {
+ Error.Fatalln(err)
+ }
+ }
+ }
+
+ tasks, ok := expandInputs(rawInputs, dirDst)
+ if !ok {
+ os.Exit(1)
+ }
+
+ if ok = expandOutputs(output, &tasks); !ok {
+ os.Exit(1)
+ }
+
+ if len(tasks) == 0 {
+ tasks = append(tasks, task{[]string{""}, "", output}) // stdin
+ }
+
+ m = min.New()
+ m.Add("text/css", cssMinifier)
+ m.Add("text/html", htmlMinifier)
+ m.Add("text/javascript", jsMinifier)
+ m.Add("image/svg+xml", svgMinifier)
+ m.AddRegexp(regexp.MustCompile("[/+]json$"), jsonMinifier)
+ m.AddRegexp(regexp.MustCompile("[/+]xml$"), xmlMinifier)
+
+ if m.URL, err = url.Parse(siteurl); err != nil {
+ Error.Fatalln(err)
+ }
+
+ start := time.Now()
+
+ var fails int32
+ if verbose || len(tasks) == 1 {
+ for _, t := range tasks {
+ if ok := minify(mimetype, t); !ok {
+ fails++
+ }
+ }
+ } else {
+ numWorkers := 4
+ if n := runtime.NumCPU(); n > numWorkers {
+ numWorkers = n
+ }
+
+ sem := make(chan struct{}, numWorkers)
+ for _, t := range tasks {
+ sem <- struct{}{}
+ go func(t task) {
+ defer func() {
+ <-sem
+ }()
+ if ok := minify(mimetype, t); !ok {
+ atomic.AddInt32(&fails, 1)
+ }
+ }(t)
+ }
+
+ // wait for all jobs to be done
+ for i := 0; i < cap(sem); i++ {
+ sem <- struct{}{}
+ }
+ }
+
+ if watch {
+ var watcher *RecursiveWatcher
+ watcher, err = NewRecursiveWatcher(recursive)
+ if err != nil {
+ Error.Fatalln(err)
+ }
+ defer watcher.Close()
+
+ var watcherTasks = make(map[string]task, len(rawInputs))
+ for _, task := range tasks {
+ for _, src := range task.srcs {
+ watcherTasks[src] = task
+ watcher.AddPath(src)
+ }
+ }
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+
+ skip := make(map[string]int)
+ changes := watcher.Run()
+ for changes != nil {
+ select {
+ case <-c:
+ watcher.Close()
+ case file, ok := <-changes:
+ if !ok {
+ changes = nil
+ break
+ }
+ file = sanitizePath(file)
+
+ if skip[file] > 0 {
+ skip[file]--
+ continue
+ }
+
+ var t task
+ if t, ok = watcherTasks[file]; ok {
+ if !verbose {
+ fmt.Fprintln(os.Stderr, file, "changed")
+ }
+ for _, src := range t.srcs {
+ if src == t.dst {
+ skip[file] = 2 // minify creates both a CREATE and WRITE on the file
+ break
+ }
+ }
+ if ok := minify(mimetype, t); !ok {
+ fails++
+ }
+ }
+ }
+ }
+ }
+
+ if verbose {
+ Info.Println(time.Since(start), "total")
+ }
+
+ if fails > 0 {
+ os.Exit(1)
+ }
+}
+
+func getMimetype(mimetype, filetype string, useStdin bool) string {
+ if mimetype == "" && filetype != "" {
+ var ok bool
+ if mimetype, ok = filetypeMime[filetype]; !ok {
+ Error.Fatalln("cannot find mimetype for filetype", filetype)
+ }
+ }
+ if mimetype == "" && useStdin {
+ Error.Fatalln("must specify mimetype or filetype for stdin")
+ }
+
+ if verbose {
+ if mimetype == "" {
+ Info.Println("infer mimetype from file extensions")
+ } else {
+ Info.Println("use mimetype", mimetype)
+ }
+ }
+ return mimetype
+}
+
+func sanitizePath(p string) string {
+ p = filepath.ToSlash(p)
+ isDir := p[len(p)-1] == '/'
+ p = path.Clean(p)
+ if isDir {
+ p += "/"
+ } else if info, err := os.Stat(p); err == nil && info.Mode().IsDir() {
+ p += "/"
+ }
+ return p
+}
+
+func validFile(info os.FileInfo) bool {
+ if info.Mode().IsRegular() && len(info.Name()) > 0 && (hidden || info.Name()[0] != '.') {
+ if pattern != nil && !pattern.MatchString(info.Name()) {
+ return false
+ }
+
+ ext := path.Ext(info.Name())
+ if len(ext) > 0 {
+ ext = ext[1:]
+ }
+
+ if _, ok := filetypeMime[ext]; !ok {
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+func validDir(info os.FileInfo) bool {
+ return info.Mode().IsDir() && len(info.Name()) > 0 && (hidden || info.Name()[0] != '.')
+}
+
+func expandInputs(inputs []string, dirDst bool) ([]task, bool) {
+ ok := true
+ tasks := []task{}
+ for _, input := range inputs {
+ input = sanitizePath(input)
+ info, err := os.Stat(input)
+ if err != nil {
+ Error.Println(err)
+ ok = false
+ continue
+ }
+
+ if info.Mode().IsRegular() {
+ tasks = append(tasks, task{[]string{filepath.ToSlash(input)}, "", ""})
+ } else if info.Mode().IsDir() {
+ expandDir(input, &tasks, &ok)
+ } else {
+ Error.Println("not a file or directory", input)
+ ok = false
+ }
+ }
+
+ if len(tasks) > 1 && !dirDst {
+ // concatenate
+ tasks[0].srcDir = ""
+ for _, task := range tasks[1:] {
+ tasks[0].srcs = append(tasks[0].srcs, task.srcs[0])
+ }
+ tasks = tasks[:1]
+ }
+
+ if verbose && ok {
+ if len(inputs) == 0 {
+ Info.Println("minify from stdin")
+ } else if len(tasks) == 1 {
+ if len(tasks[0].srcs) > 1 {
+ Info.Println("minify and concatenate", len(tasks[0].srcs), "input files")
+ } else {
+ Info.Println("minify input file", tasks[0].srcs[0])
+ }
+ } else {
+ Info.Println("minify", len(tasks), "input files")
+ }
+ }
+ return tasks, ok
+}
+
+func expandDir(input string, tasks *[]task, ok *bool) {
+ if !recursive {
+ if verbose {
+ Info.Println("expanding directory", input)
+ }
+
+ infos, err := ioutil.ReadDir(input)
+ if err != nil {
+ Error.Println(err)
+ *ok = false
+ }
+ for _, info := range infos {
+ if validFile(info) {
+ *tasks = append(*tasks, task{[]string{path.Join(input, info.Name())}, input, ""})
+ }
+ }
+ } else {
+ if verbose {
+ Info.Println("expanding directory", input, "recursively")
+ }
+
+ err := filepath.Walk(input, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if validFile(info) {
+ *tasks = append(*tasks, task{[]string{filepath.ToSlash(path)}, input, ""})
+ } else if info.Mode().IsDir() && !validDir(info) && info.Name() != "." && info.Name() != ".." { // check for IsDir, so we don't skip the rest of the directory when we have an invalid file
+ return filepath.SkipDir
+ }
+ return nil
+ })
+ if err != nil {
+ Error.Println(err)
+ *ok = false
+ }
+ }
+}
+
+func expandOutputs(output string, tasks *[]task) bool {
+ if verbose {
+ if output == "" {
+ Info.Println("minify to stdout")
+ } else if output[len(output)-1] != '/' {
+ Info.Println("minify to output file", output)
+ } else if output == "./" {
+ Info.Println("minify to current working directory")
+ } else {
+ Info.Println("minify to output directory", output)
+ }
+ }
+
+ if output == "" {
+ return true
+ }
+
+ ok := true
+ for i, t := range *tasks {
+ var err error
+ (*tasks)[i].dst, err = getOutputFilename(output, t)
+ if err != nil {
+ Error.Println(err)
+ ok = false
+ }
+ }
+ return ok
+}
+
+func getOutputFilename(output string, t task) (string, error) {
+ if len(output) > 0 && output[len(output)-1] == '/' {
+ rel, err := filepath.Rel(t.srcDir, t.srcs[0])
+ if err != nil {
+ return "", err
+ }
+ return path.Clean(filepath.ToSlash(path.Join(output, rel))), nil
+ }
+ return output, nil
+}
+
+func openInputFile(input string) (*os.File, bool) {
+ var r *os.File
+ if input == "" {
+ r = os.Stdin
+ } else {
+ err := try.Do(func(attempt int) (bool, error) {
+ var err error
+ r, err = os.Open(input)
+ return attempt < 5, err
+ })
+ if err != nil {
+ Error.Println(err)
+ return nil, false
+ }
+ }
+ return r, true
+}
+
+func openOutputFile(output string) (*os.File, bool) {
+ var w *os.File
+ if output == "" {
+ w = os.Stdout
+ } else {
+ if err := os.MkdirAll(path.Dir(output), 0777); err != nil {
+ Error.Println(err)
+ return nil, false
+ }
+ err := try.Do(func(attempt int) (bool, error) {
+ var err error
+ w, err = os.OpenFile(output, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
+ return attempt < 5, err
+ })
+ if err != nil {
+ Error.Println(err)
+ return nil, false
+ }
+ }
+ return w, true
+}
+
+func minify(mimetype string, t task) bool {
+ if mimetype == "" {
+ for _, src := range t.srcs {
+ if len(path.Ext(src)) > 0 {
+ srcMimetype, ok := filetypeMime[path.Ext(src)[1:]]
+ if !ok {
+ Error.Println("cannot infer mimetype from extension in", src)
+ return false
+ }
+ if mimetype == "" {
+ mimetype = srcMimetype
+ } else if srcMimetype != mimetype {
+ Error.Println("inferred mimetype", srcMimetype, "of", src, "for concatenation unequal to previous mimetypes", mimetype)
+ return false
+ }
+ }
+ }
+ }
+
+ srcName := strings.Join(t.srcs, " + ")
+ if len(t.srcs) > 1 {
+ srcName = "(" + srcName + ")"
+ }
+ if srcName == "" {
+ srcName = "stdin"
+ }
+ dstName := t.dst
+ if dstName == "" {
+ dstName = "stdin"
+ } else {
+ // rename original when overwriting
+ for i := range t.srcs {
+ if t.srcs[i] == t.dst {
+ t.srcs[i] += ".bak"
+ err := try.Do(func(attempt int) (bool, error) {
+ err := os.Rename(t.dst, t.srcs[i])
+ return attempt < 5, err
+ })
+ if err != nil {
+ Error.Println(err)
+ return false
+ }
+ break
+ }
+ }
+ }
+
+ frs := make([]io.Reader, len(t.srcs))
+ for i, src := range t.srcs {
+ fr, ok := openInputFile(src)
+ if !ok {
+ for _, fr := range frs {
+ fr.(io.ReadCloser).Close()
+ }
+ return false
+ }
+ if i > 0 && mimetype == filetypeMime["js"] {
+ // prepend newline when concatenating JS files
+ frs[i] = NewPrependReader(fr, []byte("\n"))
+ } else {
+ frs[i] = fr
+ }
+ }
+ r := &countingReader{io.MultiReader(frs...), 0}
+
+ fw, ok := openOutputFile(t.dst)
+ if !ok {
+ for _, fr := range frs {
+ fr.(io.ReadCloser).Close()
+ }
+ return false
+ }
+ var w *countingWriter
+ if fw == os.Stdout {
+ w = &countingWriter{fw, 0}
+ } else {
+ w = &countingWriter{bufio.NewWriter(fw), 0}
+ }
+
+ success := true
+ startTime := time.Now()
+ err := m.Minify(mimetype, w, r)
+ if err != nil {
+ Error.Println("cannot minify "+srcName+":", err)
+ success = false
+ }
+ if verbose {
+ dur := time.Since(startTime)
+ speed := "Inf MB"
+ if dur > 0 {
+ speed = humanize.Bytes(uint64(float64(r.N) / dur.Seconds()))
+ }
+ ratio := 1.0
+ if r.N > 0 {
+ ratio = float64(w.N) / float64(r.N)
+ }
+
+ stats := fmt.Sprintf("(%9v, %6v, %5.1f%%, %6v/s)", dur, humanize.Bytes(uint64(w.N)), ratio*100, speed)
+ if srcName != dstName {
+ Info.Println(stats, "-", srcName, "to", dstName)
+ } else {
+ Info.Println(stats, "-", srcName)
+ }
+ }
+
+ for _, fr := range frs {
+ fr.(io.ReadCloser).Close()
+ }
+ if bw, ok := w.Writer.(*bufio.Writer); ok {
+ bw.Flush()
+ }
+ fw.Close()
+
+ // remove original that was renamed, when overwriting files
+ for i := range t.srcs {
+ if t.srcs[i] == t.dst+".bak" {
+ if err == nil {
+ if err = os.Remove(t.srcs[i]); err != nil {
+ Error.Println(err)
+ return false
+ }
+ } else {
+ if err = os.Remove(t.dst); err != nil {
+ Error.Println(err)
+ return false
+ } else if err = os.Rename(t.srcs[i], t.dst); err != nil {
+ Error.Println(err)
+ return false
+ }
+ }
+ t.srcs[i] = t.dst
+ break
+ }
+ }
+ return success
+}
diff --git a/vendor/github.com/tdewolff/minify/cmd/minify/util.go b/vendor/github.com/tdewolff/minify/cmd/minify/util.go
new file mode 100644
index 0000000..d8db16e
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/cmd/minify/util.go
@@ -0,0 +1,46 @@
+package main
+
+import "io"
+
+type countingReader struct {
+ io.Reader
+ N int
+}
+
+func (r *countingReader) Read(p []byte) (int, error) {
+ n, err := r.Reader.Read(p)
+ r.N += n
+ return n, err
+}
+
+type countingWriter struct {
+ io.Writer
+ N int
+}
+
+func (w *countingWriter) Write(p []byte) (int, error) {
+ n, err := w.Writer.Write(p)
+ w.N += n
+ return n, err
+}
+
+type prependReader struct {
+ io.ReadCloser
+ prepend []byte
+}
+
+func NewPrependReader(r io.ReadCloser, prepend []byte) *prependReader {
+ return &prependReader{r, prepend}
+}
+
+func (r *prependReader) Read(p []byte) (int, error) {
+ if r.prepend != nil {
+ n := copy(p, r.prepend)
+ if n != len(r.prepend) {
+ return n, io.ErrShortBuffer
+ }
+ r.prepend = nil
+ return n, nil
+ }
+ return r.ReadCloser.Read(p)
+}
diff --git a/vendor/github.com/tdewolff/minify/cmd/minify/watch.go b/vendor/github.com/tdewolff/minify/cmd/minify/watch.go
new file mode 100644
index 0000000..7fdb381
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/cmd/minify/watch.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/fsnotify/fsnotify"
+)
+
+type RecursiveWatcher struct {
+ watcher *fsnotify.Watcher
+ paths map[string]bool
+ recursive bool
+}
+
+func NewRecursiveWatcher(recursive bool) (*RecursiveWatcher, error) {
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ return nil, err
+ }
+ return &RecursiveWatcher{watcher, make(map[string]bool), recursive}, nil
+}
+
+func (rw *RecursiveWatcher) Close() error {
+ return rw.watcher.Close()
+}
+
+func (rw *RecursiveWatcher) AddPath(root string) error {
+ info, err := os.Stat(root)
+ if err != nil {
+ return err
+ }
+
+ if info.Mode().IsRegular() {
+ root = filepath.Dir(root)
+ if rw.paths[root] {
+ return nil
+ }
+ if err := rw.watcher.Add(root); err != nil {
+ return err
+ }
+ rw.paths[root] = true
+ return nil
+ } else if !rw.recursive {
+ if rw.paths[root] {
+ return nil
+ }
+ if err := rw.watcher.Add(root); err != nil {
+ return err
+ }
+ rw.paths[root] = true
+ return nil
+ } else {
+ return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if info.Mode().IsDir() {
+ if !validDir(info) || rw.paths[path] {
+ return filepath.SkipDir
+ }
+ if err := rw.watcher.Add(path); err != nil {
+ return err
+ }
+ rw.paths[path] = true
+ }
+ return nil
+ })
+ }
+}
+
+func (rw *RecursiveWatcher) Run() chan string {
+ files := make(chan string, 10)
+ go func() {
+ for rw.watcher.Events != nil && rw.watcher.Errors != nil {
+ select {
+ case event, ok := <-rw.watcher.Events:
+ if !ok {
+ rw.watcher.Events = nil
+ break
+ }
+ if info, err := os.Stat(event.Name); err == nil {
+ if validDir(info) {
+ if event.Op&fsnotify.Create == fsnotify.Create {
+ if err := rw.AddPath(event.Name); err != nil {
+ Error.Println(err)
+ }
+ }
+ } else if validFile(info) {
+ if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
+ files <- event.Name
+ }
+ }
+ }
+ case err, ok := <-rw.watcher.Errors:
+ if !ok {
+ rw.watcher.Errors = nil
+ break
+ }
+ Error.Println(err)
+ }
+ }
+ close(files)
+ }()
+ return files
+}