summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2023-11-06 14:41:31 +0000
committerLibravatar GitHub <noreply@github.com>2023-11-06 14:41:31 +0000
commit74b600655ded2b62b61c7631f96fc14edd9342fe (patch)
tree3e55d8be9c0b38ff59ab34aab719a64327f62ab0
parent[chore]: Bump modernc.org/sqlite from 1.26.0 to 1.27.0 (#2339) (diff)
downloadgotosocial-74b600655ded2b62b61c7631f96fc14edd9342fe.tar.xz
[chore]: Bump github.com/tdewolff/minify/v2 from 2.20.0 to 2.20.6 (#2337)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
-rw-r--r--go.mod6
-rw-r--r--go.sum17
-rw-r--r--vendor/github.com/fsnotify/fsnotify/.cirrus.yml13
-rw-r--r--vendor/github.com/fsnotify/fsnotify/.gitignore1
-rw-r--r--vendor/github.com/fsnotify/fsnotify/CHANGELOG.md83
-rw-r--r--vendor/github.com/fsnotify/fsnotify/README.md81
-rw-r--r--vendor/github.com/fsnotify/fsnotify/backend_fen.go552
-rw-r--r--vendor/github.com/fsnotify/fsnotify/backend_inotify.go377
-rw-r--r--vendor/github.com/fsnotify/fsnotify/backend_kqueue.go295
-rw-r--r--vendor/github.com/fsnotify/fsnotify/backend_other.go205
-rw-r--r--vendor/github.com/fsnotify/fsnotify/backend_windows.go247
-rw-r--r--vendor/github.com/fsnotify/fsnotify/fsnotify.go91
-rw-r--r--vendor/github.com/fsnotify/fsnotify/mkdoc.zsh125
-rw-r--r--vendor/github.com/tdewolff/minify/v2/html/buffer.go16
-rw-r--r--vendor/github.com/tdewolff/minify/v2/html/hash.go1066
-rw-r--r--vendor/github.com/tdewolff/minify/v2/html/html.go51
-rw-r--r--vendor/github.com/tdewolff/minify/v2/html/table.go219
-rw-r--r--vendor/github.com/tdewolff/parse/v2/html/lex.go113
-rw-r--r--vendor/modules.txt8
19 files changed, 2423 insertions, 1143 deletions
diff --git a/go.mod b/go.mod
index 3bcfc90fe..38955818d 100644
--- a/go.mod
+++ b/go.mod
@@ -46,7 +46,7 @@ require (
github.com/superseriousbusiness/activity v1.4.0-gts
github.com/superseriousbusiness/exif-terminator v0.5.0
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
- github.com/tdewolff/minify/v2 v2.20.0
+ github.com/tdewolff/minify/v2 v2.20.6
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/ulule/limiter/v3 v3.11.2
github.com/uptrace/bun v1.1.16
@@ -97,7 +97,7 @@ require (
github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-errors/errors v1.4.1 // indirect
@@ -148,7 +148,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
- github.com/tdewolff/parse/v2 v2.7.0 // indirect
+ github.com/tdewolff/parse/v2 v2.7.4 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
diff --git a/go.sum b/go.sum
index 59a33934f..5f6d664f5 100644
--- a/go.sum
+++ b/go.sum
@@ -164,8 +164,8 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
@@ -491,12 +491,12 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4=
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
-github.com/tdewolff/minify/v2 v2.20.0 h1:JFoL/Jxnyebf/jw3woqpmwBjSNJYSeU+sTFl9dTMHQ8=
-github.com/tdewolff/minify/v2 v2.20.0/go.mod h1:TEE9CWftBwKQLUTZHuH9upjiqlt8zFpQOGxQ81rsG3c=
-github.com/tdewolff/parse/v2 v2.7.0 h1:eVeKTV9nQ9BNS0LPlOgrhLXisiAjacaf60aRgSEtnic=
-github.com/tdewolff/parse/v2 v2.7.0/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8=
-github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew=
-github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/tdewolff/minify/v2 v2.20.6 h1:R4+Iw1ZqJxrqH52WWHtCpukMuhmO/EasY8YlDiSxphw=
+github.com/tdewolff/minify/v2 v2.20.6/go.mod h1:9t0EY9xySGt1vrP8iscmJfywQwDCQyQBYN6ge+9GwP0=
+github.com/tdewolff/parse/v2 v2.7.4 h1:zrUn2CFg9+5llbUZcsycctFlNRyV1D5gFBZRxuGzdzk=
+github.com/tdewolff/parse/v2 v2.7.4/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
+github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA=
+github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
@@ -762,7 +762,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
diff --git a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml
new file mode 100644
index 000000000..ffc7b992b
--- /dev/null
+++ b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml
@@ -0,0 +1,13 @@
+freebsd_task:
+ name: 'FreeBSD'
+ freebsd_instance:
+ image_family: freebsd-13-2
+ install_script:
+ - pkg update -f
+ - pkg install -y go
+ test_script:
+ # run tests as user "cirrus" instead of root
+ - pw useradd cirrus -m
+ - chown -R cirrus:cirrus .
+ - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
+ - sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore
index 1d89d85ce..391cc076b 100644
--- a/vendor/github.com/fsnotify/fsnotify/.gitignore
+++ b/vendor/github.com/fsnotify/fsnotify/.gitignore
@@ -4,3 +4,4 @@
# Output of go build ./cmd/fsnotify
/fsnotify
+/fsnotify.exe
diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
index 77f9593bd..e0e575754 100644
--- a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
+++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
@@ -1,16 +1,87 @@
# Changelog
-All notable changes to this project will be documented in this file.
+Unreleased
+----------
+Nothing yet.
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+1.7.0 - 2023-10-22
+------------------
+This version of fsnotify needs Go 1.17.
-## [Unreleased]
+### Additions
-Nothing yet.
+- illumos: add FEN backend to support illumos and Solaris. ([#371])
+
+- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful
+ in cases where you can't control the kernel buffer and receive a large number
+ of events in bursts. ([#550], [#572])
+
+- all: add `AddWith()`, which is identical to `Add()` but allows passing
+ options. ([#521])
+
+- windows: allow setting the ReadDirectoryChangesW() buffer size with
+ `fsnotify.WithBufferSize()`; the default of 64K is the highest value that
+ works on all platforms and is enough for most purposes, but in some cases a
+ highest buffer is needed. ([#521])
+
+### Changes and fixes
+
+- inotify: remove watcher if a watched path is renamed ([#518])
+
+ After a rename the reported name wasn't updated, or even an empty string.
+ Inotify doesn't provide any good facilities to update it, so just remove the
+ watcher. This is already how it worked on kqueue and FEN.
+
+ On Windows this does work, and remains working.
+
+- windows: don't listen for file attribute changes ([#520])
+
+ File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API,
+ with no way to see if they're a file write or attribute change, so would show
+ up as a fsnotify.Write event. This is never useful, and could result in many
+ spurious Write events.
+
+- windows: return `ErrEventOverflow` if the buffer is full ([#525])
+
+ Before it would merely return "short read", making it hard to detect this
+ error.
+
+- kqueue: make sure events for all files are delivered properly when removing a
+ watched directory ([#526])
+
+ Previously they would get sent with `""` (empty string) or `"."` as the path
+ name.
+
+- kqueue: don't emit spurious Create events for symbolic links ([#524])
+
+ The link would get resolved but kqueue would "forget" it already saw the link
+ itself, resulting on a Create for every Write event for the directory.
+
+- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516])
+
+- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in
+ `backend_other.go`, making it easier to use on unsupported platforms such as
+ WASM, AIX, etc. ([#528])
+
+- other: use the `backend_other.go` no-op if the `appengine` build tag is set;
+ Google AppEngine forbids usage of the unsafe package so the inotify backend
+ won't compile there.
-## [1.6.0] - 2022-10-13
+[#371]: https://github.com/fsnotify/fsnotify/pull/371
+[#516]: https://github.com/fsnotify/fsnotify/pull/516
+[#518]: https://github.com/fsnotify/fsnotify/pull/518
+[#520]: https://github.com/fsnotify/fsnotify/pull/520
+[#521]: https://github.com/fsnotify/fsnotify/pull/521
+[#524]: https://github.com/fsnotify/fsnotify/pull/524
+[#525]: https://github.com/fsnotify/fsnotify/pull/525
+[#526]: https://github.com/fsnotify/fsnotify/pull/526
+[#528]: https://github.com/fsnotify/fsnotify/pull/528
+[#537]: https://github.com/fsnotify/fsnotify/pull/537
+[#550]: https://github.com/fsnotify/fsnotify/pull/550
+[#572]: https://github.com/fsnotify/fsnotify/pull/572
+1.6.0 - 2022-10-13
+------------------
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
but not documented). It also increases the minimum Linux version to 2.6.32.
diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md
index d4e6080fe..e480733d1 100644
--- a/vendor/github.com/fsnotify/fsnotify/README.md
+++ b/vendor/github.com/fsnotify/fsnotify/README.md
@@ -1,29 +1,31 @@
fsnotify is a Go library to provide cross-platform filesystem notifications on
-Windows, Linux, macOS, and BSD systems.
+Windows, Linux, macOS, BSD, and illumos.
-Go 1.16 or newer is required; the full documentation is at
+Go 1.17 or newer is required; the full documentation is at
https://pkg.go.dev/github.com/fsnotify/fsnotify
-**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
-released version, whereas this README is for the last development version which
-may include additions/changes.**
-
---
Platform support:
-| Adapter | OS | Status |
-| --------------------- | ---------------| -------------------------------------------------------------|
-| inotify | Linux 2.6.32+ | Supported |
-| kqueue | BSD, macOS | Supported |
-| ReadDirectoryChangesW | Windows | Supported |
-| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
-| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
-| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
-| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
-| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
-
-Linux and macOS should include Android and iOS, but these are currently untested.
+| Backend | OS | Status |
+| :-------------------- | :--------- | :------------------------------------------------------------------------ |
+| inotify | Linux | Supported |
+| kqueue | BSD, macOS | Supported |
+| ReadDirectoryChangesW | Windows | Supported |
+| FEN | illumos | Supported |
+| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
+| AHAFS | AIX | [aix branch]; experimental due to lack of maintainer and test environment |
+| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
+| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
+| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
+
+Linux and illumos should include Android and Solaris, but these are currently
+untested.
+
+[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
+[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
+[aix branch]: https://github.com/fsnotify/fsnotify/issues/353#issuecomment-1284590129
Usage
-----
@@ -83,20 +85,23 @@ run with:
% go run ./cmd/fsnotify
+Further detailed documentation can be found in godoc:
+https://pkg.go.dev/github.com/fsnotify/fsnotify
+
FAQ
---
### Will a file still be watched when it's moved to another directory?
No, not unless you are watching the location it was moved to.
-### Are subdirectories watched too?
+### Are subdirectories watched?
No, you must add watches for any directory you want to watch (a recursive
watcher is on the roadmap: [#18]).
[#18]: https://github.com/fsnotify/fsnotify/issues/18
### Do I have to watch the Error and Event channels in a goroutine?
-As of now, yes (you can read both channels in the same goroutine using `select`,
-you don't need a separate goroutine for both channels; see the example).
+Yes. You can read both channels in the same goroutine using `select` (you don't
+need a separate goroutine for both channels; see the example).
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
fsnotify requires support from underlying OS to work. The current NFS and SMB
@@ -107,6 +112,32 @@ This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
[#9]: https://github.com/fsnotify/fsnotify/issues/9
+### Why do I get many Chmod events?
+Some programs may generate a lot of attribute changes; for example Spotlight on
+macOS, anti-virus programs, backup applications, and some others are known to do
+this. As a rule, it's typically best to ignore Chmod events. They're often not
+useful, and tend to cause problems.
+
+Spotlight indexing on macOS can result in multiple events (see [#15]). A
+temporary workaround is to add your folder(s) to the *Spotlight Privacy
+settings* until we have a native FSEvents implementation (see [#11]).
+
+[#11]: https://github.com/fsnotify/fsnotify/issues/11
+[#15]: https://github.com/fsnotify/fsnotify/issues/15
+
+### Watching a file doesn't work well
+Watching individual files (rather than directories) is generally not recommended
+as many programs (especially editors) update files atomically: it will write to
+a temporary file which is then moved to to destination, overwriting the original
+(or some variant thereof). The watcher on the original file is now lost, as that
+no longer exists.
+
+The upshot of this is that a power failure or crash won't leave a half-written
+file.
+
+Watch the parent directory and use `Event.Name` to filter out files you're not
+interested in. There is an example of this in `cmd/fsnotify/file.go`.
+
Platform-specific notes
-----------------------
### Linux
@@ -151,11 +182,3 @@ these platforms.
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
control the maximum number of open files.
-
-### macOS
-Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
-workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
-have a native FSEvents implementation (see [#11]).
-
-[#11]: https://github.com/fsnotify/fsnotify/issues/11
-[#15]: https://github.com/fsnotify/fsnotify/issues/15
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_fen.go b/vendor/github.com/fsnotify/fsnotify/backend_fen.go
index 1a95ad8e7..28497f1dd 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_fen.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_fen.go
@@ -1,10 +1,19 @@
//go:build solaris
// +build solaris
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
"errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "golang.org/x/sys/unix"
)
// Watcher watches a set of paths, delivering events on a channel.
@@ -17,9 +26,9 @@ import (
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
-// fp := os.Open("file")
-// os.Remove("file") // Triggers Chmod
-// fp.Close() // Triggers Remove
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
@@ -33,16 +42,16 @@ import (
//
// To increase them you can use sysctl or write the value to the /proc file:
//
-// # Default values on Linux 5.18
-// sysctl fs.inotify.max_user_watches=124983
-// sysctl fs.inotify.max_user_instances=128
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
-// fs.inotify.max_user_watches=124983
-// fs.inotify.max_user_instances=128
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
@@ -58,14 +67,20 @@ import (
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
//
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
//
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
@@ -92,44 +107,129 @@ type Watcher struct {
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
- // you may get hundreds of Write events, so you
- // probably want to wait until you've stopped receiving
- // them (see the dedup example in cmd/fsnotify).
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
- // and on kqueue when a file is truncated. On Windows
- // it's never sent.
+ // when a file is truncated. On Windows it's never
+ // sent.
Events chan Event
// Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
Errors chan error
+
+ mu sync.Mutex
+ port *unix.EventPort
+ done chan struct{} // Channel for sending a "quit message" to the reader goroutine
+ dirs map[string]struct{} // Explicitly watched directories
+ watches map[string]struct{} // Explicitly watched non-directories
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
- return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
+ return NewBufferedWatcher(0)
}
-// Close removes all watches and closes the events channel.
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
+ w := &Watcher{
+ Events: make(chan Event, sz),
+ Errors: make(chan error),
+ dirs: make(map[string]struct{}),
+ watches: make(map[string]struct{}),
+ done: make(chan struct{}),
+ }
+
+ var err error
+ w.port, err = unix.NewEventPort()
+ if err != nil {
+ return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err)
+ }
+
+ go w.readEvents()
+ return w, nil
+}
+
+// sendEvent attempts to send an event to the user, returning true if the event
+// was put in the channel successfully and false if the watcher has been closed.
+func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
+ select {
+ case w.Events <- Event{Name: name, Op: op}:
+ return true
+ case <-w.done:
+ return false
+ }
+}
+
+// sendError attempts to send an error to the user, returning true if the error
+// was put in the channel successfully and false if the watcher has been closed.
+func (w *Watcher) sendError(err error) (sent bool) {
+ select {
+ case w.Errors <- err:
+ return true
+ case <-w.done:
+ return false
+ }
+}
+
+func (w *Watcher) isClosed() bool {
+ select {
+ case <-w.done:
+ return true
+ default:
+ return false
+ }
+}
+
+// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
- return nil
+ // Take the lock used by associateFile to prevent lingering events from
+ // being processed after the close
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ if w.isClosed() {
+ return nil
+ }
+ close(w.done)
+ return w.port.Close()
}
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -139,15 +239,63 @@ func (w *Watcher) Close() error {
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+ if w.isClosed() {
+ return ErrClosed
+ }
+ if w.port.PathIsWatched(name) {
+ return nil
+ }
+
+ _ = getOptions(opts...)
+
+ // Currently we resolve symlinks that were explicitly requested to be
+ // watched. Otherwise we would use LStat here.
+ stat, err := os.Stat(name)
+ if err != nil {
+ return err
+ }
+
+ // Associate all files in the directory.
+ if stat.IsDir() {
+ err := w.handleDirectory(name, stat, true, w.associateFile)
+ if err != nil {
+ return err
+ }
+
+ w.mu.Lock()
+ w.dirs[name] = struct{}{}
+ w.mu.Unlock()
+ return nil
+ }
+
+ err = w.associateFile(name, stat, true)
+ if err != nil {
+ return err
+ }
+
+ w.mu.Lock()
+ w.watches[name] = struct{}{}
+ w.mu.Unlock()
return nil
}
@@ -157,6 +305,336 @@ func (w *Watcher) Add(name string) error {
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
+ if w.isClosed() {
+ return nil
+ }
+ if !w.port.PathIsWatched(name) {
+ return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
+ }
+
+ // The user has expressed an intent. Immediately remove this name from
+ // whichever watch list it might be in. If it's not in there the delete
+ // doesn't cause harm.
+ w.mu.Lock()
+ delete(w.watches, name)
+ delete(w.dirs, name)
+ w.mu.Unlock()
+
+ stat, err := os.Stat(name)
+ if err != nil {
+ return err
+ }
+
+ // Remove associations for every file in the directory.
+ if stat.IsDir() {
+ err := w.handleDirectory(name, stat, false, w.dissociateFile)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+
+ err = w.port.DissociatePath(name)
+ if err != nil {
+ return err
+ }
+
return nil
}
+
+// readEvents contains the main loop that runs in a goroutine watching for events.
+func (w *Watcher) readEvents() {
+ // If this function returns, the watcher has been closed and we can close
+ // these channels
+ defer func() {
+ close(w.Errors)
+ close(w.Events)
+ }()
+
+ pevents := make([]unix.PortEvent, 8)
+ for {
+ count, err := w.port.Get(pevents, 1, nil)
+ if err != nil && err != unix.ETIME {
+ // Interrupted system call (count should be 0) ignore and continue
+ if errors.Is(err, unix.EINTR) && count == 0 {
+ continue
+ }
+ // Get failed because we called w.Close()
+ if errors.Is(err, unix.EBADF) && w.isClosed() {
+ return
+ }
+ // There was an error not caused by calling w.Close()
+ if !w.sendError(err) {
+ return
+ }
+ }
+
+ p := pevents[:count]
+ for _, pevent := range p {
+ if pevent.Source != unix.PORT_SOURCE_FILE {
+ // Event from unexpected source received; should never happen.
+ if !w.sendError(errors.New("Event from unexpected source received")) {
+ return
+ }
+ continue
+ }
+
+ err = w.handleEvent(&pevent)
+ if err != nil {
+ if !w.sendError(err) {
+ return
+ }
+ }
+ }
+ }
+}
+
+func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
+ files, err := os.ReadDir(path)
+ if err != nil {
+ return err
+ }
+
+ // Handle all children of the directory.
+ for _, entry := range files {
+ finfo, err := entry.Info()
+ if err != nil {
+ return err
+ }
+ err = handler(filepath.Join(path, finfo.Name()), finfo, false)
+ if err != nil {
+ return err
+ }
+ }
+
+ // And finally handle the directory itself.
+ return handler(path, stat, follow)
+}
+
+// handleEvent might need to emit more than one fsnotify event if the events
+// bitmap matches more than one event type (e.g. the file was both modified and
+// had the attributes changed between when the association was created and the
+// when event was returned)
+func (w *Watcher) handleEvent(event *unix.PortEvent) error {
+ var (
+ events = event.Events
+ path = event.Path
+ fmode = event.Cookie.(os.FileMode)
+ reRegister = true
+ )
+
+ w.mu.Lock()
+ _, watchedDir := w.dirs[path]
+ _, watchedPath := w.watches[path]
+ w.mu.Unlock()
+ isWatched := watchedDir || watchedPath
+
+ if events&unix.FILE_DELETE != 0 {
+ if !w.sendEvent(path, Remove) {
+ return nil
+ }
+ reRegister = false
+ }
+ if events&unix.FILE_RENAME_FROM != 0 {
+ if !w.sendEvent(path, Rename) {
+ return nil
+ }
+ // Don't keep watching the new file name
+ reRegister = false
+ }
+ if events&unix.FILE_RENAME_TO != 0 {
+ // We don't report a Rename event for this case, because Rename events
+ // are interpreted as referring to the _old_ name of the file, and in
+ // this case the event would refer to the new name of the file. This
+ // type of rename event is not supported by fsnotify.
+
+ // inotify reports a Remove event in this case, so we simulate this
+ // here.
+ if !w.sendEvent(path, Remove) {
+ return nil
+ }
+ // Don't keep watching the file that was removed
+ reRegister = false
+ }
+
+ // The file is gone, nothing left to do.
+ if !reRegister {
+ if watchedDir {
+ w.mu.Lock()
+ delete(w.dirs, path)
+ w.mu.Unlock()
+ }
+ if watchedPath {
+ w.mu.Lock()
+ delete(w.watches, path)
+ w.mu.Unlock()
+ }
+ return nil
+ }
+
+ // If we didn't get a deletion the file still exists and we're going to have
+ // to watch it again. Let's Stat it now so that we can compare permissions
+ // and have what we need to continue watching the file
+
+ stat, err := os.Lstat(path)
+ if err != nil {
+ // This is unexpected, but we should still emit an event. This happens
+ // most often on "rm -r" of a subdirectory inside a watched directory We
+ // get a modify event of something happening inside, but by the time we
+ // get here, the sudirectory is already gone. Clearly we were watching
+ // this path but now it is gone. Let's tell the user that it was
+ // removed.
+ if !w.sendEvent(path, Remove) {
+ return nil
+ }
+ // Suppress extra write events on removed directories; they are not
+ // informative and can be confusing.
+ return nil
+ }
+
+ // resolve symlinks that were explicitly watched as we would have at Add()
+ // time. this helps suppress spurious Chmod events on watched symlinks
+ if isWatched {
+ stat, err = os.Stat(path)
+ if err != nil {
+ // The symlink still exists, but the target is gone. Report the
+ // Remove similar to above.
+ if !w.sendEvent(path, Remove) {
+ return nil
+ }
+ // Don't return the error
+ }
+ }
+
+ if events&unix.FILE_MODIFIED != 0 {
+ if fmode.IsDir() {
+ if watchedDir {
+ if err := w.updateDirectory(path); err != nil {
+ return err
+ }
+ } else {
+ if !w.sendEvent(path, Write) {
+ return nil
+ }
+ }
+ } else {
+ if !w.sendEvent(path, Write) {
+ return nil
+ }
+ }
+ }
+ if events&unix.FILE_ATTRIB != 0 && stat != nil {
+ // Only send Chmod if perms changed
+ if stat.Mode().Perm() != fmode.Perm() {
+ if !w.sendEvent(path, Chmod) {
+ return nil
+ }
+ }
+ }
+
+ if stat != nil {
+ // If we get here, it means we've hit an event above that requires us to
+ // continue watching the file or directory
+ return w.associateFile(path, stat, isWatched)
+ }
+ return nil
+}
+
+func (w *Watcher) updateDirectory(path string) error {
+ // The directory was modified, so we must find unwatched entities and watch
+ // them. If something was removed from the directory, nothing will happen,
+ // as everything else should still be watched.
+ files, err := os.ReadDir(path)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range files {
+ path := filepath.Join(path, entry.Name())
+ if w.port.PathIsWatched(path) {
+ continue
+ }
+
+ finfo, err := entry.Info()
+ if err != nil {
+ return err
+ }
+ err = w.associateFile(path, finfo, false)
+ if err != nil {
+ if !w.sendError(err) {
+ return nil
+ }
+ }
+ if !w.sendEvent(path, Create) {
+ return nil
+ }
+ }
+ return nil
+}
+
+func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
+ if w.isClosed() {
+ return ErrClosed
+ }
+ // This is primarily protecting the call to AssociatePath but it is
+ // important and intentional that the call to PathIsWatched is also
+ // protected by this mutex. Without this mutex, AssociatePath has been seen
+ // to error out that the path is already associated.
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ if w.port.PathIsWatched(path) {
+ // Remove the old association in favor of this one If we get ENOENT,
+ // then while the x/sys/unix wrapper still thought that this path was
+ // associated, the underlying event port did not. This call will have
+ // cleared up that discrepancy. The most likely cause is that the event
+ // has fired but we haven't processed it yet.
+ err := w.port.DissociatePath(path)
+ if err != nil && err != unix.ENOENT {
+ return err
+ }
+ }
+ // FILE_NOFOLLOW means we watch symlinks themselves rather than their
+ // targets.
+ events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
+ if follow {
+ // We *DO* follow symlinks for explicitly watched entries.
+ events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
+ }
+ return w.port.AssociatePath(path, stat,
+ events,
+ stat.Mode())
+}
+
+func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
+ if !w.port.PathIsWatched(path) {
+ return nil
+ }
+ return w.port.DissociatePath(path)
+}
+
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) WatchList() []string {
+ if w.isClosed() {
+ return nil
+ }
+
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ entries := make([]string, 0, len(w.watches)+len(w.dirs))
+ for pathname := range w.dirs {
+ entries = append(entries, pathname)
+ }
+ for pathname := range w.watches {
+ entries = append(entries, pathname)
+ }
+
+ return entries
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
index 54c77fbb0..921c1c1e4 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go
@@ -1,5 +1,8 @@
-//go:build linux
-// +build linux
+//go:build linux && !appengine
+// +build linux,!appengine
+
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
package fsnotify
@@ -26,9 +29,9 @@ import (
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
-// fp := os.Open("file")
-// os.Remove("file") // Triggers Chmod
-// fp.Close() // Triggers Remove
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
@@ -42,16 +45,16 @@ import (
//
// To increase them you can use sysctl or write the value to the /proc file:
//
-// # Default values on Linux 5.18
-// sysctl fs.inotify.max_user_watches=124983
-// sysctl fs.inotify.max_user_instances=128
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
-// fs.inotify.max_user_watches=124983
-// fs.inotify.max_user_instances=128
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
@@ -67,14 +70,20 @@ import (
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
//
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
//
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
@@ -101,36 +110,148 @@ type Watcher struct {
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
- // you may get hundreds of Write events, so you
- // probably want to wait until you've stopped receiving
- // them (see the dedup example in cmd/fsnotify).
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
- // and on kqueue when a file is truncated. On Windows
- // it's never sent.
+ // when a file is truncated. On Windows it's never
+ // sent.
Events chan Event
// Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
Errors chan error
// Store fd here as os.File.Read() will no longer return on close after
// calling Fd(). See: https://github.com/golang/go/issues/26439
fd int
- mu sync.Mutex // Map access
inotifyFile *os.File
- watches map[string]*watch // Map of inotify watches (key: path)
- paths map[int]string // Map of watched paths (key: watch descriptor)
- done chan struct{} // Channel for sending a "quit message" to the reader goroutine
- doneResp chan struct{} // Channel to respond to Close
+ watches *watches
+ done chan struct{} // Channel for sending a "quit message" to the reader goroutine
+ closeMu sync.Mutex
+ doneResp chan struct{} // Channel to respond to Close
+}
+
+type (
+ watches struct {
+ mu sync.RWMutex
+ wd map[uint32]*watch // wd → watch
+ path map[string]uint32 // pathname → wd
+ }
+ watch struct {
+ wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
+ flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
+ path string // Watch path.
+ }
+)
+
+func newWatches() *watches {
+ return &watches{
+ wd: make(map[uint32]*watch),
+ path: make(map[string]uint32),
+ }
+}
+
+func (w *watches) len() int {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+ return len(w.wd)
+}
+
+func (w *watches) add(ww *watch) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ w.wd[ww.wd] = ww
+ w.path[ww.path] = ww.wd
+}
+
+func (w *watches) remove(wd uint32) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ delete(w.path, w.wd[wd].path)
+ delete(w.wd, wd)
+}
+
+func (w *watches) removePath(path string) (uint32, bool) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ wd, ok := w.path[path]
+ if !ok {
+ return 0, false
+ }
+
+ delete(w.path, path)
+ delete(w.wd, wd)
+
+ return wd, true
+}
+
+func (w *watches) byPath(path string) *watch {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+ return w.wd[w.path[path]]
+}
+
+func (w *watches) byWd(wd uint32) *watch {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+ return w.wd[wd]
+}
+
+func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
+ var existing *watch
+ wd, ok := w.path[path]
+ if ok {
+ existing = w.wd[wd]
+ }
+
+ upd, err := f(existing)
+ if err != nil {
+ return err
+ }
+ if upd != nil {
+ w.wd[upd.wd] = upd
+ w.path[upd.path] = upd.wd
+
+ if upd.wd != wd {
+ delete(w.wd, wd)
+ }
+ }
+
+ return nil
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
- // Create inotify fd
- // Need to set the FD to nonblocking mode in order for SetDeadline methods to work
- // Otherwise, blocking i/o operations won't terminate on close
+ return NewBufferedWatcher(0)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
+ // Need to set nonblocking mode for SetDeadline to work, otherwise blocking
+ // I/O operations won't terminate on close.
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
if fd == -1 {
return nil, errno
@@ -139,9 +260,8 @@ func NewWatcher() (*Watcher, error) {
w := &Watcher{
fd: fd,
inotifyFile: os.NewFile(uintptr(fd), ""),
- watches: make(map[string]*watch),
- paths: make(map[int]string),
- Events: make(chan Event),
+ watches: newWatches(),
+ Events: make(chan Event, sz),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
@@ -157,8 +277,8 @@ func (w *Watcher) sendEvent(e Event) bool {
case w.Events <- e:
return true
case <-w.done:
+ return false
}
- return false
}
// Returns true if the error was sent, or false if watcher is closed.
@@ -180,17 +300,15 @@ func (w *Watcher) isClosed() bool {
}
}
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
- w.mu.Lock()
+ w.closeMu.Lock()
if w.isClosed() {
- w.mu.Unlock()
+ w.closeMu.Unlock()
return nil
}
-
- // Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)
- w.mu.Unlock()
+ w.closeMu.Unlock()
// Causes any blocking reads to return with an error, provided the file
// still supports deadline operations.
@@ -207,17 +325,21 @@ func (w *Watcher) Close() error {
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -227,44 +349,59 @@ func (w *Watcher) Close() error {
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
- name = filepath.Clean(name)
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
- return errors.New("inotify instance already closed")
+ return ErrClosed
}
+ name = filepath.Clean(name)
+ _ = getOptions(opts...)
+
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
- w.mu.Lock()
- defer w.mu.Unlock()
- watchEntry := w.watches[name]
- if watchEntry != nil {
- flags |= watchEntry.flags | unix.IN_MASK_ADD
- }
- wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
- if wd == -1 {
- return errno
- }
+ return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
+ if existing != nil {
+ flags |= existing.flags | unix.IN_MASK_ADD
+ }
- if watchEntry == nil {
- w.watches[name] = &watch{wd: uint32(wd), flags: flags}
- w.paths[wd] = name
- } else {
- watchEntry.wd = uint32(wd)
- watchEntry.flags = flags
- }
+ wd, err := unix.InotifyAddWatch(w.fd, name, flags)
+ if wd == -1 {
+ return nil, err
+ }
- return nil
+ if existing == nil {
+ return &watch{
+ wd: uint32(wd),
+ path: name,
+ flags: flags,
+ }, nil
+ }
+
+ existing.wd = uint32(wd)
+ existing.flags = flags
+ return existing, nil
+ })
}
// Remove stops monitoring the path for changes.
@@ -273,32 +410,22 @@ func (w *Watcher) Add(name string) error {
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
- name = filepath.Clean(name)
-
- // Fetch the watch.
- w.mu.Lock()
- defer w.mu.Unlock()
- watch, ok := w.watches[name]
+ if w.isClosed() {
+ return nil
+ }
+ return w.remove(filepath.Clean(name))
+}
- // Remove it from inotify.
+func (w *Watcher) remove(name string) error {
+ wd, ok := w.watches.removePath(name)
if !ok {
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
}
- // We successfully removed the watch if InotifyRmWatch doesn't return an
- // error, we need to clean up our internal state to ensure it matches
- // inotify's kernel state.
- delete(w.paths, int(watch.wd))
- delete(w.watches, name)
-
- // inotify_rm_watch will return EINVAL if the file has been deleted;
- // the inotify will already have been removed.
- // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
- // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
- // so that EINVAL means that the wd is being rm_watch()ed or its file removed
- // by another thread and we have not received IN_IGNORE event.
- success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
+ success, errno := unix.InotifyRmWatch(w.fd, wd)
if success == -1 {
// TODO: Perhaps it's not helpful to return an error here in every case;
// The only two possible errors are:
@@ -312,28 +439,28 @@ func (w *Watcher) Remove(name string) error {
// are watching is deleted.
return errno
}
-
return nil
}
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
- w.mu.Lock()
- defer w.mu.Unlock()
+ if w.isClosed() {
+ return nil
+ }
- entries := make([]string, 0, len(w.watches))
- for pathname := range w.watches {
+ entries := make([]string, 0, w.watches.len())
+ w.watches.mu.RLock()
+ for pathname := range w.watches.path {
entries = append(entries, pathname)
}
+ w.watches.mu.RUnlock()
return entries
}
-type watch struct {
- wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
- flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
-}
-
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Events channel
func (w *Watcher) readEvents() {
@@ -367,14 +494,11 @@ func (w *Watcher) readEvents() {
if n < unix.SizeofInotifyEvent {
var err error
if n == 0 {
- // If EOF is received. This should really never happen.
- err = io.EOF
+ err = io.EOF // If EOF is received. This should really never happen.
} else if n < 0 {
- // If an error occurred while reading.
- err = errno
+ err = errno // If an error occurred while reading.
} else {
- // Read was too short.
- err = errors.New("notify: short read in readEvents()")
+ err = errors.New("notify: short read in readEvents()") // Read was too short.
}
if !w.sendError(err) {
return
@@ -403,18 +527,29 @@ func (w *Watcher) readEvents() {
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
- w.mu.Lock()
- name, ok := w.paths[int(raw.Wd)]
- // IN_DELETE_SELF occurs when the file/directory being watched is removed.
- // This is a sign to clean up the maps, otherwise we are no longer in sync
- // with the inotify kernel state which has already deleted the watch
- // automatically.
- if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
- delete(w.paths, int(raw.Wd))
- delete(w.watches, name)
+ watch := w.watches.byWd(uint32(raw.Wd))
+
+ // inotify will automatically remove the watch on deletes; just need
+ // to clean our state here.
+ if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
+ w.watches.remove(watch.wd)
+ }
+ // We can't really update the state when a watched path is moved;
+ // only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
+ // the watch.
+ if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
+ err := w.remove(watch.path)
+ if err != nil && !errors.Is(err, ErrNonExistentWatch) {
+ if !w.sendError(err) {
+ return
+ }
+ }
}
- w.mu.Unlock()
+ var name string
+ if watch != nil {
+ name = watch.path
+ }
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
index 29087469b..063a0915a 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
@@ -1,12 +1,14 @@
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
// +build freebsd openbsd netbsd dragonfly darwin
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
"errors"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"sync"
@@ -24,9 +26,9 @@ import (
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
-// fp := os.Open("file")
-// os.Remove("file") // Triggers Chmod
-// fp.Close() // Triggers Remove
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
@@ -40,16 +42,16 @@ import (
//
// To increase them you can use sysctl or write the value to the /proc file:
//
-// # Default values on Linux 5.18
-// sysctl fs.inotify.max_user_watches=124983
-// sysctl fs.inotify.max_user_instances=128
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
-// fs.inotify.max_user_watches=124983
-// fs.inotify.max_user_instances=128
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
@@ -65,14 +67,20 @@ import (
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
-// # macOS notes
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
//
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
//
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
@@ -99,18 +107,27 @@ type Watcher struct {
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
- // you may get hundreds of Write events, so you
- // probably want to wait until you've stopped receiving
- // them (see the dedup example in cmd/fsnotify).
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
- // and on kqueue when a file is truncated. On Windows
- // it's never sent.
+ // when a file is truncated. On Windows it's never
+ // sent.
Events chan Event
// Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
Errors chan error
done chan struct{}
@@ -133,6 +150,18 @@ type pathInfo struct {
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
+ return NewBufferedWatcher(0)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
kq, closepipe, err := newKqueue()
if err != nil {
return nil, err
@@ -147,7 +176,7 @@ func NewWatcher() (*Watcher, error) {
paths: make(map[int]pathInfo),
fileExists: make(map[string]struct{}),
userWatches: make(map[string]struct{}),
- Events: make(chan Event),
+ Events: make(chan Event, sz),
Errors: make(chan error),
done: make(chan struct{}),
}
@@ -197,8 +226,8 @@ func (w *Watcher) sendEvent(e Event) bool {
case w.Events <- e:
return true
case <-w.done:
+ return false
}
- return false
}
// Returns true if the error was sent, or false if watcher is closed.
@@ -207,11 +236,11 @@ func (w *Watcher) sendError(err error) bool {
case w.Errors <- err:
return true
case <-w.done:
+ return false
}
- return false
}
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
w.mu.Lock()
if w.isClosed {
@@ -239,17 +268,21 @@ func (w *Watcher) Close() error {
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -259,15 +292,28 @@ func (w *Watcher) Close() error {
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+ _ = getOptions(opts...)
+
w.mu.Lock()
w.userWatches[name] = struct{}{}
w.mu.Unlock()
@@ -281,9 +327,19 @@ func (w *Watcher) Add(name string) error {
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
+ return w.remove(name, true)
+}
+
+func (w *Watcher) remove(name string, unwatchFiles bool) error {
name = filepath.Clean(name)
w.mu.Lock()
+ if w.isClosed {
+ w.mu.Unlock()
+ return nil
+ }
watchfd, ok := w.watches[name]
w.mu.Unlock()
if !ok {
@@ -315,7 +371,7 @@ func (w *Watcher) Remove(name string) error {
w.mu.Unlock()
// Find all watched paths that are in this directory that are not external.
- if isDir {
+ if unwatchFiles && isDir {
var pathsToRemove []string
w.mu.Lock()
for fd := range w.watchesByDir[name] {
@@ -326,20 +382,25 @@ func (w *Watcher) Remove(name string) error {
}
w.mu.Unlock()
for _, name := range pathsToRemove {
- // Since these are internal, not much sense in propagating error
- // to the user, as that will just confuse them with an error about
- // a path they did not explicitly watch themselves.
+ // Since these are internal, not much sense in propagating error to
+ // the user, as that will just confuse them with an error about a
+ // path they did not explicitly watch themselves.
w.Remove(name)
}
}
-
return nil
}
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
w.mu.Lock()
defer w.mu.Unlock()
+ if w.isClosed {
+ return nil
+ }
entries := make([]string, 0, len(w.userWatches))
for pathname := range w.userWatches {
@@ -352,18 +413,18 @@ func (w *Watcher) WatchList() []string {
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
-// addWatch adds name to the watched file set.
-// The flags are interpreted as described in kevent(2).
-// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
+// addWatch adds name to the watched file set; the flags are interpreted as
+// described in kevent(2).
+//
+// Returns the real path to the file which was added, with symlinks resolved.
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
var isDir bool
- // Make ./name and name equivalent
name = filepath.Clean(name)
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
- return "", errors.New("kevent instance already closed")
+ return "", ErrClosed
}
watchfd, alreadyWatching := w.watches[name]
// We already have a watch, but we can still override flags.
@@ -383,27 +444,30 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
return "", nil
}
- // Follow Symlinks
- //
- // Linux can add unresolvable symlinks to the watch list without issue,
- // and Windows can't do symlinks period. To maintain consistency, we
- // will act like everything is fine if the link can't be resolved.
- // There will simply be no file events for broken symlinks. Hence the
- // returns of nil on errors.
+ // Follow Symlinks.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
- name, err = filepath.EvalSymlinks(name)
+ link, err := os.Readlink(name)
if err != nil {
+ // Return nil because Linux can add unresolvable symlinks to the
+ // watch list without problems, so maintain consistency with
+ // that. There will be no file events for broken symlinks.
+ // TODO: more specific check; returns os.PathError; ENOENT?
return "", nil
}
w.mu.Lock()
- _, alreadyWatching = w.watches[name]
+ _, alreadyWatching = w.watches[link]
w.mu.Unlock()
if alreadyWatching {
- return name, nil
+ // Add to watches so we don't get spurious Create events later
+ // on when we diff the directories.
+ w.watches[name] = 0
+ w.fileExists[name] = struct{}{}
+ return link, nil
}
+ name = link
fi, err = os.Lstat(name)
if err != nil {
return "", nil
@@ -411,7 +475,7 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
}
// Retry on EINTR; open() can return EINTR in practice on macOS.
- // See #354, and go issues 11180 and 39237.
+ // See #354, and Go issues 11180 and 39237.
for {
watchfd, err = unix.Open(name, openMode, 0)
if err == nil {
@@ -444,14 +508,13 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
w.watchesByDir[parentName] = watchesByDir
}
watchesByDir[watchfd] = struct{}{}
-
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
w.mu.Unlock()
}
if isDir {
- // Watch the directory if it has not been watched before,
- // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
+ // Watch the directory if it has not been watched before, or if it was
+ // watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
w.mu.Lock()
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
@@ -473,13 +536,10 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
// Event values that it sends down the Events channel.
func (w *Watcher) readEvents() {
defer func() {
- err := unix.Close(w.kq)
- if err != nil {
- w.Errors <- err
- }
- unix.Close(w.closepipe[0])
close(w.Events)
close(w.Errors)
+ _ = unix.Close(w.kq)
+ unix.Close(w.closepipe[0])
}()
eventBuffer := make([]unix.Kevent_t, 10)
@@ -513,18 +573,8 @@ func (w *Watcher) readEvents() {
event := w.newEvent(path.name, mask)
- if path.isDir && !event.Has(Remove) {
- // Double check to make sure the directory exists. This can
- // happen when we do a rm -fr on a recursively watched folders
- // and we receive a modification event first but the folder has
- // been deleted and later receive the delete event.
- if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
- event.Op |= Remove
- }
- }
-
if event.Has(Rename) || event.Has(Remove) {
- w.Remove(event.Name)
+ w.remove(event.Name, false)
w.mu.Lock()
delete(w.fileExists, event.Name)
w.mu.Unlock()
@@ -540,26 +590,30 @@ func (w *Watcher) readEvents() {
}
if event.Has(Remove) {
- // Look for a file that may have overwritten this.
- // For example, mv f1 f2 will delete f2, then create f2.
+ // Look for a file that may have overwritten this; for example,
+ // mv f1 f2 will delete f2, then create f2.
if path.isDir {
fileDir := filepath.Clean(event.Name)
w.mu.Lock()
_, found := w.watches[fileDir]
w.mu.Unlock()
if found {
- // make sure the directory exists before we watch for changes. When we
- // do a recursive watch and perform rm -fr, the parent directory might
- // have gone missing, ignore the missing directory and let the
- // upcoming delete event remove the watch from the parent directory.
- if _, err := os.Lstat(fileDir); err == nil {
- w.sendDirectoryChangeEvents(fileDir)
+ err := w.sendDirectoryChangeEvents(fileDir)
+ if err != nil {
+ if !w.sendError(err) {
+ closed = true
+ }
}
}
} else {
filePath := filepath.Clean(event.Name)
- if fileInfo, err := os.Lstat(filePath); err == nil {
- w.sendFileCreatedEventIfNew(filePath, fileInfo)
+ if fi, err := os.Lstat(filePath); err == nil {
+ err := w.sendFileCreatedEventIfNew(filePath, fi)
+ if err != nil {
+ if !w.sendError(err) {
+ closed = true
+ }
+ }
}
}
}
@@ -582,21 +636,31 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
e.Op |= Chmod
}
+ // No point sending a write and delete event at the same time: if it's gone,
+ // then it's gone.
+ if e.Op.Has(Write) && e.Op.Has(Remove) {
+ e.Op &^= Write
+ }
return e
}
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
// Get all files
- files, err := ioutil.ReadDir(dirPath)
+ files, err := os.ReadDir(dirPath)
if err != nil {
return err
}
- for _, fileInfo := range files {
- path := filepath.Join(dirPath, fileInfo.Name())
+ for _, f := range files {
+ path := filepath.Join(dirPath, f.Name())
+
+ fi, err := f.Info()
+ if err != nil {
+ return fmt.Errorf("%q: %w", path, err)
+ }
- cleanPath, err := w.internalWatch(path, fileInfo)
+ cleanPath, err := w.internalWatch(path, fi)
if err != nil {
// No permission to read the file; that's not a problem: just skip.
// But do add it to w.fileExists to prevent it from being picked up
@@ -606,7 +670,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
cleanPath = filepath.Clean(path)
default:
- return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
+ return fmt.Errorf("%q: %w", path, err)
}
}
@@ -622,26 +686,37 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
//
// This functionality is to have the BSD watcher match the inotify, which sends
// a create event for files created in a watched directory.
-func (w *Watcher) sendDirectoryChangeEvents(dir string) {
- // Get all files
- files, err := ioutil.ReadDir(dir)
+func (w *Watcher) sendDirectoryChangeEvents(dir string) error {
+ files, err := os.ReadDir(dir)
if err != nil {
- if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
- return
+ // Directory no longer exists: we can ignore this safely. kqueue will
+ // still give us the correct events.
+ if errors.Is(err, os.ErrNotExist) {
+ return nil
}
+ return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
}
- // Search for new files
- for _, fi := range files {
- err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
+ for _, f := range files {
+ fi, err := f.Info()
if err != nil {
- return
+ return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
+ }
+
+ err = w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
+ if err != nil {
+ // Don't need to send an error if this file isn't readable.
+ if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
+ return nil
+ }
+ return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
}
}
+ return nil
}
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
-func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
+func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fi os.FileInfo) (err error) {
w.mu.Lock()
_, doesExist := w.fileExists[filePath]
w.mu.Unlock()
@@ -652,7 +727,7 @@ func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInf
}
// like watchDirectoryFiles (but without doing another ReadDir)
- filePath, err = w.internalWatch(filePath, fileInfo)
+ filePath, err = w.internalWatch(filePath, fi)
if err != nil {
return err
}
@@ -664,10 +739,10 @@ func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInf
return nil
}
-func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
- if fileInfo.IsDir() {
- // mimic Linux providing delete events for subdirectories
- // but preserve the flags used if currently watching subdirectory
+func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) {
+ if fi.IsDir() {
+ // mimic Linux providing delete events for subdirectories, but preserve
+ // the flags used if currently watching subdirectory
w.mu.Lock()
flags := w.dirFlags[name]
w.mu.Unlock()
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_other.go b/vendor/github.com/fsnotify/fsnotify/backend_other.go
index a9bb1c3c4..d34a23c01 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_other.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_other.go
@@ -1,39 +1,169 @@
-//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
-// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
+// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
package fsnotify
-import (
- "fmt"
- "runtime"
-)
+import "errors"
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct{}
+// Watcher watches a set of paths, delivering events on a channel.
+//
+// A watcher should not be copied (e.g. pass it by pointer, rather than by
+// value).
+//
+// # Linux notes
+//
+// When a file is removed a Remove event won't be emitted until all file
+// descriptors are closed, and deletes will always emit a Chmod. For example:
+//
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
+//
+// This is the event that inotify sends, so not much can be changed about this.
+//
+// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
+// for the number of watches per user, and fs.inotify.max_user_instances
+// specifies the maximum number of inotify instances per user. Every Watcher you
+// create is an "instance", and every path you add is a "watch".
+//
+// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
+// /proc/sys/fs/inotify/max_user_instances
+//
+// To increase them you can use sysctl or write the value to the /proc file:
+//
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
+//
+// To make the changes persist on reboot edit /etc/sysctl.conf or
+// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
+// your distro's documentation):
+//
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
+//
+// Reaching the limit will result in a "no space left on device" or "too many open
+// files" error.
+//
+// # kqueue notes (macOS, BSD)
+//
+// kqueue requires opening a file descriptor for every file that's being watched;
+// so if you're watching a directory with five files then that's six file
+// descriptors. You will run in to your system's "max open files" limit faster on
+// these platforms.
+//
+// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
+// control the maximum number of open files, as well as /etc/login.conf on BSD
+// systems.
+//
+// # Windows notes
+//
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
+//
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
+type Watcher struct {
+ // Events sends the filesystem change events.
+ //
+ // fsnotify can send the following events; a "path" here can refer to a
+ // file, directory, symbolic link, or special file like a FIFO.
+ //
+ // fsnotify.Create A new path was created; this may be followed by one
+ // or more Write events if data also gets written to a
+ // file.
+ //
+ // fsnotify.Remove A path was removed.
+ //
+ // fsnotify.Rename A path was renamed. A rename is always sent with the
+ // old path as Event.Name, and a Create event will be
+ // sent with the new name. Renames are only sent for
+ // paths that are currently watched; e.g. moving an
+ // unmonitored file into a monitored directory will
+ // show up as just a Create. Similarly, renaming a file
+ // to outside a monitored directory will show up as
+ // only a Rename.
+ //
+ // fsnotify.Write A file or named pipe was written to. A Truncate will
+ // also trigger a Write. A single "write action"
+ // initiated by the user may show up as one or multiple
+ // writes, depending on when the system syncs things to
+ // disk. For example when compiling a large Go program
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
+ //
+ // fsnotify.Chmod Attributes were changed. On Linux this is also sent
+ // when a file is removed (or more accurately, when a
+ // link to an inode is removed). On kqueue it's sent
+ // when a file is truncated. On Windows it's never
+ // sent.
+ Events chan Event
+
+ // Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
+ Errors chan error
+}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
- return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
+ return nil, errors.New("fsnotify not supported on the current platform")
}
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
- return nil
-}
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() }
+
+// Close removes all watches and closes the Events channel.
+func (w *Watcher) Close() error { return nil }
+
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) WatchList() []string { return nil }
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -43,17 +173,26 @@ func (w *Watcher) Close() error {
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
- return nil
-}
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return nil }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }
// Remove stops monitoring the path for changes.
//
@@ -61,6 +200,6 @@ func (w *Watcher) Add(name string) error {
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
-func (w *Watcher) Remove(name string) error {
- return nil
-}
+//
+// Returns nil if [Watcher.Close] was called.
+func (w *Watcher) Remove(name string) error { return nil }
diff --git a/vendor/github.com/fsnotify/fsnotify/backend_windows.go b/vendor/github.com/fsnotify/fsnotify/backend_windows.go
index ae392867c..9bc91e5d6 100644
--- a/vendor/github.com/fsnotify/fsnotify/backend_windows.go
+++ b/vendor/github.com/fsnotify/fsnotify/backend_windows.go
@@ -1,6 +1,13 @@
//go:build windows
// +build windows
+// Windows backend based on ReadDirectoryChangesW()
+//
+// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
+//
+// Note: the documentation on the Watcher type and methods is generated from
+// mkdoc.zsh
+
package fsnotify
import (
@@ -27,9 +34,9 @@ import (
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
-// fp := os.Open("file")
-// os.Remove("file") // Triggers Chmod
-// fp.Close() // Triggers Remove
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
@@ -43,16 +50,16 @@ import (
//
// To increase them you can use sysctl or write the value to the /proc file:
//
-// # Default values on Linux 5.18
-// sysctl fs.inotify.max_user_watches=124983
-// sysctl fs.inotify.max_user_instances=128
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
-// fs.inotify.max_user_watches=124983
-// fs.inotify.max_user_instances=128
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
@@ -68,14 +75,20 @@ import (
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
-// # macOS notes
+// # Windows notes
//
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// Paths can be added as "C:\path\to\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
//
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
@@ -102,31 +115,52 @@ type Watcher struct {
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
- // you may get hundreds of Write events, so you
- // probably want to wait until you've stopped receiving
- // them (see the dedup example in cmd/fsnotify).
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
- // and on kqueue when a file is truncated. On Windows
- // it's never sent.
+ // when a file is truncated. On Windows it's never
+ // sent.
Events chan Event
// Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
Errors chan error
port windows.Handle // Handle to completion port
input chan *input // Inputs to the reader are sent on this channel
quit chan chan<- error
- mu sync.Mutex // Protects access to watches, isClosed
- watches watchMap // Map of watches (key: i-number)
- isClosed bool // Set to true when Close() is first called
+ mu sync.Mutex // Protects access to watches, closed
+ watches watchMap // Map of watches (key: i-number)
+ closed bool // Set to true when Close() is first called
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
+ return NewBufferedWatcher(50)
+}
+
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+func NewBufferedWatcher(sz uint) (*Watcher, error) {
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
if err != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
@@ -135,7 +169,7 @@ func NewWatcher() (*Watcher, error) {
port: port,
watches: make(watchMap),
input: make(chan *input, 1),
- Events: make(chan Event, 50),
+ Events: make(chan Event, sz),
Errors: make(chan error),
quit: make(chan chan<- error, 1),
}
@@ -143,6 +177,12 @@ func NewWatcher() (*Watcher, error) {
return w, nil
}
+func (w *Watcher) isClosed() bool {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ return w.closed
+}
+
func (w *Watcher) sendEvent(name string, mask uint64) bool {
if mask == 0 {
return false
@@ -167,14 +207,14 @@ func (w *Watcher) sendError(err error) bool {
return false
}
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
- w.mu.Lock()
- if w.isClosed {
- w.mu.Unlock()
+ if w.isClosed() {
return nil
}
- w.isClosed = true
+
+ w.mu.Lock()
+ w.closed = true
w.mu.Unlock()
// Send "quit" message to the reader goroutine
@@ -188,17 +228,21 @@ func (w *Watcher) Close() error {
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -208,27 +252,41 @@ func (w *Watcher) Close() error {
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
-func (w *Watcher) Add(name string) error {
- w.mu.Lock()
- if w.isClosed {
- w.mu.Unlock()
- return errors.New("watcher already closed")
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+func (w *Watcher) Add(name string) error { return w.AddWith(name) }
+
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
+func (w *Watcher) AddWith(name string, opts ...addOpt) error {
+ if w.isClosed() {
+ return ErrClosed
+ }
+
+ with := getOptions(opts...)
+ if with.bufsize < 4096 {
+ return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
}
- w.mu.Unlock()
in := &input{
- op: opAddWatch,
- path: filepath.Clean(name),
- flags: sysFSALLEVENTS,
- reply: make(chan error),
+ op: opAddWatch,
+ path: filepath.Clean(name),
+ flags: sysFSALLEVENTS,
+ reply: make(chan error),
+ bufsize: with.bufsize,
}
w.input <- in
if err := w.wakeupReader(); err != nil {
@@ -243,7 +301,13 @@ func (w *Watcher) Add(name string) error {
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
+ if w.isClosed() {
+ return nil
+ }
+
in := &input{
op: opRemoveWatch,
path: filepath.Clean(name),
@@ -256,8 +320,15 @@ func (w *Watcher) Remove(name string) error {
return <-in.reply
}
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
+ if w.isClosed() {
+ return nil
+ }
+
w.mu.Lock()
defer w.mu.Unlock()
@@ -279,7 +350,6 @@ func (w *Watcher) WatchList() []string {
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
const (
sysFSALLEVENTS = 0xfff
- sysFSATTRIB = 0x4
sysFSCREATE = 0x100
sysFSDELETE = 0x200
sysFSDELETESELF = 0x400
@@ -305,9 +375,6 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
e.Op |= Rename
}
- if mask&sysFSATTRIB == sysFSATTRIB {
- e.Op |= Chmod
- }
return e
}
@@ -321,10 +388,11 @@ const (
)
type input struct {
- op int
- path string
- flags uint32
- reply chan error
+ op int
+ path string
+ flags uint32
+ bufsize int
+ reply chan error
}
type inode struct {
@@ -334,13 +402,14 @@ type inode struct {
}
type watch struct {
- ov windows.Overlapped
- ino *inode // i-number
- path string // Directory path
- mask uint64 // Directory itself is being watched with these notify flags
- names map[string]uint64 // Map of names being watched and their notify flags
- rename string // Remembers the old name while renaming a file
- buf [65536]byte // 64K buffer
+ ov windows.Overlapped
+ ino *inode // i-number
+ recurse bool // Recursive watch?
+ path string // Directory path
+ mask uint64 // Directory itself is being watched with these notify flags
+ names map[string]uint64 // Map of names being watched and their notify flags
+ rename string // Remembers the old name while renaming a file
+ buf []byte // buffer, allocated later
}
type (
@@ -413,7 +482,10 @@ func (m watchMap) set(ino *inode, watch *watch) {
}
// Must run within the I/O thread.
-func (w *Watcher) addWatch(pathname string, flags uint64) error {
+func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
+ //pathname, recurse := recursivePath(pathname)
+ recurse := false
+
dir, err := w.getDir(pathname)
if err != nil {
return err
@@ -433,9 +505,11 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
return os.NewSyscallError("CreateIoCompletionPort", err)
}
watchEntry = &watch{
- ino: ino,
- path: dir,
- names: make(map[string]uint64),
+ ino: ino,
+ path: dir,
+ names: make(map[string]uint64),
+ recurse: recurse,
+ buf: make([]byte, bufsize),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
@@ -465,6 +539,8 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
+ pathname, recurse := recursivePath(pathname)
+
dir, err := w.getDir(pathname)
if err != nil {
return err
@@ -478,6 +554,10 @@ func (w *Watcher) remWatch(pathname string) error {
watch := w.watches.get(ino)
w.mu.Unlock()
+ if recurse && !watch.recurse {
+ return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
+ }
+
err = windows.CloseHandle(ino.handle)
if err != nil {
w.sendError(os.NewSyscallError("CloseHandle", err))
@@ -535,8 +615,11 @@ func (w *Watcher) startRead(watch *watch) error {
return nil
}
- rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
- uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
+ // We need to pass the array, rather than the slice.
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
+ rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
+ (*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
+ watch.recurse, mask, nil, &watch.ov, 0)
if rdErr != nil {
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
@@ -563,9 +646,8 @@ func (w *Watcher) readEvents() {
runtime.LockOSThread()
for {
+ // This error is handled after the watch == nil check below.
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
- // This error is handled after the watch == nil check below. NOTE: this
- // seems odd, note sure if it's correct.
watch := (*watch)(unsafe.Pointer(ov))
if watch == nil {
@@ -595,7 +677,7 @@ func (w *Watcher) readEvents() {
case in := <-w.input:
switch in.op {
case opAddWatch:
- in.reply <- w.addWatch(in.path, uint64(in.flags))
+ in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
case opRemoveWatch:
in.reply <- w.remWatch(in.path)
}
@@ -605,6 +687,8 @@ func (w *Watcher) readEvents() {
}
switch qErr {
+ case nil:
+ // No error
case windows.ERROR_MORE_DATA:
if watch == nil {
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
@@ -626,13 +710,12 @@ func (w *Watcher) readEvents() {
default:
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
continue
- case nil:
}
var offset uint32
for {
if n == 0 {
- w.sendError(errors.New("short read in readEvents()"))
+ w.sendError(ErrEventOverflow)
break
}
@@ -703,8 +786,9 @@ func (w *Watcher) readEvents() {
// Error!
if offset >= n {
+ //lint:ignore ST1005 Windows should be capitalized
w.sendError(errors.New(
- "Windows system assumed buffer larger than it is, events have likely been missed."))
+ "Windows system assumed buffer larger than it is, events have likely been missed"))
break
}
}
@@ -720,9 +804,6 @@ func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
if mask&sysFSMODIFY != 0 {
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
}
- if mask&sysFSATTRIB != 0 {
- m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
- }
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
}
diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go
index 30a5bf0f0..24c99cc49 100644
--- a/vendor/github.com/fsnotify/fsnotify/fsnotify.go
+++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go
@@ -1,13 +1,18 @@
-//go:build !plan9
-// +build !plan9
-
// Package fsnotify provides a cross-platform interface for file system
// notifications.
+//
+// Currently supported systems:
+//
+// Linux 2.6.32+ via inotify
+// BSD, macOS via kqueue
+// Windows via ReadDirectoryChangesW
+// illumos via FEN
package fsnotify
import (
"errors"
"fmt"
+ "path/filepath"
"strings"
)
@@ -33,34 +38,52 @@ type Op uint32
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
// full description, and check them with [Event.Has].
const (
+ // A new pathname was created.
Create Op = 1 << iota
+
+ // The pathname was written to; this does *not* mean the write has finished,
+ // and a write can be followed by more writes.
Write
+
+ // The path was removed; any watches on it will be removed. Some "remove"
+ // operations may trigger a Rename if the file is actually moved (for
+ // example "remove to trash" is often a rename).
Remove
+
+ // The path was renamed to something else; any watched on it will be
+ // removed.
Rename
+
+ // File attributes were changed.
+ //
+ // It's generally not recommended to take action on this event, as it may
+ // get triggered very frequently by some software. For example, Spotlight
+ // indexing on macOS, anti-virus software, backup software, etc.
Chmod
)
-// Common errors that can be reported by a watcher
+// Common errors that can be reported.
var (
- ErrNonExistentWatch = errors.New("can't remove non-existent watcher")
- ErrEventOverflow = errors.New("fsnotify queue overflow")
+ ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
+ ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
+ ErrClosed = errors.New("fsnotify: watcher already closed")
)
-func (op Op) String() string {
+func (o Op) String() string {
var b strings.Builder
- if op.Has(Create) {
+ if o.Has(Create) {
b.WriteString("|CREATE")
}
- if op.Has(Remove) {
+ if o.Has(Remove) {
b.WriteString("|REMOVE")
}
- if op.Has(Write) {
+ if o.Has(Write) {
b.WriteString("|WRITE")
}
- if op.Has(Rename) {
+ if o.Has(Rename) {
b.WriteString("|RENAME")
}
- if op.Has(Chmod) {
+ if o.Has(Chmod) {
b.WriteString("|CHMOD")
}
if b.Len() == 0 {
@@ -70,7 +93,7 @@ func (op Op) String() string {
}
// Has reports if this operation has the given operation.
-func (o Op) Has(h Op) bool { return o&h == h }
+func (o Op) Has(h Op) bool { return o&h != 0 }
// Has reports if this event has the given operation.
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
@@ -79,3 +102,45 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) }
func (e Event) String() string {
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
}
+
+type (
+ addOpt func(opt *withOpts)
+ withOpts struct {
+ bufsize int
+ }
+)
+
+var defaultOpts = withOpts{
+ bufsize: 65536, // 64K
+}
+
+func getOptions(opts ...addOpt) withOpts {
+ with := defaultOpts
+ for _, o := range opts {
+ o(&with)
+ }
+ return with
+}
+
+// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
+//
+// This only has effect on Windows systems, and is a no-op for other backends.
+//
+// The default value is 64K (65536 bytes) which is the highest value that works
+// on all filesystems and should be enough for most applications, but if you
+// have a large burst of events it may not be enough. You can increase it if
+// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
+//
+// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
+func WithBufferSize(bytes int) addOpt {
+ return func(opt *withOpts) { opt.bufsize = bytes }
+}
+
+// Check if this path is recursive (ends with "/..." or "\..."), and return the
+// path with the /... stripped.
+func recursivePath(path string) (string, bool) {
+ if filepath.Base(path) == "..." {
+ return filepath.Dir(path), true
+ }
+ return path, false
+}
diff --git a/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh b/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
index b09ef7683..99012ae65 100644
--- a/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
+++ b/vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
@@ -2,8 +2,8 @@
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
setopt err_exit no_unset pipefail extended_glob
-# Simple script to update the godoc comments on all watchers. Probably took me
-# more time to write this than doing it manually, but ah well 🙃
+# Simple script to update the godoc comments on all watchers so you don't need
+# to update the same comment 5 times.
watcher=$(<<EOF
// Watcher watches a set of paths, delivering events on a channel.
@@ -16,9 +16,9 @@ watcher=$(<<EOF
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
-// fp := os.Open("file")
-// os.Remove("file") // Triggers Chmod
-// fp.Close() // Triggers Remove
+// fp := os.Open("file")
+// os.Remove("file") // Triggers Chmod
+// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
@@ -32,16 +32,16 @@ watcher=$(<<EOF
//
// To increase them you can use sysctl or write the value to the /proc file:
//
-// # Default values on Linux 5.18
-// sysctl fs.inotify.max_user_watches=124983
-// sysctl fs.inotify.max_user_instances=128
+// # Default values on Linux 5.18
+// sysctl fs.inotify.max_user_watches=124983
+// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
-// fs.inotify.max_user_watches=124983
-// fs.inotify.max_user_instances=128
+// fs.inotify.max_user_watches=124983
+// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
@@ -57,14 +57,20 @@ watcher=$(<<EOF
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
-// # macOS notes
+// # Windows notes
//
-// Spotlight indexing on macOS can result in multiple events (see [#15]). A
-// temporary workaround is to add your folder(s) to the "Spotlight Privacy
-// Settings" until we have a native FSEvents implementation (see [#11]).
+// Paths can be added as "C:\\path\\to\\dir", but forward slashes
+// ("C:/path/to/dir") will also work.
//
-// [#11]: https://github.com/fsnotify/fsnotify/issues/11
-// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+// When a watched directory is removed it will always send an event for the
+// directory itself, but may not send events for all files in that directory.
+// Sometimes it will send events for all times, sometimes it will send no
+// events, and often only for some files.
+//
+// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
+// value that is guaranteed to work with SMB filesystems. If you have many
+// events in quick succession this may not be enough, and you will have to use
+// [WithBufferSize] to increase the value.
EOF
)
@@ -73,20 +79,36 @@ new=$(<<EOF
EOF
)
+newbuffered=$(<<EOF
+// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
+// channel.
+//
+// The main use case for this is situations with a very large number of events
+// where the kernel buffer size can't be increased (e.g. due to lack of
+// permissions). An unbuffered Watcher will perform better for almost all use
+// cases, and whenever possible you will be better off increasing the kernel
+// buffers instead of adding a large userspace buffer.
+EOF
+)
+
add=$(<<EOF
// Add starts monitoring the path for changes.
//
-// A path can only be watched once; attempting to watch it more than once will
-// return an error. Paths that do not yet exist on the filesystem cannot be
-// added. A watch will be automatically removed if the path is deleted.
+// A path can only be watched once; watching it more than once is a no-op and will
+// not return an error. Paths that do not yet exist on the filesystem cannot be
+// watched.
//
-// A path will remain watched if it gets renamed to somewhere else on the same
-// filesystem, but the monitor will get removed if the path gets deleted and
-// re-created, or if it's moved to a different filesystem.
+// A watch will be automatically removed if the watched path is deleted or
+// renamed. The exception is the Windows backend, which doesn't remove the
+// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
+// Returns [ErrClosed] if [Watcher.Close] was called.
+//
+// See [Watcher.AddWith] for a version that allows adding options.
+//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
@@ -96,14 +118,27 @@ add=$(<<EOF
// # Watching files
//
// Watching individual files (rather than directories) is generally not
-// recommended as many tools update files atomically. Instead of "just" writing
-// to the file a temporary file will be written to first, and if successful the
-// temporary file is moved to to destination removing the original, or some
-// variant thereof. The watcher on the original file is now lost, as it no
-// longer exists.
-//
-// Instead, watch the parent directory and use Event.Name to filter out files
-// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
+// recommended as many programs (especially editors) update files atomically: it
+// will write to a temporary file which is then moved to to destination,
+// overwriting the original (or some variant thereof). The watcher on the
+// original file is now lost, as that no longer exists.
+//
+// The upshot of this is that a power failure or crash won't leave a
+// half-written file.
+//
+// Watch the parent directory and use Event.Name to filter out files you're not
+// interested in. There is an example of this in cmd/fsnotify/file.go.
+EOF
+)
+
+addwith=$(<<EOF
+// AddWith is like [Watcher.Add], but allows adding options. When using Add()
+// the defaults described below are used.
+//
+// Possible options are:
+//
+// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
+// other platforms. The default is 64K (65536 bytes).
EOF
)
@@ -114,16 +149,21 @@ remove=$(<<EOF
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
+//
+// Returns nil if [Watcher.Close] was called.
EOF
)
close=$(<<EOF
-// Close removes all watches and closes the events channel.
+// Close removes all watches and closes the Events channel.
EOF
)
watchlist=$(<<EOF
-// WatchList returns all paths added with [Add] (and are not yet removed).
+// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
+// yet removed).
+//
+// Returns nil if [Watcher.Close] was called.
EOF
)
@@ -153,20 +193,29 @@ events=$(<<EOF
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
- // you may get hundreds of Write events, so you
- // probably want to wait until you've stopped receiving
- // them (see the dedup example in cmd/fsnotify).
+ // you may get hundreds of Write events, and you may
+ // want to wait until you've stopped receiving them
+ // (see the dedup example in cmd/fsnotify).
+ //
+ // Some systems may send Write event for directories
+ // when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
- // and on kqueue when a file is truncated. On Windows
- // it's never sent.
+ // when a file is truncated. On Windows it's never
+ // sent.
EOF
)
errors=$(<<EOF
// Errors sends any errors.
+ //
+ // ErrEventOverflow is used to indicate there are too many events:
+ //
+ // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
+ // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
+ // - kqueue, fen: Not used.
EOF
)
@@ -200,7 +249,9 @@ set-cmt() {
set-cmt '^type Watcher struct ' $watcher
set-cmt '^func NewWatcher(' $new
+set-cmt '^func NewBufferedWatcher(' $newbuffered
set-cmt '^func (w \*Watcher) Add(' $add
+set-cmt '^func (w \*Watcher) AddWith(' $addwith
set-cmt '^func (w \*Watcher) Remove(' $remove
set-cmt '^func (w \*Watcher) Close(' $close
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
diff --git a/vendor/github.com/tdewolff/minify/v2/html/buffer.go b/vendor/github.com/tdewolff/minify/v2/html/buffer.go
index f58367b44..f2a6f8c52 100644
--- a/vendor/github.com/tdewolff/minify/v2/html/buffer.go
+++ b/vendor/github.com/tdewolff/minify/v2/html/buffer.go
@@ -8,12 +8,13 @@ import (
// Token is a single token unit with an attribute value (if given) and hash of the data.
type Token struct {
html.TokenType
- Hash Hash
- Data []byte
- Text []byte
- AttrVal []byte
- Traits traits
- Offset int
+ Hash Hash
+ Data []byte
+ Text []byte
+ AttrVal []byte
+ Traits traits
+ Offset int
+ HasTemplate bool
}
// TokenBuffer is a buffer that allows for token look-ahead.
@@ -40,10 +41,11 @@ func (z *TokenBuffer) read(t *Token) {
t.Offset = z.r.Offset()
t.TokenType, t.Data = z.l.Next()
t.Text = z.l.Text()
+ t.HasTemplate = z.l.HasTemplate()
if t.TokenType == html.AttributeToken {
t.Offset += 1 + len(t.Text) + 1
t.AttrVal = z.l.AttrVal()
- if len(t.AttrVal) > 1 && (t.AttrVal[0] == '"' || t.AttrVal[0] == '\'') {
+ if 1 < len(t.AttrVal) && (t.AttrVal[0] == '"' || t.AttrVal[0] == '\'') {
t.Offset++
t.AttrVal = t.AttrVal[1 : len(t.AttrVal)-1] // quotes will be readded in attribute loop if necessary
}
diff --git a/vendor/github.com/tdewolff/minify/v2/html/hash.go b/vendor/github.com/tdewolff/minify/v2/html/hash.go
index 0ae20d48d..5eefcebb0 100644
--- a/vendor/github.com/tdewolff/minify/v2/html/hash.go
+++ b/vendor/github.com/tdewolff/minify/v2/html/hash.go
@@ -10,255 +10,271 @@ type Hash uint32
// Unique hash definitions to be used instead of strings
const (
- A Hash = 0x1 // a
- Abbr Hash = 0x3b804 // abbr
- About Hash = 0x5 // about
- Accept Hash = 0x1106 // accept
- Accept_Charset Hash = 0x110e // accept-charset
- Acronym Hash = 0x4a07 // acronym
- Action Hash = 0x21d06 // action
- Address Hash = 0x7807 // address
- Align Hash = 0x35b05 // align
- Alink Hash = 0x3a405 // alink
- Allowfullscreen Hash = 0x2e10f // allowfullscreen
- Amp_Boilerplate Hash = 0x7f0f // amp-boilerplate
- Applet Hash = 0xd706 // applet
- Area Hash = 0x2fd04 // area
- Article Hash = 0x2707 // article
- Aside Hash = 0x5b05 // aside
- Async Hash = 0x8e05 // async
- Audio Hash = 0x9605 // audio
- Autofocus Hash = 0xcc09 // autofocus
- Autoplay Hash = 0x10c08 // autoplay
- Axis Hash = 0x11404 // axis
- B Hash = 0x101 // b
- Background Hash = 0x300a // background
- Base Hash = 0x17804 // base
- Basefont Hash = 0x17808 // basefont
- Bb Hash = 0x3b902 // bb
- Bdi Hash = 0x18403 // bdi
- Bdo Hash = 0x35303 // bdo
- Bgcolor Hash = 0x12a07 // bgcolor
- Big Hash = 0x13103 // big
- Blockquote Hash = 0x1340a // blockquote
- Body Hash = 0xd04 // body
- Br Hash = 0x36102 // br
- Button Hash = 0x13e06 // button
- Canvas Hash = 0x5706 // canvas
- Caption Hash = 0x1fe07 // caption
- Center Hash = 0xb706 // center
- Charset Hash = 0x1807 // charset
- Checked Hash = 0x19707 // checked
- Cite Hash = 0x9204 // cite
- Class Hash = 0x15105 // class
- Classid Hash = 0x15107 // classid
- Clear Hash = 0x2b05 // clear
- Code Hash = 0x17404 // code
- Codebase Hash = 0x17408 // codebase
- Codetype Hash = 0x18808 // codetype
- Col Hash = 0x12c03 // col
- Colgroup Hash = 0x1af08 // colgroup
- Color Hash = 0x12c05 // color
- Cols Hash = 0x1c904 // cols
- Colspan Hash = 0x1c907 // colspan
- Compact Hash = 0x1d707 // compact
- Content Hash = 0x27b07 // content
- Controls Hash = 0x1e708 // controls
- Data Hash = 0x1f04 // data
- Datalist Hash = 0x1f08 // datalist
- Datatype Hash = 0xac08 // datatype
- Dd Hash = 0x7902 // dd
- Declare Hash = 0x5e07 // declare
- Default Hash = 0xeb07 // default
- DefaultChecked Hash = 0x2270e // defaultChecked
- DefaultMuted Hash = 0xeb0c // defaultMuted
- DefaultSelected Hash = 0xf60f // defaultSelected
- Defer Hash = 0x10405 // defer
- Del Hash = 0x37903 // del
- Details Hash = 0x15707 // details
- Dfn Hash = 0x16403 // dfn
- Dialog Hash = 0xc606 // dialog
- Dir Hash = 0x18503 // dir
- Disabled Hash = 0x19d08 // disabled
- Div Hash = 0x1a403 // div
- Dl Hash = 0x1e502 // dl
- Dt Hash = 0x21702 // dt
- Em Hash = 0x4302 // em
- Embed Hash = 0x37505 // embed
- Enabled Hash = 0x26307 // enabled
- Enctype Hash = 0x2a207 // enctype
- Face Hash = 0xb504 // face
- Fieldset Hash = 0x1f308 // fieldset
- Figcaption Hash = 0x1fb0a // figcaption
- Figure Hash = 0x20c06 // figure
- Font Hash = 0x17c04 // font
- Footer Hash = 0xa006 // footer
- For Hash = 0x21903 // for
- Form Hash = 0x21904 // form
- Formaction Hash = 0x2190a // formaction
- Formnovalidate Hash = 0x2350e // formnovalidate
- Frame Hash = 0x14505 // frame
- Frameborder Hash = 0x2830b // frameborder
- Frameset Hash = 0x14508 // frameset
- H1 Hash = 0x2d002 // h1
- H2 Hash = 0x24302 // h2
- H3 Hash = 0x24502 // h3
- H4 Hash = 0x24702 // h4
- H5 Hash = 0x24902 // h5
- H6 Hash = 0x24b02 // h6
- Head Hash = 0x2c204 // head
- Header Hash = 0x2c206 // header
- Hgroup Hash = 0x24d06 // hgroup
- Hidden Hash = 0x25f06 // hidden
- Hr Hash = 0x16802 // hr
- Href Hash = 0x16804 // href
- Hreflang Hash = 0x16808 // hreflang
- Html Hash = 0x26a04 // html
- Http_Equiv Hash = 0x26e0a // http-equiv
- I Hash = 0x2401 // i
- Icon Hash = 0x27a04 // icon
- Id Hash = 0x5d02 // id
- Iframe Hash = 0x28206 // iframe
- Image Hash = 0x28e05 // image
- Img Hash = 0x29303 // img
- Inert Hash = 0x5205 // inert
- Inlist Hash = 0x29606 // inlist
- Input Hash = 0x2a905 // input
- Ins Hash = 0x2ae03 // ins
- Ismap Hash = 0x11605 // ismap
- Itemscope Hash = 0xe209 // itemscope
- Kbd Hash = 0x18303 // kbd
- Keygen Hash = 0x29e06 // keygen
- Label Hash = 0x6505 // label
- Lang Hash = 0x16c04 // lang
- Language Hash = 0x16c08 // language
- Legend Hash = 0x31706 // legend
- Li Hash = 0x2302 // li
- Link Hash = 0x3a504 // link
- Longdesc Hash = 0x6908 // longdesc
- Main Hash = 0x5004 // main
- Manifest Hash = 0x11e08 // manifest
- Map Hash = 0xd603 // map
- Mark Hash = 0x2b404 // mark
- Marquee Hash = 0x2b807 // marquee
- Math Hash = 0x2bf04 // math
- Max Hash = 0x2c803 // max
- Maxlength Hash = 0x2c809 // maxlength
- Media Hash = 0xc405 // media
- Menu Hash = 0xde04 // menu
- Menuitem Hash = 0xde08 // menuitem
- Meta Hash = 0x2d204 // meta
- Meter Hash = 0x30605 // meter
- Method Hash = 0x30b06 // method
- Multiple Hash = 0x31108 // multiple
- Muted Hash = 0x31d05 // muted
- Name Hash = 0xc204 // name
- Nav Hash = 0x35803 // nav
- Nobr Hash = 0x35f04 // nobr
- Noembed Hash = 0x37307 // noembed
- Noframes Hash = 0x14308 // noframes
- Nohref Hash = 0x16606 // nohref
- Noresize Hash = 0x1cf08 // noresize
- Noscript Hash = 0x20408 // noscript
- Noshade Hash = 0x22207 // noshade
- Novalidate Hash = 0x2390a // novalidate
- Nowrap Hash = 0x2ef06 // nowrap
- Object Hash = 0x9a06 // object
- Ol Hash = 0x7202 // ol
- Open Hash = 0x35504 // open
- Optgroup Hash = 0x39908 // optgroup
- Option Hash = 0x32206 // option
- Output Hash = 0x206 // output
- P Hash = 0x501 // p
- Param Hash = 0x11a05 // param
- Pauseonexit Hash = 0x1b60b // pauseonexit
- Picture Hash = 0x25207 // picture
- Plaintext Hash = 0x2f409 // plaintext
- Portal Hash = 0x3a006 // portal
- Poster Hash = 0x38c06 // poster
- Pre Hash = 0x38503 // pre
- Prefix Hash = 0x38506 // prefix
- Profile Hash = 0x32807 // profile
- Progress Hash = 0x32f08 // progress
- Property Hash = 0x33e08 // property
- Q Hash = 0x13901 // q
- Rb Hash = 0x2f02 // rb
- Readonly Hash = 0x2fe08 // readonly
- Rel Hash = 0x6303 // rel
- Required Hash = 0x21008 // required
- Resource Hash = 0x25708 // resource
- Rev Hash = 0xa503 // rev
- Reversed Hash = 0xa508 // reversed
- Rows Hash = 0xbc04 // rows
- Rowspan Hash = 0xbc07 // rowspan
- Rp Hash = 0x8802 // rp
- Rt Hash = 0x2802 // rt
- Rtc Hash = 0x5503 // rtc
- Ruby Hash = 0x10804 // ruby
- Rules Hash = 0x36205 // rules
- S Hash = 0x1c01 // s
- Samp Hash = 0x7e04 // samp
- Scope Hash = 0xe605 // scope
- Scoped Hash = 0xe606 // scoped
- Script Hash = 0x20606 // script
- Scrolling Hash = 0x6f09 // scrolling
- Seamless Hash = 0x36608 // seamless
- Section Hash = 0x36d07 // section
- Select Hash = 0x15d06 // select
- Selected Hash = 0x15d08 // selected
- Shape Hash = 0x1ee05 // shape
- Size Hash = 0x1d304 // size
- Slot Hash = 0x2b004 // slot
- Small Hash = 0x2df05 // small
- Sortable Hash = 0x33608 // sortable
- Source Hash = 0x25906 // source
- Span Hash = 0xbf04 // span
- Src Hash = 0x34603 // src
- Srcset Hash = 0x34606 // srcset
- Start Hash = 0x2505 // start
- Strike Hash = 0x29a06 // strike
- Strong Hash = 0x12406 // strong
- Style Hash = 0x34c05 // style
- Sub Hash = 0x35103 // sub
- Summary Hash = 0x37c07 // summary
- Sup Hash = 0x38303 // sup
- Svg Hash = 0x39203 // svg
- Tabindex Hash = 0x2d408 // tabindex
- Table Hash = 0x33905 // table
- Target Hash = 0x706 // target
- Tbody Hash = 0xc05 // tbody
- Td Hash = 0x1e02 // td
- Template Hash = 0x4208 // template
- Text Hash = 0x2f904 // text
- Textarea Hash = 0x2f908 // textarea
- Tfoot Hash = 0x9f05 // tfoot
- Th Hash = 0x2c102 // th
- Thead Hash = 0x2c105 // thead
- Time Hash = 0xdc04 // time
- Title Hash = 0x14c05 // title
- Tr Hash = 0x12502 // tr
- Track Hash = 0x17f05 // track
- Translate Hash = 0x1c009 // translate
- Truespeed Hash = 0x1dd09 // truespeed
- Tt Hash = 0x14002 // tt
- Type Hash = 0xb004 // type
- Typemustmatch Hash = 0x18c0d // typemustmatch
- Typeof Hash = 0xb006 // typeof
- U Hash = 0x301 // u
- Ul Hash = 0xef02 // ul
- Undeterminate Hash = 0x370d // undeterminate
- Usemap Hash = 0xd306 // usemap
- Valign Hash = 0x35a06 // valign
- Value Hash = 0x1a605 // value
- Valuetype Hash = 0x1a609 // valuetype
- Var Hash = 0x27703 // var
- Video Hash = 0x39505 // video
- Visible Hash = 0x3a907 // visible
- Vlink Hash = 0x3b005 // vlink
- Vocab Hash = 0x3b505 // vocab
- Wbr Hash = 0x3bc03 // wbr
- Xmlns Hash = 0x2db05 // xmlns
- Xmp Hash = 0x38a03 // xmp
+ A Hash = 0x1 // a
+ Abbr Hash = 0x40004 // abbr
+ About Hash = 0x5 // about
+ Accept Hash = 0xc06 // accept
+ Accept_Charset Hash = 0xc0e // accept-charset
+ Accesskey Hash = 0x2c09 // accesskey
+ Acronym Hash = 0x3507 // acronym
+ Action Hash = 0x26006 // action
+ Address Hash = 0x6d07 // address
+ Allow Hash = 0x31f05 // allow
+ Allowfullscreen Hash = 0x31f0f // allowfullscreen
+ Amp_Boilerplate Hash = 0x5e0f // amp-boilerplate
+ Applet Hash = 0xee06 // applet
+ Area Hash = 0x2c304 // area
+ Article Hash = 0x22507 // article
+ As Hash = 0x2102 // as
+ Aside Hash = 0x9205 // aside
+ Async Hash = 0x8a05 // async
+ Audio Hash = 0x9d05 // audio
+ Autocapitalize Hash = 0xc30e // autocapitalize
+ Autocomplete Hash = 0xd10c // autocomplete
+ Autofocus Hash = 0xe309 // autofocus
+ Autoplay Hash = 0xfc08 // autoplay
+ B Hash = 0x101 // b
+ Base Hash = 0x2004 // base
+ Basefont Hash = 0x2008 // basefont
+ Bb Hash = 0x40102 // bb
+ Bdi Hash = 0x8303 // bdi
+ Bdo Hash = 0x3dc03 // bdo
+ Big Hash = 0x12f03 // big
+ Blocking Hash = 0x13208 // blocking
+ Blockquote Hash = 0x13a0a // blockquote
+ Body Hash = 0x804 // body
+ Br Hash = 0x14b02 // br
+ Button Hash = 0x14406 // button
+ Canvas Hash = 0x8e06 // canvas
+ Caption Hash = 0x23707 // caption
+ Capture Hash = 0x10d07 // capture
+ Center Hash = 0x24f06 // center
+ Charset Hash = 0x1307 // charset
+ Checked Hash = 0x37707 // checked
+ Cite Hash = 0x14d04 // cite
+ Class Hash = 0x15a05 // class
+ Code Hash = 0x17604 // code
+ Col Hash = 0x17f03 // col
+ Colgroup Hash = 0x17f08 // colgroup
+ Color Hash = 0x19e05 // color
+ Cols Hash = 0x1a304 // cols
+ Colspan Hash = 0x1a307 // colspan
+ Content Hash = 0x1b107 // content
+ Contenteditable Hash = 0x1b10f // contenteditable
+ Controls Hash = 0x1cc08 // controls
+ Coords Hash = 0x1e306 // coords
+ Crossorigin Hash = 0x2160b // crossorigin
+ Data Hash = 0xad04 // data
+ Datalist Hash = 0xad08 // datalist
+ Datatype Hash = 0x11908 // datatype
+ Datetime Hash = 0x28508 // datetime
+ Dd Hash = 0x6e02 // dd
+ Decoding Hash = 0x9508 // decoding
+ Default Hash = 0x17807 // default
+ Defer Hash = 0x4405 // defer
+ Del Hash = 0x1f203 // del
+ Details Hash = 0x20b07 // details
+ Dfn Hash = 0x16a03 // dfn
+ Dialog Hash = 0x28d06 // dialog
+ Dir Hash = 0x8403 // dir
+ Disabled Hash = 0x19208 // disabled
+ Div Hash = 0x19903 // div
+ Dl Hash = 0x1c302 // dl
+ Draggable Hash = 0x1da09 // draggable
+ Dt Hash = 0x40902 // dt
+ Em Hash = 0xdc02 // em
+ Embed Hash = 0x16605 // embed
+ Enctype Hash = 0x26a07 // enctype
+ Enterkeyhint Hash = 0x2500c // enterkeyhint
+ Fetchpriority Hash = 0x1220d // fetchpriority
+ Fieldset Hash = 0x22c08 // fieldset
+ Figcaption Hash = 0x2340a // figcaption
+ Figure Hash = 0x24506 // figure
+ Font Hash = 0x2404 // font
+ Footer Hash = 0x1a06 // footer
+ For Hash = 0x25c03 // for
+ Form Hash = 0x25c04 // form
+ Formaction Hash = 0x25c0a // formaction
+ Formenctype Hash = 0x2660b // formenctype
+ Formmethod Hash = 0x2710a // formmethod
+ Formnovalidate Hash = 0x27b0e // formnovalidate
+ Formtarget Hash = 0x2930a // formtarget
+ Frame Hash = 0x16e05 // frame
+ Frameset Hash = 0x16e08 // frameset
+ H1 Hash = 0x2d502 // h1
+ H2 Hash = 0x38602 // h2
+ H3 Hash = 0x39502 // h3
+ H4 Hash = 0x40b02 // h4
+ H5 Hash = 0x29d02 // h5
+ H6 Hash = 0x29f02 // h6
+ Head Hash = 0x36c04 // head
+ Header Hash = 0x36c06 // header
+ Headers Hash = 0x36c07 // headers
+ Height Hash = 0x2a106 // height
+ Hgroup Hash = 0x2b506 // hgroup
+ Hidden Hash = 0x2cc06 // hidden
+ High Hash = 0x2d204 // high
+ Hr Hash = 0x2d702 // hr
+ Href Hash = 0x2d704 // href
+ Hreflang Hash = 0x2d708 // hreflang
+ Html Hash = 0x2a504 // html
+ Http_Equiv Hash = 0x2df0a // http-equiv
+ I Hash = 0x2801 // i
+ Id Hash = 0x9402 // id
+ Iframe Hash = 0x2f206 // iframe
+ Image Hash = 0x30005 // image
+ Imagesizes Hash = 0x3000a // imagesizes
+ Imagesrcset Hash = 0x30d0b // imagesrcset
+ Img Hash = 0x31803 // img
+ Inert Hash = 0x10805 // inert
+ Inlist Hash = 0x21f06 // inlist
+ Input Hash = 0x3d05 // input
+ Inputmode Hash = 0x3d09 // inputmode
+ Ins Hash = 0x31b03 // ins
+ Is Hash = 0xb202 // is
+ Ismap Hash = 0x32e05 // ismap
+ Itemid Hash = 0x2fa06 // itemid
+ Itemprop Hash = 0x14e08 // itemprop
+ Itemref Hash = 0x34507 // itemref
+ Itemscope Hash = 0x35709 // itemscope
+ Itemtype Hash = 0x36108 // itemtype
+ Kbd Hash = 0x8203 // kbd
+ Kind Hash = 0xaa04 // kind
+ Label Hash = 0x1c405 // label
+ Lang Hash = 0x2db04 // lang
+ Legend Hash = 0x1be06 // legend
+ Li Hash = 0xb102 // li
+ Link Hash = 0x1c804 // link
+ List Hash = 0xb104 // list
+ Loading Hash = 0x3ad07 // loading
+ Loop Hash = 0x2a804 // loop
+ Low Hash = 0x32103 // low
+ Main Hash = 0x3b04 // main
+ Map Hash = 0xed03 // map
+ Mark Hash = 0x7f04 // mark
+ Marquee Hash = 0x3e407 // marquee
+ Math Hash = 0x36904 // math
+ Max Hash = 0x37e03 // max
+ Maxlength Hash = 0x37e09 // maxlength
+ Media Hash = 0x28b05 // media
+ Menu Hash = 0x2f604 // menu
+ Menuitem Hash = 0x2f608 // menuitem
+ Meta Hash = 0x5004 // meta
+ Meter Hash = 0x38805 // meter
+ Method Hash = 0x27506 // method
+ Min Hash = 0x38d03 // min
+ Minlength Hash = 0x38d09 // minlength
+ Multiple Hash = 0x39708 // multiple
+ Muted Hash = 0x39f05 // muted
+ Name Hash = 0x4e04 // name
+ Nav Hash = 0xbc03 // nav
+ Nobr Hash = 0x14904 // nobr
+ Noembed Hash = 0x16407 // noembed
+ Noframes Hash = 0x16c08 // noframes
+ Nomodule Hash = 0x1a908 // nomodule
+ Noscript Hash = 0x23d08 // noscript
+ Novalidate Hash = 0x27f0a // novalidate
+ Object Hash = 0xa106 // object
+ Ol Hash = 0x18002 // ol
+ Open Hash = 0x35d04 // open
+ Optgroup Hash = 0x2aa08 // optgroup
+ Optimum Hash = 0x3de07 // optimum
+ Option Hash = 0x2ec06 // option
+ Output Hash = 0x206 // output
+ P Hash = 0x501 // p
+ Param Hash = 0x7b05 // param
+ Pattern Hash = 0xb607 // pattern
+ Picture Hash = 0x18607 // picture
+ Ping Hash = 0x2b104 // ping
+ Plaintext Hash = 0x2ba09 // plaintext
+ Playsinline Hash = 0x1000b // playsinline
+ Popover Hash = 0x33207 // popover
+ Popovertarget Hash = 0x3320d // popovertarget
+ Popovertargetaction Hash = 0x33213 // popovertargetaction
+ Portal Hash = 0x3f406 // portal
+ Poster Hash = 0x41006 // poster
+ Pre Hash = 0x3a403 // pre
+ Prefix Hash = 0x3a406 // prefix
+ Preload Hash = 0x3aa07 // preload
+ Profile Hash = 0x3b407 // profile
+ Progress Hash = 0x3bb08 // progress
+ Property Hash = 0x15208 // property
+ Q Hash = 0x11401 // q
+ Rb Hash = 0x1f02 // rb
+ Readonly Hash = 0x2c408 // readonly
+ Referrerpolicy Hash = 0x3490e // referrerpolicy
+ Rel Hash = 0x3ab03 // rel
+ Required Hash = 0x11208 // required
+ Resource Hash = 0x24908 // resource
+ Rev Hash = 0x18b03 // rev
+ Reversed Hash = 0x18b08 // reversed
+ Rows Hash = 0x4804 // rows
+ Rowspan Hash = 0x4807 // rowspan
+ Rp Hash = 0x6702 // rp
+ Rt Hash = 0x10b02 // rt
+ Rtc Hash = 0x10b03 // rtc
+ Ruby Hash = 0x8604 // ruby
+ S Hash = 0x1701 // s
+ Samp Hash = 0x5d04 // samp
+ Sandbox Hash = 0x7307 // sandbox
+ Scope Hash = 0x35b05 // scope
+ Script Hash = 0x23f06 // script
+ Section Hash = 0x15e07 // section
+ Select Hash = 0x1d306 // select
+ Selected Hash = 0x1d308 // selected
+ Shadowrootdelegatesfocus Hash = 0x1e818 // shadowrootdelegatesfocus
+ Shadowrootmode Hash = 0x1ff0e // shadowrootmode
+ Shape Hash = 0x21105 // shape
+ Size Hash = 0x30504 // size
+ Sizes Hash = 0x30505 // sizes
+ Slot Hash = 0x30904 // slot
+ Small Hash = 0x31d05 // small
+ Source Hash = 0x24b06 // source
+ Span Hash = 0x4b04 // span
+ Spellcheck Hash = 0x3720a // spellcheck
+ Src Hash = 0x31203 // src
+ Srclang Hash = 0x3c207 // srclang
+ Srcset Hash = 0x31206 // srcset
+ Start Hash = 0x22305 // start
+ Step Hash = 0xb304 // step
+ Strike Hash = 0x3c906 // strike
+ Strong Hash = 0x3cf06 // strong
+ Style Hash = 0x3d505 // style
+ Sub Hash = 0x3da03 // sub
+ Summary Hash = 0x3eb07 // summary
+ Sup Hash = 0x3f203 // sup
+ Svg Hash = 0x3fa03 // svg
+ Tabindex Hash = 0x5208 // tabindex
+ Table Hash = 0x1bb05 // table
+ Target Hash = 0x29706 // target
+ Tbody Hash = 0x705 // tbody
+ Td Hash = 0x1f102 // td
+ Template Hash = 0xdb08 // template
+ Text Hash = 0x2bf04 // text
+ Textarea Hash = 0x2bf08 // textarea
+ Tfoot Hash = 0x1905 // tfoot
+ Th Hash = 0x27702 // th
+ Thead Hash = 0x36b05 // thead
+ Time Hash = 0x28904 // time
+ Title Hash = 0x2705 // title
+ Tr Hash = 0xa602 // tr
+ Track Hash = 0xa605 // track
+ Translate Hash = 0xf309 // translate
+ Tt Hash = 0xb802 // tt
+ Type Hash = 0x11d04 // type
+ Typeof Hash = 0x11d06 // typeof
+ U Hash = 0x301 // u
+ Ul Hash = 0x17c02 // ul
+ Usemap Hash = 0xea06 // usemap
+ Value Hash = 0xbe05 // value
+ Var Hash = 0x19b03 // var
+ Video Hash = 0x2e805 // video
+ Vocab Hash = 0x3fd05 // vocab
+ Wbr Hash = 0x40403 // wbr
+ Width Hash = 0x40705 // width
+ Wrap Hash = 0x40d04 // wrap
+ Xmlns Hash = 0x5905 // xmlns
+ Xmp Hash = 0x7903 // xmp
)
// String returns the hash' name.
@@ -304,273 +320,291 @@ NEXT:
return 0
}
-const _Hash_hash0 = 0x67ac9bb5
-const _Hash_maxLen = 15
-const _Hash_text = "aboutputargetbodyaccept-charsetdatalistarticlearbackgroundet" +
- "erminatemplateacronymainertcanvasideclarelabelongdescrolling" +
- "addressamp-boilerplateasynciteaudiobjectfootereversedatatype" +
- "ofacenterowspanamedialogautofocusemappletimenuitemscopedefau" +
- "ltMutedefaultSelectedeferubyautoplayaxismaparamanifestrongbg" +
- "colorbigblockquotebuttonoframesetitleclassidetailselectedfno" +
- "hreflanguagecodebasefontrackbdircodetypemustmatcheckedisable" +
- "divaluetypecolgroupauseonexitranslatecolspanoresizecompactru" +
- "espeedlcontrolshapefieldsetfigcaptionoscriptfigurequiredtfor" +
- "mactionoshadefaultCheckedformnovalidateh2h3h4h5h6hgroupictur" +
- "esourcehiddenabledhtmlhttp-equivaricontentiframeborderimagei" +
- "mginlistrikeygenctypeinputinslotmarkmarqueematheadermaxlengt" +
- "h1metabindexmlnsmallowfullscreenowraplaintextareadonlymeterm" +
- "ethodmultiplegendmutedoptionprofileprogressortablepropertysr" +
- "csetstylesubdopenavalignobruleseamlessectionoembedelsummarys" +
- "uprefixmpostersvgvideoptgrouportalinkvisiblevlinkvocabbrwbr"
+const _Hash_hash0 = 0x51243bbc
+const _Hash_maxLen = 24
+const _Hash_text = "aboutputbodyaccept-charsetfooterbasefontitleaccesskeyacronym" +
+ "ainputmodeferowspanametabindexmlnsamp-boilerplateaddressandb" +
+ "oxmparamarkbdirubyasyncanvasidecodingaudiobjectrackindatalis" +
+ "tepatternavalueautocapitalizeautocompletemplateautofocusemap" +
+ "pletranslateautoplaysinlinertcapturequiredatatypeofetchprior" +
+ "itybigblockingblockquotebuttonobrcitempropertyclassectionoem" +
+ "bedfnoframesetcodefaultcolgroupictureversedisabledivarcolorc" +
+ "olspanomodulecontenteditablegendlabelinkcontrolselectedragga" +
+ "blecoordshadowrootdelegatesfocushadowrootmodetailshapecrosso" +
+ "riginlistarticlefieldsetfigcaptionoscriptfiguresourcenterkey" +
+ "hintformactionformenctypeformmethodformnovalidatetimedialogf" +
+ "ormtargeth5h6heightmlooptgroupinghgrouplaintextareadonlyhidd" +
+ "enhigh1hreflanghttp-equivideoptioniframenuitemidimagesizeslo" +
+ "timagesrcsetimginsmallowfullscreenismapopovertargetactionite" +
+ "mreferrerpolicyitemscopenitemtypematheaderspellcheckedmaxlen" +
+ "gth2meterminlength3multiplemutedprefixpreloadingprofileprogr" +
+ "essrclangstrikestrongstylesubdoptimumarqueesummarysuportalsv" +
+ "gvocabbrwbrwidth4wraposter"
var _Hash_table = [1 << 9]Hash{
- 0x1: 0x13e06, // button
- 0x3: 0x2a207, // enctype
- 0x4: 0x32206, // option
- 0x5: 0x1fb0a, // figcaption
- 0x7: 0x2ae03, // ins
- 0x9: 0x9605, // audio
- 0xb: 0x2830b, // frameborder
- 0xd: 0x2190a, // formaction
- 0xe: 0x5, // about
- 0xf: 0x34606, // srcset
- 0x10: 0x1dd09, // truespeed
- 0x11: 0xeb0c, // defaultMuted
- 0x13: 0xa006, // footer
- 0x15: 0x19d08, // disabled
- 0x16: 0x26e0a, // http-equiv
- 0x19: 0x3a504, // link
- 0x1a: 0x29606, // inlist
- 0x1d: 0x10804, // ruby
- 0x21: 0x2a905, // input
- 0x22: 0x35803, // nav
- 0x25: 0x7902, // dd
- 0x26: 0x2350e, // formnovalidate
- 0x28: 0x16804, // href
- 0x29: 0x24702, // h4
- 0x2b: 0x10405, // defer
- 0x2d: 0x1f308, // fieldset
- 0x2e: 0xeb07, // default
- 0x34: 0x2fd04, // area
- 0x36: 0xb006, // typeof
- 0x37: 0x37307, // noembed
- 0x38: 0x5e07, // declare
- 0x3a: 0x4a07, // acronym
- 0x3b: 0xc05, // tbody
- 0x3e: 0x15107, // classid
- 0x41: 0x9a06, // object
- 0x43: 0x16403, // dfn
- 0x44: 0xef02, // ul
- 0x45: 0x16c04, // lang
- 0x47: 0x16606, // nohref
- 0x49: 0x2c803, // max
- 0x4a: 0x6505, // label
- 0x4c: 0x1d304, // size
- 0x4d: 0xe606, // scoped
- 0x4f: 0x15105, // class
- 0x50: 0x11404, // axis
- 0x54: 0xbf04, // span
- 0x56: 0x19707, // checked
- 0x59: 0x38506, // prefix
- 0x5b: 0x4208, // template
- 0x5c: 0x370d, // undeterminate
- 0x5d: 0xc606, // dialog
- 0x5e: 0x6908, // longdesc
- 0x60: 0x21903, // for
- 0x61: 0x2c102, // th
- 0x64: 0x15d08, // selected
- 0x65: 0x35103, // sub
- 0x6a: 0xd306, // usemap
- 0x6e: 0x24d06, // hgroup
- 0x6f: 0x38303, // sup
- 0x70: 0x2b404, // mark
- 0x71: 0x28206, // iframe
- 0x72: 0x30605, // meter
- 0x74: 0x21008, // required
- 0x75: 0x1f04, // data
- 0x78: 0x14308, // noframes
- 0x83: 0x7807, // address
- 0x88: 0x10c08, // autoplay
- 0x8a: 0x28e05, // image
- 0x8b: 0x16c08, // language
- 0x8e: 0x2f904, // text
- 0x8f: 0x16802, // hr
- 0x90: 0x5d02, // id
- 0x92: 0x31108, // multiple
- 0x94: 0x16808, // hreflang
- 0x95: 0x2db05, // xmlns
- 0x96: 0x24902, // h5
- 0x98: 0x25207, // picture
- 0x99: 0x1106, // accept
- 0x9a: 0x1a609, // valuetype
- 0x9b: 0x3a006, // portal
- 0x9d: 0xac08, // datatype
- 0x9e: 0x18403, // bdi
- 0xa0: 0x27a04, // icon
- 0xa2: 0xa503, // rev
- 0xa5: 0x25708, // resource
- 0xa8: 0x35504, // open
- 0xac: 0x4302, // em
- 0xae: 0x1340a, // blockquote
- 0xb0: 0x2f409, // plaintext
- 0xb1: 0x2d204, // meta
- 0xb2: 0x1c01, // s
- 0xb4: 0xdc04, // time
- 0xb5: 0x1fe07, // caption
- 0xb8: 0x33e08, // property
- 0xb9: 0x1, // a
- 0xbb: 0x2b807, // marquee
- 0xbc: 0x3b505, // vocab
- 0xbd: 0x1e502, // dl
- 0xbf: 0xbc07, // rowspan
- 0xc4: 0x18503, // dir
- 0xc5: 0x39908, // optgroup
- 0xcc: 0x38c06, // poster
- 0xcd: 0x24502, // h3
- 0xce: 0x3b804, // abbr
- 0xd1: 0x17408, // codebase
- 0xd2: 0x27b07, // content
- 0xd4: 0x7e04, // samp
- 0xd6: 0xc204, // name
- 0xd9: 0x14c05, // title
- 0xda: 0x1a605, // value
- 0xdd: 0xb004, // type
- 0xde: 0x35f04, // nobr
- 0xe0: 0x17c04, // font
- 0xe1: 0xd603, // map
- 0xe2: 0x2d002, // h1
- 0xe3: 0x22207, // noshade
- 0xe4: 0x6303, // rel
- 0xe5: 0x14002, // tt
- 0xe7: 0xde04, // menu
- 0xeb: 0x2f908, // textarea
- 0xee: 0x35b05, // align
- 0xf1: 0x29303, // img
- 0xf2: 0x35a06, // valign
- 0xf3: 0x2c204, // head
- 0xf4: 0x12a07, // bgcolor
- 0xf5: 0x5004, // main
- 0xf6: 0x2302, // li
- 0xf7: 0x5205, // inert
- 0xfa: 0x5706, // canvas
- 0xfb: 0xe605, // scope
- 0xfc: 0x15d06, // select
- 0x100: 0xa508, // reversed
- 0x101: 0x20408, // noscript
- 0x102: 0x37c07, // summary
- 0x103: 0x24b02, // h6
- 0x106: 0x17404, // code
- 0x107: 0x14508, // frameset
- 0x10a: 0x12406, // strong
- 0x10d: 0x300a, // background
- 0x10e: 0x18303, // kbd
- 0x114: 0x31706, // legend
- 0x116: 0x32f08, // progress
- 0x118: 0x2d408, // tabindex
- 0x119: 0x34603, // src
- 0x11c: 0x39505, // video
- 0x11f: 0x29a06, // strike
- 0x121: 0xd706, // applet
- 0x123: 0x2802, // rt
- 0x125: 0x20606, // script
- 0x128: 0xbc04, // rows
- 0x129: 0x2707, // article
- 0x12e: 0x9204, // cite
- 0x131: 0x18c0d, // typemustmatch
- 0x133: 0x17f05, // track
- 0x135: 0x3b902, // bb
- 0x136: 0x1ee05, // shape
- 0x137: 0x5b05, // aside
- 0x138: 0x1b60b, // pauseonexit
- 0x13c: 0x38503, // pre
- 0x140: 0x301, // u
- 0x149: 0x1a403, // div
- 0x14c: 0x3a405, // alink
- 0x14e: 0x27703, // var
- 0x14f: 0x21d06, // action
- 0x152: 0x2b05, // clear
- 0x154: 0x2401, // i
- 0x155: 0x21702, // dt
- 0x156: 0x36608, // seamless
- 0x157: 0x21904, // form
- 0x15b: 0x15707, // details
- 0x15f: 0x8e05, // async
- 0x160: 0x26a04, // html
- 0x161: 0x33608, // sortable
- 0x165: 0x2f02, // rb
- 0x167: 0x2e10f, // allowfullscreen
- 0x168: 0x17804, // base
- 0x169: 0x25f06, // hidden
- 0x16e: 0x2ef06, // nowrap
- 0x16f: 0x2505, // start
- 0x170: 0x14505, // frame
- 0x171: 0x1f08, // datalist
- 0x173: 0x12502, // tr
- 0x174: 0x30b06, // method
- 0x175: 0x101, // b
- 0x176: 0x1c904, // cols
- 0x178: 0x110e, // accept-charset
- 0x17a: 0x36205, // rules
- 0x17b: 0x7f0f, // amp-boilerplate
- 0x17f: 0x2270e, // defaultChecked
- 0x180: 0x32807, // profile
- 0x181: 0x2b004, // slot
- 0x182: 0x11a05, // param
- 0x185: 0x1c907, // colspan
- 0x186: 0x34c05, // style
- 0x187: 0x1e02, // td
- 0x188: 0x12c05, // color
- 0x18c: 0x13901, // q
- 0x18d: 0x3b005, // vlink
- 0x18e: 0x39203, // svg
- 0x18f: 0x33905, // table
- 0x190: 0x29e06, // keygen
- 0x192: 0x20c06, // figure
- 0x193: 0x3a907, // visible
- 0x195: 0x17808, // basefont
- 0x196: 0x8802, // rp
- 0x197: 0xf60f, // defaultSelected
- 0x198: 0x1af08, // colgroup
- 0x19a: 0x3bc03, // wbr
- 0x19c: 0x36d07, // section
- 0x19d: 0x25906, // source
- 0x19f: 0x2bf04, // math
- 0x1a1: 0x2fe08, // readonly
- 0x1a7: 0x1e708, // controls
- 0x1a9: 0xde08, // menuitem
- 0x1ad: 0x206, // output
- 0x1b0: 0x2c809, // maxlength
- 0x1b2: 0xe209, // itemscope
- 0x1b9: 0x501, // p
- 0x1bc: 0x2df05, // small
- 0x1bd: 0x36102, // br
- 0x1c0: 0x5503, // rtc
- 0x1c1: 0x1c009, // translate
- 0x1c4: 0x35303, // bdo
- 0x1c5: 0xd04, // body
- 0x1c8: 0xb706, // center
- 0x1c9: 0x2c105, // thead
- 0x1ca: 0xcc09, // autofocus
- 0x1cc: 0xb504, // face
- 0x1cd: 0x24302, // h2
- 0x1ce: 0x11e08, // manifest
- 0x1d0: 0x706, // target
- 0x1d1: 0x11605, // ismap
- 0x1d3: 0xc405, // media
- 0x1d7: 0x13103, // big
- 0x1da: 0x37903, // del
- 0x1dc: 0x6f09, // scrolling
- 0x1de: 0x37505, // embed
- 0x1e0: 0x31d05, // muted
- 0x1e4: 0x2390a, // novalidate
- 0x1e6: 0x7202, // ol
- 0x1eb: 0x9f05, // tfoot
- 0x1ec: 0x18808, // codetype
- 0x1ee: 0x26307, // enabled
- 0x1f0: 0x2c206, // header
- 0x1f1: 0x1cf08, // noresize
- 0x1f6: 0x1d707, // compact
- 0x1f9: 0x12c03, // col
- 0x1fa: 0x38a03, // xmp
- 0x1fb: 0x1807, // charset
+ 0x0: 0x4405, // defer
+ 0x5: 0x18002, // ol
+ 0x6: 0x3720a, // spellcheck
+ 0x7: 0x40b02, // h4
+ 0x8: 0x40705, // width
+ 0x9: 0x9402, // id
+ 0xb: 0x14904, // nobr
+ 0xc: 0x31d05, // small
+ 0xf: 0x2b506, // hgroup
+ 0x10: 0x27702, // th
+ 0x15: 0x24f06, // center
+ 0x18: 0xd10c, // autocomplete
+ 0x1b: 0x2c304, // area
+ 0x1e: 0x17f03, // col
+ 0x1f: 0x2a106, // height
+ 0x21: 0x4b04, // span
+ 0x22: 0x37e03, // max
+ 0x23: 0x3cf06, // strong
+ 0x24: 0x501, // p
+ 0x29: 0x24b06, // source
+ 0x2c: 0x8e06, // canvas
+ 0x2d: 0x2c09, // accesskey
+ 0x2e: 0x18607, // picture
+ 0x30: 0x3a403, // pre
+ 0x31: 0x5d04, // samp
+ 0x34: 0x40902, // dt
+ 0x36: 0x30505, // sizes
+ 0x37: 0x1a908, // nomodule
+ 0x39: 0x2a504, // html
+ 0x3a: 0x31203, // src
+ 0x3c: 0x28d06, // dialog
+ 0x3e: 0x3ab03, // rel
+ 0x40: 0x1a06, // footer
+ 0x43: 0x30d0b, // imagesrcset
+ 0x46: 0x3c906, // strike
+ 0x47: 0x2e805, // video
+ 0x4a: 0x2d702, // hr
+ 0x4b: 0x36108, // itemtype
+ 0x4c: 0x1c804, // link
+ 0x4e: 0x6702, // rp
+ 0x4f: 0x2801, // i
+ 0x50: 0xee06, // applet
+ 0x51: 0x17f08, // colgroup
+ 0x53: 0x1905, // tfoot
+ 0x54: 0xc06, // accept
+ 0x57: 0x14d04, // cite
+ 0x58: 0x1307, // charset
+ 0x59: 0x17604, // code
+ 0x5a: 0x4e04, // name
+ 0x5b: 0x2bf04, // text
+ 0x5d: 0x31f05, // allow
+ 0x5e: 0x36c04, // head
+ 0x61: 0x16605, // embed
+ 0x62: 0x3fa03, // svg
+ 0x63: 0x3fd05, // vocab
+ 0x64: 0x5e0f, // amp-boilerplate
+ 0x65: 0x38805, // meter
+ 0x67: 0x3320d, // popovertarget
+ 0x69: 0x3b04, // main
+ 0x6a: 0x41006, // poster
+ 0x6c: 0x1c302, // dl
+ 0x6e: 0x26006, // action
+ 0x71: 0x17807, // default
+ 0x72: 0x3d05, // input
+ 0x74: 0xb202, // is
+ 0x75: 0x27506, // method
+ 0x79: 0x7903, // xmp
+ 0x7a: 0x101, // b
+ 0x7b: 0x21f06, // inlist
+ 0x7c: 0x25c0a, // formaction
+ 0x7e: 0x39708, // multiple
+ 0x80: 0x1f203, // del
+ 0x81: 0x26a07, // enctype
+ 0x83: 0x27b0e, // formnovalidate
+ 0x84: 0x2404, // font
+ 0x85: 0x11d06, // typeof
+ 0x86: 0x2d704, // href
+ 0x87: 0x13a0a, // blockquote
+ 0x88: 0x4807, // rowspan
+ 0x89: 0x3aa07, // preload
+ 0x8a: 0x12f03, // big
+ 0x8c: 0x38d09, // minlength
+ 0x90: 0x1bb05, // table
+ 0x91: 0x39f05, // muted
+ 0x92: 0x3e407, // marquee
+ 0x94: 0x3507, // acronym
+ 0x96: 0x40d04, // wrap
+ 0x98: 0x14b02, // br
+ 0x9a: 0x10b02, // rt
+ 0x9e: 0xa602, // tr
+ 0x9f: 0x35709, // itemscope
+ 0xa4: 0xad04, // data
+ 0xa5: 0x29706, // target
+ 0xac: 0x11908, // datatype
+ 0xae: 0xb304, // step
+ 0xb3: 0x1cc08, // controls
+ 0xb5: 0xbe05, // value
+ 0xb6: 0x2ba09, // plaintext
+ 0xb7: 0x1da09, // draggable
+ 0xc0: 0x8a05, // async
+ 0xc2: 0x2a804, // loop
+ 0xc3: 0x28904, // time
+ 0xc6: 0x2004, // base
+ 0xc7: 0x23f06, // script
+ 0xce: 0x32103, // low
+ 0xcf: 0x3dc03, // bdo
+ 0xd1: 0x18b03, // rev
+ 0xd2: 0x1e306, // coords
+ 0xd3: 0x8403, // dir
+ 0xd4: 0x2f608, // menuitem
+ 0xd6: 0x22507, // article
+ 0xd8: 0x11d04, // type
+ 0xda: 0x18b08, // reversed
+ 0xdb: 0x23707, // caption
+ 0xdc: 0x35d04, // open
+ 0xdd: 0x1701, // s
+ 0xe0: 0x2705, // title
+ 0xe1: 0x9508, // decoding
+ 0xe3: 0xc0e, // accept-charset
+ 0xe4: 0x15a05, // class
+ 0xe5: 0x3f203, // sup
+ 0xe6: 0xdb08, // template
+ 0xe7: 0x16c08, // noframes
+ 0xe8: 0x3ad07, // loading
+ 0xeb: 0xa106, // object
+ 0xee: 0x3da03, // sub
+ 0xef: 0x2fa06, // itemid
+ 0xf0: 0x30904, // slot
+ 0xf1: 0x8604, // ruby
+ 0xf4: 0x1f102, // td
+ 0xf5: 0x11208, // required
+ 0xf9: 0x16e05, // frame
+ 0xfc: 0x2102, // as
+ 0xfd: 0x37e09, // maxlength
+ 0xff: 0x31f0f, // allowfullscreen
+ 0x101: 0x2160b, // crossorigin
+ 0x102: 0xed03, // map
+ 0x104: 0x6e02, // dd
+ 0x105: 0x705, // tbody
+ 0x107: 0x2d502, // h1
+ 0x109: 0x5004, // meta
+ 0x10a: 0x1, // a
+ 0x10c: 0x16a03, // dfn
+ 0x10e: 0x34507, // itemref
+ 0x110: 0x38d03, // min
+ 0x111: 0x28508, // datetime
+ 0x114: 0xdc02, // em
+ 0x115: 0x7f04, // mark
+ 0x119: 0x2d708, // hreflang
+ 0x11a: 0x3de07, // optimum
+ 0x11c: 0x1220d, // fetchpriority
+ 0x11d: 0x39502, // h3
+ 0x11e: 0x5905, // xmlns
+ 0x11f: 0x19903, // div
+ 0x121: 0x40403, // wbr
+ 0x128: 0x2bf08, // textarea
+ 0x129: 0x3d505, // style
+ 0x12a: 0x3f406, // portal
+ 0x12b: 0x1b107, // content
+ 0x12d: 0x19b03, // var
+ 0x12f: 0x40004, // abbr
+ 0x133: 0x31803, // img
+ 0x138: 0x35b05, // scope
+ 0x13b: 0x30504, // size
+ 0x13e: 0x29f02, // h6
+ 0x141: 0xfc08, // autoplay
+ 0x142: 0x2c408, // readonly
+ 0x143: 0x3d09, // inputmode
+ 0x144: 0x19208, // disabled
+ 0x145: 0x4804, // rows
+ 0x149: 0x3490e, // referrerpolicy
+ 0x14a: 0x1c405, // label
+ 0x14b: 0x36c06, // header
+ 0x14c: 0xad08, // datalist
+ 0x14d: 0xe309, // autofocus
+ 0x14e: 0xb607, // pattern
+ 0x150: 0x2cc06, // hidden
+ 0x151: 0x5, // about
+ 0x152: 0x14406, // button
+ 0x154: 0x2f206, // iframe
+ 0x155: 0x1d308, // selected
+ 0x156: 0x3c207, // srclang
+ 0x15b: 0xb102, // li
+ 0x15c: 0x22305, // start
+ 0x15d: 0x7307, // sandbox
+ 0x15e: 0x31b03, // ins
+ 0x162: 0x1a307, // colspan
+ 0x163: 0x1ff0e, // shadowrootmode
+ 0x164: 0xb104, // list
+ 0x166: 0x5208, // tabindex
+ 0x169: 0x3b407, // profile
+ 0x16b: 0x301, // u
+ 0x16c: 0x23d08, // noscript
+ 0x16e: 0x2660b, // formenctype
+ 0x16f: 0x16e08, // frameset
+ 0x170: 0x28b05, // media
+ 0x174: 0x2008, // basefont
+ 0x176: 0x2b104, // ping
+ 0x177: 0x3bb08, // progress
+ 0x178: 0x206, // output
+ 0x17a: 0x36904, // math
+ 0x17b: 0x2930a, // formtarget
+ 0x17d: 0x7b05, // param
+ 0x180: 0x13208, // blocking
+ 0x185: 0x37707, // checked
+ 0x188: 0x32e05, // ismap
+ 0x18a: 0x38602, // h2
+ 0x18c: 0x2df0a, // http-equiv
+ 0x18e: 0x10d07, // capture
+ 0x190: 0x2db04, // lang
+ 0x195: 0x27f0a, // novalidate
+ 0x197: 0x1a304, // cols
+ 0x198: 0x804, // body
+ 0x199: 0xbc03, // nav
+ 0x19a: 0x1b10f, // contenteditable
+ 0x19b: 0x15e07, // section
+ 0x19e: 0x14e08, // itemprop
+ 0x19f: 0x15208, // property
+ 0x1a1: 0xc30e, // autocapitalize
+ 0x1a4: 0x3eb07, // summary
+ 0x1a6: 0x1000b, // playsinline
+ 0x1a9: 0x8303, // bdi
+ 0x1ab: 0x29d02, // h5
+ 0x1ac: 0x6d07, // address
+ 0x1b0: 0x2d204, // high
+ 0x1b1: 0x33207, // popover
+ 0x1b3: 0xa605, // track
+ 0x1b6: 0x8203, // kbd
+ 0x1b7: 0x11401, // q
+ 0x1b8: 0x2340a, // figcaption
+ 0x1b9: 0x30005, // image
+ 0x1ba: 0x25c04, // form
+ 0x1c1: 0x3000a, // imagesizes
+ 0x1c4: 0x1e818, // shadowrootdelegatesfocus
+ 0x1c5: 0x2ec06, // option
+ 0x1c6: 0x9d05, // audio
+ 0x1c8: 0x40102, // bb
+ 0x1c9: 0x16407, // noembed
+ 0x1cc: 0x10805, // inert
+ 0x1cf: 0x1d306, // select
+ 0x1d1: 0x22c08, // fieldset
+ 0x1d2: 0x31206, // srcset
+ 0x1d3: 0x2f604, // menu
+ 0x1d5: 0x36c07, // headers
+ 0x1dd: 0x1be06, // legend
+ 0x1de: 0xaa04, // kind
+ 0x1e0: 0x24908, // resource
+ 0x1e2: 0xf309, // translate
+ 0x1e4: 0x2aa08, // optgroup
+ 0x1e6: 0x33213, // popovertargetaction
+ 0x1e7: 0x2710a, // formmethod
+ 0x1e9: 0xb802, // tt
+ 0x1ea: 0x36b05, // thead
+ 0x1eb: 0x17c02, // ul
+ 0x1ee: 0x3a406, // prefix
+ 0x1ef: 0x19e05, // color
+ 0x1f1: 0x21105, // shape
+ 0x1f3: 0x25c03, // for
+ 0x1f4: 0x2500c, // enterkeyhint
+ 0x1f7: 0xea06, // usemap
+ 0x1f8: 0x1f02, // rb
+ 0x1fa: 0x20b07, // details
+ 0x1fb: 0x10b03, // rtc
+ 0x1fc: 0x9205, // aside
+ 0x1fe: 0x24506, // figure
}
diff --git a/vendor/github.com/tdewolff/minify/v2/html/html.go b/vendor/github.com/tdewolff/minify/v2/html/html.go
index 6bec42eba..1a5aa9450 100644
--- a/vendor/github.com/tdewolff/minify/v2/html/html.go
+++ b/vendor/github.com/tdewolff/minify/v2/html/html.go
@@ -41,6 +41,13 @@ var (
////////////////////////////////////////////////////////////////
+var GoTemplateDelims = [2]string{"{{", "}}"}
+var HandlebarsTemplateDelims = [2]string{"{{", "}}"}
+var MustacheTemplateDelims = [2]string{"{{", "}}"}
+var EJSTemplateDelims = [2]string{"<%", "%>"}
+var ASPTemplateDelims = [2]string{"<%", "%>"}
+var PHPTemplateDelims = [2]string{"<?", "?>"}
+
// Minifier is an HTML minifier.
type Minifier struct {
KeepComments bool
@@ -50,6 +57,7 @@ type Minifier struct {
KeepEndTags bool
KeepQuotes bool
KeepWhitespace bool
+ TemplateDelims [2]string
}
// Minify minifies HTML data, it reads from r and writes to w.
@@ -71,7 +79,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
z := parse.NewInput(r)
defer z.Restore()
- l := html.NewLexer(z)
+ l := html.NewTemplateLexer(z, o.TemplateDelims)
tb := NewTokenBuffer(z, l)
for {
t := *tb.Shift()
@@ -126,8 +134,9 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
w.Write(t.Data)
}
case html.TextToken:
- // CSS and JS minifiers for inline code
- if rawTagHash != 0 {
+ if t.HasTemplate {
+ w.Write(t.Data)
+ } else if rawTagHash != 0 {
if rawTagHash == Style || rawTagHash == Script || rawTagHash == Iframe {
var mimetype []byte
var params map[string]string
@@ -372,6 +381,9 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
break
} else if attr.Text == nil {
continue // removed attribute
+ } else if attr.HasTemplate {
+ w.Write(attr.Data)
+ continue // don't minify attributes that contain templates
}
val := attr.AttrVal
@@ -389,35 +401,30 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
attr.Hash == Action && t.Hash == Form) {
continue // omit empty attribute values
}
- if attr.Traits&caselessAttr != 0 {
- val = parse.ToLower(val)
- }
if rawTagHash != 0 && attr.Hash == Type {
rawTagMediatype = parse.Copy(val)
}
- if attr.Hash == Enctype || attr.Hash == Codetype || attr.Hash == Accept || attr.Hash == Type && (t.Hash == A || t.Hash == Link || t.Hash == Embed || t.Hash == Object || t.Hash == Source || t.Hash == Script || t.Hash == Style) {
+ if attr.Hash == Enctype ||
+ attr.Hash == Formenctype ||
+ attr.Hash == Accept ||
+ attr.Hash == Type && (t.Hash == A || t.Hash == Link || t.Hash == Embed || t.Hash == Object || t.Hash == Source || t.Hash == Script) {
val = minify.Mediatype(val)
}
// default attribute values can be omitted
- if !o.KeepDefaultAttrVals && (attr.Hash == Type && (t.Hash == Script && jsMimetypes[string(val)] ||
- t.Hash == Style && bytes.Equal(val, cssMimeBytes) ||
- t.Hash == Link && bytes.Equal(val, cssMimeBytes) ||
- t.Hash == Input && bytes.Equal(val, textBytes) ||
- t.Hash == Button && bytes.Equal(val, submitBytes)) ||
- attr.Hash == Language && t.Hash == Script ||
- attr.Hash == Method && bytes.Equal(val, getBytes) ||
- attr.Hash == Enctype && bytes.Equal(val, formMimeBytes) ||
+ if !o.KeepDefaultAttrVals && (attr.Hash == Type && (t.Hash == Script && jsMimetypes[string(parse.ToLower(parse.Copy(val)))] ||
+ t.Hash == Style && parse.EqualFold(val, cssMimeBytes) ||
+ t.Hash == Link && parse.EqualFold(val, cssMimeBytes) ||
+ t.Hash == Input && parse.EqualFold(val, textBytes) ||
+ t.Hash == Button && parse.EqualFold(val, submitBytes)) ||
+ attr.Hash == Method && parse.EqualFold(val, getBytes) ||
+ attr.Hash == Enctype && parse.EqualFold(val, formMimeBytes) ||
attr.Hash == Colspan && bytes.Equal(val, oneBytes) ||
attr.Hash == Rowspan && bytes.Equal(val, oneBytes) ||
- attr.Hash == Shape && bytes.Equal(val, rectBytes) ||
+ attr.Hash == Shape && parse.EqualFold(val, rectBytes) ||
attr.Hash == Span && bytes.Equal(val, oneBytes) ||
- attr.Hash == Clear && bytes.Equal(val, noneBytes) ||
- attr.Hash == Frameborder && bytes.Equal(val, oneBytes) ||
- attr.Hash == Scrolling && bytes.Equal(val, autoBytes) ||
- attr.Hash == Valuetype && bytes.Equal(val, dataBytes) ||
- attr.Hash == Media && t.Hash == Style && bytes.Equal(val, allBytes)) {
+ attr.Hash == Media && t.Hash == Style && parse.EqualFold(val, allBytes)) {
continue
}
@@ -440,7 +447,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
val = val[11:]
}
attrMinifyBuffer.Reset()
- if err := m.MinifyMimetype(jsMimeBytes, attrMinifyBuffer, buffer.NewReader(val), nil); err == nil {
+ if err := m.MinifyMimetype(jsMimeBytes, attrMinifyBuffer, buffer.NewReader(val), inlineParams); err == nil {
val = attrMinifyBuffer.Bytes()
} else if err != minify.ErrNotExist {
return minify.UpdateErrorPosition(err, z, attr.Offset)
diff --git a/vendor/github.com/tdewolff/minify/v2/html/table.go b/vendor/github.com/tdewolff/minify/v2/html/table.go
index 2fd3207f1..37cc866b2 100644
--- a/vendor/github.com/tdewolff/minify/v2/html/table.go
+++ b/vendor/github.com/tdewolff/minify/v2/html/table.go
@@ -13,7 +13,6 @@ const (
const (
booleanAttr traits = 1 << iota
- caselessAttr
urlAttr
trimAttr
)
@@ -163,106 +162,124 @@ var tagMap = map[Hash]traits{
}
var attrMap = map[Hash]traits{
- Accept: trimAttr,
- Accept_Charset: caselessAttr,
- Action: urlAttr,
- Align: caselessAttr,
- Alink: caselessAttr,
- Allowfullscreen: booleanAttr,
- Async: booleanAttr,
- Autofocus: booleanAttr,
- Autoplay: booleanAttr,
- Axis: caselessAttr,
- Background: urlAttr,
- Bgcolor: caselessAttr,
- Charset: caselessAttr,
- Checked: booleanAttr,
- Cite: urlAttr,
- Class: trimAttr,
- Classid: urlAttr,
- Clear: caselessAttr,
- Codebase: urlAttr,
- Codetype: trimAttr,
- Color: caselessAttr,
- Cols: trimAttr,
- Colspan: trimAttr,
- Compact: booleanAttr,
- Controls: booleanAttr,
- Data: urlAttr,
- Declare: booleanAttr,
- Default: booleanAttr,
- DefaultChecked: booleanAttr,
- DefaultMuted: booleanAttr,
- DefaultSelected: booleanAttr,
- Defer: booleanAttr,
- Dir: caselessAttr,
- Disabled: booleanAttr,
- Enabled: booleanAttr,
- Enctype: trimAttr,
- Face: caselessAttr,
- Formaction: urlAttr,
- Formnovalidate: booleanAttr,
- Frame: caselessAttr,
- Hidden: booleanAttr,
- Href: urlAttr,
- Hreflang: caselessAttr,
- Http_Equiv: caselessAttr,
- Icon: urlAttr,
- Inert: booleanAttr,
- Ismap: booleanAttr,
- Itemscope: booleanAttr,
- Lang: trimAttr,
- Language: caselessAttr,
- Link: caselessAttr,
- Longdesc: urlAttr,
- Manifest: urlAttr,
- Maxlength: trimAttr,
- Media: caselessAttr | trimAttr,
- Method: caselessAttr,
- Multiple: booleanAttr,
- Muted: booleanAttr,
- Nohref: booleanAttr,
- Noresize: booleanAttr,
- Noshade: booleanAttr,
- Novalidate: booleanAttr,
- Nowrap: booleanAttr,
- Open: booleanAttr,
- Pauseonexit: booleanAttr,
- Poster: urlAttr,
- Profile: urlAttr,
- Readonly: booleanAttr,
- Rel: caselessAttr | trimAttr,
- Required: booleanAttr,
- Rev: caselessAttr,
- Reversed: booleanAttr,
- Rows: trimAttr,
- Rowspan: trimAttr,
- Rules: caselessAttr,
- Scope: caselessAttr,
- Scoped: booleanAttr,
- Scrolling: caselessAttr,
- Seamless: booleanAttr,
- Selected: booleanAttr,
- Shape: caselessAttr,
- Size: trimAttr,
- Sortable: booleanAttr,
- Span: trimAttr,
- Src: urlAttr,
- Srcset: trimAttr,
- Tabindex: trimAttr,
- Target: caselessAttr,
- Text: caselessAttr,
- Translate: caselessAttr,
- Truespeed: booleanAttr,
- Type: trimAttr,
- Typemustmatch: booleanAttr,
- Undeterminate: booleanAttr,
- Usemap: urlAttr,
- Valign: caselessAttr,
- Valuetype: caselessAttr,
- Vlink: caselessAttr,
- Visible: booleanAttr,
- Xmlns: urlAttr,
+ Accept: trimAttr, // list of mimetypes
+ Accept_Charset: trimAttr,
+ Accesskey: trimAttr,
+ Action: urlAttr,
+ Allow: trimAttr,
+ Allowfullscreen: booleanAttr,
+ As: trimAttr,
+ Async: booleanAttr,
+ Autocapitalize: trimAttr,
+ Autocomplete: trimAttr,
+ Autofocus: booleanAttr,
+ Autoplay: booleanAttr,
+ Blocking: trimAttr,
+ Capture: trimAttr,
+ Charset: trimAttr,
+ Checked: booleanAttr,
+ Cite: urlAttr,
+ Class: trimAttr,
+ Color: trimAttr,
+ Cols: trimAttr, // uint bigger than 0
+ Colspan: trimAttr, // uint bigger than 0
+ Contenteditable: trimAttr,
+ Controls: booleanAttr,
+ Coords: trimAttr, // list of floats
+ Crossorigin: trimAttr,
+ Data: urlAttr,
+ Datetime: trimAttr,
+ Decoding: trimAttr,
+ Default: booleanAttr,
+ Defer: booleanAttr,
+ Dir: trimAttr,
+ Disabled: booleanAttr,
+ Draggable: trimAttr,
+ Enctype: trimAttr, // mimetype
+ Enterkeyhint: trimAttr,
+ Fetchpriority: trimAttr,
+ For: trimAttr,
+ Form: trimAttr,
+ Formaction: urlAttr,
+ Formenctype: trimAttr, // mimetype
+ Formmethod: trimAttr,
+ Formnovalidate: booleanAttr,
+ Formtarget: trimAttr,
+ Headers: trimAttr,
+ Height: trimAttr, // uint
+ Hidden: trimAttr, // TODO: boolean
+ High: trimAttr, // float
+ Href: urlAttr,
+ Hreflang: trimAttr, // BCP 47
+ Http_Equiv: trimAttr,
+ Imagesizes: trimAttr,
+ Imagesrcset: trimAttr,
+ Inert: booleanAttr,
+ Inputmode: trimAttr,
+ Is: trimAttr,
+ Ismap: booleanAttr,
+ Itemid: urlAttr,
+ Itemprop: trimAttr,
+ Itemref: trimAttr,
+ Itemscope: booleanAttr,
+ Itemtype: trimAttr, // list of urls
+ Kind: trimAttr,
+ Lang: trimAttr, // BCP 47
+ List: trimAttr,
+ Loading: trimAttr,
+ Loop: booleanAttr,
+ Low: trimAttr, // float
+ Max: trimAttr, // float or varies
+ Maxlength: trimAttr, // uint
+ Media: trimAttr,
+ Method: trimAttr,
+ Min: trimAttr, // float or varies
+ Minlength: trimAttr, // uint
+ Multiple: booleanAttr,
+ Muted: booleanAttr,
+ Nomodule: booleanAttr,
+ Novalidate: booleanAttr,
+ Open: booleanAttr,
+ Optimum: trimAttr, // float
+ Pattern: trimAttr, // regex
+ Ping: trimAttr, // list of urls
+ Playsinline: booleanAttr,
+ Popover: trimAttr,
+ Popovertarget: trimAttr,
+ Popovertargetaction: trimAttr,
+ Poster: urlAttr,
+ Preload: trimAttr,
+ Profile: urlAttr,
+ Readonly: booleanAttr,
+ Referrerpolicy: trimAttr,
+ Rel: trimAttr,
+ Required: booleanAttr,
+ Reversed: booleanAttr,
+ Rows: trimAttr, // uint bigger than 0
+ Rowspan: trimAttr, // uint
+ Sandbox: trimAttr,
+ Scope: trimAttr,
+ Selected: booleanAttr,
+ Shadowrootmode: trimAttr,
+ Shadowrootdelegatesfocus: booleanAttr,
+ Shape: trimAttr,
+ Size: trimAttr, // uint bigger than 0
+ Sizes: trimAttr,
+ Span: trimAttr, // uint bigger than 0
+ Spellcheck: trimAttr,
+ Src: urlAttr,
+ Srclang: trimAttr, // BCP 47
+ Srcset: trimAttr,
+ Start: trimAttr, // int
+ Step: trimAttr, // float or "any"
+ Tabindex: trimAttr, // int
+ Target: trimAttr,
+ Translate: trimAttr,
+ Type: trimAttr,
+ Usemap: trimAttr,
+ Width: trimAttr, // uint
+ Wrap: trimAttr,
+ Xmlns: urlAttr,
}
var jsMimetypes = map[string]bool{
diff --git a/vendor/github.com/tdewolff/parse/v2/html/lex.go b/vendor/github.com/tdewolff/parse/v2/html/lex.go
index 5619ce9e7..8fc9073d8 100644
--- a/vendor/github.com/tdewolff/parse/v2/html/lex.go
+++ b/vendor/github.com/tdewolff/parse/v2/html/lex.go
@@ -56,16 +56,26 @@ func (tt TokenType) String() string {
////////////////////////////////////////////////////////////////
+var GoTemplate = [2]string{"{{", "}}"}
+var HandlebarsTemplate = [2]string{"{{", "}}"}
+var MustacheTemplate = [2]string{"{{", "}}"}
+var EJSTemplate = [2]string{"<%", "%>"}
+var ASPTemplate = [2]string{"<%", "%>"}
+var PHPTemplate = [2]string{"<?", "?>"}
+
// Lexer is the state for the lexer.
type Lexer struct {
- r *parse.Input
- err error
+ r *parse.Input
+ tmplBegin []byte
+ tmplEnd []byte
+ err error
rawTag Hash
inTag bool
text []byte
attrVal []byte
+ hasTmpl bool
}
// NewLexer returns a new Lexer for a given io.Reader.
@@ -75,6 +85,14 @@ func NewLexer(r *parse.Input) *Lexer {
}
}
+func NewTemplateLexer(r *parse.Input, tmpl [2]string) *Lexer {
+ return &Lexer{
+ r: r,
+ tmplBegin: []byte(tmpl[0]),
+ tmplEnd: []byte(tmpl[1]),
+ }
+}
+
// Err returns the error encountered during lexing, this is often io.EOF but also other errors can be returned.
func (l *Lexer) Err() error {
if l.err != nil {
@@ -88,14 +106,25 @@ func (l *Lexer) Text() []byte {
return l.text
}
+// AttrKey returns the attribute key when an AttributeToken was returned from Next.
+func (l *Lexer) AttrKey() []byte {
+ return l.text
+}
+
// AttrVal returns the attribute value when an AttributeToken was returned from Next.
func (l *Lexer) AttrVal() []byte {
return l.attrVal
}
+// HasTemplate returns the true if the token value contains a template.
+func (l *Lexer) HasTemplate() bool {
+ return l.hasTmpl
+}
+
// Next returns the next Token. It returns ErrorToken when an error was encountered. Using Err() one can retrieve the error message.
func (l *Lexer) Next() (TokenType, []byte) {
l.text = nil
+ l.hasTmpl = false
var c byte
if l.inTag {
l.attrVal = nil
@@ -122,7 +151,7 @@ func (l *Lexer) Next() (TokenType, []byte) {
}
if l.rawTag != 0 {
- if rawText := l.shiftRawText(); len(rawText) > 0 {
+ if rawText := l.shiftRawText(); 0 < len(rawText) {
l.text = rawText
l.rawTag = 0
return TextToken, rawText
@@ -135,12 +164,12 @@ func (l *Lexer) Next() (TokenType, []byte) {
if c == '<' {
c = l.r.Peek(1)
isEndTag := c == '/' && l.r.Peek(2) != '>' && (l.r.Peek(2) != 0 || l.r.PeekErr(2) == nil)
- if l.r.Pos() > 0 {
- if isEndTag || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '!' || c == '?' {
- // return currently buffered texttoken so that we can return tag next iteration
- l.text = l.r.Shift()
- return TextToken, l.text
- }
+ if !isEndTag && (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && c != '!' && c != '?' {
+ // not a tag
+ } else if 0 < l.r.Pos() {
+ // return currently buffered texttoken so that we can return tag next iteration
+ l.text = l.r.Shift()
+ return TextToken, l.text
} else if isEndTag {
l.r.Move(2)
// only endtags that are not followed by > or EOF arrive here
@@ -159,8 +188,12 @@ func (l *Lexer) Next() (TokenType, []byte) {
l.r.Move(1)
return CommentToken, l.shiftBogusComment()
}
+ } else if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
} else if c == 0 && l.r.Err() != nil {
- if l.r.Pos() > 0 {
+ if 0 < l.r.Pos() {
l.text = l.r.Shift()
return TextToken, l.text
}
@@ -241,6 +274,10 @@ func (l *Lexer) shiftRawText() []byte {
} else {
l.r.Move(1)
}
+ } else if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
} else if c == 0 && l.r.Err() != nil {
return l.r.Shift()
} else {
@@ -346,6 +383,11 @@ func (l *Lexer) shiftStartTag() (TokenType, []byte) {
func (l *Lexer) shiftAttribute() []byte {
nameStart := l.r.Pos()
var c byte
+ if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
+ }
for { // attribute name state
if c = l.r.Peek(0); c == ' ' || c == '=' || c == '>' || c == '/' && l.r.Peek(1) == '>' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == 0 && l.r.Err() != nil {
break
@@ -360,6 +402,7 @@ func (l *Lexer) shiftAttribute() []byte {
}
break
}
+ nameHasTmpl := l.hasTmpl
if c == '=' {
l.r.Move(1)
for { // before attribute value state
@@ -378,11 +421,20 @@ func (l *Lexer) shiftAttribute() []byte {
if c == delim {
l.r.Move(1)
break
+ } else if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
} else if c == 0 && l.r.Err() != nil {
break
+ } else {
+ l.r.Move(1)
}
- l.r.Move(1)
}
+ } else if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
} else { // attribute value unquoted state
for {
if c := l.r.Peek(0); c == ' ' || c == '>' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == 0 && l.r.Err() != nil {
@@ -396,7 +448,15 @@ func (l *Lexer) shiftAttribute() []byte {
l.r.Rewind(nameEnd)
l.attrVal = nil
}
- l.text = parse.ToLower(l.r.Lexeme()[nameStart:nameEnd])
+ if 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
+ l.r.Move(len(l.tmplBegin))
+ l.moveTemplate()
+ l.hasTmpl = true
+ }
+ l.text = l.r.Lexeme()[nameStart:nameEnd]
+ if !nameHasTmpl {
+ l.text = parse.ToLower(l.text)
+ }
return l.r.Shift()
}
@@ -473,6 +533,35 @@ func (l *Lexer) shiftXML(rawTag Hash) []byte {
return l.r.Shift()
}
+func (l *Lexer) moveTemplate() {
+ for {
+ if c := l.r.Peek(0); l.at(l.tmplEnd...) || c == 0 && l.r.Err() != nil {
+ if c != 0 {
+ l.r.Move(len(l.tmplEnd))
+ }
+ break
+ } else if c == '"' || c == '\'' {
+ l.r.Move(1)
+ escape := false
+ for {
+ if c2 := l.r.Peek(0); !escape && c2 == c || c2 == 0 && l.r.Err() != nil {
+ if c2 != 0 {
+ l.r.Move(1)
+ }
+ break
+ } else if c2 == '\\' {
+ escape = !escape
+ } else {
+ escape = false
+ }
+ l.r.Move(1)
+ }
+ } else {
+ l.r.Move(1)
+ }
+ }
+}
+
////////////////////////////////////////////////////////////////
func (l *Lexer) at(b ...byte) bool {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 64bd37650..cc692029f 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -167,8 +167,8 @@ github.com/dsoprea/go-utility/v2/image
# github.com/dustin/go-humanize v1.0.1
## explicit; go 1.16
github.com/dustin/go-humanize
-# github.com/fsnotify/fsnotify v1.6.0
-## explicit; go 1.16
+# github.com/fsnotify/fsnotify v1.7.0
+## explicit; go 1.17
github.com/fsnotify/fsnotify
# github.com/gabriel-vasile/mimetype v1.4.2
## explicit; go 1.20
@@ -636,11 +636,11 @@ github.com/superseriousbusiness/oauth2/v4/generates
github.com/superseriousbusiness/oauth2/v4/manage
github.com/superseriousbusiness/oauth2/v4/models
github.com/superseriousbusiness/oauth2/v4/server
-# github.com/tdewolff/minify/v2 v2.20.0
+# github.com/tdewolff/minify/v2 v2.20.6
## explicit; go 1.18
github.com/tdewolff/minify/v2
github.com/tdewolff/minify/v2/html
-# github.com/tdewolff/parse/v2 v2.7.0
+# github.com/tdewolff/parse/v2 v2.7.4
## explicit; go 1.13
github.com/tdewolff/parse/v2
github.com/tdewolff/parse/v2/buffer