From 74b600655ded2b62b61c7631f96fc14edd9342fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:41:31 +0000 Subject: [PATCH] [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> --- go.mod | 6 +- go.sum | 17 +- .../github.com/fsnotify/fsnotify/.cirrus.yml | 13 + .../github.com/fsnotify/fsnotify/.gitignore | 1 + .../github.com/fsnotify/fsnotify/CHANGELOG.md | 87 +- vendor/github.com/fsnotify/fsnotify/README.md | 79 +- .../fsnotify/fsnotify/backend_fen.go | 550 ++++++++- .../fsnotify/fsnotify/backend_inotify.go | 381 ++++-- .../fsnotify/fsnotify/backend_kqueue.go | 293 +++-- .../fsnotify/fsnotify/backend_other.go | 203 +++- .../fsnotify/fsnotify/backend_windows.go | 245 ++-- .../github.com/fsnotify/fsnotify/fsnotify.go | 91 +- vendor/github.com/fsnotify/fsnotify/mkdoc.zsh | 123 +- .../tdewolff/minify/v2/html/buffer.go | 16 +- .../tdewolff/minify/v2/html/hash.go | 1066 +++++++++-------- .../tdewolff/minify/v2/html/html.go | 51 +- .../tdewolff/minify/v2/html/table.go | 219 ++-- .../github.com/tdewolff/parse/v2/html/lex.go | 113 +- vendor/modules.txt | 8 +- 19 files changed, 2421 insertions(+), 1141 deletions(-) create mode 100644 vendor/github.com/fsnotify/fsnotify/.cirrus.yml 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. - -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). - -## [Unreleased] - +Unreleased +---------- Nothing yet. -## [1.6.0] - 2022-10-13 +1.7.0 - 2023-10-22 +------------------ +This version of fsnotify needs Go 1.17. +### Additions + +- 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. + +[#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) | +| 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 macOS should include Android and iOS, but these are currently untested. +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 // -// 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. // @@ -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. +// 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. // -// 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 { +// 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 // -// 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. // @@ -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. +// 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. // -// 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) +// 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) + if w.isClosed() { + return nil + } + return w.remove(filepath.Clean(name)) +} - // Fetch the watch. - w.mu.Lock() - defer w.mu.Unlock() - watch, ok := w.watches[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,26 +439,26 @@ 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() - - entries := make([]string, 0, len(w.watches)) - for pathname := range w.watches { - entries = append(entries, pathname) + if w.isClosed() { + return nil } - return entries -} + 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() -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) + return entries } // readEvents reads from the inotify file descriptor, converts the @@ -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) - } - w.mu.Unlock() + 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 + } + } + } + + 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 // -// 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. // @@ -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. +// 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. // -// 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 { +// 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()) - cleanPath, err := w.internalWatch(path, fileInfo) + fi, err := f.Info() + if err != nil { + return fmt.Errorf("%q: %w", path, err) + } + + 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. +// 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. // -// 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 -} +// 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. +// 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. // -// 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") +// 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=$(< 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