Change layout to more go-style-ish, replace some modules
* Deejay files, split them to achieve go-style project layout * Replace logrus with log/slog module to reduce dependencies * Replace gopkg.in/ini.v1 with github.com/wlevene/ini to make sure all modules BSD licensed * Update linter config && fix tons of linter warnings * Update .gitignore * Update README.md
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,5 +1,3 @@
|
|||||||
.vscode/**
|
/joyproxy.ini
|
||||||
.idea/**
|
/joyproxy
|
||||||
joyproxy.ini
|
|
||||||
joyproxy
|
|
||||||
|
|
||||||
|
|||||||
743
.golangci.yml
743
.golangci.yml
@@ -1,28 +1,12 @@
|
|||||||
|
version: "2"
|
||||||
run:
|
run:
|
||||||
tests: false
|
go: "1.25"
|
||||||
skip-dirs:
|
|
||||||
- data
|
|
||||||
- vendor
|
|
||||||
modules-download-mode: readonly
|
modules-download-mode: readonly
|
||||||
|
tests: false
|
||||||
allow-parallel-runners: true
|
allow-parallel-runners: true
|
||||||
go: '1.21'
|
|
||||||
output:
|
|
||||||
sort-results: true
|
|
||||||
linters-settings:
|
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
|
|
||||||
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
|
||||||
- G404 # Insecure random number source (rand)
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- errcheck
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- staticcheck
|
|
||||||
- unused
|
|
||||||
- asasalint
|
- asasalint
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bodyclose
|
- bodyclose
|
||||||
@@ -30,24 +14,30 @@ linters:
|
|||||||
- contextcheck
|
- contextcheck
|
||||||
- decorder
|
- decorder
|
||||||
- durationcheck
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exptostd
|
||||||
- gci
|
|
||||||
- ginkgolinter
|
- ginkgolinter
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- gocritic
|
- gocritic
|
||||||
|
- godoclint
|
||||||
- godot
|
- godot
|
||||||
- gofmt
|
- godox
|
||||||
- goimports
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
- gosmopolitan
|
||||||
|
- govet
|
||||||
- grouper
|
- grouper
|
||||||
- importas
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
- intrange
|
||||||
- ireturn
|
- ireturn
|
||||||
- makezero
|
- makezero
|
||||||
- mirror
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- modernize
|
||||||
- nakedret
|
- nakedret
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnil
|
- nilnil
|
||||||
@@ -55,13 +45,714 @@ linters:
|
|||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- nonamedreturns
|
- nonamedreturns
|
||||||
|
- perfsprint
|
||||||
- predeclared
|
- predeclared
|
||||||
- promlinter
|
- promlinter
|
||||||
- reassign
|
- reassign
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- stylecheck
|
- staticcheck
|
||||||
- unconvert
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- usestdlibvars
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
- wsl
|
- wsl_v5
|
||||||
|
settings:
|
||||||
|
decorder:
|
||||||
|
dec-order:
|
||||||
|
- type
|
||||||
|
- const
|
||||||
|
- var
|
||||||
|
- func
|
||||||
|
gocritic:
|
||||||
|
disable-all: false
|
||||||
|
enable-all: true
|
||||||
|
disabled-checks:
|
||||||
|
- singleCaseSwitch
|
||||||
|
- octalLiteral
|
||||||
|
- regexpSimplify
|
||||||
|
- paramTypeCombine
|
||||||
|
- whyNoLint
|
||||||
|
- hugeParam
|
||||||
|
godoclint:
|
||||||
|
default: all
|
||||||
|
ignore-unexported: false
|
||||||
|
ignore-exported: false
|
||||||
|
include-unexported: true
|
||||||
|
disable:
|
||||||
|
- max-len
|
||||||
|
godot:
|
||||||
|
scope: all
|
||||||
|
exclude:
|
||||||
|
# Exclude todo and fixme comments.
|
||||||
|
- "^fixme:"
|
||||||
|
- "^todo:"
|
||||||
|
period: true
|
||||||
|
capital: false
|
||||||
|
godox:
|
||||||
|
keywords:
|
||||||
|
- FIXME
|
||||||
|
gosec: # go-fucking-sic
|
||||||
|
excludes:
|
||||||
|
# Боже, как же вы затрахали своей крипто-ебучкой, наркоманы хреновы!
|
||||||
|
- G405 # Detect the usage of DES or RC4
|
||||||
|
- G406 # Detect the usage of MD4 or RIPEMD160
|
||||||
|
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
|
||||||
|
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
||||||
|
- G404 # Insecure random number source (rand)
|
||||||
|
- G501 # Import blocklist: crypto/md5
|
||||||
|
- G502 # Import blocklist: crypto/des
|
||||||
|
- G503 # Import blocklist: crypto/rc4
|
||||||
|
- G504 # Import blocklist: net/http/cgi
|
||||||
|
- G505 # Import blocklist: crypto/sha1
|
||||||
|
- G506 # Import blocklist: golang.org/x/crypto/md4
|
||||||
|
- G507 # Import blocklist: golang.org/x/crypto/ripemd160
|
||||||
|
config:
|
||||||
|
global:
|
||||||
|
audit: true
|
||||||
|
# Ваша сраная паранойя при создании файлов и каталогов мешает нормальной работе сервисов. Группы придуманы для
|
||||||
|
# удобства, группе удобно давать право чтения. Поэтому идите нахуй со своими ограничениями на чтение для
|
||||||
|
# группы, "безопасные" клоуны.
|
||||||
|
G301: "0755"
|
||||||
|
G306: "0644"
|
||||||
|
govet:
|
||||||
|
disable-all: false
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- shadow
|
||||||
|
grouper:
|
||||||
|
# Require the use of a single global 'const' declaration only.
|
||||||
|
# Default: false
|
||||||
|
const-require-single-const: true
|
||||||
|
# Require the use of grouped global 'const' declarations.
|
||||||
|
# Default: false
|
||||||
|
const-require-grouping: true
|
||||||
|
# Require the use of a single 'import' declaration only.
|
||||||
|
# Default: false
|
||||||
|
import-require-single-import: true
|
||||||
|
# Require the use of grouped 'import' declarations.
|
||||||
|
# Default: false
|
||||||
|
import-require-grouping: true
|
||||||
|
# Require the use of a single global 'type' declaration only.
|
||||||
|
# Default: false
|
||||||
|
type-require-single-type: true
|
||||||
|
# Require the use of grouped global 'type' declarations.
|
||||||
|
# Default: false
|
||||||
|
type-require-grouping: true
|
||||||
|
# Require the use of a single global 'var' declaration only.
|
||||||
|
# Default: false
|
||||||
|
var-require-single-var: true
|
||||||
|
# Require the use of grouped global 'var' declarations.
|
||||||
|
# Default: false
|
||||||
|
var-require-grouping: true
|
||||||
|
misspell:
|
||||||
|
# Correct spellings using locale preferences for US or UK.
|
||||||
|
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||||
|
# Default is to use a neutral variety of English.
|
||||||
|
locale: US
|
||||||
|
# Typos to ignore.
|
||||||
|
# Should be in lower case.
|
||||||
|
# Default: []
|
||||||
|
ignore-rules:
|
||||||
|
- someword
|
||||||
|
# Extra word corrections.
|
||||||
|
# `typo` and `correction` should only contain letters.
|
||||||
|
# The words are case-insensitive.
|
||||||
|
# Default: []
|
||||||
|
extra-words:
|
||||||
|
- typo: "iff"
|
||||||
|
correction: "if"
|
||||||
|
- typo: "cancelation"
|
||||||
|
correction: "cancellation"
|
||||||
|
# Mode of the analysis:
|
||||||
|
# - default: checks all the file content.
|
||||||
|
# - restricted: checks only comments.
|
||||||
|
# Default: ""
|
||||||
|
mode: restricted
|
||||||
|
nonamedreturns:
|
||||||
|
# Report named error if it is assigned inside defer.
|
||||||
|
# Default: false
|
||||||
|
report-error-in-defer: true
|
||||||
|
perfsprint:
|
||||||
|
integer-format: true
|
||||||
|
int-conversion: true
|
||||||
|
error-format: true
|
||||||
|
err-error: false
|
||||||
|
errorf: true
|
||||||
|
string-format: true
|
||||||
|
sprintf1: true
|
||||||
|
strconcat: true
|
||||||
|
bool-format: true
|
||||||
|
hex-format: true
|
||||||
|
concat-loop: true
|
||||||
|
loop-other-ops: false
|
||||||
|
revive:
|
||||||
|
enable-all-rules: false
|
||||||
|
staticcheck:
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
|
||||||
|
# Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
|
||||||
|
dot-import-whitelist:
|
||||||
|
- fmt
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#initialisms
|
||||||
|
# Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
||||||
|
initialisms: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
|
||||||
|
# Default: ["200", "400", "404", "500"]
|
||||||
|
http-status-code-whitelist: ["200", "400", "404", "500"]
|
||||||
|
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks
|
||||||
|
# Example (to disable some checks): [ "all", "-SA1000", "-SA1001"]
|
||||||
|
# Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks.
|
||||||
|
# Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
|
||||||
|
checks:
|
||||||
|
# Invalid regular expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1000
|
||||||
|
- SA1000
|
||||||
|
# Invalid template.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1001
|
||||||
|
- SA1001
|
||||||
|
# Invalid format in 'time.Parse'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1002
|
||||||
|
- SA1002
|
||||||
|
# Unsupported argument to functions in 'encoding/binary'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1003
|
||||||
|
- SA1003
|
||||||
|
# Suspiciously small untyped constant in 'time.Sleep'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1004
|
||||||
|
- SA1004
|
||||||
|
# Invalid first argument to 'exec.Command'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1005
|
||||||
|
- SA1005
|
||||||
|
# 'Printf' with dynamic first argument and no further arguments.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1006
|
||||||
|
- SA1006
|
||||||
|
# Invalid URL in 'net/url.Parse'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1007
|
||||||
|
- SA1007
|
||||||
|
# Non-canonical key in 'http.Header' map.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1008
|
||||||
|
- SA1008
|
||||||
|
# '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1010
|
||||||
|
- SA1010
|
||||||
|
# Various methods in the "strings" package expect valid UTF-8, but invalid input is provided.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1011
|
||||||
|
- SA1011
|
||||||
|
# A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1012
|
||||||
|
- SA1012
|
||||||
|
# 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1013
|
||||||
|
- SA1013
|
||||||
|
# Non-pointer value passed to 'Unmarshal' or 'Decode'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1014
|
||||||
|
- SA1014
|
||||||
|
# Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1015
|
||||||
|
- SA1015
|
||||||
|
# Trapping a signal that cannot be trapped.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1016
|
||||||
|
- SA1016
|
||||||
|
# Channels used with 'os/signal.Notify' should be buffered.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1017
|
||||||
|
- SA1017
|
||||||
|
# 'strings.Replace' called with 'n == 0', which does nothing.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1018
|
||||||
|
- SA1018
|
||||||
|
# Using a deprecated function, variable, constant or field.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1019
|
||||||
|
- SA1019
|
||||||
|
# Using an invalid host:port pair with a 'net.Listen'-related function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1020
|
||||||
|
- SA1020
|
||||||
|
# Using 'bytes.Equal' to compare two 'net.IP'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1021
|
||||||
|
- SA1021
|
||||||
|
# Modifying the buffer in an 'io.Writer' implementation.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1023
|
||||||
|
- SA1023
|
||||||
|
# A string cutset contains duplicate characters.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1024
|
||||||
|
- SA1024
|
||||||
|
# It is not possible to use '(*time.Timer).Reset''s return value correctly.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1025
|
||||||
|
- SA1025
|
||||||
|
# Cannot marshal channels or functions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1026
|
||||||
|
- SA1026
|
||||||
|
# Atomic access to 64-bit variable must be 64-bit aligned.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1027
|
||||||
|
- SA1027
|
||||||
|
# 'sort.Slice' can only be used on slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1028
|
||||||
|
- SA1028
|
||||||
|
# Inappropriate key in call to 'context.WithValue'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1029
|
||||||
|
- SA1029
|
||||||
|
# Invalid argument in call to a 'strconv' function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1030
|
||||||
|
- SA1030
|
||||||
|
# Overlapping byte slices passed to an encoder.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1031
|
||||||
|
- SA1031
|
||||||
|
# Wrong order of arguments to 'errors.Is'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1032
|
||||||
|
- SA1032
|
||||||
|
# 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2000
|
||||||
|
- SA2000
|
||||||
|
# Empty critical section, did you mean to defer the unlock?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2001
|
||||||
|
- SA2001
|
||||||
|
# Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2002
|
||||||
|
- SA2002
|
||||||
|
# Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2003
|
||||||
|
- SA2003
|
||||||
|
# 'TestMain' doesn't call 'os.Exit', hiding test failures.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA3000
|
||||||
|
- SA3000
|
||||||
|
# Assigning to 'b.N' in benchmarks distorts the results.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA3001
|
||||||
|
- SA3001
|
||||||
|
# Binary operator has identical expressions on both sides.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4000
|
||||||
|
- SA4000
|
||||||
|
# '&*x' gets simplified to 'x', it does not copy 'x'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4001
|
||||||
|
- SA4001
|
||||||
|
# Comparing unsigned values against negative values is pointless.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4003
|
||||||
|
- SA4003
|
||||||
|
# The loop exits unconditionally after one iteration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4004
|
||||||
|
- SA4004
|
||||||
|
# Field assignment that will never be observed. Did you mean to use a pointer receiver?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4005
|
||||||
|
- SA4005
|
||||||
|
# A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4006
|
||||||
|
- SA4006
|
||||||
|
# The variable in the loop condition never changes, are you incrementing the wrong variable?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4008
|
||||||
|
- SA4008
|
||||||
|
# A function argument is overwritten before its first use.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4009
|
||||||
|
- SA4009
|
||||||
|
# The result of 'append' will never be observed anywhere.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4010
|
||||||
|
- SA4010
|
||||||
|
# Break statement with no effect. Did you mean to break out of an outer loop?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4011
|
||||||
|
- SA4011
|
||||||
|
# Comparing a value against NaN even though no value is equal to NaN.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4012
|
||||||
|
- SA4012
|
||||||
|
# Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4013
|
||||||
|
- SA4013
|
||||||
|
# An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4014
|
||||||
|
- SA4014
|
||||||
|
# Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4015
|
||||||
|
- SA4015
|
||||||
|
# Certain bitwise operations, such as 'x ^ 0', do not do anything useful.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4016
|
||||||
|
- SA4016
|
||||||
|
# Discarding the return values of a function without side effects, making the call pointless.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4017
|
||||||
|
- SA4017
|
||||||
|
# Self-assignment of variables.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4018
|
||||||
|
- SA4018
|
||||||
|
# Multiple, identical build constraints in the same file.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4019
|
||||||
|
- SA4019
|
||||||
|
# Unreachable case clause in a type switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4020
|
||||||
|
- SA4020
|
||||||
|
# "x = append(y)" is equivalent to "x = y".
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4021
|
||||||
|
- SA4021
|
||||||
|
# Comparing the address of a variable against nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4022
|
||||||
|
- SA4022
|
||||||
|
# Impossible comparison of interface value with untyped nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4023
|
||||||
|
- SA4023
|
||||||
|
# Checking for impossible return value from a builtin function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4024
|
||||||
|
- SA4024
|
||||||
|
# Integer division of literals that results in zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4025
|
||||||
|
- SA4025
|
||||||
|
# Go constants cannot express negative zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4026
|
||||||
|
- SA4026
|
||||||
|
# '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4027
|
||||||
|
- SA4027
|
||||||
|
# 'x % 1' is always zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4028
|
||||||
|
- SA4028
|
||||||
|
# Ineffective attempt at sorting slice.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4029
|
||||||
|
- SA4029
|
||||||
|
# Ineffective attempt at generating random number.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4030
|
||||||
|
- SA4030
|
||||||
|
# Checking never-nil value against nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4031
|
||||||
|
- SA4031
|
||||||
|
# Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4032
|
||||||
|
- SA4032
|
||||||
|
# Assignment to nil map.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5000
|
||||||
|
- SA5000
|
||||||
|
# Deferring 'Close' before checking for a possible error.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5001
|
||||||
|
- SA5001
|
||||||
|
# The empty for loop ("for {}") spins and can block the scheduler.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5002
|
||||||
|
- SA5002
|
||||||
|
# Defers in infinite loops will never execute.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5003
|
||||||
|
- SA5003
|
||||||
|
# "for { select { ..." with an empty default branch spins.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5004
|
||||||
|
- SA5004
|
||||||
|
# The finalizer references the finalized object, preventing garbage collection.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5005
|
||||||
|
- SA5005
|
||||||
|
# Infinite recursive call.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5007
|
||||||
|
- SA5007
|
||||||
|
# Invalid struct tag.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5008
|
||||||
|
- SA5008
|
||||||
|
# Invalid Printf call.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5009
|
||||||
|
- SA5009
|
||||||
|
# Impossible type assertion.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5010
|
||||||
|
- SA5010
|
||||||
|
# Possible nil pointer dereference.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5011
|
||||||
|
- SA5011
|
||||||
|
# Passing odd-sized slice to function expecting even size.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5012
|
||||||
|
- SA5012
|
||||||
|
# Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6000
|
||||||
|
- SA6000
|
||||||
|
# Missing an optimization opportunity when indexing maps by byte slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6001
|
||||||
|
- SA6001
|
||||||
|
# Storing non-pointer values in 'sync.Pool' allocates memory.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6002
|
||||||
|
- SA6002
|
||||||
|
# Converting a string to a slice of runes before ranging over it.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6003
|
||||||
|
- SA6003
|
||||||
|
# Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6005
|
||||||
|
- SA6005
|
||||||
|
# Using io.WriteString to write '[]byte'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6006
|
||||||
|
- SA6006
|
||||||
|
# Defers in range loops may not run when you expect them to.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9001
|
||||||
|
- SA9001
|
||||||
|
# Using a non-octal 'os.FileMode' that looks like it was meant to be in octal.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9002
|
||||||
|
- SA9002
|
||||||
|
# Empty body in an if or else branch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9003
|
||||||
|
- SA9003
|
||||||
|
# Only the first constant has an explicit type.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9004
|
||||||
|
- SA9004
|
||||||
|
# Trying to marshal a struct with no public fields nor custom marshaling.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9005
|
||||||
|
- SA9005
|
||||||
|
# Dubious bit shifting of a fixed size integer value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9006
|
||||||
|
- SA9006
|
||||||
|
# Deleting a directory that shouldn't be deleted.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9007
|
||||||
|
- SA9007
|
||||||
|
# 'else' branch of a type assertion is probably not reading the right value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9008
|
||||||
|
- SA9008
|
||||||
|
# Ineffectual Go compiler directive.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9009
|
||||||
|
- SA9009
|
||||||
|
# Incorrect or missing package comment.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1000
|
||||||
|
- ST1000
|
||||||
|
# Dot imports are discouraged.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1001
|
||||||
|
- ST1001
|
||||||
|
# Poorly chosen identifier.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1003
|
||||||
|
- ST1003
|
||||||
|
# Incorrectly formatted error string.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1005
|
||||||
|
- ST1005
|
||||||
|
# Poorly chosen receiver name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1006
|
||||||
|
- ST1006
|
||||||
|
# A function's error value should be its last return value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1008
|
||||||
|
- ST1008
|
||||||
|
# Poorly chosen name for variable of type 'time.Duration'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1011
|
||||||
|
- ST1011
|
||||||
|
# Poorly chosen name for error variable.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1012
|
||||||
|
- ST1012
|
||||||
|
# Should use constants for HTTP error codes, not magic numbers.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1013
|
||||||
|
- ST1013
|
||||||
|
# A switch's default case should be the first or last case.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1015
|
||||||
|
- ST1015
|
||||||
|
# Use consistent method receiver names.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1016
|
||||||
|
- ST1016
|
||||||
|
# Don't use Yoda conditions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1017
|
||||||
|
- ST1017
|
||||||
|
# Avoid zero-width and control characters in string literals.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1018
|
||||||
|
- ST1018
|
||||||
|
# Importing the same package multiple times.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1019
|
||||||
|
- ST1019
|
||||||
|
# The documentation of an exported function should start with the function's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1020
|
||||||
|
- ST1020
|
||||||
|
# The documentation of an exported type should start with type's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1021
|
||||||
|
- ST1021
|
||||||
|
# The documentation of an exported variable or constant should start with variable's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1022
|
||||||
|
- ST1022
|
||||||
|
# Redundant type in variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1023
|
||||||
|
- ST1023
|
||||||
|
# Use plain channel send or receive instead of single-case select.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1000
|
||||||
|
- S1000
|
||||||
|
# Replace for loop with call to copy.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1001
|
||||||
|
- S1001
|
||||||
|
# Omit comparison with boolean constant.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1002
|
||||||
|
- S1002
|
||||||
|
# Replace call to 'strings.Index' with 'strings.Contains'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1003
|
||||||
|
- S1003
|
||||||
|
# Replace call to 'bytes.Compare' with 'bytes.Equal'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1004
|
||||||
|
- S1004
|
||||||
|
# Drop unnecessary use of the blank identifier.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1005
|
||||||
|
- S1005
|
||||||
|
# Use "for { ... }" for infinite loops.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1006
|
||||||
|
- S1006
|
||||||
|
# Simplify regular expression by using raw string literal.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1007
|
||||||
|
- S1007
|
||||||
|
# Simplify returning boolean expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1008
|
||||||
|
- S1008
|
||||||
|
# Omit redundant nil check on slices, maps, and channels.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1009
|
||||||
|
- S1009
|
||||||
|
# Omit default slice index.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1010
|
||||||
|
- S1010
|
||||||
|
# Use a single 'append' to concatenate two slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1011
|
||||||
|
- S1011
|
||||||
|
# Replace 'time.Now().Sub(x)' with 'time.Since(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1012
|
||||||
|
- S1012
|
||||||
|
# Use a type conversion instead of manually copying struct fields.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1016
|
||||||
|
- S1016
|
||||||
|
# Replace manual trimming with 'strings.TrimPrefix'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1017
|
||||||
|
- S1017
|
||||||
|
# Use "copy" for sliding elements.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1018
|
||||||
|
- S1018
|
||||||
|
# Simplify "make" call by omitting redundant arguments.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1019
|
||||||
|
- S1019
|
||||||
|
# Omit redundant nil check in type assertion.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1020
|
||||||
|
- S1020
|
||||||
|
# Merge variable declaration and assignment.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1021
|
||||||
|
- S1021
|
||||||
|
# Omit redundant control flow.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1023
|
||||||
|
- S1023
|
||||||
|
# Replace 'x.Sub(time.Now())' with 'time.Until(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1024
|
||||||
|
- S1024
|
||||||
|
# Don't use 'fmt.Sprintf("%s", x)' unnecessarily.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1025
|
||||||
|
- S1025
|
||||||
|
# Simplify error construction with 'fmt.Errorf'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1028
|
||||||
|
- S1028
|
||||||
|
# Range over the string directly.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1029
|
||||||
|
- S1029
|
||||||
|
# Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1030
|
||||||
|
- S1030
|
||||||
|
# Omit redundant nil check around loop.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1031
|
||||||
|
- S1031
|
||||||
|
# Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1032
|
||||||
|
- S1032
|
||||||
|
# Unnecessary guard around call to "delete".
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1033
|
||||||
|
- S1033
|
||||||
|
# Use result of type assertion to simplify cases.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1034
|
||||||
|
- S1034
|
||||||
|
# Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1035
|
||||||
|
- S1035
|
||||||
|
# Unnecessary guard around map access.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1036
|
||||||
|
- S1036
|
||||||
|
# Elaborate way of sleeping.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1037
|
||||||
|
- S1037
|
||||||
|
# Unnecessarily complex way of printing formatted string.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1038
|
||||||
|
- S1038
|
||||||
|
# Unnecessary use of 'fmt.Sprint'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1039
|
||||||
|
- S1039
|
||||||
|
# Type assertion to current type.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1040
|
||||||
|
- S1040
|
||||||
|
# Apply De Morgan's law.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1001
|
||||||
|
- QF1001
|
||||||
|
# Convert untagged switch to tagged switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1002
|
||||||
|
- QF1002
|
||||||
|
# Convert if/else-if chain to tagged switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1003
|
||||||
|
- QF1003
|
||||||
|
# Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1004
|
||||||
|
- QF1004
|
||||||
|
# Expand call to 'math.Pow'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1005
|
||||||
|
- QF1005
|
||||||
|
# Lift 'if'+'break' into loop condition.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1006
|
||||||
|
- QF1006
|
||||||
|
# Merge conditional assignment into variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1007
|
||||||
|
- QF1007
|
||||||
|
# Omit embedded fields from selector expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1008
|
||||||
|
- QF1008
|
||||||
|
# Use 'time.Time.Equal' instead of '==' operator.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1009
|
||||||
|
- QF1009
|
||||||
|
# Convert slice of bytes to string when printing it.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1010
|
||||||
|
- QF1010
|
||||||
|
# Omit redundant type from variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1011
|
||||||
|
- QF1011
|
||||||
|
# Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1012
|
||||||
|
- QF1012
|
||||||
|
unconvert:
|
||||||
|
# Remove conversions that force intermediate rounding.
|
||||||
|
# Default: false
|
||||||
|
fast-math: true
|
||||||
|
# Be more conservative (experimental).
|
||||||
|
# Default: false
|
||||||
|
safe: true
|
||||||
|
usestdlibvars:
|
||||||
|
# Suggest the use of http.MethodXX.
|
||||||
|
# Default: true
|
||||||
|
http-method: false
|
||||||
|
# Suggest the use of http.StatusXX.
|
||||||
|
# Default: true
|
||||||
|
http-status-code: false
|
||||||
|
# Suggest the use of time.Month in time.Date.
|
||||||
|
# Default: false
|
||||||
|
time-date-month: true
|
||||||
|
# Suggest the use of time.Weekday.String().
|
||||||
|
# Default: true
|
||||||
|
time-weekday: true
|
||||||
|
# Suggest the use of time.Month.String().
|
||||||
|
# Default: false
|
||||||
|
time-month: true
|
||||||
|
# Suggest the use of time.Layout.
|
||||||
|
# Default: false
|
||||||
|
time-layout: true
|
||||||
|
# Suggest the use of crypto.Hash.String().
|
||||||
|
# Default: false
|
||||||
|
crypto-hash: true
|
||||||
|
# Suggest the use of rpc.DefaultXXPath.
|
||||||
|
# Default: false
|
||||||
|
default-rpc-path: true
|
||||||
|
# Suggest the use of sql.LevelXX.String().
|
||||||
|
# Default: false
|
||||||
|
sql-isolation-level: true
|
||||||
|
# Suggest the use of tls.SignatureScheme.String().
|
||||||
|
# Default: false
|
||||||
|
tls-signature-scheme: true
|
||||||
|
# Suggest the use of constant.Kind.String().
|
||||||
|
# Default: false
|
||||||
|
constant-kind: true
|
||||||
|
wsl_v5:
|
||||||
|
allow-first-in-block: true
|
||||||
|
allow-whole-block: true
|
||||||
|
branch-max-lines: 2
|
||||||
|
case-max-lines: 2
|
||||||
|
default: all
|
||||||
|
disable:
|
||||||
|
- if
|
||||||
|
- err
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -1,19 +1,20 @@
|
|||||||
#!/usr/bin/env gmake -f
|
#!/usr/bin/env gmake -f
|
||||||
|
|
||||||
GOOPTS=CGO_ENABLED=0
|
GOOPTS=CGO_ENABLED=0
|
||||||
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
|
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false
|
||||||
|
BINARY=joyproxy
|
||||||
|
|
||||||
all: clean build
|
all: clean build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
${GOOPTS} go build ${BUILDOPTS}
|
${GOOPTS} go build ${BUILDOPTS} -o ${BINARY} ./cmd/${BINARY}
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean
|
$(RM) ${BINARY}
|
||||||
|
|
||||||
upgrade:
|
upgrade:
|
||||||
rm -rf vendor
|
go get -u ./...
|
||||||
go get -d -u -t ./...
|
go mod tidy
|
||||||
go mod vendor
|
go mod vendor
|
||||||
|
|
||||||
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
## Что это?
|
## Что это?
|
||||||
|
|
||||||
Своего рода API, делающий запросы в img#.reactor.cc. Вторая инкарнация, на сей
|
Своего рода API, делающий запросы в img#.reactor.cc. Вторая инкарнация, на сей раз на гошке.
|
||||||
раз на гошке.
|
|
||||||
|
|
||||||
## Зачем это нужно?
|
## Зачем это нужно?
|
||||||
|
|
||||||
Сайт reactor.cc не даёт возможности постить на внешние ресурсы ссылки без
|
Сайт reactor.cc не даёт возможности постить на внешние ресурсы ссылки на видосики без указания правильного referer-а. :)
|
||||||
указания правильного referer-а. :)
|
А поскольку referer - это заголовок, идеальным решением выглядит проксирование запроса.
|
||||||
|
|
||||||
Эта прокся призвана частично решить эту проблему через создание линков вида
|
Эта прокся призвана частично решить эту проблему через создание линков вида
|
||||||
|
|
||||||
@@ -20,25 +19,28 @@
|
|||||||
|
|
||||||
## Как это работает?
|
## Как это работает?
|
||||||
|
|
||||||
При обращении к сгенерённому урлу, апи идёт на целевой сайт, делает запрос,
|
При обращении к сгенерённому урлу, апи идёт на целевой сайт, делает запрос, а ответ стримит клиенту.
|
||||||
а ответ стримит клиенту.
|
|
||||||
|
Из нюансов, запрос просиходит с указанием заголовка Range: 0-, что автоматически не даёт возможности делать
|
||||||
|
range-запросы, то есть, скачивать видосики в несколько потоков). Впрочем, видосики всё ровно маленькие и нет
|
||||||
|
необходимости качать их в много потоков.
|
||||||
|
|
||||||
|
Теоретически, можно поддержать range-запросы, однако, для этого надо поддержать запрос с методом HEAD, для узнавания
|
||||||
|
длины файла, но мне откровенно лень. Для этого надо проводить r&d на предмет нюансов таких запросов.
|
||||||
|
|
||||||
## Как это запустить?
|
## Как это запустить?
|
||||||
|
|
||||||
Предполагается сборка гошкой версии 1.22.x и скорее всего возможна сборка
|
Предполагается сборка гошкой версии 1.24.x и скорее всего возможна сборка более новыми версиями.
|
||||||
более новыми версиями.
|
|
||||||
|
|
||||||
Команда make сгенерирует в текущем каталоге бинарник joyproxy, тут же рядом
|
Команда make сгенерирует в текущем каталоге бинарник joyproxy, тут же рядом есть joyproxy_sample.ini, который следует
|
||||||
есть joyproxy_sample.ini, который следует скопировать в joyproxy.ini и
|
скопировать в joyproxy.ini и исправить для своего случая.
|
||||||
исправить для своего случая.
|
|
||||||
|
|
||||||
После этого сервис можно запускать.
|
После этого сервис можно запускать.
|
||||||
|
|
||||||
Joyproxy2 работает как foreground-приложение и возможен его запуск из-под
|
Joyproxy2 работает как foreground-приложение и возможен его запуск из-под супервизора systemd, daemon-tools, runit,
|
||||||
супервизора systemd, daemon-tools, runit, openrc, supervisord итп, которые
|
openrc, supervisord итп, которые умеют работать с foreground-приложениями как с сервисами. Если init хочет именно
|
||||||
умеют работать с foreground-приложениями как с сервисами. Если init хочет
|
демонизированный сервис, то можно воспользоваться утилитой [daemonize](https://github.com/bmc/daemonize).
|
||||||
именно домонизированный сервис, то можно воспользоваться утилитой [daemonize](https://github.com/bmc/daemonize).
|
|
||||||
|
|
||||||
## Кому сказать спасибо?
|
## Кому сказать спасибо?
|
||||||
|
|
||||||
Авторам, редакторам и тем, кто поддерживает `reactor.cc`
|
Авторам, редакторам и тем, кто поддерживает `reactor.cc`. Пацаны стараются ради нас, спасибо им.
|
||||||
|
|||||||
37
cmd/joyproxy/main.go
Normal file
37
cmd/joyproxy/main.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Package main represents entry point function of application.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"joyproxy/internal/joyproxy"
|
||||||
|
"joyproxy/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// main application entry point.
|
||||||
|
func main() {
|
||||||
|
joyproxy.LoadConf()
|
||||||
|
|
||||||
|
log.Init(joyproxy.Cfg.Loglevel, os.Stderr)
|
||||||
|
|
||||||
|
http.HandleFunc("/joyproxy/", joyproxy.JoyproxyHandler)
|
||||||
|
http.HandleFunc("/joyurl", joyproxy.JoyurlHandler)
|
||||||
|
|
||||||
|
log.Infof("Starting application on port %v\n", joyproxy.Cfg.Port)
|
||||||
|
|
||||||
|
// To prevent slow loris ddos, we have to define custom handler because builtin http.ListenAndServe() have no
|
||||||
|
// timeout and sits here forever.
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: joyproxy.Cfg.Port,
|
||||||
|
ReadHeaderTimeout: time.Duration(joyproxy.Cfg.Timeout) * time.Second,
|
||||||
|
ReadTimeout: time.Duration(joyproxy.Cfg.Timeout) * time.Second,
|
||||||
|
WriteTimeout: time.Duration(joyproxy.Cfg.Timeout) * time.Second,
|
||||||
|
IdleTimeout: time.Duration(joyproxy.Cfg.Timeout) * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warn(server.ListenAndServe().Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
10
go.mod
10
go.mod
@@ -1,11 +1,13 @@
|
|||||||
module joyproxy
|
module joyproxy
|
||||||
|
|
||||||
go 1.22
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/wlevene/ini v0.1.5
|
||||||
gopkg.in/ini.v1 v1.67.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.22.0 // indirect
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
30
go.sum
30
go.sum
@@ -1,24 +1,10 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/wlevene/ini v0.1.5 h1:mEY1ed7UxMA/nygo0eLW2a66+ix8s85ZSRMsg+qbyeU=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/wlevene/ini v0.1.5/go.mod h1:KNjKNkdBYp9vCERTy5VnudV4wEP3lHOIrO5xs7ssxPs=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|||||||
47
internal/joyproxy/globals.go
Normal file
47
internal/joyproxy/globals.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Package joyproxy is main appliction lib.
|
||||||
|
package joyproxy
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Cfg represents struct with application config settings.
|
||||||
|
Cfg Config
|
||||||
|
|
||||||
|
// userAgentString represents client names from real browsers to make joyreactor thing that we're real browser, not a
|
||||||
|
// proxy. We want to play good after all.
|
||||||
|
userAgentString = []string{
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.50",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Edg/97.0.1072.69",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 YaBrowser/22.1.0 Yowser/2.5 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
postForm = "<html>\n<body>\n<form method='get' action='joyurl'>\n<input type='text' name='joyurl' size=100 autofocus><br>\n<input type='submit' value='Post it!' style='font-size:115%%;'>\n<br>%s<br>\n</body>\n</html>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
140
internal/joyproxy/joyproxyHandler.go
Normal file
140
internal/joyproxy/joyproxyHandler.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package joyproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"joyproxy/internal/log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JoyproxyHandler request handler for /joyproxy location.
|
||||||
|
func JoyproxyHandler(w http.ResponseWriter, r *http.Request) { //nolint: revive
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
log.Infof("request method is not GET, but %s", r.Method)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
|
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq := r
|
||||||
|
joyPath := strings.Split(r.RequestURI, "/")
|
||||||
|
// joyPath[0] is empty, joyPath[1] is joyproxy.
|
||||||
|
joyHost := joyPath[2]
|
||||||
|
|
||||||
|
if !regexp.MustCompile("^img[0-9]+[.]reactor[.]cc$").MatchString(joyHost) {
|
||||||
|
log.Infof("supplied proxy target host is not match with ^img[0-9]+[.]reactor[.]cc$ pattern: %s", joyHost)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
|
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
joyPath = joyPath[3:]
|
||||||
|
dlReq.RequestURI = "/" + strings.Join(joyPath, "/")
|
||||||
|
|
||||||
|
if !regexp.MustCompile("^/pics/post/mp4/.+[.]mp4$").MatchString(dlReq.RequestURI) {
|
||||||
|
log.Infof("supplied uri is not match with /pics/post/mp4/.+[.]mp4$ pattern: %s", dlReq.RequestURI)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
|
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Host = joyHost
|
||||||
|
dlReq.URL, err = url.Parse(dlReq.RequestURI)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("unable to parse %s via url.Parse(): %s", dlReq.RequestURI, err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
|
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forge request as it comes from browser… kinda.
|
||||||
|
if dlReq.Header.Get("Referer") != "" {
|
||||||
|
dlReq.Header.Del("Referer")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("Referer", "https://old.reactor.cc/all")
|
||||||
|
|
||||||
|
if dlReq.Header.Get("User-Agent") != "" {
|
||||||
|
dlReq.Header.Del("User-Agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("User-Agent", userAgentString[rand.Intn(len(userAgentString)-1)])
|
||||||
|
|
||||||
|
if dlReq.Header.Get("Accept") != "" {
|
||||||
|
dlReq.Header.Del("Accept")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("Accept", "*/*")
|
||||||
|
|
||||||
|
if dlReq.Header.Get("Accept-Encoding") != "" {
|
||||||
|
dlReq.Header.Del("Accept-Encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("Accept-Encoding", "identity;q=1, *;q=0")
|
||||||
|
|
||||||
|
if dlReq.Header.Get("Accept-Language") != "" {
|
||||||
|
dlReq.Header.Del("Accept-Language")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
|
||||||
|
|
||||||
|
if dlReq.Header.Get("Range") != "" {
|
||||||
|
dlReq.Header.Del("Range")
|
||||||
|
}
|
||||||
|
|
||||||
|
dlReq.Header.Add("Range", "bytes=0-")
|
||||||
|
log.Debug(spew.Sdump(dlReq))
|
||||||
|
|
||||||
|
u := "https://" + joyHost
|
||||||
|
target, err := url.Parse(u)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("unable to parse %s via url.Parse(): %s", u, err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
|
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
joyproxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
|
||||||
|
joyproxy.Transport = &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: time.Duration(Cfg.Timeout) * time.Second,
|
||||||
|
KeepAlive: time.Duration(Cfg.Timeout) * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: time.Duration(Cfg.Timeout) * time.Second,
|
||||||
|
}
|
||||||
|
joyproxy.ServeHTTP(w, dlReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
62
internal/joyproxy/joyurlHandler.go
Normal file
62
internal/joyproxy/joyurlHandler.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package joyproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"joyproxy/internal/log"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JoyurlHandler request handler function for /joyurl location.
|
||||||
|
func JoyurlHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
param = r.URL.Query()
|
||||||
|
proxyURLString = " "
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Debug(spew.Sdump(param))
|
||||||
|
|
||||||
|
re := regexp.MustCompile("^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$")
|
||||||
|
|
||||||
|
if len(param) > 0 && re.MatchString(param["joyurl"][0]) {
|
||||||
|
log.Debug("match with ^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$ pattern")
|
||||||
|
// https://img1.reactor.cc/pics/post/webm/видосик.webm
|
||||||
|
|
||||||
|
p := regexp.MustCompile("/").Split(param["joyurl"][0], -1)
|
||||||
|
log.Debug(spew.Sdump(p))
|
||||||
|
|
||||||
|
if len(p) > 6 && p[5] == "webm" {
|
||||||
|
log.Debug("webm url part detected, let's make joyproxy url")
|
||||||
|
|
||||||
|
file := p[6][:len(p[6])-5]
|
||||||
|
|
||||||
|
proxyURLString = fmt.Sprintf(
|
||||||
|
"%s://%s/joyproxy/%s/%s/%s/mp4/%s.mp4",
|
||||||
|
Cfg.Proto,
|
||||||
|
r.Host,
|
||||||
|
p[2],
|
||||||
|
p[3],
|
||||||
|
p[4],
|
||||||
|
file,
|
||||||
|
)
|
||||||
|
|
||||||
|
proxyURLString = html.EscapeString(proxyURLString)
|
||||||
|
} else {
|
||||||
|
log.Debug("webm url part not detected, skip making joyproxy url")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("not match with ^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$ regex")
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlText := fmt.Sprintf(postForm, proxyURLString)
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintln(w, htmlText); err != nil {
|
||||||
|
log.Infof("Unable to write response to client: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
57
internal/joyproxy/loadConf.go
Normal file
57
internal/joyproxy/loadConf.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package joyproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"joyproxy/internal/log"
|
||||||
|
|
||||||
|
"github.com/wlevene/ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadConf reads config data from config file and parses it to Cfg variable.
|
||||||
|
func LoadConf() {
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to get current executable path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configINIPath := filepath.Dir(executablePath) + "/joyproxy.ini"
|
||||||
|
configFH := ini.New().LoadFile(configINIPath)
|
||||||
|
|
||||||
|
Cfg.Port = configFH.Section("settings").Get("port")
|
||||||
|
|
||||||
|
if Cfg.Port == "" {
|
||||||
|
log.Fatalf("unable to get port setting from settings section of %s", configINIPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cfg.Port = ":" + Cfg.Port
|
||||||
|
|
||||||
|
Cfg.Proto = configFH.Section("settings").Get("proto")
|
||||||
|
|
||||||
|
if Cfg.Proto == "" {
|
||||||
|
log.Fatalf("unable to get proto setting from settings section of %s", configINIPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Cfg.Proto != "http" && Cfg.Proto != "https" {
|
||||||
|
log.Fatalf("proto setting from settings section of %s must be either http or https, but we got %s",
|
||||||
|
configINIPath,
|
||||||
|
Cfg.Proto,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cfg.Timeout = configFH.Section("settings").GetInt("timeout")
|
||||||
|
|
||||||
|
if Cfg.Timeout == 0 {
|
||||||
|
log.Fatalf("unable to get timeout value from settings section of %s", configINIPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cfg.Loglevel = configFH.Section("settings").Get("loglevel")
|
||||||
|
|
||||||
|
if Cfg.Loglevel == "" {
|
||||||
|
log.Fatalf("unable to get loglevel setting from settings section of %s", configINIPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
13
internal/joyproxy/types.go
Normal file
13
internal/joyproxy/types.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package joyproxy
|
||||||
|
|
||||||
|
// Config represents application config data structure.
|
||||||
|
type (
|
||||||
|
Config struct {
|
||||||
|
Port string
|
||||||
|
Proto string
|
||||||
|
Loglevel string
|
||||||
|
Timeout int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
179
internal/log/log.go
Normal file
179
internal/log/log.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// Package log wraps log/slog for more easier and convinient use.
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Handler exposes slog handler struct.
|
||||||
|
Handler slog.Handler
|
||||||
|
|
||||||
|
// Ctx log context. For now it is just sits here and do nothing.
|
||||||
|
Ctx = context.Background()
|
||||||
|
|
||||||
|
// Writer go-writer eported for capturing other types of logs, even for std log go facility.
|
||||||
|
Writer io.Writer
|
||||||
|
|
||||||
|
// reader is required for Writer to work properly. It is connected via io.Pipe() to Writer.
|
||||||
|
reader io.Reader
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init setup logger stuff.
|
||||||
|
// level can be error, warn, info, debug if something other supplied info level selected.
|
||||||
|
// fileDescriptor should be opened before supplying it to Init().
|
||||||
|
func Init(level string, fileDescriptor *os.File) {
|
||||||
|
var loglevel slog.Level
|
||||||
|
|
||||||
|
// no panic, no trace.
|
||||||
|
switch level {
|
||||||
|
case "debug":
|
||||||
|
loglevel = slog.LevelDebug
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
|
||||||
|
case "warn":
|
||||||
|
loglevel = slog.LevelWarn
|
||||||
|
|
||||||
|
case "error":
|
||||||
|
loglevel = slog.LevelError
|
||||||
|
|
||||||
|
default:
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &slog.HandlerOptions{ //nolint: exhaustruct
|
||||||
|
// Use the ReplaceAttr function on the handler options
|
||||||
|
// to be able to replace any single attribute in the log output.
|
||||||
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive
|
||||||
|
// Check that we are handling the time key.
|
||||||
|
if a.Key != slog.TimeKey {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
t := a.Value.Time()
|
||||||
|
|
||||||
|
// change the value from a time.Time to a String
|
||||||
|
// where the string has the correct time format.
|
||||||
|
a.Value = slog.StringValue(t.Format(time.DateTime))
|
||||||
|
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
|
||||||
|
Level: loglevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler = slog.NewTextHandler(fileDescriptor, opts)
|
||||||
|
|
||||||
|
slog.SetDefault(
|
||||||
|
slog.New(Handler),
|
||||||
|
)
|
||||||
|
|
||||||
|
reader, Writer = io.Pipe()
|
||||||
|
|
||||||
|
go readIoPipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// readIoPipe reads pipe and writes it to slog.
|
||||||
|
func readIoPipe() {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
for {
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text() // Get the current line as a string.
|
||||||
|
slog.Debug(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
// io.EOF is expected when done reading, e.g. on exit.
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// something weird happen, we should it log to guess later what it was.
|
||||||
|
Errorf("Error during scanning log writer pipe: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns current loglevel.
|
||||||
|
func GetLevel() string {
|
||||||
|
switch {
|
||||||
|
case Handler.Enabled(Ctx, slog.LevelDebug):
|
||||||
|
return "debug"
|
||||||
|
|
||||||
|
case Handler.Enabled(Ctx, slog.LevelInfo):
|
||||||
|
return "info"
|
||||||
|
|
||||||
|
case Handler.Enabled(Ctx, slog.LevelWarn):
|
||||||
|
return "warn"
|
||||||
|
|
||||||
|
case Handler.Enabled(Ctx, slog.LevelError):
|
||||||
|
return "error"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal passes message directly to slog.Error() and exits with error code 1.
|
||||||
|
func Fatal(message string) {
|
||||||
|
slog.Error(message)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs message on error log level and exits with error code 1. It allows to supply arguments in printf() style .
|
||||||
|
func Fatalf(format string, a ...any) {
|
||||||
|
slog.Error(fmt.Sprintf(format, a...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error passes message directly to slog.Error().
|
||||||
|
func Error(message string) {
|
||||||
|
slog.Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs message on error log level and allows to supply arguments in printf() style.
|
||||||
|
func Errorf(format string, a ...any) {
|
||||||
|
slog.Error(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn passes message directly to slog.Warn().
|
||||||
|
func Warn(message string) {
|
||||||
|
slog.Warn(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs message on warn log level and allows to supply arguments in printf() style.
|
||||||
|
func Warnf(format string, a ...any) {
|
||||||
|
slog.Warn(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info passes message directly to slog.Info().
|
||||||
|
func Info(message string) {
|
||||||
|
slog.Info(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs message on info log level and allows to supply arguments in printf() style.
|
||||||
|
func Infof(format string, a ...any) {
|
||||||
|
slog.Info(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug passes message directly to slog.Debug().
|
||||||
|
func Debug(message string) {
|
||||||
|
slog.Debug(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs message on debug log level and allows to supply arguments in printf() style.
|
||||||
|
func Debugf(format string, a ...any) {
|
||||||
|
slog.Debug(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
327
main.go
327
main.go
@@ -1,327 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
port string
|
|
||||||
proto string
|
|
||||||
timeout int
|
|
||||||
loglevel string
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg config
|
|
||||||
|
|
||||||
// Pretend, we're… isp nat or proxy6 so we have different user agent.
|
|
||||||
var userAgentString = []string{
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.50",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Edg/97.0.1072.69",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 YaBrowser/22.1.0 Yowser/2.5 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
|
||||||
"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
|
||||||
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:91.0) Gecko/20100101 Firefox/91.0",
|
|
||||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
|
||||||
}
|
|
||||||
|
|
||||||
var postForm = "<html>\n<body>\n<form method='get' action='joyurl'>\n<input type='text' name='joyurl' size=100 autofocus><br>\n<input type='submit' value='Post it!' style='font-size:115%%;'>\n<br>%s<br>\n</body>\n</html>\n"
|
|
||||||
|
|
||||||
func loadConf() {
|
|
||||||
executablePath, err := os.Executable()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Unable to get current executable path: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
configINIPath := fmt.Sprintf("%s/joyproxy.ini", filepath.Dir(executablePath))
|
|
||||||
configFH, err := ini.Load(configINIPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Unable to read config file: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.port = configFH.Section("settings").Key("port").String()
|
|
||||||
|
|
||||||
if cfg.port == "0" {
|
|
||||||
log.Errorf("unable to get port setting from settings section of %s", configINIPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.port = fmt.Sprintf(":%s", cfg.port)
|
|
||||||
|
|
||||||
cfg.proto = configFH.Section("settings").Key("proto").String()
|
|
||||||
|
|
||||||
if cfg.proto == "0" {
|
|
||||||
log.Errorf("unable to get proto setting from settings section of %s", configINIPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.proto != "http" && cfg.proto != "https" {
|
|
||||||
log.Errorf("proto setting from settings section of %s must be either http or https, but we got %s", configINIPath, cfg.proto)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.timeout, err = configFH.Section("settings").Key("timeout").Int()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("unable to get timeout value from settings section: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.loglevel = configFH.Section("settings").Key("loglevel").String()
|
|
||||||
|
|
||||||
if cfg.loglevel == "0" {
|
|
||||||
log.Errorf("unable to get loglevel setting from settings section of %s", configINIPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func joyproxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
log.Infof("request method is not GET, but %s", r.Method)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq := r
|
|
||||||
joyPath := strings.Split(r.RequestURI, "/")
|
|
||||||
// joyPath[0] is empty, joyPath[1] is joyproxy
|
|
||||||
joyHost := joyPath[2]
|
|
||||||
|
|
||||||
if !regexp.MustCompile("^img[0-9]+[.]reactor[.]cc$").MatchString(joyHost) {
|
|
||||||
log.Infof("supplied proxy target host is not match with ^img[0-9]+[.]reactor[.]cc$ pattern: %s", joyHost)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
joyPath = joyPath[3:]
|
|
||||||
dlReq.RequestURI = "/" + strings.Join(joyPath, "/")
|
|
||||||
|
|
||||||
if !regexp.MustCompile("^/pics/post/mp4/.+[.]mp4$").MatchString(dlReq.RequestURI) {
|
|
||||||
log.Infof("supplied uri is not match with /pics/post/mp4/.+[.]mp4$ pattern: %s", dlReq.RequestURI)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Host = joyHost
|
|
||||||
dlReq.URL, err = url.Parse(dlReq.RequestURI)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("unable to parse %s via url.Parse(): %s", dlReq.RequestURI, err)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forge request as it comes from browser… kinda
|
|
||||||
if dlReq.Header.Get("Referer") != "" {
|
|
||||||
dlReq.Header.Del("Referer")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("Referer", "https://old.reactor.cc/all")
|
|
||||||
|
|
||||||
if dlReq.Header.Get("User-Agent") != "" {
|
|
||||||
dlReq.Header.Del("User-Agent")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("User-Agent", userAgentString[rand.Intn(len(userAgentString)-1)])
|
|
||||||
|
|
||||||
if dlReq.Header.Get("Accept") != "" {
|
|
||||||
dlReq.Header.Del("Accept")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("Accept", "*/*")
|
|
||||||
|
|
||||||
if dlReq.Header.Get("Accept-Encoding") != "" {
|
|
||||||
dlReq.Header.Del("Accept-Encoding")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("Accept-Encoding", "identity;q=1, *;q=0")
|
|
||||||
|
|
||||||
if dlReq.Header.Get("Accept-Language") != "" {
|
|
||||||
dlReq.Header.Del("Accept-Language")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
|
|
||||||
|
|
||||||
if dlReq.Header.Get("Range") != "" {
|
|
||||||
dlReq.Header.Del("Range")
|
|
||||||
}
|
|
||||||
|
|
||||||
dlReq.Header.Add("Range", "bytes=0-")
|
|
||||||
log.Debugf(spew.Sdump(dlReq))
|
|
||||||
|
|
||||||
u := fmt.Sprintf("https://%s", joyHost)
|
|
||||||
target, err := url.Parse(u)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("unable to parse %s via url.Parse(): %s", u, err)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
if _, err = w.Write([]byte("404 Not Found")); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
joyproxy := httputil.NewSingleHostReverseProxy(target)
|
|
||||||
joyproxy.Transport = &http.Transport{
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
KeepAlive: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
}
|
|
||||||
joyproxy.ServeHTTP(w, dlReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func joyurlHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var (
|
|
||||||
param = r.URL.Query()
|
|
||||||
proxyURLString = " "
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Debugln(spew.Sdump(param))
|
|
||||||
|
|
||||||
re := regexp.MustCompile("^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$")
|
|
||||||
|
|
||||||
if len(param) > 0 && re.MatchString(param["joyurl"][0]) {
|
|
||||||
log.Debug("match with ^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$ pattern")
|
|
||||||
// https://img1.reactor.cc/pics/post/webm/видосик.webm
|
|
||||||
|
|
||||||
p := regexp.MustCompile("/").Split(param["joyurl"][0], -1)
|
|
||||||
log.Debug(spew.Sdump(p))
|
|
||||||
|
|
||||||
if len(p) > 6 && p[5] == "webm" {
|
|
||||||
log.Debug("webm url part detected, let's make joyproxy url")
|
|
||||||
|
|
||||||
file := p[6][:len(p[6])-5]
|
|
||||||
|
|
||||||
proxyURLString = fmt.Sprintf(
|
|
||||||
"%s://%s/joyproxy/%s/%s/%s/mp4/%s.mp4",
|
|
||||||
cfg.proto,
|
|
||||||
r.Host,
|
|
||||||
p[2],
|
|
||||||
p[3],
|
|
||||||
p[4],
|
|
||||||
file,
|
|
||||||
)
|
|
||||||
|
|
||||||
proxyURLString = html.EscapeString(proxyURLString)
|
|
||||||
} else {
|
|
||||||
log.Debug("webm url part not detected, skip making joyproxy url")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Info("not match with ^https?://img[0-9]+[.]reactor[.]cc/.+[.][Ww][Ee][Bb][Mm]$ regex")
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlText := fmt.Sprintf(postForm, proxyURLString)
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintln(w, htmlText); err != nil {
|
|
||||||
log.Infof("Unable to write response to client: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// setup logging
|
|
||||||
log.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableQuote: true,
|
|
||||||
DisableLevelTruncation: false,
|
|
||||||
DisableColors: true,
|
|
||||||
FullTimestamp: true,
|
|
||||||
TimestampFormat: "2006-01-02 15:04:05",
|
|
||||||
})
|
|
||||||
|
|
||||||
loadConf()
|
|
||||||
// info or debug, default info
|
|
||||||
switch cfg.loglevel {
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
default:
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleFunc("/joyproxy/", joyproxyHandler)
|
|
||||||
http.HandleFunc("/joyurl", joyurlHandler)
|
|
||||||
|
|
||||||
log.Infof("Starting application on port %v\n", cfg.port)
|
|
||||||
|
|
||||||
// To prevent slow loris ddos, we have to define custom handler because builtin http.ListenAndServe() have no
|
|
||||||
// timeout and sits here forever.
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: cfg.port,
|
|
||||||
ReadHeaderTimeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
ReadTimeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
WriteTimeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
IdleTimeout: time.Duration(cfg.timeout) * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warn(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
|
||||||
14
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
Normal file
14
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
freebsd_task:
|
||||||
|
name: 'FreeBSD'
|
||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-14-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 ./...
|
||||||
|
- FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...
|
||||||
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# go test -c output
|
||||||
|
*.test
|
||||||
|
*.test.exe
|
||||||
|
|
||||||
|
# Output of go build ./cmd/fsnotify
|
||||||
|
/fsnotify
|
||||||
|
/fsnotify.exe
|
||||||
|
|
||||||
|
/test/kqueue
|
||||||
|
/test/a.out
|
||||||
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||||
|
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
||||||
602
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
602
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
1.9.0 2024-04-04
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- all: make BufferedWatcher buffered again ([#657])
|
||||||
|
|
||||||
|
- inotify: fix race when adding/removing watches while a watched path is being
|
||||||
|
deleted ([#678], [#686])
|
||||||
|
|
||||||
|
- inotify: don't send empty event if a watched path is unmounted ([#655])
|
||||||
|
|
||||||
|
- inotify: don't register duplicate watches when watching both a symlink and its
|
||||||
|
target; previously that would get "half-added" and removing the second would
|
||||||
|
panic ([#679])
|
||||||
|
|
||||||
|
- kqueue: fix watching relative symlinks ([#681])
|
||||||
|
|
||||||
|
- kqueue: correctly mark pre-existing entries when watching a link to a dir on
|
||||||
|
kqueue ([#682])
|
||||||
|
|
||||||
|
- illumos: don't send error if changed file is deleted while processing the
|
||||||
|
event ([#678])
|
||||||
|
|
||||||
|
|
||||||
|
[#657]: https://github.com/fsnotify/fsnotify/pull/657
|
||||||
|
[#678]: https://github.com/fsnotify/fsnotify/pull/678
|
||||||
|
[#686]: https://github.com/fsnotify/fsnotify/pull/686
|
||||||
|
[#655]: https://github.com/fsnotify/fsnotify/pull/655
|
||||||
|
[#681]: https://github.com/fsnotify/fsnotify/pull/681
|
||||||
|
[#679]: https://github.com/fsnotify/fsnotify/pull/679
|
||||||
|
[#682]: https://github.com/fsnotify/fsnotify/pull/682
|
||||||
|
|
||||||
|
1.8.0 2024-10-31
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619])
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610])
|
||||||
|
|
||||||
|
- kqueue: ignore events with Ident=0 ([#590])
|
||||||
|
|
||||||
|
- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617])
|
||||||
|
|
||||||
|
- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625])
|
||||||
|
|
||||||
|
- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620])
|
||||||
|
|
||||||
|
- inotify: fix panic when calling Remove() in a goroutine ([#650])
|
||||||
|
|
||||||
|
- fen: allow watching subdirectories of watched directories ([#621])
|
||||||
|
|
||||||
|
[#590]: https://github.com/fsnotify/fsnotify/pull/590
|
||||||
|
[#610]: https://github.com/fsnotify/fsnotify/pull/610
|
||||||
|
[#617]: https://github.com/fsnotify/fsnotify/pull/617
|
||||||
|
[#619]: https://github.com/fsnotify/fsnotify/pull/619
|
||||||
|
[#620]: https://github.com/fsnotify/fsnotify/pull/620
|
||||||
|
[#621]: https://github.com/fsnotify/fsnotify/pull/621
|
||||||
|
[#625]: https://github.com/fsnotify/fsnotify/pull/625
|
||||||
|
[#650]: https://github.com/fsnotify/fsnotify/pull/650
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||||
|
|
||||||
|
This makes checking events a lot easier; for example:
|
||||||
|
|
||||||
|
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
if event.Has(Write) && !event.Has(Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
- all: add cmd/fsnotify ([#463])
|
||||||
|
|
||||||
|
A command-line utility for testing and some examples.
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||||
|
|
||||||
|
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||||
|
still exists before emitting events.
|
||||||
|
|
||||||
|
This was inconsistent with other platforms and resulted in inconsistent event
|
||||||
|
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||||
|
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||||
|
longer exists.
|
||||||
|
|
||||||
|
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||||
|
not watched ([#460])
|
||||||
|
|
||||||
|
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||||
|
|
||||||
|
Non-blocking inotify was not generally available at the time this library was
|
||||||
|
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||||
|
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||||
|
|
||||||
|
- kqueue: don't check for events every 100ms ([#480])
|
||||||
|
|
||||||
|
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||||
|
it waits until there is something to do.
|
||||||
|
|
||||||
|
- macos: retry opening files on EINTR ([#475])
|
||||||
|
|
||||||
|
- kqueue: skip unreadable files ([#479])
|
||||||
|
|
||||||
|
kqueue requires a file descriptor for every file in a directory; this would
|
||||||
|
fail if a file was unreadable by the current user. Now these files are simply
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||||
|
|
||||||
|
- windows: increase buffer size from 4K to 64K ([#485])
|
||||||
|
|
||||||
|
- windows: close file handle on Remove() ([#288])
|
||||||
|
|
||||||
|
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||||
|
|
||||||
|
- inotify, windows: calling Close() more than once could race ([#465])
|
||||||
|
|
||||||
|
- kqueue: improve Close() performance ([#233])
|
||||||
|
|
||||||
|
- all: various documentation additions and clarifications.
|
||||||
|
|
||||||
|
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||||
|
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||||
|
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||||
|
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||||
|
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||||
|
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||||
|
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||||
|
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||||
|
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||||
|
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||||
|
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||||
|
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||||
|
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||||
|
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||||
|
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-04-25
|
||||||
|
|
||||||
|
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||||
|
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||||
|
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-04-22
|
||||||
|
|
||||||
|
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-04-21
|
||||||
|
|
||||||
|
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||||
|
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||||
|
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||||
|
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||||
|
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-08-24
|
||||||
|
|
||||||
|
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-08-20
|
||||||
|
|
||||||
|
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||||
|
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||||
|
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||||
|
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||||
|
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||||
|
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||||
|
|
||||||
|
## [1.4.9] - 2020-03-11
|
||||||
|
|
||||||
|
* Move example usage to the readme #329. This may resolve #328.
|
||||||
|
|
||||||
|
## [1.4.8] - 2020-03-10
|
||||||
|
|
||||||
|
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||||
|
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||||
|
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||||
|
* CI: Less verbosity (@nathany #267)
|
||||||
|
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||||
|
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||||
|
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||||
|
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||||
|
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||||
|
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||||
|
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||||
|
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||||
|
* Project: Add go.mod (@nathany #309)
|
||||||
|
* Project: Revise editor config (@nathany #309)
|
||||||
|
* Project: Update copyright for 2019 (@nathany #309)
|
||||||
|
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||||
|
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||||
|
|
||||||
|
## [1.4.7] - 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## [1.4.2] - 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.1] - 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.0] - 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## [1.3.1] - 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## [1.3.0] - 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## [1.2.10] - 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## [1.2.9] - 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## [1.2.8] - 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## [1.2.5] - 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## [1.2.1] - 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## [1.2.0] - 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## [1.1.1] - 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## [1.1.0] - 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [1.0.4] - 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## [1.0.3] - 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## [1.0.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## [1.0.0] - 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## [0.9.3] - 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [0.9.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## [0.9.1] - 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## [0.9.0] - 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## [0.8.12] - 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## [0.8.11] - 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## [0.8.10] - 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## [0.8.9] - 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## [0.8.8] - 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## [0.8.7] - 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## [0.8.6] - 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## [0.8.5] - 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## [0.8.4] - 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## [0.8.3] - 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## [0.8.2] - 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## [0.8.1] - 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## [0.8.0] - 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## [0.7.4] - 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## [0.7.3] - 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## [0.7.2] - 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## [0.7.1] - 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## [0.7.0] - 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## [0.6.0] - 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## [0.5.1] - 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## [0.5.0] - 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## [0.4.0] - 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## [0.3.0] - 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## [0.2.0] - 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## [0.1.0] - 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
||||||
145
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
145
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||||
|
merge PRs in a reasonable timeframe, but please be aware that:
|
||||||
|
|
||||||
|
- To avoid "wasted" work, please discuss changes on the issue tracker first. You
|
||||||
|
can just send PRs, but they may end up being rejected for one reason or the
|
||||||
|
other.
|
||||||
|
|
||||||
|
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||||
|
all supported platforms.
|
||||||
|
|
||||||
|
- Changes will need to be compatible; old code should still compile, and the
|
||||||
|
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||||
|
users.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||||
|
platforms. Testing different platforms locally can be done with something like
|
||||||
|
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||||
|
|
||||||
|
Use the `-short` flag to make the "stress test" run faster.
|
||||||
|
|
||||||
|
Writing new tests
|
||||||
|
-----------------
|
||||||
|
Scripts in the testdata directory allow creating test cases in a "shell-like"
|
||||||
|
syntax. The basic format is:
|
||||||
|
|
||||||
|
script
|
||||||
|
|
||||||
|
Output:
|
||||||
|
desired output
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
# Create a new empty file with some data.
|
||||||
|
watch /
|
||||||
|
echo data >/file
|
||||||
|
|
||||||
|
Output:
|
||||||
|
create /file
|
||||||
|
write /file
|
||||||
|
|
||||||
|
Just create a new file to add a new test; select which tests to run with
|
||||||
|
`-run TestScript/[path]`.
|
||||||
|
|
||||||
|
script
|
||||||
|
------
|
||||||
|
The script is a "shell-like" script:
|
||||||
|
|
||||||
|
cmd arg arg
|
||||||
|
|
||||||
|
Comments are supported with `#`:
|
||||||
|
|
||||||
|
# Comment
|
||||||
|
cmd arg arg # Comment
|
||||||
|
|
||||||
|
All operations are done in a temp directory; a path like "/foo" is rewritten to
|
||||||
|
"/tmp/TestFoo/foo".
|
||||||
|
|
||||||
|
Arguments can be quoted with `"` or `'`; there are no escapes and they're
|
||||||
|
functionally identical right now, but this may change in the future, so best to
|
||||||
|
assume shell-like rules.
|
||||||
|
|
||||||
|
touch "/file with spaces"
|
||||||
|
|
||||||
|
End-of-line escapes with `\` are not supported.
|
||||||
|
|
||||||
|
### Supported commands
|
||||||
|
|
||||||
|
watch path [ops] # Watch the path, reporting events for it. Nothing is
|
||||||
|
# watched by default. Optionally a list of ops can be
|
||||||
|
# given, as with AddWith(path, WithOps(...)).
|
||||||
|
unwatch path # Stop watching the path.
|
||||||
|
watchlist n # Assert watchlist length.
|
||||||
|
|
||||||
|
stop # Stop running the script; for debugging.
|
||||||
|
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
|
||||||
|
parallel by default, so -parallel=1 is probably a good
|
||||||
|
idea).
|
||||||
|
print [any strings] # Print text to stdout; for debugging.
|
||||||
|
|
||||||
|
touch path
|
||||||
|
mkdir [-p] dir
|
||||||
|
ln -s target link # Only ln -s supported.
|
||||||
|
mkfifo path
|
||||||
|
mknod dev path
|
||||||
|
mv src dst
|
||||||
|
rm [-r] path
|
||||||
|
chmod mode path # Octal only
|
||||||
|
sleep time-in-ms
|
||||||
|
|
||||||
|
cat path # Read path (does nothing with the data; just reads it).
|
||||||
|
echo str >>path # Append "str" to "path".
|
||||||
|
echo str >path # Truncate "path" and write "str".
|
||||||
|
|
||||||
|
require reason # Skip the test if "reason" is true; "skip" and
|
||||||
|
skip reason # "require" behave identical; it supports both for
|
||||||
|
# readability. Possible reasons are:
|
||||||
|
#
|
||||||
|
# always Always skip this test.
|
||||||
|
# symlink Symlinks are supported (requires admin
|
||||||
|
# permissions on Windows).
|
||||||
|
# mkfifo Platform doesn't support FIFO named sockets.
|
||||||
|
# mknod Platform doesn't support device nodes.
|
||||||
|
|
||||||
|
|
||||||
|
output
|
||||||
|
------
|
||||||
|
After `Output:` the desired output is given; this is indented by convention, but
|
||||||
|
that's not required.
|
||||||
|
|
||||||
|
The format of that is:
|
||||||
|
|
||||||
|
# Comment
|
||||||
|
event path # Comment
|
||||||
|
|
||||||
|
system:
|
||||||
|
event path
|
||||||
|
system2:
|
||||||
|
event path
|
||||||
|
|
||||||
|
Every event is one line, and any whitespace between the event and path are
|
||||||
|
ignored. The path can optionally be surrounded in ". Anything after a "#" is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
Platform-specific tests can be added after GOOS; for example:
|
||||||
|
|
||||||
|
watch /
|
||||||
|
touch /file
|
||||||
|
|
||||||
|
Output:
|
||||||
|
# Tested if nothing else matches
|
||||||
|
create /file
|
||||||
|
|
||||||
|
# Windows-specific test.
|
||||||
|
windows:
|
||||||
|
write /file
|
||||||
|
|
||||||
|
You can specify multiple platforms with a comma (e.g. "windows, linux:").
|
||||||
|
"kqueue" is a shortcut for all kqueue systems (BSD, macOS).
|
||||||
|
|
||||||
|
|
||||||
|
[goon]: https://github.com/arp242/goon
|
||||||
|
[Vagrant]: https://www.vagrantup.com/
|
||||||
|
[integration_test.go]: /integration_test.go
|
||||||
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright © 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright © fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
182
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
182
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||||
|
Windows, Linux, macOS, BSD, and illumos.
|
||||||
|
|
||||||
|
Go 1.17 or newer is required; the full documentation is at
|
||||||
|
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Platform support:
|
||||||
|
|
||||||
|
| 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) |
|
||||||
|
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
||||||
|
| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
|
||||||
|
| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||||
|
|
||||||
|
Linux and illumos should include Android and Solaris, but these are currently
|
||||||
|
untested.
|
||||||
|
|
||||||
|
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
||||||
|
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
A basic example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create new watcher.
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
// Start listening for events.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Has(fsnotify.Write) {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Add a path.
|
||||||
|
err = watcher.Add("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block main goroutine forever.
|
||||||
|
<-make(chan struct{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||||
|
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?
|
||||||
|
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?
|
||||||
|
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
|
||||||
|
protocols does not provide network level support for file notifications, and
|
||||||
|
neither do the /proc and /sys virtual filesystems.
|
||||||
|
|
||||||
|
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
|
||||||
|
When a file is removed a REMOVE event won't be emitted until all file
|
||||||
|
descriptors are closed; it will emit a CHMOD instead:
|
||||||
|
|
||||||
|
fp := os.Open("file")
|
||||||
|
os.Remove("file") // CHMOD
|
||||||
|
fp.Close() // 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 proc file:
|
||||||
|
|
||||||
|
# The 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 (macOS, all BSD systems)
|
||||||
|
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.
|
||||||
467
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
467
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
//go:build solaris
|
||||||
|
|
||||||
|
// FEN backend for illumos (supported) and Solaris (untested, but should work).
|
||||||
|
//
|
||||||
|
// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fen struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
port *unix.EventPort
|
||||||
|
dirs map[string]Op // Explicitly watched directories
|
||||||
|
watches map[string]Op // Explicitly watched non-directories
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
w := &fen{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
dirs: make(map[string]Op),
|
||||||
|
watches: make(map[string]Op),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.port.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *fen) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] = with.op
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.associateFile(name, stat, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = with.op
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !w.port.PathIsWatched(name) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), 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 *fen) 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(fmt.Errorf("port.Get: %w", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(pevent.Path, pevent.Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.handleEvent(&pevent)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) 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 *fen) 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(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
reRegister = false
|
||||||
|
}
|
||||||
|
if events&unix.FILE_RENAME_FROM != 0 {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: 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(Event{Name: path, Op: 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(Event{Name: path, Op: 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(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't return the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if events&unix.FILE_MODIFIED != 0 {
|
||||||
|
if fmode.IsDir() && watchedDir {
|
||||||
|
if err := w.updateDirectory(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: 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(Event{Name: path, Op: 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
|
||||||
|
err := w.associateFile(path, stat, isWatched)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// Path may have been removed since the stat.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (w *fen) updateDirectory(path string) error {
|
||||||
|
files, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
// Directory no longer exists: probably just deleted since we got the
|
||||||
|
// event.
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return 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 errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// File may have disappeared between getting the dir listing and
|
||||||
|
// adding the port: that's okay to ignore.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) 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 && !errors.Is(err, unix.ENOENT) {
|
||||||
|
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var events int
|
||||||
|
if !follow {
|
||||||
|
// Watch symlinks themselves rather than their targets unless this entry
|
||||||
|
// is explicitly watched.
|
||||||
|
events |= unix.FILE_NOFOLLOW
|
||||||
|
}
|
||||||
|
if true { // TODO: implement withOps()
|
||||||
|
events |= unix.FILE_MODIFIED
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
events |= unix.FILE_ATTRIB
|
||||||
|
}
|
||||||
|
err := w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port.AssociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
|
||||||
|
if !w.port.PathIsWatched(path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := w.port.DissociatePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) xSupports(op Op) bool {
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
583
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
583
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
//go:build linux && !appengine
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inotify struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
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
|
||||||
|
inotifyFile *os.File
|
||||||
|
watches *watches
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
|
||||||
|
// Store rename cookies in an array, with the index wrapping to 0. Almost
|
||||||
|
// all of the time what we get is a MOVED_FROM to set the cookie and the
|
||||||
|
// next event inotify sends will be MOVED_TO to read it. However, this is
|
||||||
|
// not guaranteed – as described in inotify(7) – and we may get other events
|
||||||
|
// between the two MOVED_* events (including other MOVED_* ones).
|
||||||
|
//
|
||||||
|
// A second issue is that moving a file outside the watched directory will
|
||||||
|
// trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to
|
||||||
|
// read and delete it. So just storing it in a map would slowly leak memory.
|
||||||
|
//
|
||||||
|
// Doing it like this gives us a simple fast LRU-cache that won't allocate.
|
||||||
|
// Ten items should be more than enough for our purpose, and a loop over
|
||||||
|
// such a short array is faster than a map access anyway (not that it hugely
|
||||||
|
// matters since we're talking about hundreds of ns at the most, but still).
|
||||||
|
cookies [10]koekje
|
||||||
|
cookieIndex uint8
|
||||||
|
cookiesMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
watches struct {
|
||||||
|
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.
|
||||||
|
recurse bool // Recursion with ./...?
|
||||||
|
}
|
||||||
|
koekje struct {
|
||||||
|
cookie uint32
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatches() *watches {
|
||||||
|
return &watches{
|
||||||
|
wd: make(map[uint32]*watch),
|
||||||
|
path: make(map[string]uint32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] }
|
||||||
|
func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] }
|
||||||
|
func (w *watches) len() int { return len(w.wd) }
|
||||||
|
func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
|
||||||
|
func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) }
|
||||||
|
|
||||||
|
func (w *watches) removePath(path string) ([]uint32, error) {
|
||||||
|
path, recurse := recursivePath(path)
|
||||||
|
wd, ok := w.path[path]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch := w.wd[wd]
|
||||||
|
if recurse && !watch.recurse {
|
||||||
|
return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.path, path)
|
||||||
|
delete(w.wd, wd)
|
||||||
|
if !watch.recurse {
|
||||||
|
return []uint32{wd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
wds := make([]uint32, 0, 8)
|
||||||
|
wds = append(wds, wd)
|
||||||
|
for p, rwd := range w.path {
|
||||||
|
if strings.HasPrefix(p, path) {
|
||||||
|
delete(w.path, p)
|
||||||
|
delete(w.wd, rwd)
|
||||||
|
wds = append(wds, rwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &inotify{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
fd: fd,
|
||||||
|
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||||
|
watches: newWatches(),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Causes any blocking reads to return with an error, provided the file
|
||||||
|
// still supports deadline operations.
|
||||||
|
err := w.inotifyFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
<-w.doneResp // Wait for readEvents() to finish.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
add := func(path string, with withOpts, recurse bool) error {
|
||||||
|
var flags uint32
|
||||||
|
if with.noFollow {
|
||||||
|
flags |= unix.IN_DONT_FOLLOW
|
||||||
|
}
|
||||||
|
if with.op.Has(Create) {
|
||||||
|
flags |= unix.IN_CREATE
|
||||||
|
}
|
||||||
|
if with.op.Has(Write) {
|
||||||
|
flags |= unix.IN_MODIFY
|
||||||
|
}
|
||||||
|
if with.op.Has(Remove) {
|
||||||
|
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
}
|
||||||
|
if with.op.Has(Rename) {
|
||||||
|
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||||
|
}
|
||||||
|
if with.op.Has(Chmod) {
|
||||||
|
flags |= unix.IN_ATTRIB
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableOpen) {
|
||||||
|
flags |= unix.IN_OPEN
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableRead) {
|
||||||
|
flags |= unix.IN_ACCESS
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableCloseWrite) {
|
||||||
|
flags |= unix.IN_CLOSE_WRITE
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableCloseRead) {
|
||||||
|
flags |= unix.IN_CLOSE_NOWRITE
|
||||||
|
}
|
||||||
|
return w.register(path, flags, recurse)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
path, recurse := recursivePath(path)
|
||||||
|
if recurse {
|
||||||
|
return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
if root == path {
|
||||||
|
return fmt.Errorf("fsnotify: not a directory: %q", path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a Create event when adding new directory from a recursive
|
||||||
|
// watch; this is for "mkdir -p one/two/three". Usually all those
|
||||||
|
// directories will be created before we can set up watchers on the
|
||||||
|
// subdirectories, so only "one" would be sent as a Create event and
|
||||||
|
// not "one/two" and "one/two/three" (inotifywait -r has the same
|
||||||
|
// problem).
|
||||||
|
if with.sendCreate && root != path {
|
||||||
|
w.sendEvent(Event{Name: root, Op: Create})
|
||||||
|
}
|
||||||
|
|
||||||
|
return add(root, with, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return add(path, with, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||||
|
return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
|
||||||
|
if existing != nil {
|
||||||
|
flags |= existing.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := unix.InotifyAddWatch(w.fd, path, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := w.watches.wd[uint32(wd)]; ok {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing == nil {
|
||||||
|
return &watch{
|
||||||
|
wd: uint32(wd),
|
||||||
|
path: path,
|
||||||
|
flags: flags,
|
||||||
|
recurse: recurse,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.wd = uint32(wd)
|
||||||
|
existing.flags = flags
|
||||||
|
return existing, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.remove(filepath.Clean(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) remove(name string) error {
|
||||||
|
wds, err := w.watches.removePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, wd := range wds {
|
||||||
|
_, err := unix.InotifyRmWatch(w.fd, wd)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every
|
||||||
|
// case; the only two possible errors are:
|
||||||
|
//
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of
|
||||||
|
// any kind.
|
||||||
|
//
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is
|
||||||
|
// not a valid watch descriptor. Watch descriptors are invalidated
|
||||||
|
// when they are removed explicitly or implicitly; explicitly by
|
||||||
|
// inotify_rm_watch, implicitly when the file they are watching is
|
||||||
|
// deleted.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
entries := make([]string, 0, w.watches.len())
|
||||||
|
for pathname := range w.watches.path {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *inotify) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.doneResp)
|
||||||
|
close(w.Errors)
|
||||||
|
close(w.Events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
for {
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.inotifyFile.Read(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
err := errors.New("notify: short read in readEvents()") // Read was too short.
|
||||||
|
if n == 0 {
|
||||||
|
err = io.EOF // If EOF is received. This should really never happen.
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't know how many events we just read into the buffer While the
|
||||||
|
// offset points to at least one whole event.
|
||||||
|
var offset uint32
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point to the event in the buffer.
|
||||||
|
inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
if !w.sendError(ErrEventOverflow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev, ok := w.handleEvent(inEvent, &buf, offset)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !w.sendEvent(ev) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + inEvent.Len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
/// If the event happened to the watched directory or the watched file, the
|
||||||
|
/// kernel 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.
|
||||||
|
///
|
||||||
|
/// Can be nil if Remove() was called in another goroutine for this path
|
||||||
|
/// inbetween reading the events from the kernel and reading the internal
|
||||||
|
/// state. Not much we can do about it, so just skip. See #616.
|
||||||
|
watch := w.watches.byWd(uint32(inEvent.Wd))
|
||||||
|
if watch == nil {
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
name = watch.path
|
||||||
|
nameLen = uint32(inEvent.Len)
|
||||||
|
)
|
||||||
|
if nameLen > 0 {
|
||||||
|
/// Point "bytes" at the first byte of the filename
|
||||||
|
bb := *buf
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||||
|
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00")
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(name, inEvent.Mask, inEvent.Cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 {
|
||||||
|
w.watches.remove(watch)
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// inotify will automatically remove the watch on deletes; just need
|
||||||
|
// to clean our state here.
|
||||||
|
if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
w.watches.remove(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||||
|
if watch.recurse { // Do nothing
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.remove(watch.path)
|
||||||
|
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return Event{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip if we're watching both this path and the parent; the parent will
|
||||||
|
/// already send a delete so no need to do it twice.
|
||||||
|
if inEvent.Mask&unix.IN_DELETE_SELF != 0 {
|
||||||
|
_, ok := w.watches.path[filepath.Dir(watch.path)]
|
||||||
|
if ok {
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
|
||||||
|
// Need to update watch path for recurse.
|
||||||
|
if watch.recurse {
|
||||||
|
isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||||
|
/// New directory created: set up watch on it.
|
||||||
|
if isDir && ev.Has(Create) {
|
||||||
|
err := w.register(ev.Name, watch.flags, true)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return Event{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This was a directory rename, so we need to update all the
|
||||||
|
// children.
|
||||||
|
//
|
||||||
|
// TODO: this is of course pretty slow; we should use a better data
|
||||||
|
// structure for storing all of this, e.g. store children in the
|
||||||
|
// watch. I have some code for this in my kqueue refactor we can use
|
||||||
|
// in the future. For now I'm okay with this as it's not publicly
|
||||||
|
// available. Correctness first, performance second.
|
||||||
|
if ev.renamedFrom != "" {
|
||||||
|
for k, ww := range w.watches.wd {
|
||||||
|
if k == watch.wd || ww.path == ev.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||||
|
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||||
|
w.watches.wd[k] = ww
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ev, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) isRecursive(path string) bool {
|
||||||
|
ww := w.watches.byPath(path)
|
||||||
|
if ww == nil { // path could be a file, so also check the Dir.
|
||||||
|
ww = w.watches.byPath(filepath.Dir(path))
|
||||||
|
}
|
||||||
|
return ww != nil && ww.recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_OPEN == unix.IN_OPEN {
|
||||||
|
e.Op |= xUnportableOpen
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ACCESS == unix.IN_ACCESS {
|
||||||
|
e.Op |= xUnportableRead
|
||||||
|
}
|
||||||
|
if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE {
|
||||||
|
e.Op |= xUnportableCloseWrite
|
||||||
|
}
|
||||||
|
if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE {
|
||||||
|
e.Op |= xUnportableCloseRead
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie != 0 {
|
||||||
|
if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
w.cookiesMu.Lock()
|
||||||
|
w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name}
|
||||||
|
w.cookieIndex++
|
||||||
|
if w.cookieIndex > 9 {
|
||||||
|
w.cookieIndex = 0
|
||||||
|
}
|
||||||
|
w.cookiesMu.Unlock()
|
||||||
|
} else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
w.cookiesMu.Lock()
|
||||||
|
var prev string
|
||||||
|
for _, c := range w.cookies {
|
||||||
|
if c.cookie == cookie {
|
||||||
|
prev = c.path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.cookiesMu.Unlock()
|
||||||
|
e.renamedFrom = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) xSupports(op Op) bool {
|
||||||
|
return true // Supports everything.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) state() {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
for wd, ww := range w.watches.wd {
|
||||||
|
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
705
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
705
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kqueue struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
closepipe [2]int // Pipe used for closing kq.
|
||||||
|
watches *watches
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
watches struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
wd map[int]watch // wd → watch
|
||||||
|
path map[string]int // pathname → wd
|
||||||
|
byDir map[string]map[int]struct{} // dirname(path) → wd
|
||||||
|
seen map[string]struct{} // Keep track of if we know this file exists.
|
||||||
|
byUser map[string]struct{} // Watches added with Watcher.Add()
|
||||||
|
}
|
||||||
|
watch struct {
|
||||||
|
wd int
|
||||||
|
name string
|
||||||
|
linkName string // In case of links; name is the target, and this is the link.
|
||||||
|
isDir bool
|
||||||
|
dirFlags uint32
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatches() *watches {
|
||||||
|
return &watches{
|
||||||
|
wd: make(map[int]watch),
|
||||||
|
path: make(map[string]int),
|
||||||
|
byDir: make(map[string]map[int]struct{}),
|
||||||
|
seen: make(map[string]struct{}),
|
||||||
|
byUser: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) listPaths(userOnly bool) []string {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
|
||||||
|
if userOnly {
|
||||||
|
l := make([]string, 0, len(w.byUser))
|
||||||
|
for p := range w.byUser {
|
||||||
|
l = append(l, p)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
l := make([]string, 0, len(w.path))
|
||||||
|
for p := range w.path {
|
||||||
|
l = append(l, p)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) watchesInDir(path string) []string {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
|
||||||
|
l := make([]string, 0, 4)
|
||||||
|
for fd := range w.byDir[path] {
|
||||||
|
info := w.wd[fd]
|
||||||
|
if _, ok := w.byUser[info.name]; !ok {
|
||||||
|
l = append(l, info.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark path as added by the user.
|
||||||
|
func (w *watches) addUserWatch(path string) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
w.byUser[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) addLink(path string, fd int) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
w.path[path] = fd
|
||||||
|
w.seen[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) add(path, linkPath string, fd int, isDir bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
w.path[path] = fd
|
||||||
|
w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir}
|
||||||
|
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
byDir, ok := w.byDir[parent]
|
||||||
|
if !ok {
|
||||||
|
byDir = make(map[int]struct{}, 1)
|
||||||
|
w.byDir[parent] = byDir
|
||||||
|
}
|
||||||
|
byDir[fd] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byWd(fd int) (watch, bool) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
info, ok := w.wd[fd]
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byPath(path string) (watch, bool) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
info, ok := w.wd[w.path[path]]
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) updateDirFlags(path string, flags uint32) bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
fd, ok := w.path[path]
|
||||||
|
if !ok { // Already deleted: don't re-set it here.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
info := w.wd[fd]
|
||||||
|
info.dirFlags = flags
|
||||||
|
w.wd[fd] = info
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) remove(fd int, path string) bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
isDir := w.wd[fd].isDir
|
||||||
|
delete(w.path, path)
|
||||||
|
delete(w.byUser, path)
|
||||||
|
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
delete(w.byDir[parent], fd)
|
||||||
|
|
||||||
|
if len(w.byDir[parent]) == 0 {
|
||||||
|
delete(w.byDir, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.wd, fd)
|
||||||
|
delete(w.seen, path)
|
||||||
|
return isDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) markSeen(path string, exists bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if exists {
|
||||||
|
w.seen[path] = struct{}{}
|
||||||
|
} else {
|
||||||
|
delete(w.seen, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) seenBefore(path string) bool {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
_, ok := w.seen[path]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
kq, closepipe, err := newKqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &kqueue{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
kq: kq,
|
||||||
|
closepipe: closepipe,
|
||||||
|
watches: newWatches(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
//
|
||||||
|
// This registers a new event on closepipe, which will trigger an event when
|
||||||
|
// it's closed. This way we can use kevent() without timeout/polling; without
|
||||||
|
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||||||
|
// all.
|
||||||
|
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the close pipe.
|
||||||
|
err = unix.Pipe(closepipe[:])
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(kq)
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
unix.CloseOnExec(closepipe[0])
|
||||||
|
unix.CloseOnExec(closepipe[1])
|
||||||
|
|
||||||
|
// Register changes to listen on the closepipe.
|
||||||
|
changes := make([]unix.Kevent_t, 1)
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||||||
|
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||||||
|
|
||||||
|
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if ok == -1 {
|
||||||
|
unix.Close(kq)
|
||||||
|
unix.Close(closepipe[0])
|
||||||
|
unix.Close(closepipe[1])
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
return kq, closepipe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathsToRemove := w.watches.listPaths(false)
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *kqueue) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.addWatch(name, noteAllEvents, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.watches.addUserWatch(name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Remove(name string) error {
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
return w.remove(name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) remove(name string, unwatchFiles bool) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
info, ok := w.watches.byPath(name)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{info.wd}, unix.EV_DELETE, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(info.wd)
|
||||||
|
|
||||||
|
isDir := w.watches.remove(info.wd, name)
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if unwatchFiles && isDir {
|
||||||
|
pathsToRemove := w.watches.watchesInDir(name)
|
||||||
|
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.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.watches.listPaths(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, with symlinks resolved.
|
||||||
|
func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) {
|
||||||
|
if w.isClosed() {
|
||||||
|
return "", ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
info, alreadyWatching := w.watches.byPath(name)
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets or named pipes.
|
||||||
|
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow symlinks, but only for paths added with Add(), and not paths
|
||||||
|
// we're adding from internalWatch from a listdir.
|
||||||
|
if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
link, err := os.Readlink(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(link) {
|
||||||
|
link = filepath.Join(filepath.Dir(name), link)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alreadyWatching = w.watches.byPath(link)
|
||||||
|
if alreadyWatching {
|
||||||
|
// Add to watches so we don't get spurious Create events later
|
||||||
|
// on when we diff the directories.
|
||||||
|
w.watches.addLink(name, 0)
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info.linkName = name
|
||||||
|
name = link
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||||
|
// See #354, and Go issues 11180 and 39237.
|
||||||
|
for {
|
||||||
|
info.wd, err = unix.Open(name, openMode, 0)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
info.isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(info.wd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.watches.add(name, info.linkName, info.wd, info.isDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch the directory if it has not been watched before, or if it was
|
||||||
|
// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
if info.isDir {
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
if !w.watches.updateDirFlags(name, flags) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
d := name
|
||||||
|
if info.linkName != "" {
|
||||||
|
d = info.linkName
|
||||||
|
}
|
||||||
|
if err := w.watchDirectoryFiles(d); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *kqueue) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
_ = unix.Close(w.kq)
|
||||||
|
unix.Close(w.closepipe[0])
|
||||||
|
}()
|
||||||
|
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
for {
|
||||||
|
kevents, err := w.read(eventBuffer)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kevent := range kevents {
|
||||||
|
var (
|
||||||
|
wd = int(kevent.Ident)
|
||||||
|
mask = uint32(kevent.Fflags)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shut down the loop when the pipe is closed, but only after all
|
||||||
|
// other events have been processed.
|
||||||
|
if wd == w.closepipe[0] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path, ok := w.watches.byWd(wd)
|
||||||
|
if debug {
|
||||||
|
internal.Debug(path.name, &kevent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS it seems that sometimes an event with Ident=0 is
|
||||||
|
// delivered, and no other flags/information beyond that, even
|
||||||
|
// though we never saw such a file descriptor. For example in
|
||||||
|
// TestWatchSymlink/277 (usually at the end, but sometimes sooner):
|
||||||
|
//
|
||||||
|
// fmt.Printf("READ: %2d %#v\n", kevent.Ident, kevent)
|
||||||
|
// unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||||||
|
// unix.Kevent_t{Ident:0x0, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||||||
|
//
|
||||||
|
// The first is a normal event, the second with Ident 0. No error
|
||||||
|
// flag, no data, no ... nothing.
|
||||||
|
//
|
||||||
|
// I read a bit through bsd/kern_event.c from the xnu source, but I
|
||||||
|
// don't really see an obvious location where this is triggered –
|
||||||
|
// this doesn't seem intentional, but idk...
|
||||||
|
//
|
||||||
|
// Technically fd 0 is a valid descriptor, so only skip it if
|
||||||
|
// there's no path, and if we're on macOS.
|
||||||
|
if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(path.name, path.linkName, mask)
|
||||||
|
|
||||||
|
if event.Has(Rename) || event.Has(Remove) {
|
||||||
|
w.remove(event.Name, false)
|
||||||
|
w.watches.markSeen(event.Name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||||||
|
w.dirChange(event.Name)
|
||||||
|
} else if !w.sendEvent(event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Remove) {
|
||||||
|
// 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)
|
||||||
|
_, found := w.watches.byPath(fileDir)
|
||||||
|
if found {
|
||||||
|
// TODO: this branch is never triggered in any test.
|
||||||
|
// Added in d6220df (2012).
|
||||||
|
// isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111
|
||||||
|
//
|
||||||
|
// I don't really get how this can be triggered either.
|
||||||
|
// And it wasn't triggered in the patch that added it,
|
||||||
|
// either.
|
||||||
|
//
|
||||||
|
// Original also had a comment:
|
||||||
|
// make sure the directory exists before we watch for
|
||||||
|
// changes. When we do a recursive watch and perform
|
||||||
|
// rm -rf, the parent directory might have gone
|
||||||
|
// missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the
|
||||||
|
// parent directory.
|
||||||
|
err := w.dirChange(fileDir)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path := filepath.Clean(event.Name)
|
||||||
|
if fi, err := os.Lstat(path); err == nil {
|
||||||
|
err := w.sendCreateIfNew(path, fi)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func (w *kqueue) newEvent(name, linkName string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if linkName != "" {
|
||||||
|
// If the user watched "/path/link" then emit events as "/path/link"
|
||||||
|
// rather than "/path/target".
|
||||||
|
e.Name = linkName
|
||||||
|
}
|
||||||
|
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
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 *kqueue) watchDirectoryFiles(dirPath string) error {
|
||||||
|
files, err := os.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
path := filepath.Join(dirPath, f.Name())
|
||||||
|
|
||||||
|
fi, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPath, err := w.internalWatch(path, 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
|
||||||
|
// as a "new" file later (it still shows up in the directory
|
||||||
|
// listing).
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||||
|
cleanPath = filepath.Clean(path)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%q: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.watches.markSeen(cleanPath, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the directory for new files and send an event for them.
|
||||||
|
//
|
||||||
|
// 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 *kqueue) dirChange(dir string) error {
|
||||||
|
files, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
// 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.dirChange %q: %w", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
fi, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.sendCreateIfNew(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) || errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a create event if the file isn't already being tracked, and start
|
||||||
|
// watching this file.
|
||||||
|
func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error {
|
||||||
|
if !w.watches.seenBefore(path) {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like watchDirectoryFiles, but without doing another ReadDir.
|
||||||
|
path, err := w.internalWatch(path, fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.watches.markSeen(path, true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) 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
|
||||||
|
info, _ := w.watches.byPath(name)
|
||||||
|
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch file to mimic Linux inotify.
|
||||||
|
return w.addWatch(name, noteAllEvents, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register events with the queue.
|
||||||
|
func (w *kqueue) register(fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the events.
|
||||||
|
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) xSupports(op Op) bool {
|
||||||
|
//if runtime.GOOS == "freebsd" {
|
||||||
|
// return true // Supports everything.
|
||||||
|
//}
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
22
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
22
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type other struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
return nil, errors.New("fsnotify not supported on the current platform")
|
||||||
|
}
|
||||||
|
func (w *other) Close() error { return nil }
|
||||||
|
func (w *other) WatchList() []string { return nil }
|
||||||
|
func (w *other) Add(name string) error { return nil }
|
||||||
|
func (w *other) AddWith(name string, opts ...addOpt) error { return nil }
|
||||||
|
func (w *other) Remove(name string) error { return nil }
|
||||||
|
func (w *other) xSupports(op Op) bool { return false }
|
||||||
680
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
680
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
@@ -0,0 +1,680 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Windows backend based on ReadDirectoryChangesW()
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readDirChangesW struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
port windows.Handle // Handle to completion port
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
done chan chan<- error
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 50
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
w := &readDirChangesW{
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
done: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) isClosed() bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, uint32(mask))
|
||||||
|
event.renamedFrom = renamedFrom
|
||||||
|
select {
|
||||||
|
case ch := <-w.done:
|
||||||
|
w.done <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *readDirChangesW) sendError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.closed = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Send "done" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.done <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
if with.bufsize < 4096 {
|
||||||
|
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
bufsize: with.bufsize,
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for _, entry := range w.watches {
|
||||||
|
for _, watchEntry := range entry {
|
||||||
|
for name := range watchEntry.names {
|
||||||
|
entries = append(entries, filepath.Join(watchEntry.path, name))
|
||||||
|
}
|
||||||
|
// the directory itself is being watched
|
||||||
|
if watchEntry.mask != 0 {
|
||||||
|
entries = append(entries, watchEntry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
||||||
|
// add various options to the watch. This has long since been removed.
|
||||||
|
//
|
||||||
|
// The "sys" in the name is misleading as they're not part of any "system".
|
||||||
|
//
|
||||||
|
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
||||||
|
const (
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *readDirChangesW) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
bufsize int
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle windows.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
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 (
|
||||||
|
indexMap map[uint64]*watch
|
||||||
|
watchMap map[uint32]indexMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *readDirChangesW) wakeupReader() error {
|
||||||
|
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
||||||
|
if err != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", err)
|
||||||
|
}
|
||||||
|
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) getIno(path string) (ino *inode, err error) {
|
||||||
|
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
||||||
|
windows.FILE_LIST_DIRECTORY,
|
||||||
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||||
|
nil, windows.OPEN_EXISTING,
|
||||||
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fi windows.ByHandleFileInformation
|
||||||
|
err = windows.GetFileInformationByHandle(h, &fi)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error {
|
||||||
|
pathname, recurse := recursivePath(pathname)
|
||||||
|
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
recurse: recurse,
|
||||||
|
buf: make([]byte, bufsize),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.startRead(watchEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) remWatch(pathname string) error {
|
||||||
|
pathname, recurse := recursivePath(pathname)
|
||||||
|
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) startRead(watch *watch) error {
|
||||||
|
err := windows.CancelIo(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CancelIo", err))
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := w.toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= w.toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
err := windows.CloseHandle(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *readDirChangesW) readEvents() {
|
||||||
|
var (
|
||||||
|
n uint32
|
||||||
|
key uintptr
|
||||||
|
ov *windows.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// This error is handled after the watch == nil check below.
|
||||||
|
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
||||||
|
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.done:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.CloseHandle(w.port)
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", err)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case windows.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case windows.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.sendError(ErrEventOverflow)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
|
||||||
|
// Create a buf that is the size of the path name
|
||||||
|
size := int(raw.FileNameLength / 2)
|
||||||
|
var buf []uint16
|
||||||
|
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||||
|
sh.Len = size
|
||||||
|
sh.Cap = size
|
||||||
|
name := windows.UTF16ToString(buf)
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(fullname, raw.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
// Update saved path of all sub-watches.
|
||||||
|
old := filepath.Join(watch.path, watch.rename)
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, watchMap := range w.watches {
|
||||||
|
for _, ww := range watchMap {
|
||||||
|
if strings.HasPrefix(ww.path, old) {
|
||||||
|
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(fullname, "", watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
} else {
|
||||||
|
w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// 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"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.sendError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case windows.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) xSupports(op Op) bool {
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
496
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
496
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
// Package fsnotify provides a cross-platform interface for file system
|
||||||
|
// notifications.
|
||||||
|
//
|
||||||
|
// Currently supported systems:
|
||||||
|
//
|
||||||
|
// - Linux via inotify
|
||||||
|
// - BSD, macOS via kqueue
|
||||||
|
// - Windows via ReadDirectoryChangesW
|
||||||
|
// - illumos via FEN
|
||||||
|
//
|
||||||
|
// # FSNOTIFY_DEBUG
|
||||||
|
//
|
||||||
|
// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
|
||||||
|
// stderr. This can be useful to track down some problems, especially in cases
|
||||||
|
// where fsnotify is used as an indirect dependency.
|
||||||
|
//
|
||||||
|
// Every event will be printed as soon as there's something useful to print,
|
||||||
|
// with as little processing from fsnotify.
|
||||||
|
//
|
||||||
|
// Example output:
|
||||||
|
//
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1"
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1"
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1"
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 files, 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 {
|
||||||
|
b backend
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event represents a file system notification.
|
||||||
|
type Event struct {
|
||||||
|
// Path to the file or directory.
|
||||||
|
//
|
||||||
|
// Paths are relative to the input; for example with Add("dir") the Name
|
||||||
|
// will be set to "dir/file" if you create that file, but if you use
|
||||||
|
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// File operation that triggered the event.
|
||||||
|
//
|
||||||
|
// This is a bitmask and some systems may send multiple operations at once.
|
||||||
|
// Use the Event.Has() method instead of comparing with ==.
|
||||||
|
Op Op
|
||||||
|
|
||||||
|
// Create events will have this set to the old path if it's a rename. This
|
||||||
|
// only works when both the source and destination are watched. It's not
|
||||||
|
// reliable when watching individual files, only directories.
|
||||||
|
//
|
||||||
|
// For example "mv /tmp/file /tmp/rename" will emit:
|
||||||
|
//
|
||||||
|
// Event{Op: Rename, Name: "/tmp/file"}
|
||||||
|
// Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
|
||||||
|
renamedFrom string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
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 watches 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
|
||||||
|
|
||||||
|
// File descriptor was opened.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableOpen
|
||||||
|
|
||||||
|
// File was read from.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableRead
|
||||||
|
|
||||||
|
// File opened for writing was closed.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
//
|
||||||
|
// The advantage of using this over Write is that it's more reliable than
|
||||||
|
// waiting for Write events to stop. It's also faster (if you're not
|
||||||
|
// listening to Write events): copying a file of a few GB can easily
|
||||||
|
// generate tens of thousands of Write events in a short span of time.
|
||||||
|
xUnportableCloseWrite
|
||||||
|
|
||||||
|
// File opened for reading was closed.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableCloseRead
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNonExistentWatch is used when Remove() is called on a path that's not
|
||||||
|
// added.
|
||||||
|
ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
|
||||||
|
|
||||||
|
// ErrClosed is used when trying to operate on a closed Watcher.
|
||||||
|
ErrClosed = errors.New("fsnotify: watcher already closed")
|
||||||
|
|
||||||
|
// ErrEventOverflow is reported from the Errors channel when there are too
|
||||||
|
// many events:
|
||||||
|
//
|
||||||
|
// - inotify: inotify returns IN_Q_OVERFLOW – because there are too
|
||||||
|
// many queued events (the fs.inotify.max_queued_events
|
||||||
|
// sysctl can be used to increase this).
|
||||||
|
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
||||||
|
// - kqueue, fen: Not used.
|
||||||
|
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
|
||||||
|
|
||||||
|
// ErrUnsupported is returned by AddWith() when WithOps() specified an
|
||||||
|
// Unportable event that's not supported on this platform.
|
||||||
|
//lint:ignore ST1012 not relevant
|
||||||
|
xErrUnsupported = errors.New("fsnotify: not supported with this backend")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
ev, errs := make(chan Event, defaultBufferSize), make(chan error)
|
||||||
|
b, err := newBackend(ev, errs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Watcher{b: b, Events: ev, Errors: errs}, 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) {
|
||||||
|
ev, errs := make(chan Event, sz), make(chan error)
|
||||||
|
b, err := newBackend(ev, errs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Watcher{b: b, Events: ev, Errors: errs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// 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 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
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// 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 destination,
|
||||||
|
// overwriting the original (or some variant thereof). The watcher on the
|
||||||
|
// original file is now lost, as that no longer exists.
|
||||||
|
//
|
||||||
|
// The upshot of this is that a power failure or crash won't leave a
|
||||||
|
// half-written file.
|
||||||
|
//
|
||||||
|
// Watch the parent directory and use Event.Name to filter out files you're not
|
||||||
|
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
||||||
|
func (w *Watcher) Add(path string) error { return w.b.Add(path) }
|
||||||
|
|
||||||
|
// 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(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /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(path string) error { return w.b.Remove(path) }
|
||||||
|
|
||||||
|
// Close removes all watches and closes the Events channel.
|
||||||
|
func (w *Watcher) Close() error { return w.b.Close() }
|
||||||
|
|
||||||
|
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
||||||
|
// yet removed).
|
||||||
|
//
|
||||||
|
// The order is undefined, and may differ per call. Returns nil if
|
||||||
|
// [Watcher.Close] was called.
|
||||||
|
func (w *Watcher) WatchList() []string { return w.b.WatchList() }
|
||||||
|
|
||||||
|
// Supports reports if all the listed operations are supported by this platform.
|
||||||
|
//
|
||||||
|
// Create, Write, Remove, Rename, and Chmod are always supported. It can only
|
||||||
|
// return false for an Op starting with Unportable.
|
||||||
|
func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
|
||||||
|
|
||||||
|
func (o Op) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
if o.Has(Create) {
|
||||||
|
b.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if o.Has(Remove) {
|
||||||
|
b.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if o.Has(Write) {
|
||||||
|
b.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableOpen) {
|
||||||
|
b.WriteString("|OPEN")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableRead) {
|
||||||
|
b.WriteString("|READ")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableCloseWrite) {
|
||||||
|
b.WriteString("|CLOSE_WRITE")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableCloseRead) {
|
||||||
|
b.WriteString("|CLOSE_READ")
|
||||||
|
}
|
||||||
|
if o.Has(Rename) {
|
||||||
|
b.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if o.Has(Chmod) {
|
||||||
|
b.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if b.Len() == 0 {
|
||||||
|
return "[no events]"
|
||||||
|
}
|
||||||
|
return b.String()[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has reports if this operation has the given operation.
|
||||||
|
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) }
|
||||||
|
|
||||||
|
// String returns a string representation of the event with their path.
|
||||||
|
func (e Event) String() string {
|
||||||
|
if e.renamedFrom != "" {
|
||||||
|
return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
backend interface {
|
||||||
|
Add(string) error
|
||||||
|
AddWith(string, ...addOpt) error
|
||||||
|
Remove(string) error
|
||||||
|
WatchList() []string
|
||||||
|
Close() error
|
||||||
|
xSupports(Op) bool
|
||||||
|
}
|
||||||
|
addOpt func(opt *withOpts)
|
||||||
|
withOpts struct {
|
||||||
|
bufsize int
|
||||||
|
op Op
|
||||||
|
noFollow bool
|
||||||
|
sendCreate bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug = func() bool {
|
||||||
|
// Check for exactly "1" (rather than mere existence) so we can add
|
||||||
|
// options/flags in the future. I don't know if we ever want that, but it's
|
||||||
|
// nice to leave the option open.
|
||||||
|
return os.Getenv("FSNOTIFY_DEBUG") == "1"
|
||||||
|
}()
|
||||||
|
|
||||||
|
var defaultOpts = withOpts{
|
||||||
|
bufsize: 65536, // 64K
|
||||||
|
op: Create | Write | Remove | Rename | Chmod,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOptions(opts ...addOpt) withOpts {
|
||||||
|
with := defaultOpts
|
||||||
|
for _, o := range opts {
|
||||||
|
if o != nil {
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOps sets which operations to listen for. The default is [Create],
|
||||||
|
// [Write], [Remove], [Rename], and [Chmod].
|
||||||
|
//
|
||||||
|
// Excluding operations you're not interested in can save quite a bit of CPU
|
||||||
|
// time; in some use cases there may be hundreds of thousands of useless Write
|
||||||
|
// or Chmod operations per second.
|
||||||
|
//
|
||||||
|
// This can also be used to add unportable operations not supported by all
|
||||||
|
// platforms; unportable operations all start with "Unportable":
|
||||||
|
// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
|
||||||
|
// [UnportableCloseRead].
|
||||||
|
//
|
||||||
|
// AddWith returns an error when using an unportable operation that's not
|
||||||
|
// supported. Use [Watcher.Support] to check for support.
|
||||||
|
func withOps(op Op) addOpt {
|
||||||
|
return func(opt *withOpts) { opt.op = op }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoFollow disables following symlinks, so the symlinks themselves are
|
||||||
|
// watched.
|
||||||
|
func withNoFollow() addOpt {
|
||||||
|
return func(opt *withOpts) { opt.noFollow = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Internal" option for recursive watches on inotify.
|
||||||
|
func withCreate() addOpt {
|
||||||
|
return func(opt *withOpts) { opt.sendCreate = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableRecurse = false
|
||||||
|
|
||||||
|
// Check if this path is recursive (ends with "/..." or "\..."), and return the
|
||||||
|
// path with the /... stripped.
|
||||||
|
func recursivePath(path string) (string, bool) {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if !enableRecurse { // Only enabled in tests for now.
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
if filepath.Base(path) == "..." {
|
||||||
|
return filepath.Dir(path), true
|
||||||
|
}
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
39
vendor/github.com/fsnotify/fsnotify/internal/darwin.go
generated
vendored
Normal file
39
vendor/github.com/fsnotify/fsnotify/internal/darwin.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = l.Cur
|
||||||
|
|
||||||
|
if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles {
|
||||||
|
maxfiles = uint64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles {
|
||||||
|
maxfiles = uint64(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||||
57
vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
generated
vendored
Normal file
57
vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE},
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_BACKGROUND", unix.NOTE_BACKGROUND},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_CRITICAL", unix.NOTE_CRITICAL},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS},
|
||||||
|
{"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR},
|
||||||
|
{"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL},
|
||||||
|
{"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL},
|
||||||
|
{"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK},
|
||||||
|
{"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY},
|
||||||
|
{"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_FUNLOCK", unix.NOTE_FUNLOCK},
|
||||||
|
{"NOTE_LEEWAY", unix.NOTE_LEEWAY},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_MACHTIME", unix.NOTE_MACHTIME},
|
||||||
|
{"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME},
|
||||||
|
{"NOTE_NONE", unix.NOTE_NONE},
|
||||||
|
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||||
|
{"NOTE_OOB", unix.NOTE_OOB},
|
||||||
|
//{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!)
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_REAP", unix.NOTE_REAP},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||||
|
{"NOTE_SIGNAL", unix.NOTE_SIGNAL},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||||
|
{"NOTE_VM_ERROR", unix.NOTE_VM_ERROR},
|
||||||
|
{"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE},
|
||||||
|
{"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE},
|
||||||
|
{"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
33
vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
generated
vendored
Normal file
33
vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_OOB", unix.NOTE_OOB},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
42
vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
generated
vendored
Normal file
42
vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ABSTIME", unix.NOTE_ABSTIME},
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_CLOSE", unix.NOTE_CLOSE},
|
||||||
|
{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FILE_POLL", unix.NOTE_FILE_POLL},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_MSECONDS", unix.NOTE_MSECONDS},
|
||||||
|
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||||
|
{"NOTE_OPEN", unix.NOTE_OPEN},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_READ", unix.NOTE_READ},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
32
vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
generated
vendored
Normal file
32
vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, kevent *unix.Kevent_t) {
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
|
||||||
|
}
|
||||||
56
vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
generated
vendored
Normal file
56
vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask, cookie uint32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"IN_ACCESS", unix.IN_ACCESS},
|
||||||
|
{"IN_ATTRIB", unix.IN_ATTRIB},
|
||||||
|
{"IN_CLOSE", unix.IN_CLOSE},
|
||||||
|
{"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE},
|
||||||
|
{"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE},
|
||||||
|
{"IN_CREATE", unix.IN_CREATE},
|
||||||
|
{"IN_DELETE", unix.IN_DELETE},
|
||||||
|
{"IN_DELETE_SELF", unix.IN_DELETE_SELF},
|
||||||
|
{"IN_IGNORED", unix.IN_IGNORED},
|
||||||
|
{"IN_ISDIR", unix.IN_ISDIR},
|
||||||
|
{"IN_MODIFY", unix.IN_MODIFY},
|
||||||
|
{"IN_MOVE", unix.IN_MOVE},
|
||||||
|
{"IN_MOVED_FROM", unix.IN_MOVED_FROM},
|
||||||
|
{"IN_MOVED_TO", unix.IN_MOVED_TO},
|
||||||
|
{"IN_MOVE_SELF", unix.IN_MOVE_SELF},
|
||||||
|
{"IN_OPEN", unix.IN_OPEN},
|
||||||
|
{"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW},
|
||||||
|
{"IN_UNMOUNT", unix.IN_UNMOUNT},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
var c string
|
||||||
|
if cookie > 0 {
|
||||||
|
c = fmt.Sprintf("(cookie: %d) ", cookie)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-30s → %s%q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name)
|
||||||
|
}
|
||||||
25
vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
28
vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
// {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386?
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EOF", unix.NOTE_EOF},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRUNCATE", unix.NOTE_TRUNCATE},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
45
vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
generated
vendored
Normal file
45
vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask int32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m int32
|
||||||
|
}{
|
||||||
|
{"FILE_ACCESS", unix.FILE_ACCESS},
|
||||||
|
{"FILE_MODIFIED", unix.FILE_MODIFIED},
|
||||||
|
{"FILE_ATTRIB", unix.FILE_ATTRIB},
|
||||||
|
{"FILE_TRUNC", unix.FILE_TRUNC},
|
||||||
|
{"FILE_NOFOLLOW", unix.FILE_NOFOLLOW},
|
||||||
|
{"FILE_DELETE", unix.FILE_DELETE},
|
||||||
|
{"FILE_RENAME_TO", unix.FILE_RENAME_TO},
|
||||||
|
{"FILE_RENAME_FROM", unix.FILE_RENAME_FROM},
|
||||||
|
{"UNMOUNTED", unix.UNMOUNTED},
|
||||||
|
{"MOUNTEDOVER", unix.MOUNTEDOVER},
|
||||||
|
{"FILE_EXCEPTION", unix.FILE_EXCEPTION},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-30s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
|
||||||
|
}
|
||||||
40
vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
generated
vendored
Normal file
40
vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask uint32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED},
|
||||||
|
{"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED},
|
||||||
|
{"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED},
|
||||||
|
{"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME},
|
||||||
|
{"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-65s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
31
vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
generated
vendored
Normal file
31
vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = uint64(l.Cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) }
|
||||||
2
vendor/github.com/fsnotify/fsnotify/internal/internal.go
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package internal contains some helpers.
|
||||||
|
package internal
|
||||||
31
vendor/github.com/fsnotify/fsnotify/internal/unix.go
generated
vendored
Normal file
31
vendor/github.com/fsnotify/fsnotify/internal/unix.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build !windows && !darwin && !freebsd && !plan9
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = uint64(l.Cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||||
7
vendor/github.com/fsnotify/fsnotify/internal/unix2.go
generated
vendored
Normal file
7
vendor/github.com/fsnotify/fsnotify/internal/unix2.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
func HasPrivilegesForSymlink() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
41
vendor/github.com/fsnotify/fsnotify/internal/windows.go
generated
vendored
Normal file
41
vendor/github.com/fsnotify/fsnotify/internal/windows.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Just a dummy.
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = errors.New("dummy")
|
||||||
|
ErrUnixEACCES = errors.New("dummy")
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetRlimit() {}
|
||||||
|
func Maxfiles() uint64 { return 1<<64 - 1 }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") }
|
||||||
|
|
||||||
|
func HasPrivilegesForSymlink() bool {
|
||||||
|
var sid *windows.SID
|
||||||
|
err := windows.AllocateAndInitializeSid(
|
||||||
|
&windows.SECURITY_NT_AUTHORITY,
|
||||||
|
2,
|
||||||
|
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||||
|
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
&sid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer windows.FreeSid(sid)
|
||||||
|
token := windows.Token(0)
|
||||||
|
member, err := token.IsMember(sid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return member || token.IsElevated()
|
||||||
|
}
|
||||||
64
vendor/github.com/fsnotify/fsnotify/shared.go
generated
vendored
Normal file
64
vendor/github.com/fsnotify/fsnotify/shared.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type shared struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan struct{}
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShared(ev chan Event, errs chan error) *shared {
|
||||||
|
return &shared{
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *shared) sendEvent(e Event) bool {
|
||||||
|
if e.Op == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *shared) sendError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *shared) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as closed; returns true if it was already closed.
|
||||||
|
func (w *shared) close() bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if w.isClosed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
close(w.done)
|
||||||
|
return false
|
||||||
|
}
|
||||||
3
vendor/github.com/fsnotify/fsnotify/staticcheck.conf
generated
vendored
Normal file
3
vendor/github.com/fsnotify/fsnotify/staticcheck.conf
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
checks = ['all',
|
||||||
|
'-U1000', # Don't complain about unused functions.
|
||||||
|
]
|
||||||
7
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
7
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
||||||
8
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
8
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
||||||
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
@@ -1,4 +0,0 @@
|
|||||||
logrus
|
|
||||||
vendor
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
@@ -1,40 +0,0 @@
|
|||||||
run:
|
|
||||||
# do not run on test files yet
|
|
||||||
tests: false
|
|
||||||
|
|
||||||
# all available settings of specific linters
|
|
||||||
linters-settings:
|
|
||||||
errcheck:
|
|
||||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
|
||||||
# default is false: such cases aren't reported by default.
|
|
||||||
check-type-assertions: false
|
|
||||||
|
|
||||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
|
||||||
# default is false: such cases aren't reported by default.
|
|
||||||
check-blank: false
|
|
||||||
|
|
||||||
lll:
|
|
||||||
line-length: 100
|
|
||||||
tab-width: 4
|
|
||||||
|
|
||||||
prealloc:
|
|
||||||
simple: false
|
|
||||||
range-loops: false
|
|
||||||
for-loops: false
|
|
||||||
|
|
||||||
whitespace:
|
|
||||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
|
||||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- megacheck
|
|
||||||
- govet
|
|
||||||
disable:
|
|
||||||
- maligned
|
|
||||||
- prealloc
|
|
||||||
disable-all: false
|
|
||||||
presets:
|
|
||||||
- bugs
|
|
||||||
- unused
|
|
||||||
fast: false
|
|
||||||
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
@@ -1,15 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/sirupsen/logrus
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
go: 1.15.x
|
|
||||||
os: linux
|
|
||||||
install:
|
|
||||||
- ./travis/install.sh
|
|
||||||
script:
|
|
||||||
- cd ci
|
|
||||||
- go run mage.go -v -w ../ crossBuild
|
|
||||||
- go run mage.go -v -w ../ lint
|
|
||||||
- go run mage.go -v -w ../ test
|
|
||||||
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
@@ -1,259 +0,0 @@
|
|||||||
# 1.8.1
|
|
||||||
Code quality:
|
|
||||||
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
|
||||||
* improve timestamp format documentation
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* fix race condition on logger hooks
|
|
||||||
|
|
||||||
|
|
||||||
# 1.8.0
|
|
||||||
|
|
||||||
Correct versioning number replacing v1.7.1.
|
|
||||||
|
|
||||||
# 1.7.1
|
|
||||||
|
|
||||||
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
|
||||||
|
|
||||||
Code quality:
|
|
||||||
* use go 1.15 in travis
|
|
||||||
* use magefile as task runner
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* small fixes about new go 1.13 error formatting system
|
|
||||||
* Fix for long time race condiction with mutating data hooks
|
|
||||||
|
|
||||||
Features:
|
|
||||||
* build support for zos
|
|
||||||
|
|
||||||
# 1.7.0
|
|
||||||
Fixes:
|
|
||||||
* the dependency toward a windows terminal library has been removed
|
|
||||||
|
|
||||||
Features:
|
|
||||||
* a new buffer pool management API has been added
|
|
||||||
* a set of `<LogLevel>Fn()` functions have been added
|
|
||||||
|
|
||||||
# 1.6.0
|
|
||||||
Fixes:
|
|
||||||
* end of line cleanup
|
|
||||||
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
|
||||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
|
||||||
|
|
||||||
Features:
|
|
||||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
|
||||||
|
|
||||||
# 1.5.0
|
|
||||||
Code quality:
|
|
||||||
* add golangci linter run on travis
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* add mutex for hooks concurrent access on `Entry` data
|
|
||||||
* caller function field for go1.14
|
|
||||||
* fix build issue for gopherjs target
|
|
||||||
|
|
||||||
Feature:
|
|
||||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
|
||||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
|
||||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
|
||||||
|
|
||||||
# 1.4.2
|
|
||||||
* Fixes build break for plan9, nacl, solaris
|
|
||||||
# 1.4.1
|
|
||||||
This new release introduces:
|
|
||||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
|
||||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
|
||||||
|
|
||||||
# 1.4.0
|
|
||||||
This new release introduces:
|
|
||||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
|
||||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
|
||||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
|
||||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
|
||||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
|
||||||
* Fix race condition in `getCaller` (#916).
|
|
||||||
|
|
||||||
|
|
||||||
# 1.3.0
|
|
||||||
This new release introduces:
|
|
||||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Building prometheus node_exporter on AIX (#840)
|
|
||||||
* Race condition in TextFormatter (#468)
|
|
||||||
* Travis CI import path (#868)
|
|
||||||
* Remove coloured output on Windows (#862)
|
|
||||||
* Pointer to func as field in JSONFormatter (#870)
|
|
||||||
* Properly marshal Levels (#873)
|
|
||||||
|
|
||||||
# 1.2.0
|
|
||||||
This new release introduces:
|
|
||||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
|
||||||
* A new trace level named `Trace` whose level is below `Debug`
|
|
||||||
* A configurable exit function to be called upon a Fatal trace
|
|
||||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
|
||||||
|
|
||||||
# 1.1.1
|
|
||||||
This is a bug fix release.
|
|
||||||
* fix the build break on Solaris
|
|
||||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
|
||||||
|
|
||||||
# 1.1.0
|
|
||||||
This new release introduces:
|
|
||||||
* several fixes:
|
|
||||||
* a fix for a race condition on entry formatting
|
|
||||||
* proper cleanup of previously used entries before putting them back in the pool
|
|
||||||
* the extra new line at the end of message in text formatter has been removed
|
|
||||||
* a new global public API to check if a level is activated: IsLevelEnabled
|
|
||||||
* the following methods have been added to the Logger object
|
|
||||||
* IsLevelEnabled
|
|
||||||
* SetFormatter
|
|
||||||
* SetOutput
|
|
||||||
* ReplaceHooks
|
|
||||||
* introduction of go module
|
|
||||||
* an indent configuration for the json formatter
|
|
||||||
* output colour support for windows
|
|
||||||
* the field sort function is now configurable for text formatter
|
|
||||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
|
||||||
|
|
||||||
# 1.0.6
|
|
||||||
|
|
||||||
This new release introduces:
|
|
||||||
* a new api WithTime which allows to easily force the time of the log entry
|
|
||||||
which is mostly useful for logger wrapper
|
|
||||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
|
||||||
a new configuration field of the json formatter in order to put all the fields
|
|
||||||
in a nested dictionnary
|
|
||||||
* a new SetOutput method in the Logger
|
|
||||||
* a new configuration of the textformatter to configure the name of the default keys
|
|
||||||
* a new configuration of the text formatter to disable the level truncation
|
|
||||||
|
|
||||||
# 1.0.5
|
|
||||||
|
|
||||||
* Fix hooks race (#707)
|
|
||||||
* Fix panic deadlock (#695)
|
|
||||||
|
|
||||||
# 1.0.4
|
|
||||||
|
|
||||||
* Fix race when adding hooks (#612)
|
|
||||||
* Fix terminal check in AppEngine (#635)
|
|
||||||
|
|
||||||
# 1.0.3
|
|
||||||
|
|
||||||
* Replace example files with testable examples
|
|
||||||
|
|
||||||
# 1.0.2
|
|
||||||
|
|
||||||
* bug: quote non-string values in text formatter (#583)
|
|
||||||
* Make (*Logger) SetLevel a public method
|
|
||||||
|
|
||||||
# 1.0.1
|
|
||||||
|
|
||||||
* bug: fix escaping in text formatter (#575)
|
|
||||||
|
|
||||||
# 1.0.0
|
|
||||||
|
|
||||||
* Officially changed name to lower-case
|
|
||||||
* bug: colors on Windows 10 (#541)
|
|
||||||
* bug: fix race in accessing level (#512)
|
|
||||||
|
|
||||||
# 0.11.5
|
|
||||||
|
|
||||||
* feature: add writer and writerlevel to entry (#372)
|
|
||||||
|
|
||||||
# 0.11.4
|
|
||||||
|
|
||||||
* bug: fix undefined variable on solaris (#493)
|
|
||||||
|
|
||||||
# 0.11.3
|
|
||||||
|
|
||||||
* formatter: configure quoting of empty values (#484)
|
|
||||||
* formatter: configure quoting character (default is `"`) (#484)
|
|
||||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
|
||||||
|
|
||||||
# 0.11.2
|
|
||||||
|
|
||||||
* bug: fix windows terminal detection (#476)
|
|
||||||
|
|
||||||
# 0.11.1
|
|
||||||
|
|
||||||
* bug: fix tty detection with custom out (#471)
|
|
||||||
|
|
||||||
# 0.11.0
|
|
||||||
|
|
||||||
* performance: Use bufferpool to allocate (#370)
|
|
||||||
* terminal: terminal detection for app-engine (#343)
|
|
||||||
* feature: exit handler (#375)
|
|
||||||
|
|
||||||
# 0.10.0
|
|
||||||
|
|
||||||
* feature: Add a test hook (#180)
|
|
||||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
|
||||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
|
||||||
* performance: avoid re-allocations on `WithFields` (#335)
|
|
||||||
|
|
||||||
# 0.9.0
|
|
||||||
|
|
||||||
* logrus/text_formatter: don't emit empty msg
|
|
||||||
* logrus/hooks/airbrake: move out of main repository
|
|
||||||
* logrus/hooks/sentry: move out of main repository
|
|
||||||
* logrus/hooks/papertrail: move out of main repository
|
|
||||||
* logrus/hooks/bugsnag: move out of main repository
|
|
||||||
* logrus/core: run tests with `-race`
|
|
||||||
* logrus/core: detect TTY based on `stderr`
|
|
||||||
* logrus/core: support `WithError` on logger
|
|
||||||
* logrus/core: Solaris support
|
|
||||||
|
|
||||||
# 0.8.7
|
|
||||||
|
|
||||||
* logrus/core: fix possible race (#216)
|
|
||||||
* logrus/doc: small typo fixes and doc improvements
|
|
||||||
|
|
||||||
|
|
||||||
# 0.8.6
|
|
||||||
|
|
||||||
* hooks/raven: allow passing an initialized client
|
|
||||||
|
|
||||||
# 0.8.5
|
|
||||||
|
|
||||||
* logrus/core: revert #208
|
|
||||||
|
|
||||||
# 0.8.4
|
|
||||||
|
|
||||||
* formatter/text: fix data race (#218)
|
|
||||||
|
|
||||||
# 0.8.3
|
|
||||||
|
|
||||||
* logrus/core: fix entry log level (#208)
|
|
||||||
* logrus/core: improve performance of text formatter by 40%
|
|
||||||
* logrus/core: expose `LevelHooks` type
|
|
||||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
|
||||||
* formatter/text: print structs more verbosely
|
|
||||||
|
|
||||||
# 0.8.2
|
|
||||||
|
|
||||||
* logrus: fix more Fatal family functions
|
|
||||||
|
|
||||||
# 0.8.1
|
|
||||||
|
|
||||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
|
||||||
|
|
||||||
# 0.8.0
|
|
||||||
|
|
||||||
* logrus: defaults to stderr instead of stdout
|
|
||||||
* hooks/sentry: add special field for `*http.Request`
|
|
||||||
* formatter/text: ignore Windows for colors
|
|
||||||
|
|
||||||
# 0.7.3
|
|
||||||
|
|
||||||
* formatter/\*: allow configuration of timestamp layout
|
|
||||||
|
|
||||||
# 0.7.2
|
|
||||||
|
|
||||||
* formatter/text: Add configuration option for time format (#158)
|
|
||||||
515
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
515
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
@@ -1,515 +0,0 @@
|
|||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [](https://travis-ci.org/sirupsen/logrus) [](https://pkg.go.dev/github.com/sirupsen/logrus)
|
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
|
||||||
the standard library logger.
|
|
||||||
|
|
||||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
|
||||||
simply too hard to do in a way that won't break many people's projects, which is
|
|
||||||
the last thing you want from your Logging library (again...).
|
|
||||||
|
|
||||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
|
||||||
security, (backwards compatible) bug fixes, and performance (where we are
|
|
||||||
limited by the interface).
|
|
||||||
|
|
||||||
I believe Logrus' biggest contribution is to have played a part in today's
|
|
||||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
|
||||||
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
|
||||||
community has built those independently. Many fantastic alternatives have sprung
|
|
||||||
up. Logrus would look like those, had it been re-designed with what we know
|
|
||||||
about structured logging in Go today. Check out, for example,
|
|
||||||
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
|
||||||
|
|
||||||
[zerolog]: https://github.com/rs/zerolog
|
|
||||||
[zap]: https://github.com/uber-go/zap
|
|
||||||
[apex]: https://github.com/apex/log
|
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
|
||||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
|
||||||
this caused issues in the community and we needed a standard. Some environments
|
|
||||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
|
||||||
Everything using `logrus` will need to use the lower-case:
|
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
|
||||||
|
|
||||||
To fix Glide, see [these
|
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
|
||||||
For an in-depth explanation of the casing issue, see [this
|
|
||||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
|
||||||
plain text):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
|
||||||
or Splunk:
|
|
||||||
|
|
||||||
```text
|
|
||||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
|
||||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
|
||||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
||||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
||||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
||||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
|
||||||
attached, the output is compatible with the
|
|
||||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
|
||||||
```
|
|
||||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableColors: true,
|
|
||||||
FullTimestamp: true,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logging Method Name
|
|
||||||
|
|
||||||
If you wish to add the calling method as a field, instruct the logger via:
|
|
||||||
```go
|
|
||||||
log.SetReportCaller(true)
|
|
||||||
```
|
|
||||||
This adds the caller as 'method' like so:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
|
||||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
|
||||||
```
|
|
||||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
|
||||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
|
||||||
environment via benchmarks:
|
|
||||||
```
|
|
||||||
go test -bench=.*CallerTracing
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Case-sensitivity
|
|
||||||
|
|
||||||
The organization's name was changed to lower-case--and this will not be changed
|
|
||||||
back. If you are getting import conflicts due to case sensitivity, please use
|
|
||||||
the lower-case import: `github.com/sirupsen/logrus`.
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
|
||||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
|
||||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
|
||||||
want:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr
|
|
||||||
// Can be any io.Writer, see below for File example
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
|
|
||||||
// A common pattern is to re-use fields between logging statements by re-using
|
|
||||||
// the logrus.Entry returned from WithFields()
|
|
||||||
contextLogger := log.WithFields(log.Fields{
|
|
||||||
"common": "this is a common field",
|
|
||||||
"other": "I also should be logged always",
|
|
||||||
})
|
|
||||||
|
|
||||||
contextLogger.Info("I'll be logged with common and other field")
|
|
||||||
contextLogger.Info("Me too")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more advanced usage such as logging to multiple locations from the same
|
|
||||||
application, you can also create an instance of the `logrus` Logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a new instance of the logger. You can have any number of instances.
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// The API for setting attributes is a little different than the package level
|
|
||||||
// exported logger. See Godoc.
|
|
||||||
log.Out = os.Stdout
|
|
||||||
|
|
||||||
// You could set this to any `io.Writer` such as a file
|
|
||||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
||||||
// if err == nil {
|
|
||||||
// log.Out = file
|
|
||||||
// } else {
|
|
||||||
// log.Info("Failed to log to file, using default stderr")
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fields
|
|
||||||
|
|
||||||
Logrus encourages careful, structured logging through logging fields instead of
|
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
|
||||||
to send event %s to topic %s with key %d")`, you should log the much more
|
|
||||||
discoverable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"event": event,
|
|
||||||
"topic": topic,
|
|
||||||
"key": key,
|
|
||||||
}).Fatal("Failed to send event")
|
|
||||||
```
|
|
||||||
|
|
||||||
We've found this API forces you to think about logging in a way that produces
|
|
||||||
much more useful logging messages. We've been in countless situations where just
|
|
||||||
a single added field to a log statement that was already there would've saved us
|
|
||||||
hours. The `WithFields` call is optional.
|
|
||||||
|
|
||||||
In general, with Logrus using any of the `printf`-family functions should be
|
|
||||||
seen as a hint you should add a field, however, you can still use the
|
|
||||||
`printf`-family functions with Logrus.
|
|
||||||
|
|
||||||
#### Default Fields
|
|
||||||
|
|
||||||
Often it's helpful to have fields _always_ attached to log statements in an
|
|
||||||
application or parts of one. For example, you may want to always log the
|
|
||||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
|
||||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
|
||||||
every line, you can create a `logrus.Entry` to pass around instead:
|
|
||||||
|
|
||||||
```go
|
|
||||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
|
||||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
|
||||||
requestLogger.Warn("something not great happened")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Hooks
|
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
|
||||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
|
||||||
multiple places simultaneously, e.g. syslog.
|
|
||||||
|
|
||||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
|
||||||
`init`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
|
||||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
|
||||||
"log/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
|
||||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
|
||||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
|
||||||
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to connect to local syslog daemon")
|
|
||||||
} else {
|
|
||||||
log.AddHook(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
|
||||||
|
|
||||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
|
||||||
|
|
||||||
|
|
||||||
#### Level logging
|
|
||||||
|
|
||||||
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.Trace("Something very low level.")
|
|
||||||
log.Debug("Useful debugging information.")
|
|
||||||
log.Info("Something noteworthy happened!")
|
|
||||||
log.Warn("You should probably take a look at this.")
|
|
||||||
log.Error("Something failed but I'm not quitting.")
|
|
||||||
// Calls os.Exit(1) after logging
|
|
||||||
log.Fatal("Bye.")
|
|
||||||
// Calls panic() after logging
|
|
||||||
log.Panic("I'm bailing.")
|
|
||||||
```
|
|
||||||
|
|
||||||
You can set the logging level on a `Logger`, then it will only log entries with
|
|
||||||
that severity or anything above it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
```
|
|
||||||
|
|
||||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
|
||||||
environment if your application has that.
|
|
||||||
|
|
||||||
Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging).
|
|
||||||
|
|
||||||
#### Entries
|
|
||||||
|
|
||||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
|
||||||
automatically added to all logging events:
|
|
||||||
|
|
||||||
1. `time`. The timestamp when the entry was created.
|
|
||||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
|
||||||
the `AddFields` call. E.g. `Failed to send event.`
|
|
||||||
3. `level`. The logging level. E.g. `info`.
|
|
||||||
|
|
||||||
#### Environments
|
|
||||||
|
|
||||||
Logrus has no notion of environment.
|
|
||||||
|
|
||||||
If you wish for hooks and formatters to only be used in specific environments,
|
|
||||||
you should handle that yourself. For example, if your application has a global
|
|
||||||
variable `Environment`, which is a string representation of the environment you
|
|
||||||
could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// do something here to set environment depending on an environment variable
|
|
||||||
// or command-line flag
|
|
||||||
if Environment == "production" {
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
} else {
|
|
||||||
// The TextFormatter is default, you don't actually have to do this.
|
|
||||||
log.SetFormatter(&log.TextFormatter{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration is how `logrus` was intended to be used, but JSON in
|
|
||||||
production is mostly only useful if you do log aggregation with tools like
|
|
||||||
Splunk or Logstash.
|
|
||||||
|
|
||||||
#### Formatters
|
|
||||||
|
|
||||||
The built-in logging formatters are:
|
|
||||||
|
|
||||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
|
||||||
without colors.
|
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
|
||||||
`DisableColors` field to `true`. For Windows, see
|
|
||||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
|
||||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
|
||||||
truncation set the `DisableLevelTruncation` field to `true`.
|
|
||||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
|
||||||
|
|
||||||
Third party logging formatters:
|
|
||||||
|
|
||||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
|
||||||
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
|
||||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
|
||||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
|
||||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
|
||||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
|
||||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
|
||||||
|
|
||||||
You can define your formatter by implementing the `Formatter` interface,
|
|
||||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
|
||||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
|
||||||
default ones (see Entries section above):
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MyJSONFormatter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFormatter(new(MyJSONFormatter))
|
|
||||||
|
|
||||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
// Note this doesn't include Time, Level and Message which are available on
|
|
||||||
// the Entry. Consult `godoc` on information about those fields or read the
|
|
||||||
// source of the official loggers.
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logger as an `io.Writer`
|
|
||||||
|
|
||||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
|
||||||
|
|
||||||
```go
|
|
||||||
w := logger.Writer()
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
srv := http.Server{
|
|
||||||
// create a stdlib log.Logger that writes to
|
|
||||||
// logrus.Logger.
|
|
||||||
ErrorLog: log.New(w, "", 0),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
|
||||||
and hooks. The level for those entries is `info`.
|
|
||||||
|
|
||||||
This means that we can override the standard library logger easily:
|
|
||||||
|
|
||||||
```go
|
|
||||||
logger := logrus.New()
|
|
||||||
logger.Formatter = &logrus.JSONFormatter{}
|
|
||||||
|
|
||||||
// Use logrus for standard log output
|
|
||||||
// Note that `log` here references stdlib's log
|
|
||||||
// Not logrus imported under the name `log`.
|
|
||||||
log.SetOutput(logger.Writer())
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Rotation
|
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
|
||||||
external program (like `logrotate(8)`) that can compress and delete old log
|
|
||||||
entries. It should not be a feature of the application-level logger.
|
|
||||||
|
|
||||||
#### Tools
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
| ---- | ----------- |
|
|
||||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|
||||||
|
|
||||||
#### Testing
|
|
||||||
|
|
||||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
|
||||||
|
|
||||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
|
||||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
|
||||||
|
|
||||||
```go
|
|
||||||
import(
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSomething(t*testing.T){
|
|
||||||
logger, hook := test.NewNullLogger()
|
|
||||||
logger.Error("Helloerror")
|
|
||||||
|
|
||||||
assert.Equal(t, 1, len(hook.Entries))
|
|
||||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
|
||||||
|
|
||||||
hook.Reset()
|
|
||||||
assert.Nil(t, hook.LastEntry())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fatal handlers
|
|
||||||
|
|
||||||
Logrus can register one or more functions that will be called when any `fatal`
|
|
||||||
level message is logged. The registered handlers will be executed before
|
|
||||||
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
|
||||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
handler := func() {
|
|
||||||
// gracefully shutdown something...
|
|
||||||
}
|
|
||||||
logrus.RegisterExitHandler(handler)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Thread safety
|
|
||||||
|
|
||||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
|
||||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
|
||||||
|
|
||||||
Situation when locking is not needed includes:
|
|
||||||
|
|
||||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
|
||||||
|
|
||||||
* Writing to logger.Out is already thread-safe, for example:
|
|
||||||
|
|
||||||
1) logger.Out is protected by locks.
|
|
||||||
|
|
||||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
|
||||||
|
|
||||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
||||||
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
@@ -1,76 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
|
||||||
// https://github.com/tebeka/atexit package governed by the following license:
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
// this software and associated documentation files (the "Software"), to deal in
|
|
||||||
// the Software without restriction, including without limitation the rights to
|
|
||||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
// subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in all
|
|
||||||
// copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var handlers = []func(){}
|
|
||||||
|
|
||||||
func runHandler(handler func()) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHandlers() {
|
|
||||||
for _, handler := range handlers {
|
|
||||||
runHandler(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
|
||||||
func Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
|
||||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
|
||||||
// any Fatal log entry is made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func RegisterExitHandler(handler func()) {
|
|
||||||
handlers = append(handlers, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
|
||||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
|
||||||
// any Fatal log entry is made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func DeferExitHandler(handler func()) {
|
|
||||||
handlers = append([]func(){handler}, handlers...)
|
|
||||||
}
|
|
||||||
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
@@ -1,14 +0,0 @@
|
|||||||
version: "{build}"
|
|
||||||
platform: x64
|
|
||||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
|
||||||
environment:
|
|
||||||
GOPATH: c:\gopath
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
install:
|
|
||||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
|
||||||
- go version
|
|
||||||
build_script:
|
|
||||||
- go get -t
|
|
||||||
- go test
|
|
||||||
43
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
43
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
@@ -1,43 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bufferPool BufferPool
|
|
||||||
)
|
|
||||||
|
|
||||||
type BufferPool interface {
|
|
||||||
Put(*bytes.Buffer)
|
|
||||||
Get() *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultPool struct {
|
|
||||||
pool *sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
|
||||||
p.pool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *defaultPool) Get() *bytes.Buffer {
|
|
||||||
return p.pool.Get().(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBufferPool allows to replace the default logrus buffer pool
|
|
||||||
// to better meets the specific needs of an application.
|
|
||||||
func SetBufferPool(bp BufferPool) {
|
|
||||||
bufferPool = bp
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
SetBufferPool(&defaultPool{
|
|
||||||
pool: &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
|
||||||
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"number": 1,
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
|
|
||||||
Output:
|
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
|
||||||
|
|
||||||
For a full guide visit https://github.com/sirupsen/logrus
|
|
||||||
*/
|
|
||||||
package logrus
|
|
||||||
442
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
442
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
@@ -1,442 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
|
|
||||||
// qualified package name, cached at first use
|
|
||||||
logrusPackage string
|
|
||||||
|
|
||||||
// Positions in the call stack when tracing to report the calling method
|
|
||||||
minimumCallerDepth int
|
|
||||||
|
|
||||||
// Used for caller information initialisation
|
|
||||||
callerInitOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maximumCallerDepth int = 25
|
|
||||||
knownLogrusFrames int = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// start at the bottom of the stack before the package-name cache is primed
|
|
||||||
minimumCallerDepth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the key when adding errors using WithError.
|
|
||||||
var ErrorKey = "error"
|
|
||||||
|
|
||||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
|
||||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
|
||||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
|
||||||
// reused and passed around as much as you wish to avoid field duplication.
|
|
||||||
type Entry struct {
|
|
||||||
Logger *Logger
|
|
||||||
|
|
||||||
// Contains all the fields set by the user.
|
|
||||||
Data Fields
|
|
||||||
|
|
||||||
// Time at which the log entry was created
|
|
||||||
Time time.Time
|
|
||||||
|
|
||||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
|
||||||
Level Level
|
|
||||||
|
|
||||||
// Calling method, with package name
|
|
||||||
Caller *runtime.Frame
|
|
||||||
|
|
||||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
Message string
|
|
||||||
|
|
||||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
|
||||||
Buffer *bytes.Buffer
|
|
||||||
|
|
||||||
// Contains the context set by the user. Useful for hook processing etc.
|
|
||||||
Context context.Context
|
|
||||||
|
|
||||||
// err may contain a field formatting error
|
|
||||||
err string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
Logger: logger,
|
|
||||||
// Default is three fields, plus one optional. Give a little extra room.
|
|
||||||
Data: make(Fields, 6),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Dup() *Entry {
|
|
||||||
data := make(Fields, len(entry.Data))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the bytes representation of this entry from the formatter.
|
|
||||||
func (entry *Entry) Bytes() ([]byte, error) {
|
|
||||||
return entry.Logger.Formatter.Format(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the string representation from the reader and ultimately the
|
|
||||||
// formatter.
|
|
||||||
func (entry *Entry) String() (string, error) {
|
|
||||||
serialized, err := entry.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
str := string(serialized)
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
|
||||||
func (entry *Entry) WithError(err error) *Entry {
|
|
||||||
return entry.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a context to the Entry.
|
|
||||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
dataCopy[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a single field to the Entry.
|
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
||||||
return entry.WithFields(Fields{key: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a map of fields to the Entry.
|
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
||||||
data := make(Fields, len(entry.Data)+len(fields))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
fieldErr := entry.err
|
|
||||||
for k, v := range fields {
|
|
||||||
isErrField := false
|
|
||||||
if t := reflect.TypeOf(v); t != nil {
|
|
||||||
switch {
|
|
||||||
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
|
||||||
isErrField = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isErrField {
|
|
||||||
tmp := fmt.Sprintf("can not add field %q", k)
|
|
||||||
if fieldErr != "" {
|
|
||||||
fieldErr = entry.err + ", " + tmp
|
|
||||||
} else {
|
|
||||||
fieldErr = tmp
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the Entry.
|
|
||||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
dataCopy[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPackageName reduces a fully qualified function name to the package name
|
|
||||||
// There really ought to be to be a better way...
|
|
||||||
func getPackageName(f string) string {
|
|
||||||
for {
|
|
||||||
lastPeriod := strings.LastIndex(f, ".")
|
|
||||||
lastSlash := strings.LastIndex(f, "/")
|
|
||||||
if lastPeriod > lastSlash {
|
|
||||||
f = f[:lastPeriod]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCaller retrieves the name of the first non-logrus calling function
|
|
||||||
func getCaller() *runtime.Frame {
|
|
||||||
// cache this package's fully-qualified name
|
|
||||||
callerInitOnce.Do(func() {
|
|
||||||
pcs := make([]uintptr, maximumCallerDepth)
|
|
||||||
_ = runtime.Callers(0, pcs)
|
|
||||||
|
|
||||||
// dynamic get the package name and the minimum caller depth
|
|
||||||
for i := 0; i < maximumCallerDepth; i++ {
|
|
||||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
|
||||||
if strings.Contains(funcName, "getCaller") {
|
|
||||||
logrusPackage = getPackageName(funcName)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumCallerDepth = knownLogrusFrames
|
|
||||||
})
|
|
||||||
|
|
||||||
// Restrict the lookback frames to avoid runaway lookups
|
|
||||||
pcs := make([]uintptr, maximumCallerDepth)
|
|
||||||
depth := runtime.Callers(minimumCallerDepth, pcs)
|
|
||||||
frames := runtime.CallersFrames(pcs[:depth])
|
|
||||||
|
|
||||||
for f, again := frames.Next(); again; f, again = frames.Next() {
|
|
||||||
pkg := getPackageName(f.Function)
|
|
||||||
|
|
||||||
// If the caller isn't part of this package, we're done
|
|
||||||
if pkg != logrusPackage {
|
|
||||||
return &f //nolint:scopelint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here, we failed to find the caller's context
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry Entry) HasCaller() (has bool) {
|
|
||||||
return entry.Logger != nil &&
|
|
||||||
entry.Logger.ReportCaller &&
|
|
||||||
entry.Caller != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) log(level Level, msg string) {
|
|
||||||
var buffer *bytes.Buffer
|
|
||||||
|
|
||||||
newEntry := entry.Dup()
|
|
||||||
|
|
||||||
if newEntry.Time.IsZero() {
|
|
||||||
newEntry.Time = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
newEntry.Level = level
|
|
||||||
newEntry.Message = msg
|
|
||||||
|
|
||||||
newEntry.Logger.mu.Lock()
|
|
||||||
reportCaller := newEntry.Logger.ReportCaller
|
|
||||||
bufPool := newEntry.getBufferPool()
|
|
||||||
newEntry.Logger.mu.Unlock()
|
|
||||||
|
|
||||||
if reportCaller {
|
|
||||||
newEntry.Caller = getCaller()
|
|
||||||
}
|
|
||||||
|
|
||||||
newEntry.fireHooks()
|
|
||||||
buffer = bufPool.Get()
|
|
||||||
defer func() {
|
|
||||||
newEntry.Buffer = nil
|
|
||||||
buffer.Reset()
|
|
||||||
bufPool.Put(buffer)
|
|
||||||
}()
|
|
||||||
buffer.Reset()
|
|
||||||
newEntry.Buffer = buffer
|
|
||||||
|
|
||||||
newEntry.write()
|
|
||||||
|
|
||||||
newEntry.Buffer = nil
|
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
|
||||||
// directly here.
|
|
||||||
if level <= PanicLevel {
|
|
||||||
panic(newEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) getBufferPool() (pool BufferPool) {
|
|
||||||
if entry.Logger.BufferPool != nil {
|
|
||||||
return entry.Logger.BufferPool
|
|
||||||
}
|
|
||||||
return bufferPool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) fireHooks() {
|
|
||||||
var tmpHooks LevelHooks
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
|
||||||
for k, v := range entry.Logger.Hooks {
|
|
||||||
tmpHooks[k] = v
|
|
||||||
}
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
|
|
||||||
err := tmpHooks.Fire(entry.Level, entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) write() {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log will log a message at the level given as parameter.
|
|
||||||
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
|
||||||
// For this behaviour Entry.Panic or Entry.Fatal should be used instead.
|
|
||||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.log(level, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Trace(args ...interface{}) {
|
|
||||||
entry.Log(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
|
||||||
entry.Log(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Print(args ...interface{}) {
|
|
||||||
entry.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
|
||||||
entry.Log(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
|
||||||
entry.Log(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warning(args ...interface{}) {
|
|
||||||
entry.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
|
||||||
entry.Log(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
|
||||||
entry.Log(FatalLevel, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
|
||||||
entry.Log(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Printf family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.Log(level, fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
|
||||||
entry.Logf(TraceLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(DebugLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
||||||
entry.Logf(InfoLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(WarnLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(ErrorLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(FatalLevel, format, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(PanicLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Println family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.Log(level, entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Traceln(args ...interface{}) {
|
|
||||||
entry.Logln(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
|
||||||
entry.Logln(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
|
||||||
entry.Logln(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Println(args ...interface{}) {
|
|
||||||
entry.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
|
||||||
entry.Logln(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningln(args ...interface{}) {
|
|
||||||
entry.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
|
||||||
entry.Logln(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
|
||||||
entry.Logln(FatalLevel, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
|
||||||
entry.Logln(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
|
||||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
|
||||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
|
||||||
// string allocation, we do the simplest thing.
|
|
||||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
||||||
msg := fmt.Sprintln(args...)
|
|
||||||
return msg[:len(msg)-1]
|
|
||||||
}
|
|
||||||
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
@@ -1,270 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// std is the name of the standard logger in stdlib `log`
|
|
||||||
std = New()
|
|
||||||
)
|
|
||||||
|
|
||||||
func StandardLogger() *Logger {
|
|
||||||
return std
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the standard logger output.
|
|
||||||
func SetOutput(out io.Writer) {
|
|
||||||
std.SetOutput(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the standard logger formatter.
|
|
||||||
func SetFormatter(formatter Formatter) {
|
|
||||||
std.SetFormatter(formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReportCaller sets whether the standard logger will include the calling
|
|
||||||
// method as a field.
|
|
||||||
func SetReportCaller(include bool) {
|
|
||||||
std.SetReportCaller(include)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
|
||||||
func SetLevel(level Level) {
|
|
||||||
std.SetLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
|
||||||
func GetLevel() Level {
|
|
||||||
return std.GetLevel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
|
||||||
func IsLevelEnabled(level Level) bool {
|
|
||||||
return std.IsLevelEnabled(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
|
||||||
func AddHook(hook Hook) {
|
|
||||||
std.AddHook(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
|
||||||
func WithError(err error) *Entry {
|
|
||||||
return std.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
|
||||||
func WithContext(ctx context.Context) *Entry {
|
|
||||||
return std.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
|
||||||
// it. If you want multiple fields, use `WithFields`.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithField(key string, value interface{}) *Entry {
|
|
||||||
return std.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFields creates an entry from the standard logger and adds multiple
|
|
||||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
|
||||||
// once for each field.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithFields(fields Fields) *Entry {
|
|
||||||
return std.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTime creates an entry from the standard logger and overrides the time of
|
|
||||||
// logs generated with it.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithTime(t time.Time) *Entry {
|
|
||||||
return std.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace logs a message at level Trace on the standard logger.
|
|
||||||
func Trace(args ...interface{}) {
|
|
||||||
std.Trace(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
|
||||||
func Debug(args ...interface{}) {
|
|
||||||
std.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print logs a message at level Info on the standard logger.
|
|
||||||
func Print(args ...interface{}) {
|
|
||||||
std.Print(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at level Info on the standard logger.
|
|
||||||
func Info(args ...interface{}) {
|
|
||||||
std.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at level Warn on the standard logger.
|
|
||||||
func Warn(args ...interface{}) {
|
|
||||||
std.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning logs a message at level Warn on the standard logger.
|
|
||||||
func Warning(args ...interface{}) {
|
|
||||||
std.Warning(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at level Error on the standard logger.
|
|
||||||
func Error(args ...interface{}) {
|
|
||||||
std.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic logs a message at level Panic on the standard logger.
|
|
||||||
func Panic(args ...interface{}) {
|
|
||||||
std.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatal(args ...interface{}) {
|
|
||||||
std.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
|
||||||
func TraceFn(fn LogFunction) {
|
|
||||||
std.TraceFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
|
||||||
func DebugFn(fn LogFunction) {
|
|
||||||
std.DebugFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
|
||||||
func PrintFn(fn LogFunction) {
|
|
||||||
std.PrintFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
|
||||||
func InfoFn(fn LogFunction) {
|
|
||||||
std.InfoFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
|
||||||
func WarnFn(fn LogFunction) {
|
|
||||||
std.WarnFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
|
||||||
func WarningFn(fn LogFunction) {
|
|
||||||
std.WarningFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
|
||||||
func ErrorFn(fn LogFunction) {
|
|
||||||
std.ErrorFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
|
||||||
func PanicFn(fn LogFunction) {
|
|
||||||
std.PanicFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func FatalFn(fn LogFunction) {
|
|
||||||
std.FatalFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tracef logs a message at level Trace on the standard logger.
|
|
||||||
func Tracef(format string, args ...interface{}) {
|
|
||||||
std.Tracef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
std.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf logs a message at level Info on the standard logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
std.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs a message at level Info on the standard logger.
|
|
||||||
func Infof(format string, args ...interface{}) {
|
|
||||||
std.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnf logs a message at level Warn on the standard logger.
|
|
||||||
func Warnf(format string, args ...interface{}) {
|
|
||||||
std.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs a message at level Warn on the standard logger.
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
|
||||||
std.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs a message at level Error on the standard logger.
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
|
||||||
std.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf logs a message at level Panic on the standard logger.
|
|
||||||
func Panicf(format string, args ...interface{}) {
|
|
||||||
std.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalf(format string, args ...interface{}) {
|
|
||||||
std.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traceln logs a message at level Trace on the standard logger.
|
|
||||||
func Traceln(args ...interface{}) {
|
|
||||||
std.Traceln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
|
||||||
func Debugln(args ...interface{}) {
|
|
||||||
std.Debugln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println logs a message at level Info on the standard logger.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
std.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infoln logs a message at level Info on the standard logger.
|
|
||||||
func Infoln(args ...interface{}) {
|
|
||||||
std.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnln logs a message at level Warn on the standard logger.
|
|
||||||
func Warnln(args ...interface{}) {
|
|
||||||
std.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningln logs a message at level Warn on the standard logger.
|
|
||||||
func Warningln(args ...interface{}) {
|
|
||||||
std.Warningln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorln logs a message at level Error on the standard logger.
|
|
||||||
func Errorln(args ...interface{}) {
|
|
||||||
std.Errorln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicln logs a message at level Panic on the standard logger.
|
|
||||||
func Panicln(args ...interface{}) {
|
|
||||||
std.Panicln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalln(args ...interface{}) {
|
|
||||||
std.Fatalln(args...)
|
|
||||||
}
|
|
||||||
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
@@ -1,78 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Default key names for the default fields
|
|
||||||
const (
|
|
||||||
defaultTimestampFormat = time.RFC3339
|
|
||||||
FieldKeyMsg = "msg"
|
|
||||||
FieldKeyLevel = "level"
|
|
||||||
FieldKeyTime = "time"
|
|
||||||
FieldKeyLogrusError = "logrus_error"
|
|
||||||
FieldKeyFunc = "func"
|
|
||||||
FieldKeyFile = "file"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
|
||||||
//
|
|
||||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
|
||||||
// * `entry.Data["time"]`. The timestamp.
|
|
||||||
// * `entry.Data["level"]. The level the entry was logged at.
|
|
||||||
//
|
|
||||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
|
||||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
|
||||||
// logged to `logger.Out`.
|
|
||||||
type Formatter interface {
|
|
||||||
Format(*Entry) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
|
||||||
// dumping it. If this code wasn't there doing:
|
|
||||||
//
|
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
|
||||||
//
|
|
||||||
// Would just silently drop the user provided level. Instead with this code
|
|
||||||
// it'll logged as:
|
|
||||||
//
|
|
||||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
|
||||||
//
|
|
||||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
|
||||||
// avoid code duplication between the two default formatters.
|
|
||||||
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
|
||||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
|
||||||
if t, ok := data[timeKey]; ok {
|
|
||||||
data["fields."+timeKey] = t
|
|
||||||
delete(data, timeKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
|
||||||
if m, ok := data[msgKey]; ok {
|
|
||||||
data["fields."+msgKey] = m
|
|
||||||
delete(data, msgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
|
||||||
if l, ok := data[levelKey]; ok {
|
|
||||||
data["fields."+levelKey] = l
|
|
||||||
delete(data, levelKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
|
||||||
if l, ok := data[logrusErrKey]; ok {
|
|
||||||
data["fields."+logrusErrKey] = l
|
|
||||||
delete(data, logrusErrKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If reportCaller is not set, 'func' will not conflict.
|
|
||||||
if reportCaller {
|
|
||||||
funcKey := fieldMap.resolve(FieldKeyFunc)
|
|
||||||
if l, ok := data[funcKey]; ok {
|
|
||||||
data["fields."+funcKey] = l
|
|
||||||
}
|
|
||||||
fileKey := fieldMap.resolve(FieldKeyFile)
|
|
||||||
if l, ok := data[fileKey]; ok {
|
|
||||||
data["fields."+fileKey] = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
@@ -1,34 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
// A hook to be fired when logging on the logging levels returned from
|
|
||||||
// `Levels()` on your implementation of the interface. Note that this is not
|
|
||||||
// fired in a goroutine or a channel with workers, you should handle such
|
|
||||||
// functionality yourself if your call is non-blocking and you don't wish for
|
|
||||||
// the logging calls for levels returned from `Levels()` to block.
|
|
||||||
type Hook interface {
|
|
||||||
Levels() []Level
|
|
||||||
Fire(*Entry) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal type for storing the hooks on a logger instance.
|
|
||||||
type LevelHooks map[Level][]Hook
|
|
||||||
|
|
||||||
// Add a hook to an instance of logger. This is called with
|
|
||||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
|
||||||
func (hooks LevelHooks) Add(hook Hook) {
|
|
||||||
for _, level := range hook.Levels() {
|
|
||||||
hooks[level] = append(hooks[level], hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
|
||||||
// appropriate hooks for a log entry.
|
|
||||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
|
||||||
for _, hook := range hooks[level] {
|
|
||||||
if err := hook.Fire(entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
@@ -1,128 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldKey string
|
|
||||||
|
|
||||||
// FieldMap allows customization of the key names for default fields.
|
|
||||||
type FieldMap map[fieldKey]string
|
|
||||||
|
|
||||||
func (f FieldMap) resolve(key fieldKey) string {
|
|
||||||
if k, ok := f[key]; ok {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONFormatter formats logs into parsable json
|
|
||||||
type JSONFormatter struct {
|
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
|
||||||
// The format to use is the same than for time.Format or time.Parse from the standard
|
|
||||||
// library.
|
|
||||||
// The standard Library already provides a set of predefined format.
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// DisableHTMLEscape allows disabling html escaping in output
|
|
||||||
DisableHTMLEscape bool
|
|
||||||
|
|
||||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
|
||||||
DataKey string
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &JSONFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message",
|
|
||||||
// FieldKeyFunc: "@caller",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
FieldMap FieldMap
|
|
||||||
|
|
||||||
// CallerPrettyfier can be set by the user to modify the content
|
|
||||||
// of the function and file keys in the json data when ReportCaller is
|
|
||||||
// activated. If any of the returned value is the empty string the
|
|
||||||
// corresponding key will be removed from json fields.
|
|
||||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
|
||||||
|
|
||||||
// PrettyPrint will indent all json logs
|
|
||||||
PrettyPrint bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields, len(entry.Data)+4)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case error:
|
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
|
||||||
// https://github.com/sirupsen/logrus/issues/137
|
|
||||||
data[k] = v.Error()
|
|
||||||
default:
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.DataKey != "" {
|
|
||||||
newData := make(Fields, 4)
|
|
||||||
newData[f.DataKey] = data
|
|
||||||
data = newData
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.err != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
|
||||||
}
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
|
||||||
}
|
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
|
||||||
if entry.HasCaller() {
|
|
||||||
funcVal := entry.Caller.Function
|
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
}
|
|
||||||
if funcVal != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
|
||||||
}
|
|
||||||
if fileVal != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var b *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(b)
|
|
||||||
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
|
||||||
if f.PrettyPrint {
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
}
|
|
||||||
if err := encoder.Encode(data); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
417
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
417
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
@@ -1,417 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogFunction For big messages, it can be more efficient to pass a function
|
|
||||||
// and only call it if the log level is actually enables rather than
|
|
||||||
// generating the log message and then checking if the level is enabled
|
|
||||||
type LogFunction func() []interface{}
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
|
||||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
|
||||||
// something more adventurous, such as logging to Kafka.
|
|
||||||
Out io.Writer
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
|
||||||
Hooks LevelHooks
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
|
||||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
|
||||||
// formatters for examples.
|
|
||||||
Formatter Formatter
|
|
||||||
|
|
||||||
// Flag for whether to log caller info (off by default)
|
|
||||||
ReportCaller bool
|
|
||||||
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
|
||||||
// logged.
|
|
||||||
Level Level
|
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
|
||||||
mu MutexWrap
|
|
||||||
// Reusable empty entry
|
|
||||||
entryPool sync.Pool
|
|
||||||
// Function to exit the application, defaults to `os.Exit()`
|
|
||||||
ExitFunc exitFunc
|
|
||||||
// The buffer pool used to format the log. If it is nil, the default global
|
|
||||||
// buffer pool will be used.
|
|
||||||
BufferPool BufferPool
|
|
||||||
}
|
|
||||||
|
|
||||||
type exitFunc func(int)
|
|
||||||
|
|
||||||
type MutexWrap struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Lock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Unlock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Disable() {
|
|
||||||
mw.disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
|
||||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
|
||||||
// instantiate your own:
|
|
||||||
//
|
|
||||||
// var log = &logrus.Logger{
|
|
||||||
// Out: os.Stderr,
|
|
||||||
// Formatter: new(logrus.TextFormatter),
|
|
||||||
// Hooks: make(logrus.LevelHooks),
|
|
||||||
// Level: logrus.DebugLevel,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stderr,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(LevelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
ExitFunc: os.Exit,
|
|
||||||
ReportCaller: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) newEntry() *Entry {
|
|
||||||
entry, ok := logger.entryPool.Get().(*Entry)
|
|
||||||
if ok {
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
return NewEntry(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
|
||||||
entry.Data = map[string]interface{}{}
|
|
||||||
logger.entryPool.Put(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField allocates a new entry and adds a field to it.
|
|
||||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
|
||||||
// this new returned entry.
|
|
||||||
// If you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
|
||||||
// each `Field`.
|
|
||||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field to the log entry. All it does is call
|
|
||||||
// `WithError` for the given `error`.
|
|
||||||
func (logger *Logger) WithError(err error) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a context to the log entry.
|
|
||||||
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the log entry.
|
|
||||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Logf(level, format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
|
||||||
logger.Logf(TraceLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(DebugLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
logger.Logf(InfoLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Printf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(WarnLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
logger.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(ErrorLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(FatalLevel, format, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(PanicLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log will log a message at the level given as parameter.
|
|
||||||
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
|
||||||
// For this behaviour Logger.Panic or Logger.Fatal should be used instead.
|
|
||||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Log(level, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Log(level, fn()...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Trace(args ...interface{}) {
|
|
||||||
logger.Log(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
logger.Log(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
logger.Log(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Print(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
logger.Log(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
logger.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
logger.Log(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
logger.Log(FatalLevel, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
logger.Log(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
|
||||||
logger.LogFn(TraceLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) DebugFn(fn LogFunction) {
|
|
||||||
logger.LogFn(DebugLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) InfoFn(fn LogFunction) {
|
|
||||||
logger.LogFn(InfoLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) PrintFn(fn LogFunction) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Print(fn()...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WarnFn(fn LogFunction) {
|
|
||||||
logger.LogFn(WarnLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WarningFn(fn LogFunction) {
|
|
||||||
logger.WarnFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) ErrorFn(fn LogFunction) {
|
|
||||||
logger.LogFn(ErrorLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) FatalFn(fn LogFunction) {
|
|
||||||
logger.LogFn(FatalLevel, fn)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
|
||||||
logger.LogFn(PanicLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Logln(level, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Traceln(args ...interface{}) {
|
|
||||||
logger.Logln(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
logger.Logln(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
logger.Logln(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Println(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
logger.Logln(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
logger.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
logger.Logln(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
logger.Logln(FatalLevel, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
logger.Logln(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
if logger.ExitFunc == nil {
|
|
||||||
logger.ExitFunc = os.Exit
|
|
||||||
}
|
|
||||||
logger.ExitFunc(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
//When file is opened with appending mode, it's safe to
|
|
||||||
//write concurrently to a file (within 4k message on Linux).
|
|
||||||
//In these cases user can choose to disable the lock.
|
|
||||||
func (logger *Logger) SetNoLock() {
|
|
||||||
logger.mu.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) level() Level {
|
|
||||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the logger level.
|
|
||||||
func (logger *Logger) SetLevel(level Level) {
|
|
||||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the logger level.
|
|
||||||
func (logger *Logger) GetLevel() Level {
|
|
||||||
return logger.level()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the logger hooks.
|
|
||||||
func (logger *Logger) AddHook(hook Hook) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
|
||||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
|
||||||
return logger.level() >= level
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the logger formatter.
|
|
||||||
func (logger *Logger) SetFormatter(formatter Formatter) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Formatter = formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the logger output.
|
|
||||||
func (logger *Logger) SetOutput(output io.Writer) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Out = output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.ReportCaller = reportCaller
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
|
||||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
|
||||||
logger.mu.Lock()
|
|
||||||
oldHooks := logger.Hooks
|
|
||||||
logger.Hooks = hooks
|
|
||||||
logger.mu.Unlock()
|
|
||||||
return oldHooks
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBufferPool sets the logger buffer pool.
|
|
||||||
func (logger *Logger) SetBufferPool(pool BufferPool) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.BufferPool = pool
|
|
||||||
}
|
|
||||||
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
@@ -1,186 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fields type, used to pass to `WithFields`.
|
|
||||||
type Fields map[string]interface{}
|
|
||||||
|
|
||||||
// Level type
|
|
||||||
type Level uint32
|
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
|
||||||
func (level Level) String() string {
|
|
||||||
if b, err := level.MarshalText(); err == nil {
|
|
||||||
return string(b)
|
|
||||||
} else {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
|
||||||
func ParseLevel(lvl string) (Level, error) {
|
|
||||||
switch strings.ToLower(lvl) {
|
|
||||||
case "panic":
|
|
||||||
return PanicLevel, nil
|
|
||||||
case "fatal":
|
|
||||||
return FatalLevel, nil
|
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
case "trace":
|
|
||||||
return TraceLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var l Level
|
|
||||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (level *Level) UnmarshalText(text []byte) error {
|
|
||||||
l, err := ParseLevel(string(text))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*level = l
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (level Level) MarshalText() ([]byte, error) {
|
|
||||||
switch level {
|
|
||||||
case TraceLevel:
|
|
||||||
return []byte("trace"), nil
|
|
||||||
case DebugLevel:
|
|
||||||
return []byte("debug"), nil
|
|
||||||
case InfoLevel:
|
|
||||||
return []byte("info"), nil
|
|
||||||
case WarnLevel:
|
|
||||||
return []byte("warning"), nil
|
|
||||||
case ErrorLevel:
|
|
||||||
return []byte("error"), nil
|
|
||||||
case FatalLevel:
|
|
||||||
return []byte("fatal"), nil
|
|
||||||
case PanicLevel:
|
|
||||||
return []byte("panic"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A constant exposing all logging levels
|
|
||||||
var AllLevels = []Level{
|
|
||||||
PanicLevel,
|
|
||||||
FatalLevel,
|
|
||||||
ErrorLevel,
|
|
||||||
WarnLevel,
|
|
||||||
InfoLevel,
|
|
||||||
DebugLevel,
|
|
||||||
TraceLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the different logging levels. You can set the logging level to log
|
|
||||||
// on your instance of logger, obtained with `logrus.New()`.
|
|
||||||
const (
|
|
||||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
|
||||||
// message passed to Debug, Info, ...
|
|
||||||
PanicLevel Level = iota
|
|
||||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
|
||||||
// logging level is set to Panic.
|
|
||||||
FatalLevel
|
|
||||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
|
||||||
// Commonly used for hooks to send errors to an error tracking service.
|
|
||||||
ErrorLevel
|
|
||||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
|
||||||
WarnLevel
|
|
||||||
// InfoLevel level. General operational entries about what's going on inside the
|
|
||||||
// application.
|
|
||||||
InfoLevel
|
|
||||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
|
||||||
DebugLevel
|
|
||||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
|
||||||
TraceLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
|
||||||
var (
|
|
||||||
_ StdLogger = &log.Logger{}
|
|
||||||
_ StdLogger = &Entry{}
|
|
||||||
_ StdLogger = &Logger{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger is what your logrus-enabled library should take, that way
|
|
||||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
|
||||||
// interface, this is the closest we get, unfortunately.
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(...interface{})
|
|
||||||
Printf(string, ...interface{})
|
|
||||||
Println(...interface{})
|
|
||||||
|
|
||||||
Fatal(...interface{})
|
|
||||||
Fatalf(string, ...interface{})
|
|
||||||
Fatalln(...interface{})
|
|
||||||
|
|
||||||
Panic(...interface{})
|
|
||||||
Panicf(string, ...interface{})
|
|
||||||
Panicln(...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// The FieldLogger interface generalizes the Entry and Logger types
|
|
||||||
type FieldLogger interface {
|
|
||||||
WithField(key string, value interface{}) *Entry
|
|
||||||
WithFields(fields Fields) *Entry
|
|
||||||
WithError(err error) *Entry
|
|
||||||
|
|
||||||
Debugf(format string, args ...interface{})
|
|
||||||
Infof(format string, args ...interface{})
|
|
||||||
Printf(format string, args ...interface{})
|
|
||||||
Warnf(format string, args ...interface{})
|
|
||||||
Warningf(format string, args ...interface{})
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
Fatalf(format string, args ...interface{})
|
|
||||||
Panicf(format string, args ...interface{})
|
|
||||||
|
|
||||||
Debug(args ...interface{})
|
|
||||||
Info(args ...interface{})
|
|
||||||
Print(args ...interface{})
|
|
||||||
Warn(args ...interface{})
|
|
||||||
Warning(args ...interface{})
|
|
||||||
Error(args ...interface{})
|
|
||||||
Fatal(args ...interface{})
|
|
||||||
Panic(args ...interface{})
|
|
||||||
|
|
||||||
Debugln(args ...interface{})
|
|
||||||
Infoln(args ...interface{})
|
|
||||||
Println(args ...interface{})
|
|
||||||
Warnln(args ...interface{})
|
|
||||||
Warningln(args ...interface{})
|
|
||||||
Errorln(args ...interface{})
|
|
||||||
Fatalln(args ...interface{})
|
|
||||||
Panicln(args ...interface{})
|
|
||||||
|
|
||||||
// IsDebugEnabled() bool
|
|
||||||
// IsInfoEnabled() bool
|
|
||||||
// IsWarnEnabled() bool
|
|
||||||
// IsErrorEnabled() bool
|
|
||||||
// IsFatalEnabled() bool
|
|
||||||
// IsPanicEnabled() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
|
||||||
// here for consistancy. Do not use. Use Logger or Entry instead.
|
|
||||||
type Ext1FieldLogger interface {
|
|
||||||
FieldLogger
|
|
||||||
Tracef(format string, args ...interface{})
|
|
||||||
Trace(args ...interface{})
|
|
||||||
Traceln(args ...interface{})
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build darwin dragonfly freebsd netbsd openbsd
|
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TIOCGETA
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
// +build js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build js nacl plan9
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
return isTerminal(int(v.Fd()))
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build linux aix zos
|
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
// +build !appengine,!js,windows
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
handle := windows.Handle(v.Fd())
|
|
||||||
var mode uint32
|
|
||||||
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
||||||
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
339
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
339
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
@@ -1,339 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
red = 31
|
|
||||||
yellow = 33
|
|
||||||
blue = 36
|
|
||||||
gray = 37
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTimestamp time.Time
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
baseTimestamp = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextFormatter formats logs into text
|
|
||||||
type TextFormatter struct {
|
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
|
||||||
ForceColors bool
|
|
||||||
|
|
||||||
// Force disabling colors.
|
|
||||||
DisableColors bool
|
|
||||||
|
|
||||||
// Force quoting of all values
|
|
||||||
ForceQuote bool
|
|
||||||
|
|
||||||
// DisableQuote disables quoting for all values.
|
|
||||||
// DisableQuote will have a lower priority than ForceQuote.
|
|
||||||
// If both of them are set to true, quote will be forced on all values.
|
|
||||||
DisableQuote bool
|
|
||||||
|
|
||||||
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
|
|
||||||
EnvironmentOverrideColors bool
|
|
||||||
|
|
||||||
// Disable timestamp logging. useful when output is redirected to logging
|
|
||||||
// system that already adds timestamps.
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
|
||||||
// the time passed since beginning of execution.
|
|
||||||
FullTimestamp bool
|
|
||||||
|
|
||||||
// TimestampFormat to use for display when a full timestamp is printed.
|
|
||||||
// The format to use is the same than for time.Format or time.Parse from the standard
|
|
||||||
// library.
|
|
||||||
// The standard Library already provides a set of predefined format.
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// The fields are sorted by default for a consistent output. For applications
|
|
||||||
// that log extremely frequently and don't use the JSON formatter this may not
|
|
||||||
// be desired.
|
|
||||||
DisableSorting bool
|
|
||||||
|
|
||||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
|
||||||
SortingFunc func([]string)
|
|
||||||
|
|
||||||
// Disables the truncation of the level text to 4 characters.
|
|
||||||
DisableLevelTruncation bool
|
|
||||||
|
|
||||||
// PadLevelText Adds padding the level text so that all the levels output at the same length
|
|
||||||
// PadLevelText is a superset of the DisableLevelTruncation option
|
|
||||||
PadLevelText bool
|
|
||||||
|
|
||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
|
||||||
QuoteEmptyFields bool
|
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
|
||||||
isTerminal bool
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &TextFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message"}}
|
|
||||||
FieldMap FieldMap
|
|
||||||
|
|
||||||
// CallerPrettyfier can be set by the user to modify the content
|
|
||||||
// of the function and file keys in the data when ReportCaller is
|
|
||||||
// activated. If any of the returned value is the empty string the
|
|
||||||
// corresponding key will be removed from fields.
|
|
||||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
|
||||||
|
|
||||||
terminalInitOnce sync.Once
|
|
||||||
|
|
||||||
// The max length of the level text, generated dynamically on init
|
|
||||||
levelTextMaxLength int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
|
||||||
if entry.Logger != nil {
|
|
||||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
|
||||||
}
|
|
||||||
// Get the max length of the level text
|
|
||||||
for _, level := range AllLevels {
|
|
||||||
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
|
||||||
if levelTextLength > f.levelTextMaxLength {
|
|
||||||
f.levelTextMaxLength = levelTextLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) isColored() bool {
|
|
||||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
|
||||||
|
|
||||||
if f.EnvironmentOverrideColors {
|
|
||||||
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
|
||||||
case ok && force != "0":
|
|
||||||
isColored = true
|
|
||||||
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
|
||||||
isColored = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isColored && !f.DisableColors
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
|
||||||
keys := make([]string, 0, len(data))
|
|
||||||
for k := range data {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcVal, fileVal string
|
|
||||||
|
|
||||||
fixedKeys := make([]string, 0, 4+len(data))
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
|
||||||
}
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
|
|
||||||
if entry.Message != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
|
|
||||||
}
|
|
||||||
if entry.err != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
|
||||||
}
|
|
||||||
if entry.HasCaller() {
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
} else {
|
|
||||||
funcVal = entry.Caller.Function
|
|
||||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if funcVal != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
|
|
||||||
}
|
|
||||||
if fileVal != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableSorting {
|
|
||||||
if f.SortingFunc == nil {
|
|
||||||
sort.Strings(keys)
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
} else {
|
|
||||||
if !f.isColored() {
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
f.SortingFunc(fixedKeys)
|
|
||||||
} else {
|
|
||||||
f.SortingFunc(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
if f.isColored() {
|
|
||||||
f.printColored(b, entry, keys, data, timestampFormat)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for _, key := range fixedKeys {
|
|
||||||
var value interface{}
|
|
||||||
switch {
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
|
||||||
value = entry.Time.Format(timestampFormat)
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
|
||||||
value = entry.Level.String()
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
|
||||||
value = entry.Message
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
|
||||||
value = entry.err
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
|
||||||
value = funcVal
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
|
||||||
value = fileVal
|
|
||||||
default:
|
|
||||||
value = data[key]
|
|
||||||
}
|
|
||||||
f.appendKeyValue(b, key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case DebugLevel, TraceLevel:
|
|
||||||
levelColor = gray
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
case InfoLevel:
|
|
||||||
levelColor = blue
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())
|
|
||||||
if !f.DisableLevelTruncation && !f.PadLevelText {
|
|
||||||
levelText = levelText[0:4]
|
|
||||||
}
|
|
||||||
if f.PadLevelText {
|
|
||||||
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
|
||||||
// Based on the max level text length.
|
|
||||||
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
|
||||||
// Formats the level text by appending spaces up to the max length, for example:
|
|
||||||
// - "INFO "
|
|
||||||
// - "WARNING"
|
|
||||||
levelText = fmt.Sprintf(formatString, levelText)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a single newline if it already exists in the message to keep
|
|
||||||
// the behavior of logrus text_formatter the same as the stdlib log package
|
|
||||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
|
||||||
|
|
||||||
caller := ""
|
|
||||||
if entry.HasCaller() {
|
|
||||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileVal == "" {
|
|
||||||
caller = funcVal
|
|
||||||
} else if funcVal == "" {
|
|
||||||
caller = fileVal
|
|
||||||
} else {
|
|
||||||
caller = fileVal + " " + funcVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case f.DisableTimestamp:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
|
||||||
case !f.FullTimestamp:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
v := data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
|
||||||
f.appendValue(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
|
||||||
if f.ForceQuote {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if f.QuoteEmptyFields && len(text) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if f.DisableQuote {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, ch := range text {
|
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
|
||||||
(ch >= '0' && ch <= '9') ||
|
|
||||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
|
||||||
if b.Len() > 0 {
|
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
|
||||||
b.WriteString(key)
|
|
||||||
b.WriteByte('=')
|
|
||||||
f.appendValue(b, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
|
||||||
stringVal, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
stringVal = fmt.Sprint(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.needsQuoting(stringVal) {
|
|
||||||
b.WriteString(stringVal)
|
|
||||||
} else {
|
|
||||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
102
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
102
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
@@ -1,102 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Writer at INFO level. See WriterLevel for details.
|
|
||||||
func (logger *Logger) Writer() *io.PipeWriter {
|
|
||||||
return logger.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
|
|
||||||
// the logger at the given log level. Each line written to the writer will be
|
|
||||||
// printed in the usual way using formatters and hooks. The writer is part of an
|
|
||||||
// io.Pipe and it is the callers responsibility to close the writer when done.
|
|
||||||
// This can be used to override the standard library logger easily.
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
return NewEntry(logger).WriterLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer returns an io.Writer that writes to the logger at the info log level
|
|
||||||
func (entry *Entry) Writer() *io.PipeWriter {
|
|
||||||
return entry.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriterLevel returns an io.Writer that writes to the logger at the given log level
|
|
||||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
|
||||||
|
|
||||||
// Determine which log function to use based on the specified log level
|
|
||||||
switch level {
|
|
||||||
case TraceLevel:
|
|
||||||
printFunc = entry.Trace
|
|
||||||
case DebugLevel:
|
|
||||||
printFunc = entry.Debug
|
|
||||||
case InfoLevel:
|
|
||||||
printFunc = entry.Info
|
|
||||||
case WarnLevel:
|
|
||||||
printFunc = entry.Warn
|
|
||||||
case ErrorLevel:
|
|
||||||
printFunc = entry.Error
|
|
||||||
case FatalLevel:
|
|
||||||
printFunc = entry.Fatal
|
|
||||||
case PanicLevel:
|
|
||||||
printFunc = entry.Panic
|
|
||||||
default:
|
|
||||||
printFunc = entry.Print
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new goroutine to scan the input and write it to the logger using the specified print function.
|
|
||||||
// It splits the input into chunks of up to 64KB to avoid buffer overflows.
|
|
||||||
go entry.writerScanner(reader, printFunc)
|
|
||||||
|
|
||||||
// Set a finalizer function to close the writer when it is garbage collected
|
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
|
||||||
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// writerScanner scans the input from the reader and writes it to the logger
|
|
||||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
|
|
||||||
// Set the buffer size to the maximum token size to avoid buffer overflows
|
|
||||||
scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), bufio.MaxScanTokenSize)
|
|
||||||
|
|
||||||
// Define a split function to split the input into chunks of up to 64KB
|
|
||||||
chunkSize := bufio.MaxScanTokenSize // 64KB
|
|
||||||
splitFunc := func(data []byte, atEOF bool) (int, []byte, error) {
|
|
||||||
if len(data) >= chunkSize {
|
|
||||||
return chunkSize, data[:chunkSize], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return bufio.ScanLines(data, atEOF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the custom split function to split the input
|
|
||||||
scanner.Split(splitFunc)
|
|
||||||
|
|
||||||
// Scan the input and write it to the logger using the specified print function
|
|
||||||
for scanner.Scan() {
|
|
||||||
printFunc(strings.TrimRight(scanner.Text(), "\r\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there was an error while scanning the input, log an error
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
entry.Errorf("Error while reading from Writer: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the reader when we are done
|
|
||||||
reader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriterFinalizer is a finalizer function that closes then given writer when it is garbage collected
|
|
||||||
func writerFinalizer(writer *io.PipeWriter) {
|
|
||||||
writer.Close()
|
|
||||||
}
|
|
||||||
12
vendor/github.com/sirupsen/logrus/LICENSE → vendor/github.com/wlevene/ini/LICENSE
generated
vendored
12
vendor/github.com/sirupsen/logrus/LICENSE → vendor/github.com/wlevene/ini/LICENSE
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2014 Simon Eskildsen
|
Copyright (c) 2021 levene
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in all
|
||||||
all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
THE SOFTWARE.
|
SOFTWARE.
|
||||||
420
vendor/github.com/wlevene/ini/README.md
generated
vendored
Normal file
420
vendor/github.com/wlevene/ini/README.md
generated
vendored
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
# INI Parser & Write Library
|
||||||
|
|
||||||
|
ini parser and write library for Golang,easy-use,fast
|
||||||
|
|
||||||
|
|
||||||
|
[](https://github.com/wlevene/ini)
|
||||||
|

|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
* Can be read by []byte
|
||||||
|
* Can be read by file
|
||||||
|
* Supports file monitoring and takes effect in real time without reloading
|
||||||
|
* Unmarshal to Struct
|
||||||
|
* Marshal to Json
|
||||||
|
* Write ini to File
|
||||||
|
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/wlevene/ini
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/wlevene/ini"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### GetValue
|
||||||
|
|
||||||
|
```go
|
||||||
|
doc := `
|
||||||
|
[section]
|
||||||
|
k=v
|
||||||
|
|
||||||
|
[section1]
|
||||||
|
k1=v1
|
||||||
|
k2=1
|
||||||
|
k3=3.5
|
||||||
|
k4=0.0.0.0
|
||||||
|
`
|
||||||
|
v1 := ini.New().Load([]byte(doc)).Section("section1").Get("k1")
|
||||||
|
fmt.Println(v1)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
v1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
i := ini.New().Load([]byte(doc))
|
||||||
|
v1 := i.Section("section1").Get("k1")
|
||||||
|
v2 := i.GetInt("k2")
|
||||||
|
v3 := i.GetFloat64("k3")
|
||||||
|
v4 := i.Get("k4")
|
||||||
|
v5 := i.GetIntDef("keyint", 10)
|
||||||
|
v6 := i.GetDef("keys", "defualt")
|
||||||
|
|
||||||
|
fmt.Printf("v1:%v v2:%v v3:%v v4:%v v5:%v v6:%v\n", v1, v2, v3, v4, v5, v6)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
v1:v1 v2:1 v3:3.5 v4:0.0.0.0 v5:10 v6:defualt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Marshal2Json
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Println(string(i.Marshal2Json()))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"section":{"k":"v"},"section1":{"k1":"v1","k2":"1","k3":"3.5","k4":"0.0.0.0"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unmarshal Struct
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TestConfig struct {
|
||||||
|
K string `ini:"k" json:"k,omitempty"`
|
||||||
|
K1 int `ini:"k1" json:"k1,omitempty"`
|
||||||
|
K2 float64 `ini:"k2"`
|
||||||
|
K3 int64 `ini:"k3"`
|
||||||
|
User User `ini:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string `ini:"name"`
|
||||||
|
Age int `ini:"age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
doc := `
|
||||||
|
k=v
|
||||||
|
k1=2
|
||||||
|
k2=2.2
|
||||||
|
k3=3
|
||||||
|
|
||||||
|
[user]
|
||||||
|
name=tom
|
||||||
|
age=-23
|
||||||
|
`
|
||||||
|
|
||||||
|
cfg := TestConfig{}
|
||||||
|
|
||||||
|
ini.Unmarshal([]byte(doc), &cfg)
|
||||||
|
fmt.Println("cfg:", cfg)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
cfg: {v 2 2.2 3 {tom -23}}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Parse File
|
||||||
|
|
||||||
|
ini file
|
||||||
|
|
||||||
|
```ini
|
||||||
|
; this is comment
|
||||||
|
; author levene
|
||||||
|
; date 2021-8-1
|
||||||
|
|
||||||
|
|
||||||
|
a='23'34?::'<>,.'
|
||||||
|
c=d
|
||||||
|
|
||||||
|
[s1]
|
||||||
|
k=67676
|
||||||
|
k1 =fdasf
|
||||||
|
k2= sdafj3490&@)34 34w2
|
||||||
|
|
||||||
|
# comment
|
||||||
|
# 12.0.0.1
|
||||||
|
[s2]
|
||||||
|
|
||||||
|
k=3
|
||||||
|
|
||||||
|
|
||||||
|
k2=945
|
||||||
|
k3=-435
|
||||||
|
k4=0.0.0.0
|
||||||
|
|
||||||
|
k5=127.0.0.1
|
||||||
|
k6=levene@github.com
|
||||||
|
|
||||||
|
k7=~/.path.txt
|
||||||
|
k8=./34/34/uh.txt
|
||||||
|
|
||||||
|
k9=234@!@#$%^&*()324
|
||||||
|
k10='23'34?::'<>,.'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := "./test.ini"
|
||||||
|
ini.New().LoadFile(file).Section("s2").Get("k2")
|
||||||
|
|
||||||
|
fmt.Println(string(ini.Marshal2Json()))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
945
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Watch File
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := "./test.ini"
|
||||||
|
|
||||||
|
idoc := ini.New().WatchFile(file)
|
||||||
|
v := idoc.Section("s2").Get("k1")
|
||||||
|
fmt.Println("v:", v1)
|
||||||
|
|
||||||
|
// modify k1=v1 ==> k1=v2
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
v = idoc.Section("s2").Get("k1")
|
||||||
|
fmt.Println("v:", v1)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```
|
||||||
|
v: v1
|
||||||
|
v: v2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Print file with json
|
||||||
|
|
||||||
|
```go
|
||||||
|
file := "./test.ini"
|
||||||
|
fmt.Println(string(ini.New().LoadFile(file).Marshal2Json()))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"a": "'23'34?::'<>,.'",
|
||||||
|
"c": "d",
|
||||||
|
"s1": {
|
||||||
|
"k": "67676",
|
||||||
|
"k2": "34w2"
|
||||||
|
},
|
||||||
|
"s2": {
|
||||||
|
"k": "3",
|
||||||
|
"k10": "'23'34?::'<>,.'",
|
||||||
|
"k2": "945",
|
||||||
|
"k3": "-435",
|
||||||
|
"k4": "0.0.0.0",
|
||||||
|
"k5": "127.0.0.1",
|
||||||
|
"k6": "levene@github.com",
|
||||||
|
"k7": "~/.path.txt",
|
||||||
|
"k8": "./34/34/uh.txt",
|
||||||
|
"k9": "234@!@#$%^&*()324"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set Ini
|
||||||
|
```go
|
||||||
|
doc := `
|
||||||
|
k =v
|
||||||
|
[section]
|
||||||
|
a=b
|
||||||
|
c=d
|
||||||
|
`
|
||||||
|
ini := New().Load([]byte(doc)).Section("section")
|
||||||
|
fmt.Println("--------------------------------")
|
||||||
|
ini.Dump()
|
||||||
|
|
||||||
|
fmt.Println("--------------------------------")
|
||||||
|
ini.Set("a", 11).Set("c", 12.3).Section("").Set("k", "SET")
|
||||||
|
ini.Dump()
|
||||||
|
|
||||||
|
v := ini.Section("section").GetInt("a")
|
||||||
|
|
||||||
|
if v != 11 {
|
||||||
|
t.Errorf("Error: %d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
v1 := ini.GetFloat64("c")
|
||||||
|
|
||||||
|
if v1 != 12.3 {
|
||||||
|
t.Errorf("Error: %f", v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 := ini.Section("").Get("k")
|
||||||
|
if v2 != "SET" {
|
||||||
|
t.Errorf("Error: %s", v2)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Wirte Ini
|
||||||
|
|
||||||
|
```go
|
||||||
|
filename := "./save.ini"
|
||||||
|
ini := New().Set("a1", 1)
|
||||||
|
ini.Save(filename)
|
||||||
|
fmt.Println(ini.Err())
|
||||||
|
|
||||||
|
ini2 := New().Set("a1", 1).Section("s1").Set("a2", "v2")
|
||||||
|
ini2.Save(filename)
|
||||||
|
fmt.Println(ini2.Err())
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Dump AST struct
|
||||||
|
|
||||||
|
```
|
||||||
|
INIDocNode {
|
||||||
|
CommentNode {
|
||||||
|
Comment: ; this is comment
|
||||||
|
Line: 0
|
||||||
|
}
|
||||||
|
CommentNode {
|
||||||
|
Comment: ; author levene
|
||||||
|
Line: 1
|
||||||
|
}
|
||||||
|
CommentNode {
|
||||||
|
Comment: ; date 2021-8-1
|
||||||
|
Line: 2
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: a
|
||||||
|
Value: '23'34?::'<>,.'
|
||||||
|
Line: 5
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: c
|
||||||
|
Value: d
|
||||||
|
Line: 6
|
||||||
|
}
|
||||||
|
Section {
|
||||||
|
Section: [s1]
|
||||||
|
Line: 8
|
||||||
|
KVNode {
|
||||||
|
Value: 67676
|
||||||
|
Line: 9
|
||||||
|
Key: k
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k1
|
||||||
|
Value: fdasf
|
||||||
|
Line: 10
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Value: 4w2
|
||||||
|
Line: 11
|
||||||
|
Key: k2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommentNode {
|
||||||
|
Comment: # comment
|
||||||
|
Line: 13
|
||||||
|
}
|
||||||
|
CommentNode {
|
||||||
|
Line: 14
|
||||||
|
Comment: # 12.0.0.1
|
||||||
|
}
|
||||||
|
Section {
|
||||||
|
Section: [s2]
|
||||||
|
Line: 15
|
||||||
|
KVNode {
|
||||||
|
Value: 3
|
||||||
|
Line: 17
|
||||||
|
Key: k
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Value: 945
|
||||||
|
Line: 20
|
||||||
|
Key: k2
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k3
|
||||||
|
Value: -435
|
||||||
|
Line: 21
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Line: 22
|
||||||
|
Key: k4
|
||||||
|
Value: 0.0.0.0
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Line: 24
|
||||||
|
Key: k5
|
||||||
|
Value: 127.0.0.1
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k6
|
||||||
|
Value: levene@github.com
|
||||||
|
Line: 25
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k7
|
||||||
|
Value: ~/.path.txt
|
||||||
|
Line: 27
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Line: 28
|
||||||
|
Key: k8
|
||||||
|
Value: ./34/34/uh.txt
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k9
|
||||||
|
Value: 234@!@#$%^&*()324
|
||||||
|
Line: 30
|
||||||
|
}
|
||||||
|
KVNode {
|
||||||
|
Key: k10
|
||||||
|
Value: '23'34?::'<>,.'
|
||||||
|
Line: 31
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](https://github.com/RichardLitt/standard-readme/blob/master/LICENSE)
|
||||||
551
vendor/github.com/wlevene/ini/ast/ast.go
generated
vendored
Normal file
551
vendor/github.com/wlevene/ini/ast/ast.go
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodeType int
|
||||||
|
type NodeKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TypeBlock indicates that a node is kind of block nodes.
|
||||||
|
TypeBlock NodeType = iota + 1
|
||||||
|
// TypeDocument indicates that a node is kind of document nodes.
|
||||||
|
TypeDocNode
|
||||||
|
)
|
||||||
|
|
||||||
|
var kindMax NodeKind
|
||||||
|
var kindNames = []string{""}
|
||||||
|
|
||||||
|
// NewNodeKind returns a new Kind value.
|
||||||
|
func NewNodeKind(name string) NodeKind {
|
||||||
|
kindMax++
|
||||||
|
kindNames = append(kindNames, name)
|
||||||
|
return kindMax
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k NodeKind) String() string {
|
||||||
|
return kindNames[k]
|
||||||
|
// return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Node interface defines basic AST node functionalities.
|
||||||
|
type Node interface {
|
||||||
|
// Type returns a type of this node.
|
||||||
|
Type() NodeType
|
||||||
|
|
||||||
|
// Kind returns a kind of this node.
|
||||||
|
Kind() NodeKind
|
||||||
|
|
||||||
|
// NextSibling returns a next sibling node of this node.
|
||||||
|
NextSibling() Node
|
||||||
|
|
||||||
|
// PreviousSibling returns a previous sibling node of this node.
|
||||||
|
PreviousSibling() Node
|
||||||
|
|
||||||
|
// Parent returns a parent node of this node.
|
||||||
|
Parent() Node
|
||||||
|
|
||||||
|
// SetParent sets a parent node to this node.
|
||||||
|
SetParent(Node)
|
||||||
|
|
||||||
|
// SetPreviousSibling sets a previous sibling node to this node.
|
||||||
|
SetPreviousSibling(Node)
|
||||||
|
|
||||||
|
// SetNextSibling sets a next sibling node to this node.
|
||||||
|
SetNextSibling(Node)
|
||||||
|
|
||||||
|
// HasChildren returns true if this node has any children, otherwise false.
|
||||||
|
HasChildren() bool
|
||||||
|
|
||||||
|
// ChildCount returns a total number of children.
|
||||||
|
ChildCount() int
|
||||||
|
|
||||||
|
// FirstChild returns a first child of this node.
|
||||||
|
FirstChild() Node
|
||||||
|
|
||||||
|
// LastChild returns a last child of this node.
|
||||||
|
LastChild() Node
|
||||||
|
|
||||||
|
// AppendChild append a node child to the tail of the children.
|
||||||
|
AppendChild(self, child Node)
|
||||||
|
|
||||||
|
// RemoveChild removes a node child from this node.
|
||||||
|
// If a node child is not children of this node, RemoveChild nothing to do.
|
||||||
|
RemoveChild(self, child Node)
|
||||||
|
|
||||||
|
// RemoveChildren removes all children from this node.
|
||||||
|
RemoveChildren(self Node)
|
||||||
|
|
||||||
|
// SortChildren sorts childrens by comparator.
|
||||||
|
SortChildren(comparator func(n1, n2 Node) int)
|
||||||
|
|
||||||
|
// ReplaceChild replace a node v1 with a node insertee.
|
||||||
|
// If v1 is not children of this node, ReplaceChild append a insetee to the
|
||||||
|
// tail of the children.
|
||||||
|
ReplaceChild(self, v1, insertee Node)
|
||||||
|
|
||||||
|
// InsertBefore inserts a node insertee before a node v1.
|
||||||
|
// If v1 is not children of this node, InsertBefore append a insetee to the
|
||||||
|
// tail of the children.
|
||||||
|
InsertBefore(self, v1, insertee Node)
|
||||||
|
|
||||||
|
// InsertAfterinserts a node insertee after a node v1.
|
||||||
|
// If v1 is not children of this node, InsertBefore append a insetee to the
|
||||||
|
// tail of the children.
|
||||||
|
InsertAfter(self, v1, insertee Node)
|
||||||
|
|
||||||
|
IsRoot() bool
|
||||||
|
Depth() int
|
||||||
|
IsLeaf() bool
|
||||||
|
|
||||||
|
// OwnerDocument returns this node's owner document.
|
||||||
|
// If this node is not a child of the Document node, OwnerDocument
|
||||||
|
// returns nil.
|
||||||
|
// OwnerDocument() *Document
|
||||||
|
|
||||||
|
// Dump dumps an AST tree structure to stdout.
|
||||||
|
// This function completely aimed for debugging.
|
||||||
|
// level is a indent level. Implementer should indent informations with
|
||||||
|
// 2 * level spaces.
|
||||||
|
Dump(source []byte, level int)
|
||||||
|
|
||||||
|
// Text returns text values of this node.
|
||||||
|
Text(source []byte) []byte
|
||||||
|
|
||||||
|
// HasBlankPreviousLines returns true if the row before this node is blank,
|
||||||
|
// otherwise false.
|
||||||
|
// This method is valid only for block nodes.
|
||||||
|
// HasBlankPreviousLines() bool
|
||||||
|
|
||||||
|
// SetBlankPreviousLines sets whether the row before this node is blank.
|
||||||
|
// This method is valid only for block nodes.
|
||||||
|
// SetBlankPreviousLines(v bool)
|
||||||
|
|
||||||
|
// Lines returns text segments that hold positions in a source.
|
||||||
|
// This method is valid only for block nodes.
|
||||||
|
// Lines() *textm.Segments
|
||||||
|
|
||||||
|
// SetLines sets text segments that hold positions in a source.
|
||||||
|
// This method is valid only for block nodes.
|
||||||
|
// SetLines(*textm.Segments)
|
||||||
|
|
||||||
|
// IsRaw returns true if contents should be rendered as 'raw' contents.
|
||||||
|
IsRaw() bool
|
||||||
|
|
||||||
|
// SetAttribute sets the given value to the attributes.
|
||||||
|
SetAttribute(name []byte, value interface{})
|
||||||
|
|
||||||
|
// SetAttributeString sets the given value to the attributes.
|
||||||
|
SetAttributeString(name string, value interface{})
|
||||||
|
|
||||||
|
// Attribute returns a (attribute value, true) if an attribute
|
||||||
|
// associated with the given name is found, otherwise
|
||||||
|
// (nil, false)
|
||||||
|
Attribute(name []byte) (interface{}, bool)
|
||||||
|
|
||||||
|
// AttributeString returns a (attribute value, true) if an attribute
|
||||||
|
// associated with the given name is found, otherwise
|
||||||
|
// (nil, false)
|
||||||
|
AttributeString(name string) (interface{}, bool)
|
||||||
|
|
||||||
|
// Attributes returns a list of attributes.
|
||||||
|
// This may be a nil if there are no attributes.
|
||||||
|
Attributes() []Attribute
|
||||||
|
|
||||||
|
// RemoveAttributes removes all attributes from this node.
|
||||||
|
RemoveAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
Name []byte
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A BaseNode struct implements the Node interface partialliy.
|
||||||
|
type BaseNode struct {
|
||||||
|
firstChild Node
|
||||||
|
lastChild Node
|
||||||
|
parent Node
|
||||||
|
next Node
|
||||||
|
prev Node
|
||||||
|
childCount int
|
||||||
|
attributes []Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureIsolated(v Node) {
|
||||||
|
if p := v.Parent(); p != nil {
|
||||||
|
p.RemoveChild(p, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BaseNode) Kind() NodeKind {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BaseNode) Type() NodeType {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BaseNode) Depth() int {
|
||||||
|
depth := 0
|
||||||
|
|
||||||
|
var i_node Node
|
||||||
|
i_node = n.Parent()
|
||||||
|
|
||||||
|
if i_node == nil {
|
||||||
|
return depth
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
i_node = i_node.Parent()
|
||||||
|
if i_node == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
depth = depth + 1
|
||||||
|
}
|
||||||
|
return depth
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRoot checks if node is a root node
|
||||||
|
func (n *BaseNode) IsRoot() bool {
|
||||||
|
return n.Parent() != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BaseNode) IsLeaf() bool {
|
||||||
|
return n.childCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasChildren implements Node.HasChildren .
|
||||||
|
func (n *BaseNode) HasChildren() bool {
|
||||||
|
return n.firstChild != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreviousSibling implements Node.SetPreviousSibling .
|
||||||
|
func (n *BaseNode) SetPreviousSibling(v Node) {
|
||||||
|
n.prev = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNextSibling implements Node.SetNextSibling .
|
||||||
|
func (n *BaseNode) SetNextSibling(v Node) {
|
||||||
|
n.next = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreviousSibling implements Node.PreviousSibling .
|
||||||
|
func (n *BaseNode) PreviousSibling() Node {
|
||||||
|
return n.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextSibling implements Node.NextSibling .
|
||||||
|
func (n *BaseNode) NextSibling() Node {
|
||||||
|
return n.next
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChild implements Node.RemoveChild .
|
||||||
|
func (n *BaseNode) RemoveChild(self, v Node) {
|
||||||
|
if v.Parent() != self {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.childCount--
|
||||||
|
prev := v.PreviousSibling()
|
||||||
|
next := v.NextSibling()
|
||||||
|
if prev != nil {
|
||||||
|
prev.SetNextSibling(next)
|
||||||
|
} else {
|
||||||
|
n.firstChild = next
|
||||||
|
}
|
||||||
|
if next != nil {
|
||||||
|
next.SetPreviousSibling(prev)
|
||||||
|
} else {
|
||||||
|
n.lastChild = prev
|
||||||
|
}
|
||||||
|
v.SetParent(nil)
|
||||||
|
v.SetPreviousSibling(nil)
|
||||||
|
v.SetNextSibling(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChildren implements Node.RemoveChildren .
|
||||||
|
func (n *BaseNode) RemoveChildren(self Node) {
|
||||||
|
for c := n.firstChild; c != nil; {
|
||||||
|
c.SetParent(nil)
|
||||||
|
c.SetPreviousSibling(nil)
|
||||||
|
next := c.NextSibling()
|
||||||
|
c.SetNextSibling(nil)
|
||||||
|
c = next
|
||||||
|
}
|
||||||
|
n.firstChild = nil
|
||||||
|
n.lastChild = nil
|
||||||
|
n.childCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortChildren implements Node.SortChildren
|
||||||
|
func (n *BaseNode) SortChildren(comparator func(n1, n2 Node) int) {
|
||||||
|
var sorted Node
|
||||||
|
current := n.firstChild
|
||||||
|
for current != nil {
|
||||||
|
next := current.NextSibling()
|
||||||
|
if sorted == nil || comparator(sorted, current) >= 0 {
|
||||||
|
current.SetNextSibling(sorted)
|
||||||
|
if sorted != nil {
|
||||||
|
sorted.SetPreviousSibling(current)
|
||||||
|
}
|
||||||
|
sorted = current
|
||||||
|
sorted.SetPreviousSibling(nil)
|
||||||
|
} else {
|
||||||
|
c := sorted
|
||||||
|
for c.NextSibling() != nil && comparator(c.NextSibling(), current) < 0 {
|
||||||
|
c = c.NextSibling()
|
||||||
|
}
|
||||||
|
current.SetNextSibling(c.NextSibling())
|
||||||
|
current.SetPreviousSibling(c)
|
||||||
|
if c.NextSibling() != nil {
|
||||||
|
c.NextSibling().SetPreviousSibling(current)
|
||||||
|
}
|
||||||
|
c.SetNextSibling(current)
|
||||||
|
}
|
||||||
|
current = next
|
||||||
|
}
|
||||||
|
n.firstChild = sorted
|
||||||
|
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||||
|
n.lastChild = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstChild implements Node.FirstChild .
|
||||||
|
func (n *BaseNode) FirstChild() Node {
|
||||||
|
return n.firstChild
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastChild implements Node.LastChild .
|
||||||
|
func (n *BaseNode) LastChild() Node {
|
||||||
|
return n.lastChild
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildCount implements Node.ChildCount .
|
||||||
|
func (n *BaseNode) ChildCount() int {
|
||||||
|
return n.childCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent implements Node.Parent .
|
||||||
|
func (n *BaseNode) Parent() Node {
|
||||||
|
return n.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetParent implements Node.SetParent .
|
||||||
|
func (n *BaseNode) SetParent(v Node) {
|
||||||
|
n.parent = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendChild implements Node.AppendChild .
|
||||||
|
func (n *BaseNode) AppendChild(self, v Node) {
|
||||||
|
ensureIsolated(v)
|
||||||
|
if n.firstChild == nil {
|
||||||
|
n.firstChild = v
|
||||||
|
v.SetNextSibling(nil)
|
||||||
|
v.SetPreviousSibling(nil)
|
||||||
|
} else {
|
||||||
|
last := n.lastChild
|
||||||
|
last.SetNextSibling(v)
|
||||||
|
v.SetPreviousSibling(last)
|
||||||
|
}
|
||||||
|
v.SetParent(self)
|
||||||
|
n.lastChild = v
|
||||||
|
n.childCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceChild implements Node.ReplaceChild .
|
||||||
|
func (n *BaseNode) ReplaceChild(self, v1, insertee Node) {
|
||||||
|
n.InsertBefore(self, v1, insertee)
|
||||||
|
n.RemoveChild(self, v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertAfter implements Node.InsertAfter .
|
||||||
|
func (n *BaseNode) InsertAfter(self, v1, insertee Node) {
|
||||||
|
n.InsertBefore(self, v1.NextSibling(), insertee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBefore implements Node.InsertBefore .
|
||||||
|
func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
|
||||||
|
n.childCount++
|
||||||
|
if v1 == nil {
|
||||||
|
n.AppendChild(self, insertee)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ensureIsolated(insertee)
|
||||||
|
if v1.Parent() == self {
|
||||||
|
c := v1
|
||||||
|
prev := c.PreviousSibling()
|
||||||
|
if prev != nil {
|
||||||
|
prev.SetNextSibling(insertee)
|
||||||
|
insertee.SetPreviousSibling(prev)
|
||||||
|
} else {
|
||||||
|
n.firstChild = insertee
|
||||||
|
insertee.SetPreviousSibling(nil)
|
||||||
|
}
|
||||||
|
insertee.SetNextSibling(c)
|
||||||
|
c.SetPreviousSibling(insertee)
|
||||||
|
insertee.SetParent(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // OwnerDocument implements Node.OwnerDocument
|
||||||
|
// func (n *BaseNode) OwnerDocument() *Document {
|
||||||
|
// d := n.Parent()
|
||||||
|
// for {
|
||||||
|
// p := d.Parent()
|
||||||
|
// if p == nil {
|
||||||
|
// if v, ok := d.(*Document); ok {
|
||||||
|
// return v
|
||||||
|
// }
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// d = p
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (n *BaseNode) IsRaw() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text .
|
||||||
|
func (n *BaseNode) Text(source []byte) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||||
|
buf.Write(c.Text(source))
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAttribute implements Node.SetAttribute.
|
||||||
|
func (n *BaseNode) SetAttribute(name []byte, value interface{}) {
|
||||||
|
if n.attributes == nil {
|
||||||
|
n.attributes = make([]Attribute, 0, 10)
|
||||||
|
} else {
|
||||||
|
for i, a := range n.attributes {
|
||||||
|
if bytes.Equal(a.Name, name) {
|
||||||
|
n.attributes[i].Name = name
|
||||||
|
n.attributes[i].Value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.attributes = append(n.attributes, Attribute{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAttributeString implements Node.SetAttributeString
|
||||||
|
func (n *BaseNode) SetAttributeString(name string, value interface{}) {
|
||||||
|
// n.SetAttribute(util.StringToReadOnlyBytes(name), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute implements Node.Attribute.
|
||||||
|
func (n *BaseNode) Attribute(name []byte) (interface{}, bool) {
|
||||||
|
if n.attributes == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
for i, a := range n.attributes {
|
||||||
|
if bytes.Equal(a.Name, name) {
|
||||||
|
return n.attributes[i].Value, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttributeString implements Node.AttributeString.
|
||||||
|
func (n *BaseNode) AttributeString(s string) (interface{}, bool) {
|
||||||
|
// return n.Attribute(util.StringToReadOnlyBytes(s))
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes implements Node.Attributes
|
||||||
|
func (n *BaseNode) Attributes() []Attribute {
|
||||||
|
return n.attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAttributes implements Node.RemoveAttributes
|
||||||
|
func (n *BaseNode) RemoveAttributes() {
|
||||||
|
n.attributes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BaseNode) Dump(source []byte, level int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpHelper is a helper function to implement Node.Dump.
|
||||||
|
// kv is pairs of an attribute name and an attribute value.
|
||||||
|
// cb is a function called after wrote a name and attributes.
|
||||||
|
func DumpHelper(v Node, source []byte, level int, kv map[string]string, cb func(int)) {
|
||||||
|
name := v.Kind().String()
|
||||||
|
indent := strings.Repeat(" ", level)
|
||||||
|
fmt.Printf("%s%s {\n", indent, name)
|
||||||
|
indent2 := strings.Repeat(" ", level+1)
|
||||||
|
// if v.Type() == TypeBlock {
|
||||||
|
// fmt.Printf("%sRawText: \"", indent2)
|
||||||
|
// for i := 0; i < v.Lines().Len(); i++ {
|
||||||
|
// line := v.Lines().At(i)
|
||||||
|
// fmt.Printf("%s", line.Value(source))
|
||||||
|
// }
|
||||||
|
// fmt.Printf("\"\n")
|
||||||
|
// fmt.Printf("%sHasBlankPreviousLines: %v\n", indent2, v.HasBlankPreviousLines())
|
||||||
|
// }
|
||||||
|
for name, value := range kv {
|
||||||
|
fmt.Printf("%s%s: %s\n", indent2, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cb != nil {
|
||||||
|
cb(level + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := v.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
c.Dump(source, level+1)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s}\n", indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkStatus represents a current status of the Walk function.
|
||||||
|
type WalkStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WalkStop indicates no more walking needed.
|
||||||
|
WalkStop WalkStatus = iota + 1
|
||||||
|
|
||||||
|
// WalkSkipChildren indicates that Walk wont walk on children of current
|
||||||
|
// node.
|
||||||
|
WalkSkipChildren
|
||||||
|
|
||||||
|
// WalkContinue indicates that Walk can continue to walk.
|
||||||
|
WalkContinue
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walker is a function that will be called when Walk find a
|
||||||
|
// new node.
|
||||||
|
// entering is set true before walks children, false after walked children.
|
||||||
|
// If Walker returns error, Walk function immediately stop walking.
|
||||||
|
type Walker func(n Node, entering bool) (WalkStatus, error)
|
||||||
|
|
||||||
|
// Walk walks a AST tree by the depth first search algorithm.
|
||||||
|
func Walk(n Node, walker Walker) error {
|
||||||
|
_, err := walkHelper(n, walker)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkHelper(n Node, walker Walker) (WalkStatus, error) {
|
||||||
|
status, err := walker(n, true)
|
||||||
|
if err != nil || status == WalkStop {
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
if status != WalkSkipChildren {
|
||||||
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if st, err := walkHelper(c, walker); err != nil || st == WalkStop {
|
||||||
|
return WalkStop, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status, err = walker(n, false)
|
||||||
|
if err != nil || status == WalkStop {
|
||||||
|
return WalkStop, err
|
||||||
|
}
|
||||||
|
return WalkContinue, nil
|
||||||
|
}
|
||||||
121
vendor/github.com/wlevene/ini/ast/ast_node.go
generated
vendored
Normal file
121
vendor/github.com/wlevene/ini/ast/ast_node.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wlevene/ini/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetcionNode struct {
|
||||||
|
BaseNode
|
||||||
|
Name token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
var KindSetcion = NewNodeKind("Section")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *SetcionNode) Kind() NodeKind {
|
||||||
|
return KindSetcion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *SetcionNode) Type() NodeType {
|
||||||
|
return TypeDocNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *SetcionNode) IsRaw() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
func (n *SetcionNode) Text(source []byte) []byte {
|
||||||
|
return []byte(n.Name.Literal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *SetcionNode) Dump(source []byte, level int) {
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := n.Name.Literal
|
||||||
|
m := map[string]string{
|
||||||
|
"Section": fmt.Sprintf("[%v]", name),
|
||||||
|
"Line": fmt.Sprintf("%v", n.Name.Line),
|
||||||
|
}
|
||||||
|
DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type KVNode struct {
|
||||||
|
BaseNode
|
||||||
|
Key token.Token
|
||||||
|
Value token.Token
|
||||||
|
Index int
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
var KindKVNode = NewNodeKind("KVNode")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *KVNode) Kind() NodeKind {
|
||||||
|
return KindKVNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *KVNode) Type() NodeType {
|
||||||
|
return TypeDocNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *KVNode) IsRaw() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *KVNode) Text(source []byte) []byte {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *KVNode) Dump(source []byte, level int) {
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"Key": fmt.Sprintf("%v", n.Key.Literal),
|
||||||
|
"Value": fmt.Sprintf("%v", n.Value.Literal),
|
||||||
|
"Line": fmt.Sprintf("%v", n.Key.Line),
|
||||||
|
}
|
||||||
|
|
||||||
|
DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentNode struct {
|
||||||
|
BaseNode
|
||||||
|
Comment token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
var KindCommentNode = NewNodeKind("CommentNode")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *CommentNode) Kind() NodeKind {
|
||||||
|
return KindCommentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CommentNode) Type() NodeType {
|
||||||
|
return TypeDocNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CommentNode) IsRaw() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CommentNode) Text(source []byte) []byte {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *CommentNode) Dump(source []byte, level int) {
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"Comment": fmt.Sprintf("%v", n.Comment.Literal),
|
||||||
|
"Line": fmt.Sprintf("%v", n.Comment.Line),
|
||||||
|
}
|
||||||
|
|
||||||
|
DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
60
vendor/github.com/wlevene/ini/ast/doc.go
generated
vendored
Normal file
60
vendor/github.com/wlevene/ini/ast/doc.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Doc struct {
|
||||||
|
BaseNode
|
||||||
|
|
||||||
|
// KVs []*KVNode
|
||||||
|
// SectS []*SetcionNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDoc() *Doc {
|
||||||
|
doc := &Doc{
|
||||||
|
// KVs: make([]*KVNode, 0),
|
||||||
|
// SectS: make([]*SetcionNode, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (doc *Doc) DumpV2() {
|
||||||
|
|
||||||
|
// for _, item := range doc.KVs {
|
||||||
|
// item.Dump(nil, 0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, item := range doc.SectS {
|
||||||
|
// item.Dump(nil, 0)
|
||||||
|
// }
|
||||||
|
DumpHelper(doc, nil, 0, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var KindDocNode = NewNodeKind("INIDocNode")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *Doc) Kind() NodeKind {
|
||||||
|
return KindDocNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Doc) Type() NodeType {
|
||||||
|
return TypeDocNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Doc) IsRaw() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Doc) Text(source []byte) []byte {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *Doc) Dump(source []byte, level int) {
|
||||||
|
|
||||||
|
m := map[string]string{
|
||||||
|
"doc": fmt.Sprintf("%v", string(source)),
|
||||||
|
}
|
||||||
|
|
||||||
|
DumpHelper(n, source, level, m, nil)
|
||||||
|
}
|
||||||
597
vendor/github.com/wlevene/ini/ini.go
generated
vendored
Normal file
597
vendor/github.com/wlevene/ini/ini.go
generated
vendored
Normal file
@@ -0,0 +1,597 @@
|
|||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wlevene/ini/ast"
|
||||||
|
"github.com/wlevene/ini/lexer"
|
||||||
|
"github.com/wlevene/ini/parser"
|
||||||
|
"github.com/wlevene/ini/token"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: new section
|
||||||
|
|
||||||
|
type (
|
||||||
|
Ini struct {
|
||||||
|
currectSection *ast.SetcionNode
|
||||||
|
src []byte // source
|
||||||
|
l *lexer.Lexer
|
||||||
|
p *parser.Parser
|
||||||
|
doc *ast.Doc
|
||||||
|
err error
|
||||||
|
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
exitWatchChan chan bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() *Ini {
|
||||||
|
|
||||||
|
in := &Ini{
|
||||||
|
doc: &ast.Doc{},
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) Err() error {
|
||||||
|
return in.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) LoadFile(file string) *Ini {
|
||||||
|
|
||||||
|
// read file content
|
||||||
|
bts, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
in.err = err
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
in.Load(bts)
|
||||||
|
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) WatchFile(file string) *Ini {
|
||||||
|
|
||||||
|
in.LoadFile(file)
|
||||||
|
in.watch(file)
|
||||||
|
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) watch(file string) {
|
||||||
|
if file == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
in.watcher, in.err = fsnotify.NewWatcher()
|
||||||
|
in.exitWatchChan = make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-in.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
in.LoadFile(file)
|
||||||
|
}
|
||||||
|
case _, ok := <-in.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-in.exitWatchChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
in.err = in.watcher.Add(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) StopWatch() *Ini {
|
||||||
|
|
||||||
|
in.watcher.Close()
|
||||||
|
in.exitWatchChan <- true
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) Load(doc []byte) *Ini {
|
||||||
|
|
||||||
|
if len(doc) <= 0 {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
in.src = doc
|
||||||
|
in.l = lexer.New(string(in.src))
|
||||||
|
in.p = parser.New(in.l)
|
||||||
|
|
||||||
|
in.doc, in.err = in.p.ParseDocument()
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *Ini) Dump() {
|
||||||
|
|
||||||
|
if in.doc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
in.doc.DumpV2()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Marshal2Map() map[string]interface{} {
|
||||||
|
|
||||||
|
if this.doc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kvMaps := make(map[string]interface{})
|
||||||
|
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
kvMaps[kv_node.Key.Literal] = kv_node.Value.Literal
|
||||||
|
}
|
||||||
|
|
||||||
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
|
||||||
|
secMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
for kv := sect_node.FirstChild(); kv != nil; kv = kv.NextSibling() {
|
||||||
|
if kvnode, ok := kv.(*ast.KVNode); ok {
|
||||||
|
secMap[kvnode.Key.Literal] = kvnode.Value.Literal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kvMaps[sect_node.Name.Literal] = secMap
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kvMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Marshal2Json() []byte {
|
||||||
|
|
||||||
|
kvMaps := this.Marshal2Map()
|
||||||
|
|
||||||
|
if kvMaps == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := json.Marshal(kvMaps)
|
||||||
|
this.err = err
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Section(section string) *Ini {
|
||||||
|
|
||||||
|
if this.doc == nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.err != nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sectionForAstDoc(section)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Get(key string) string {
|
||||||
|
return this.GetDef(key, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetDef(key string, def string) string {
|
||||||
|
|
||||||
|
if this.doc == nil ||
|
||||||
|
this.err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
tok := this.getToken(key)
|
||||||
|
if tok.Type != token.TokenType_VALUE {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok.Literal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetInt(key string) int {
|
||||||
|
|
||||||
|
return this.GetIntDef(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetIntDef(key string, def int) int {
|
||||||
|
|
||||||
|
val := this.Get(key)
|
||||||
|
if val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
ival, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
return ival
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetInt64(key string) int64 {
|
||||||
|
|
||||||
|
return this.GetInt64Def(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetInt64Def(key string, def int64) int64 {
|
||||||
|
|
||||||
|
val := this.Get(key)
|
||||||
|
if val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
ival, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
return ival
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetFloat64(key string) float64 {
|
||||||
|
|
||||||
|
return this.GetFloat64Def(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) GetFloat64Def(key string, def float64) float64 {
|
||||||
|
|
||||||
|
val := this.Get(key)
|
||||||
|
if val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
fval, err := strconv.ParseFloat(val, 64)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return fval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Set(key string, val interface{}) *Ini {
|
||||||
|
|
||||||
|
if this.doc == nil ||
|
||||||
|
this.err != nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "" || val == nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
var val_str string
|
||||||
|
switch val.(type) {
|
||||||
|
case int:
|
||||||
|
val_str = fmt.Sprintf("%d", val.(int))
|
||||||
|
case int32:
|
||||||
|
val_str = fmt.Sprintf("%d", val.(int32))
|
||||||
|
case int64:
|
||||||
|
val_str = fmt.Sprintf("%d", val.(int64))
|
||||||
|
case float32:
|
||||||
|
val_str = strconv.FormatFloat(float64(val.(float32)), 'f', -1, 32)
|
||||||
|
case float64:
|
||||||
|
val_str = strconv.FormatFloat(float64(val.(float64)), 'f', -1, 64)
|
||||||
|
case string:
|
||||||
|
val_str = val.(string)
|
||||||
|
default:
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
val_str = strings.Replace(val_str, "\n", "", -1)
|
||||||
|
val_str = strings.Replace(val_str, "\t", "", -1)
|
||||||
|
val_str = strings.Trim(val_str, " ")
|
||||||
|
|
||||||
|
this.setKVNode(key, val_str)
|
||||||
|
|
||||||
|
return this
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) Del(key string) *Ini {
|
||||||
|
|
||||||
|
if this.doc == nil ||
|
||||||
|
this.err != nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.currectSection == nil {
|
||||||
|
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
if kv_node.Key.Literal == key {
|
||||||
|
this.doc.RemoveChild(this.doc, kv_node)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
kvnodev2 := c.(*ast.KVNode)
|
||||||
|
if kvnodev2.Key.Literal == key {
|
||||||
|
this.currectSection.RemoveChild(this.currectSection, c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) DelSection(section string) *Ini {
|
||||||
|
if this.doc == nil ||
|
||||||
|
this.err != nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if section == "" {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.currectSection == nil {
|
||||||
|
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
|
||||||
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
if sect_node.Name.Literal == section {
|
||||||
|
this.doc.RemoveChild(this.doc, sect_node)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement Save
|
||||||
|
func (this *Ini) Save(filename string) *Ini {
|
||||||
|
|
||||||
|
if filename == "" {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.doc == nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
var result string
|
||||||
|
|
||||||
|
is_last_type_comment := false
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
is_last_type_comment = false
|
||||||
|
result = fmt.Sprintf("%s%s = %v\n", result, kv_node.Key.Literal, kv_node.Value.Literal)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
|
||||||
|
if !is_last_type_comment {
|
||||||
|
result = fmt.Sprintf("%s\n", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
is_last_type_comment = false
|
||||||
|
result = fmt.Sprintf("%s[%s]\n", result, sect_node.Name.Literal)
|
||||||
|
|
||||||
|
for c := sect_node.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
result = fmt.Sprintf("%s%s = %v\n", result, kv_node.Key.Literal, kv_node.Value.Literal)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if comm_node, ok := c.(*ast.CommentNode); ok {
|
||||||
|
if !is_last_type_comment {
|
||||||
|
result = fmt.Sprintf("%s\n", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
is_last_type_comment = true
|
||||||
|
result = fmt.Sprintf("%s%s\n", result, comm_node.Comment.Literal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.err = os.Remove(filename)
|
||||||
|
if this.err != nil {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = []byte(result)
|
||||||
|
this.err = ioutil.WriteFile(filename, data, 0666)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
func (this *Ini) sectionForAstDoc(section string) {
|
||||||
|
|
||||||
|
if this.doc == nil ||
|
||||||
|
this.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currectSection = nil
|
||||||
|
if section == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
|
||||||
|
if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
|
||||||
|
if sect_node.Name.Literal == section {
|
||||||
|
this.currectSection = sect_node
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currectSection = &ast.SetcionNode{
|
||||||
|
Name: token.Token{
|
||||||
|
Type: token.TokenType_SECTION,
|
||||||
|
Literal: section,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.doc.AppendChild(this.doc, this.currectSection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) getToken(key string) token.Token {
|
||||||
|
|
||||||
|
var tok token.Token
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.currectSection == nil {
|
||||||
|
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
if kv_node.Key.Literal == key {
|
||||||
|
tok = kv_node.Value
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
|
||||||
|
kvnodev2 := c.(*ast.KVNode)
|
||||||
|
if kvnodev2.Key.Literal == key {
|
||||||
|
tok = kvnodev2.Value
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Ini) setKVNode(key string, val string) *Ini {
|
||||||
|
|
||||||
|
if key == "" || val == "" {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
line := 1
|
||||||
|
found := false
|
||||||
|
|
||||||
|
if this.currectSection == nil {
|
||||||
|
|
||||||
|
var last_kv_node *ast.KVNode
|
||||||
|
for c := this.doc.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if kv_node, ok := c.(*ast.KVNode); ok {
|
||||||
|
last_kv_node = kv_node
|
||||||
|
if kv_node.Key.Literal == key {
|
||||||
|
kv_node.Value.Literal = val
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sect_node, ok := c.(*ast.SetcionNode); ok {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
kvnode := &ast.KVNode{
|
||||||
|
Key: token.Token{
|
||||||
|
Type: token.TokenType_KEY,
|
||||||
|
Literal: key,
|
||||||
|
Line: line,
|
||||||
|
},
|
||||||
|
Value: token.Token{
|
||||||
|
Type: token.TokenType_VALUE,
|
||||||
|
Literal: val,
|
||||||
|
Line: line,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_kv_node == nil {
|
||||||
|
this.doc.AppendChild(this.doc, kvnode)
|
||||||
|
} else {
|
||||||
|
this.doc.InsertAfter(this.doc, last_kv_node, kvnode)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.re_adjust_node_line()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for c := this.currectSection.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
|
||||||
|
kvnodev2 := c.(*ast.KVNode)
|
||||||
|
line = kvnodev2.Key.Line + 1
|
||||||
|
if kvnodev2.Key.Literal == key {
|
||||||
|
kvnodev2.Value.Literal = val
|
||||||
|
return this
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == false {
|
||||||
|
|
||||||
|
kvnode := &ast.KVNode{
|
||||||
|
Key: token.Token{
|
||||||
|
Type: token.TokenType_KEY,
|
||||||
|
Literal: key,
|
||||||
|
Line: line,
|
||||||
|
},
|
||||||
|
Value: token.Token{
|
||||||
|
Type: token.TokenType_VALUE,
|
||||||
|
Literal: val,
|
||||||
|
Line: line,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currectSection.AppendChild(this.currectSection, kvnode)
|
||||||
|
this.re_adjust_node_line()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support
|
||||||
|
func (this *Ini) re_adjust_node_line() {
|
||||||
|
|
||||||
|
}
|
||||||
195
vendor/github.com/wlevene/ini/ini_tag.go
generated
vendored
Normal file
195
vendor/github.com/wlevene/ini/ini_tag.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mp := New().Load(data).Marshal2Map()
|
||||||
|
|
||||||
|
fmt.Println("map:", mp)
|
||||||
|
bindTag("ini", v, mp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind binds the content of data into the struct s
|
||||||
|
func bindTag(tagName string, s interface{}, data map[string]interface{}) interface{} {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(s)
|
||||||
|
tk := t.Kind()
|
||||||
|
|
||||||
|
if tk != reflect.Ptr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t = t.Elem()
|
||||||
|
tk = t.Kind()
|
||||||
|
|
||||||
|
if tk != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(s).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
fi := parseField(tagName, f)
|
||||||
|
|
||||||
|
fv := v.FieldByName(fi.Name)
|
||||||
|
fk := fv.Kind()
|
||||||
|
ft := fv.Type()
|
||||||
|
|
||||||
|
if v, ok := data[fi.Alias]; ok {
|
||||||
|
vt := reflect.TypeOf(v)
|
||||||
|
vk := vt.Kind()
|
||||||
|
|
||||||
|
if !vt.AssignableTo(ft) {
|
||||||
|
|
||||||
|
change_fun := func(k reflect.Kind, v interface{}) (val reflect.Value, err error) {
|
||||||
|
|
||||||
|
err = errors.New("unsupper kind")
|
||||||
|
|
||||||
|
var str_v string
|
||||||
|
var ok bool
|
||||||
|
if str_v, ok = v.(string); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// str_v = v.(string)
|
||||||
|
|
||||||
|
if k == reflect.Int {
|
||||||
|
rv, er := strconv.Atoi(str_v)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Int8 {
|
||||||
|
rv, er := strconv.ParseInt(str_v, 10, 8)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Int16 {
|
||||||
|
rv, er := strconv.ParseInt(str_v, 10, 16)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Int32 {
|
||||||
|
rv, er := strconv.ParseInt(str_v, 10, 32)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Int64 {
|
||||||
|
rv, er := strconv.ParseInt(str_v, 10, 64)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Float32 {
|
||||||
|
rv, er := strconv.ParseFloat(str_v, 32)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(float32(rv))
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else if k == reflect.Float64 {
|
||||||
|
rv, er := strconv.ParseFloat(str_v, 64)
|
||||||
|
if er == nil {
|
||||||
|
val = reflect.ValueOf(rv)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := change_fun(fk, v)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
fv.Set(val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fv.Set(reflect.ValueOf(v))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fk == reflect.Struct && vk == reflect.Map {
|
||||||
|
|
||||||
|
if fv.CanInterface() {
|
||||||
|
bindTag(tagName, fv.Addr().Interface(), v.(map[string]interface{}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindSlice(tagName string, s reflect.Value, data []interface{}) {
|
||||||
|
// sk := s.Kind()
|
||||||
|
et := s.Type().Elem()
|
||||||
|
ek := et.Kind()
|
||||||
|
|
||||||
|
ret := reflect.MakeSlice(et, s.Len(), s.Cap())
|
||||||
|
vet := reflect.TypeOf(data).Elem()
|
||||||
|
if vet.AssignableTo(et) {
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
ret.Index(i).Set(reflect.ValueOf(data[i]))
|
||||||
|
}
|
||||||
|
} else if ek == reflect.Struct {
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
// v := Bind(tagName, ret.Index(i).Addr().Interface(), data[i].(map[string]interface{}))
|
||||||
|
v := bindTag(tagName, ret.Index(i).Addr().Interface(), data[i].(map[string]interface{}))
|
||||||
|
ret.Index(i).Set(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldInfo struct {
|
||||||
|
Alias string
|
||||||
|
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseField parses [FieldInfo] for the given struct field [f] from struct tag with name [tagName]
|
||||||
|
func parseField(tagName string, f reflect.StructField) *fieldInfo {
|
||||||
|
var parts []string
|
||||||
|
alias := f.Name
|
||||||
|
|
||||||
|
tag, tagOk := f.Tag.Lookup(tagName)
|
||||||
|
if tagOk {
|
||||||
|
partsTemp := strings.Split(tag, ",")
|
||||||
|
parts = make([]string, 0, len(partsTemp))
|
||||||
|
for i := 0; i < len(partsTemp); i++ {
|
||||||
|
part := strings.TrimSpace(partsTemp[i])
|
||||||
|
if len(part) != 0 {
|
||||||
|
parts = append(parts, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) != 0 {
|
||||||
|
alias = parts[0]
|
||||||
|
// TODO parse other tags
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fieldInfo{
|
||||||
|
Alias: alias,
|
||||||
|
Name: f.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
234
vendor/github.com/wlevene/ini/lexer/lexer.go
generated
vendored
Normal file
234
vendor/github.com/wlevene/ini/lexer/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wlevene/ini/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LineBreak = '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
Input []byte
|
||||||
|
char byte
|
||||||
|
position int
|
||||||
|
read_position int
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(input string) *Lexer {
|
||||||
|
l := &Lexer{Input: []byte(input)}
|
||||||
|
l.readChar()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) peekChar() byte {
|
||||||
|
if l.read_position >= len(l.Input) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return l.Input[l.read_position]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readChar() {
|
||||||
|
if l.read_position >= len(l.Input) {
|
||||||
|
l.char = 0
|
||||||
|
} else {
|
||||||
|
l.char = l.Input[l.read_position]
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.char == LineBreak {
|
||||||
|
l.line++
|
||||||
|
}
|
||||||
|
|
||||||
|
l.position = l.read_position
|
||||||
|
l.read_position++
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readLine() []byte {
|
||||||
|
position := l.position
|
||||||
|
for {
|
||||||
|
if l.char == LineBreak {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.Input[position:l.position]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) skipWhitespace() {
|
||||||
|
for l.char == ' ' ||
|
||||||
|
l.char == '\t' ||
|
||||||
|
l.char == LineBreak ||
|
||||||
|
l.char == '\r' {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) skipspace() {
|
||||||
|
for l.char == ' ' {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
// for l.char == ' ' ||
|
||||||
|
// l.char == '\t' {
|
||||||
|
// l.readChar()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) skipline() {
|
||||||
|
|
||||||
|
for {
|
||||||
|
if l.char == LineBreak {
|
||||||
|
// l.line++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) NextToken() token.Token {
|
||||||
|
var tok token.Token
|
||||||
|
var ch byte
|
||||||
|
l.skipWhitespace()
|
||||||
|
|
||||||
|
switch l.char {
|
||||||
|
case '[':
|
||||||
|
tok = newToken(token.TokenType_LBRACKET, l.char, l.line)
|
||||||
|
case '=':
|
||||||
|
tok = newToken(token.TokenType_ASSIGN, l.char, l.line)
|
||||||
|
case ']':
|
||||||
|
tok = newToken(token.TokenType_RBRACKET, l.char, l.line)
|
||||||
|
case 0:
|
||||||
|
tok.Literal = ""
|
||||||
|
tok.Type = token.TokenType_EOF
|
||||||
|
return tok
|
||||||
|
case '#':
|
||||||
|
fallthrough
|
||||||
|
case ';':
|
||||||
|
tok.Line = l.line
|
||||||
|
tok.Literal = string(l.readLine())
|
||||||
|
tok.Type = token.TokenType_COMMENT
|
||||||
|
|
||||||
|
default:
|
||||||
|
tok.Line = l.line
|
||||||
|
tok.Literal = string(l.readStatement())
|
||||||
|
l.skipspace()
|
||||||
|
|
||||||
|
ch = l.char
|
||||||
|
|
||||||
|
if ch == '=' {
|
||||||
|
tok.Type = token.TokenType_KEY
|
||||||
|
} else if ch == ']' {
|
||||||
|
tok.Type = token.TokenType_SECTION
|
||||||
|
} else if ch == LineBreak {
|
||||||
|
tok.Type = token.TokenType_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.readChar()
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
func (s *lexer) getNextToken() token.Token {
|
||||||
|
reToken:
|
||||||
|
ch := s.peek()
|
||||||
|
switch {
|
||||||
|
case isSpace(ch):
|
||||||
|
s.skipWhitespace()
|
||||||
|
goto reToken
|
||||||
|
case isEOF(ch):
|
||||||
|
return s.NewToken(token.EOF).Lit(string(s.read()))
|
||||||
|
case ch == ';':
|
||||||
|
return s.NewToken(token.Semicolon).Lit(string(s.read()))
|
||||||
|
case ch == '{':
|
||||||
|
return s.NewToken(token.BlockStart).Lit(string(s.read()))
|
||||||
|
case ch == '}':
|
||||||
|
return s.NewToken(token.BlockEnd).Lit(string(s.read()))
|
||||||
|
case ch == '#':
|
||||||
|
return s.scanComment()
|
||||||
|
case ch == '$':
|
||||||
|
return s.scanVariable()
|
||||||
|
case isQuote(ch):
|
||||||
|
return s.scanQuotedString(ch)
|
||||||
|
default:
|
||||||
|
return s.scanKeyword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func isLetter(ch byte) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(ch byte) bool {
|
||||||
|
return ('0' <= ch && ch <= '9') || ch == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
func newToken(tokenType token.TokenType, ch byte, line int) token.Token {
|
||||||
|
return token.Token{Type: tokenType,
|
||||||
|
Literal: string(ch),
|
||||||
|
Line: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readStatement() []byte {
|
||||||
|
position := l.position
|
||||||
|
for l.char != LineBreak &&
|
||||||
|
l.char != '=' &&
|
||||||
|
l.char != ']' &&
|
||||||
|
l.char != ' ' {
|
||||||
|
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
return l.Input[position:l.position]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) readSection() token.Token {
|
||||||
|
var tok token.Token
|
||||||
|
if l.char != '[' {
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
position := l.position
|
||||||
|
|
||||||
|
for {
|
||||||
|
if l.char != ']' {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.char == LineBreak {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Token{
|
||||||
|
Type: token.TokenType_SECTION,
|
||||||
|
Literal: string(l.Input[position:l.position]),
|
||||||
|
Line: l.line,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
func (s *lexer) read() rune {
|
||||||
|
ch, _, err := s.reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return rune(token.EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\n' {
|
||||||
|
s.column = 1
|
||||||
|
s.line++
|
||||||
|
} else {
|
||||||
|
s.column++
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
BIN
vendor/github.com/wlevene/ini/logo.png
generated
vendored
Normal file
BIN
vendor/github.com/wlevene/ini/logo.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
113
vendor/github.com/wlevene/ini/parser/parser.go
generated
vendored
Normal file
113
vendor/github.com/wlevene/ini/parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wlevene/ini/ast"
|
||||||
|
"github.com/wlevene/ini/lexer"
|
||||||
|
"github.com/wlevene/ini/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type (
|
||||||
|
// prefixParseFn func() ast.Expression
|
||||||
|
// infixParseFn func(ast.Expression) ast.Expression
|
||||||
|
// )
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
lexer *lexer.Lexer
|
||||||
|
errors []string
|
||||||
|
currentToken token.Token
|
||||||
|
peekToken token.Token
|
||||||
|
|
||||||
|
// prefixParseFns map[token.TokenType]prefixParseFn
|
||||||
|
// infixParseFns map[token.TokenType]infixParseFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(l *lexer.Lexer) *Parser {
|
||||||
|
p := &Parser{
|
||||||
|
lexer: l,
|
||||||
|
errors: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// p.prefixParseFns = make(map[token.Type]prefixParseFn)
|
||||||
|
// p.registerPrefix(token.INTEGER, p.parseIntegerLiteral)
|
||||||
|
|
||||||
|
// p.infixParseFns = make(map[token.Type]infixParseFn)
|
||||||
|
// p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) ParseDocument() (doc *ast.Doc, err error) {
|
||||||
|
|
||||||
|
doc = ast.NewDoc()
|
||||||
|
|
||||||
|
// Read two tokens, so currentToken and peekToken are both set.
|
||||||
|
p.nextToken()
|
||||||
|
p.nextToken()
|
||||||
|
|
||||||
|
var curSection *ast.SetcionNode
|
||||||
|
var KV *ast.KVNode
|
||||||
|
|
||||||
|
for !p.curTokenIs(token.TokenType_EOF) {
|
||||||
|
|
||||||
|
if p.currentToken.Type == token.TokenType_SECTION {
|
||||||
|
|
||||||
|
curSection = nil
|
||||||
|
curSection = &ast.SetcionNode{
|
||||||
|
Name: p.currentToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
if curSection != nil {
|
||||||
|
doc.AppendChild(doc, curSection)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if p.currentToken.Type == token.TokenType_KEY {
|
||||||
|
KV = nil
|
||||||
|
KV = &ast.KVNode{}
|
||||||
|
KV.Key = p.currentToken
|
||||||
|
|
||||||
|
} else if p.currentToken.Type == token.TokenType_VALUE {
|
||||||
|
if KV != nil {
|
||||||
|
KV.Value = p.currentToken
|
||||||
|
if curSection != nil {
|
||||||
|
curSection.AppendChild(curSection, KV)
|
||||||
|
} else {
|
||||||
|
doc.AppendChild(doc, KV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if p.currentToken.Type == token.TokenType_COMMENT {
|
||||||
|
comment := &ast.CommentNode{
|
||||||
|
Comment: p.currentToken,
|
||||||
|
}
|
||||||
|
doc.AppendChild(doc, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc, nil
|
||||||
|
}
|
||||||
|
func (p *Parser) nextToken() {
|
||||||
|
p.currentToken = p.peekToken
|
||||||
|
p.peekToken = p.lexer.NextToken()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) curTokenIs(t token.TokenType) bool {
|
||||||
|
return p.currentToken.Type == t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) peekTokenIs(t token.TokenType) bool {
|
||||||
|
return p.peekToken.Type == t
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
|
||||||
|
// p.prefixParseFns[tokenType] = fn
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
|
||||||
|
// p.infixParseFns[tokenType] = fn
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (p *Parser) peekTokenAtSameLine() bool {
|
||||||
|
return p.currentToken.Line == p.peekToken.Line && p.peekToken.Type != token.TokenType_EOF
|
||||||
|
}
|
||||||
4
vendor/github.com/wlevene/ini/save.ini
generated
vendored
Normal file
4
vendor/github.com/wlevene/ini/save.ini
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
a1 = 1
|
||||||
|
|
||||||
|
[s1]
|
||||||
|
a2 = v2
|
||||||
32
vendor/github.com/wlevene/ini/test.ini
generated
vendored
Normal file
32
vendor/github.com/wlevene/ini/test.ini
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
; this is comment
|
||||||
|
; author levene
|
||||||
|
; date 2021-8-1
|
||||||
|
|
||||||
|
|
||||||
|
a='23'34?::'<>,.'
|
||||||
|
c=d
|
||||||
|
|
||||||
|
[s1]
|
||||||
|
k=67676
|
||||||
|
k1 =fdasf
|
||||||
|
k2= sdafj3490&@)34 34w2
|
||||||
|
|
||||||
|
# comment
|
||||||
|
# 12.0.0.1
|
||||||
|
[s2]
|
||||||
|
|
||||||
|
k=3
|
||||||
|
|
||||||
|
|
||||||
|
k2=945
|
||||||
|
k3=-435
|
||||||
|
k4=0.0.0.0
|
||||||
|
|
||||||
|
k5=127.0.0.1
|
||||||
|
k6=levene@github.com
|
||||||
|
|
||||||
|
k7=~/.path.txt
|
||||||
|
k8=./34/34/uh.txt
|
||||||
|
|
||||||
|
k9=234@!@#$%^&*()324
|
||||||
|
k10='23'34?::'<>,.'
|
||||||
18
vendor/github.com/wlevene/ini/test2.ini
generated
vendored
Normal file
18
vendor/github.com/wlevene/ini/test2.ini
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
[s2]
|
||||||
|
|
||||||
|
k=3
|
||||||
|
|
||||||
|
|
||||||
|
k2=945
|
||||||
|
k3=-435
|
||||||
|
k4=0.0.0.0
|
||||||
|
|
||||||
|
k5=127.0.0.1
|
||||||
|
k6=levene@github.com
|
||||||
|
|
||||||
|
k7=~/.path.txt
|
||||||
|
k8=./34/34/uh.txt
|
||||||
|
|
||||||
|
k9=234@!@#$%^&*()324
|
||||||
|
k10='23'34?::'<>,.'
|
||||||
52
vendor/github.com/wlevene/ini/token/token.go
generated
vendored
Normal file
52
vendor/github.com/wlevene/ini/token/token.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type TokenType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenType_ILLEGAL = "ILLEGAL"
|
||||||
|
TokenType_EOF = "EOF"
|
||||||
|
Ident = "Ident"
|
||||||
|
|
||||||
|
TokenType_SECTION = "SECTION"
|
||||||
|
TokenType_KEY = "KEY"
|
||||||
|
TokenType_VALUE = "VALUE"
|
||||||
|
TokenType_COMMENT = "#"
|
||||||
|
TokenType_COMMENT2 = "#"
|
||||||
|
|
||||||
|
TokenType_ASSIGN = "="
|
||||||
|
TokenType_LBRACKET = "["
|
||||||
|
TokenType_RBRACKET = "]"
|
||||||
|
|
||||||
|
RETURN = "RETURN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType
|
||||||
|
Literal string
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
var keywords = map[string]TokenType{
|
||||||
|
"return": RETURN,
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupIdent(ident string) TokenType {
|
||||||
|
if tok, ok := keywords[ident]; ok {
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
return Ident
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("{Type:%s,Literal:\"%s\" Linie:%d}",
|
||||||
|
t.Type,
|
||||||
|
t.Literal,
|
||||||
|
t.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Is check type of a token
|
||||||
|
func (t Token) Is(typ TokenType) bool {
|
||||||
|
return t.Type == typ
|
||||||
|
}
|
||||||
4
vendor/golang.org/x/sys/LICENSE
generated
vendored
4
vendor/golang.org/x/sys/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
copyright notice, this list of conditions and the following disclaimer
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
in the documentation and/or other materials provided with the
|
in the documentation and/or other materials provided with the
|
||||||
distribution.
|
distribution.
|
||||||
* Neither the name of Google Inc. nor the names of its
|
* Neither the name of Google LLC nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/README.md
generated
vendored
2
vendor/golang.org/x/sys/unix/README.md
generated
vendored
@@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these
|
|||||||
into a common file for each OS.
|
into a common file for each OS.
|
||||||
|
|
||||||
The merge is performed in the following steps:
|
The merge is performed in the following steps:
|
||||||
1. Construct the set of common code that is idential in all architecture-specific files.
|
1. Construct the set of common code that is identical in all architecture-specific files.
|
||||||
2. Write this common code to the merged file.
|
2. Write this common code to the merged file.
|
||||||
3. Remove the common code from all architecture-specific files.
|
3. Remove the common code from all architecture-specific files.
|
||||||
|
|
||||||
|
|||||||
9
vendor/golang.org/x/sys/unix/affinity_linux.go
generated
vendored
9
vendor/golang.org/x/sys/unix/affinity_linux.go
generated
vendored
@@ -38,8 +38,15 @@ func SchedSetaffinity(pid int, set *CPUSet) error {
|
|||||||
|
|
||||||
// Zero clears the set s, so that it contains no CPUs.
|
// Zero clears the set s, so that it contains no CPUs.
|
||||||
func (s *CPUSet) Zero() {
|
func (s *CPUSet) Zero() {
|
||||||
|
clear(s[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinity]
|
||||||
|
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
|
||||||
|
// efficient way of resetting the CPU affinity of a process.
|
||||||
|
func (s *CPUSet) Fill() {
|
||||||
for i := range s {
|
for i := range s {
|
||||||
s[i] = 0
|
s[i] = ^cpuMask(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
vendor/golang.org/x/sys/unix/auxv.go
generated
vendored
Normal file
36
vendor/golang.org/x/sys/unix/auxv.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname runtime_getAuxv runtime.getAuxv
|
||||||
|
func runtime_getAuxv() []uintptr
|
||||||
|
|
||||||
|
// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs.
|
||||||
|
// The returned slice is always a fresh copy, owned by the caller.
|
||||||
|
// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed,
|
||||||
|
// which happens in some locked-down environments and build modes.
|
||||||
|
func Auxv() ([][2]uintptr, error) {
|
||||||
|
vec := runtime_getAuxv()
|
||||||
|
vecLen := len(vec)
|
||||||
|
|
||||||
|
if vecLen == 0 {
|
||||||
|
return nil, syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
if vecLen%2 != 0 {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]uintptr, vecLen)
|
||||||
|
copy(result, vec)
|
||||||
|
return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil
|
||||||
|
}
|
||||||
13
vendor/golang.org/x/sys/unix/auxv_unsupported.go
generated
vendored
Normal file
13
vendor/golang.org/x/sys/unix/auxv_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func Auxv() ([][2]uintptr, error) {
|
||||||
|
return nil, syscall.ENOTSUP
|
||||||
|
}
|
||||||
4
vendor/golang.org/x/sys/unix/fdset.go
generated
vendored
4
vendor/golang.org/x/sys/unix/fdset.go
generated
vendored
@@ -23,7 +23,5 @@ func (fds *FdSet) IsSet(fd int) bool {
|
|||||||
|
|
||||||
// Zero clears the set fds.
|
// Zero clears the set fds.
|
||||||
func (fds *FdSet) Zero() {
|
func (fds *FdSet) Zero() {
|
||||||
for i := range fds.Bits {
|
clear(fds.Bits[:])
|
||||||
fds.Bits[i] = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
4
vendor/golang.org/x/sys/unix/ifreq_linux.go
generated
vendored
4
vendor/golang.org/x/sys/unix/ifreq_linux.go
generated
vendored
@@ -111,9 +111,7 @@ func (ifr *Ifreq) SetUint32(v uint32) {
|
|||||||
// clear zeroes the ifreq's union field to prevent trailing garbage data from
|
// clear zeroes the ifreq's union field to prevent trailing garbage data from
|
||||||
// being sent to the kernel if an ifreq is reused.
|
// being sent to the kernel if an ifreq is reused.
|
||||||
func (ifr *Ifreq) clear() {
|
func (ifr *Ifreq) clear() {
|
||||||
for i := range ifr.raw.Ifru {
|
clear(ifr.raw.Ifru[:])
|
||||||
ifr.raw.Ifru[i] = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mdlayher): export as IfreqData? For now we can provide helpers such as
|
// TODO(mdlayher): export as IfreqData? For now we can provide helpers such as
|
||||||
|
|||||||
96
vendor/golang.org/x/sys/unix/ioctl_linux.go
generated
vendored
96
vendor/golang.org/x/sys/unix/ioctl_linux.go
generated
vendored
@@ -58,6 +58,102 @@ func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
|
|||||||
return &value, err
|
return &value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC
|
||||||
|
// association for the network device specified by ifname.
|
||||||
|
func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) {
|
||||||
|
ifr, err := NewIfreq(ifname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO}
|
||||||
|
ifrd := ifr.withData(unsafe.Pointer(&value))
|
||||||
|
|
||||||
|
err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd)
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlGetHwTstamp retrieves the hardware timestamping configuration
|
||||||
|
// for the network device specified by ifname.
|
||||||
|
func IoctlGetHwTstamp(fd int, ifname string) (*HwTstampConfig, error) {
|
||||||
|
ifr, err := NewIfreq(ifname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := HwTstampConfig{}
|
||||||
|
ifrd := ifr.withData(unsafe.Pointer(&value))
|
||||||
|
|
||||||
|
err = ioctlIfreqData(fd, SIOCGHWTSTAMP, &ifrd)
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlSetHwTstamp updates the hardware timestamping configuration for
|
||||||
|
// the network device specified by ifname.
|
||||||
|
func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error {
|
||||||
|
ifr, err := NewIfreq(ifname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ifrd := ifr.withData(unsafe.Pointer(cfg))
|
||||||
|
return ioctlIfreqData(fd, SIOCSHWTSTAMP, &ifrd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FdToClockID derives the clock ID from the file descriptor number
|
||||||
|
// - see clock_gettime(3), FD_TO_CLOCKID macros. The resulting ID is
|
||||||
|
// suitable for system calls like ClockGettime.
|
||||||
|
func FdToClockID(fd int) int32 { return int32((int(^fd) << 3) | 3) }
|
||||||
|
|
||||||
|
// IoctlPtpClockGetcaps returns the description of a given PTP device.
|
||||||
|
func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) {
|
||||||
|
var value PtpClockCaps
|
||||||
|
err := ioctlPtr(fd, PTP_CLOCK_GETCAPS2, unsafe.Pointer(&value))
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpSysOffsetPrecise returns a description of the clock
|
||||||
|
// offset compared to the system clock.
|
||||||
|
func IoctlPtpSysOffsetPrecise(fd int) (*PtpSysOffsetPrecise, error) {
|
||||||
|
var value PtpSysOffsetPrecise
|
||||||
|
err := ioctlPtr(fd, PTP_SYS_OFFSET_PRECISE2, unsafe.Pointer(&value))
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpSysOffsetExtended returns an extended description of the
|
||||||
|
// clock offset compared to the system clock. The samples parameter
|
||||||
|
// specifies the desired number of measurements.
|
||||||
|
func IoctlPtpSysOffsetExtended(fd int, samples uint) (*PtpSysOffsetExtended, error) {
|
||||||
|
value := PtpSysOffsetExtended{Samples: uint32(samples)}
|
||||||
|
err := ioctlPtr(fd, PTP_SYS_OFFSET_EXTENDED2, unsafe.Pointer(&value))
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpPinGetfunc returns the configuration of the specified
|
||||||
|
// I/O pin on given PTP device.
|
||||||
|
func IoctlPtpPinGetfunc(fd int, index uint) (*PtpPinDesc, error) {
|
||||||
|
value := PtpPinDesc{Index: uint32(index)}
|
||||||
|
err := ioctlPtr(fd, PTP_PIN_GETFUNC2, unsafe.Pointer(&value))
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpPinSetfunc updates configuration of the specified PTP
|
||||||
|
// I/O pin.
|
||||||
|
func IoctlPtpPinSetfunc(fd int, pd *PtpPinDesc) error {
|
||||||
|
return ioctlPtr(fd, PTP_PIN_SETFUNC2, unsafe.Pointer(pd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpPeroutRequest configures the periodic output mode of the
|
||||||
|
// PTP I/O pins.
|
||||||
|
func IoctlPtpPeroutRequest(fd int, r *PtpPeroutRequest) error {
|
||||||
|
return ioctlPtr(fd, PTP_PEROUT_REQUEST2, unsafe.Pointer(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoctlPtpExttsRequest configures the external timestamping mode
|
||||||
|
// of the PTP I/O pins.
|
||||||
|
func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error {
|
||||||
|
return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r))
|
||||||
|
}
|
||||||
|
|
||||||
// IoctlGetWatchdogInfo fetches information about a watchdog device from the
|
// IoctlGetWatchdogInfo fetches information about a watchdog device from the
|
||||||
// Linux watchdog API. For more information, see:
|
// Linux watchdog API. For more information, see:
|
||||||
// https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html.
|
// https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html.
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
1
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
@@ -49,6 +49,7 @@ esac
|
|||||||
if [[ "$GOOS" = "linux" ]]; then
|
if [[ "$GOOS" = "linux" ]]; then
|
||||||
# Use the Docker-based build system
|
# Use the Docker-based build system
|
||||||
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
||||||
|
set -e
|
||||||
$cmd docker build --tag generate:$GOOS $GOOS
|
$cmd docker build --tag generate:$GOOS $GOOS
|
||||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
||||||
exit
|
exit
|
||||||
|
|||||||
23
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
23
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
@@ -58,6 +58,7 @@ includes_Darwin='
|
|||||||
#define _DARWIN_USE_64_BIT_INODE
|
#define _DARWIN_USE_64_BIT_INODE
|
||||||
#define __APPLE_USE_RFC_3542
|
#define __APPLE_USE_RFC_3542
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <sys/stdio.h>
|
||||||
#include <sys/attr.h>
|
#include <sys/attr.h>
|
||||||
#include <sys/clonefile.h>
|
#include <sys/clonefile.h>
|
||||||
#include <sys/kern_control.h>
|
#include <sys/kern_control.h>
|
||||||
@@ -157,6 +158,16 @@ includes_Linux='
|
|||||||
#endif
|
#endif
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
// See the description in unix/linux/types.go
|
||||||
|
#if defined(__ARM_EABI__) || \
|
||||||
|
(defined(__mips__) && (_MIPS_SIM == _ABIO32)) || \
|
||||||
|
(defined(__powerpc__) && (!defined(__powerpc64__)))
|
||||||
|
# ifdef _TIME_BITS
|
||||||
|
# undef _TIME_BITS
|
||||||
|
# endif
|
||||||
|
# define _TIME_BITS 32
|
||||||
|
#endif
|
||||||
|
|
||||||
// <sys/ioctl.h> is broken on powerpc64, as it fails to include definitions of
|
// <sys/ioctl.h> is broken on powerpc64, as it fails to include definitions of
|
||||||
// these structures. We just include them copied from <bits/termios.h>.
|
// these structures. We just include them copied from <bits/termios.h>.
|
||||||
#if defined(__powerpc__)
|
#if defined(__powerpc__)
|
||||||
@@ -215,6 +226,7 @@ struct ltchars {
|
|||||||
#include <linux/cryptouser.h>
|
#include <linux/cryptouser.h>
|
||||||
#include <linux/devlink.h>
|
#include <linux/devlink.h>
|
||||||
#include <linux/dm-ioctl.h>
|
#include <linux/dm-ioctl.h>
|
||||||
|
#include <linux/elf.h>
|
||||||
#include <linux/errqueue.h>
|
#include <linux/errqueue.h>
|
||||||
#include <linux/ethtool_netlink.h>
|
#include <linux/ethtool_netlink.h>
|
||||||
#include <linux/falloc.h>
|
#include <linux/falloc.h>
|
||||||
@@ -255,6 +267,7 @@ struct ltchars {
|
|||||||
#include <linux/nsfs.h>
|
#include <linux/nsfs.h>
|
||||||
#include <linux/perf_event.h>
|
#include <linux/perf_event.h>
|
||||||
#include <linux/pps.h>
|
#include <linux/pps.h>
|
||||||
|
#include <linux/ptp_clock.h>
|
||||||
#include <linux/ptrace.h>
|
#include <linux/ptrace.h>
|
||||||
#include <linux/random.h>
|
#include <linux/random.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
@@ -337,6 +350,9 @@ struct ltchars {
|
|||||||
#define _HIDIOCGRAWPHYS HIDIOCGRAWPHYS(_HIDIOCGRAWPHYS_LEN)
|
#define _HIDIOCGRAWPHYS HIDIOCGRAWPHYS(_HIDIOCGRAWPHYS_LEN)
|
||||||
#define _HIDIOCGRAWUNIQ HIDIOCGRAWUNIQ(_HIDIOCGRAWUNIQ_LEN)
|
#define _HIDIOCGRAWUNIQ HIDIOCGRAWUNIQ(_HIDIOCGRAWUNIQ_LEN)
|
||||||
|
|
||||||
|
// Renamed in v6.16, commit c6d732c38f93 ("net: ethtool: remove duplicate defines for family info")
|
||||||
|
#define ETHTOOL_FAMILY_NAME ETHTOOL_GENL_NAME
|
||||||
|
#define ETHTOOL_FAMILY_VERSION ETHTOOL_GENL_VERSION
|
||||||
'
|
'
|
||||||
|
|
||||||
includes_NetBSD='
|
includes_NetBSD='
|
||||||
@@ -514,6 +530,7 @@ ccflags="$@"
|
|||||||
$2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ ||
|
$2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ ||
|
||||||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ ||
|
$2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ ||
|
||||||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ ||
|
$2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ ||
|
||||||
|
$2 ~ /^(DT|EI|ELF|EV|NN|NT|PF|SHF|SHN|SHT|STB|STT|VER)_/ ||
|
||||||
$2 ~ /^O?XTABS$/ ||
|
$2 ~ /^O?XTABS$/ ||
|
||||||
$2 ~ /^TC[IO](ON|OFF)$/ ||
|
$2 ~ /^TC[IO](ON|OFF)$/ ||
|
||||||
$2 ~ /^IN_/ ||
|
$2 ~ /^IN_/ ||
|
||||||
@@ -526,6 +543,7 @@ ccflags="$@"
|
|||||||
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ ||
|
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ ||
|
||||||
$2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ ||
|
$2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ ||
|
||||||
$2 ~ /^NFC_.*_(MAX)?SIZE$/ ||
|
$2 ~ /^NFC_.*_(MAX)?SIZE$/ ||
|
||||||
|
$2 ~ /^PTP_/ ||
|
||||||
$2 ~ /^RAW_PAYLOAD_/ ||
|
$2 ~ /^RAW_PAYLOAD_/ ||
|
||||||
$2 ~ /^[US]F_/ ||
|
$2 ~ /^[US]F_/ ||
|
||||||
$2 ~ /^TP_STATUS_/ ||
|
$2 ~ /^TP_STATUS_/ ||
|
||||||
@@ -551,6 +569,7 @@ ccflags="$@"
|
|||||||
$2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ &&
|
$2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ &&
|
||||||
$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ ||
|
$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ ||
|
||||||
$2 ~ /^SOCK_|SK_DIAG_|SKNLGRP_$/ ||
|
$2 ~ /^SOCK_|SK_DIAG_|SKNLGRP_$/ ||
|
||||||
|
$2 ~ /^(CONNECT|SAE)_/ ||
|
||||||
$2 ~ /^FIORDCHK$/ ||
|
$2 ~ /^FIORDCHK$/ ||
|
||||||
$2 ~ /^SIOC/ ||
|
$2 ~ /^SIOC/ ||
|
||||||
$2 ~ /^TIOC/ ||
|
$2 ~ /^TIOC/ ||
|
||||||
@@ -654,7 +673,7 @@ errors=$(
|
|||||||
signals=$(
|
signals=$(
|
||||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' |
|
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' |
|
||||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' |
|
grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' |
|
||||||
sort
|
sort
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -664,7 +683,7 @@ echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
|||||||
sort >_error.grep
|
sort >_error.grep
|
||||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' |
|
grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' |
|
||||||
sort >_signal.grep
|
sort >_signal.grep
|
||||||
|
|
||||||
echo '// mkerrors.sh' "$@"
|
echo '// mkerrors.sh' "$@"
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/syscall_aix.go
generated
vendored
2
vendor/golang.org/x/sys/unix/syscall_aix.go
generated
vendored
@@ -360,7 +360,7 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int,
|
|||||||
var status _C_int
|
var status _C_int
|
||||||
var r Pid_t
|
var r Pid_t
|
||||||
err = ERESTART
|
err = ERESTART
|
||||||
// AIX wait4 may return with ERESTART errno, while the processus is still
|
// AIX wait4 may return with ERESTART errno, while the process is still
|
||||||
// active.
|
// active.
|
||||||
for err == ERESTART {
|
for err == ERESTART {
|
||||||
r, err = wait4(Pid_t(pid), &status, options, rusage)
|
r, err = wait4(Pid_t(pid), &status, options, rusage)
|
||||||
|
|||||||
142
vendor/golang.org/x/sys/unix/syscall_darwin.go
generated
vendored
142
vendor/golang.org/x/sys/unix/syscall_darwin.go
generated
vendored
@@ -402,6 +402,18 @@ func IoctlSetIfreqMTU(fd int, ifreq *IfreqMTU) error {
|
|||||||
return ioctlPtr(fd, SIOCSIFMTU, unsafe.Pointer(ifreq))
|
return ioctlPtr(fd, SIOCSIFMTU, unsafe.Pointer(ifreq))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//sys renamexNp(from string, to string, flag uint32) (err error)
|
||||||
|
|
||||||
|
func RenamexNp(from string, to string, flag uint32) (err error) {
|
||||||
|
return renamexNp(from, to, flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys renameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error)
|
||||||
|
|
||||||
|
func RenameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error) {
|
||||||
|
return renameatxNp(fromfd, from, tofd, to, flag)
|
||||||
|
}
|
||||||
|
|
||||||
//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS_SYSCTL
|
//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS_SYSCTL
|
||||||
|
|
||||||
func Uname(uname *Utsname) error {
|
func Uname(uname *Utsname) error {
|
||||||
@@ -554,6 +566,132 @@ func PthreadFchdir(fd int) (err error) {
|
|||||||
return pthread_fchdir_np(fd)
|
return pthread_fchdir_np(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connectx calls connectx(2) to initiate a connection on a socket.
|
||||||
|
//
|
||||||
|
// srcIf, srcAddr, and dstAddr are filled into a [SaEndpoints] struct and passed as the endpoints argument.
|
||||||
|
//
|
||||||
|
// - srcIf is the optional source interface index. 0 means unspecified.
|
||||||
|
// - srcAddr is the optional source address. nil means unspecified.
|
||||||
|
// - dstAddr is the destination address.
|
||||||
|
//
|
||||||
|
// On success, Connectx returns the number of bytes enqueued for transmission.
|
||||||
|
func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocID, flags uint32, iov []Iovec, connid *SaeConnID) (n uintptr, err error) {
|
||||||
|
endpoints := SaEndpoints{
|
||||||
|
Srcif: srcIf,
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcAddr != nil {
|
||||||
|
addrp, addrlen, err := srcAddr.sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
endpoints.Srcaddr = (*RawSockaddr)(addrp)
|
||||||
|
endpoints.Srcaddrlen = uint32(addrlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstAddr != nil {
|
||||||
|
addrp, addrlen, err := dstAddr.sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
endpoints.Dstaddr = (*RawSockaddr)(addrp)
|
||||||
|
endpoints.Dstaddrlen = uint32(addrlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connectx(fd, &endpoints, associd, flags, iov, &n, connid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const minIovec = 8
|
||||||
|
|
||||||
|
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
n, err = readv(fd, iovecs)
|
||||||
|
readvRacedetect(iovecs, n, err)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
n, err = preadv(fd, iovecs, offset)
|
||||||
|
readvRacedetect(iovecs, n, err)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
if raceenabled {
|
||||||
|
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
n, err = writev(fd, iovecs)
|
||||||
|
writevRacedetect(iovecs, n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
if raceenabled {
|
||||||
|
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
n, err = pwritev(fd, iovecs, offset)
|
||||||
|
writevRacedetect(iovecs, n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
|
||||||
|
for _, b := range bs {
|
||||||
|
var v Iovec
|
||||||
|
v.SetLen(len(b))
|
||||||
|
if len(b) > 0 {
|
||||||
|
v.Base = &b[0]
|
||||||
|
} else {
|
||||||
|
v.Base = (*byte)(unsafe.Pointer(&_zero))
|
||||||
|
}
|
||||||
|
vecs = append(vecs, v)
|
||||||
|
}
|
||||||
|
return vecs
|
||||||
|
}
|
||||||
|
|
||||||
|
func writevRacedetect(iovecs []Iovec, n int) {
|
||||||
|
if !raceenabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; n > 0 && i < len(iovecs); i++ {
|
||||||
|
m := int(iovecs[i].Len)
|
||||||
|
if m > n {
|
||||||
|
m = n
|
||||||
|
}
|
||||||
|
n -= m
|
||||||
|
if m > 0 {
|
||||||
|
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readvRacedetect(iovecs []Iovec, n int, err error) {
|
||||||
|
if !raceenabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; n > 0 && i < len(iovecs); i++ {
|
||||||
|
m := int(iovecs[i].Len)
|
||||||
|
if m > n {
|
||||||
|
m = n
|
||||||
|
}
|
||||||
|
n -= m
|
||||||
|
if m > 0 {
|
||||||
|
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
raceAcquire(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error)
|
||||||
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
|
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
|
||||||
|
|
||||||
//sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error)
|
//sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error)
|
||||||
@@ -656,3 +794,7 @@ func PthreadFchdir(fd int) (err error) {
|
|||||||
//sys write(fd int, p []byte) (n int, err error)
|
//sys write(fd int, p []byte) (n int, err error)
|
||||||
//sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error)
|
//sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error)
|
||||||
//sys munmap(addr uintptr, length uintptr) (err error)
|
//sys munmap(addr uintptr, length uintptr) (err error)
|
||||||
|
//sys readv(fd int, iovecs []Iovec) (n int, err error)
|
||||||
|
//sys preadv(fd int, iovecs []Iovec, offset int64) (n int, err error)
|
||||||
|
//sys writev(fd int, iovecs []Iovec) (n int, err error)
|
||||||
|
//sys pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error)
|
||||||
|
|||||||
12
vendor/golang.org/x/sys/unix/syscall_dragonfly.go
generated
vendored
12
vendor/golang.org/x/sys/unix/syscall_dragonfly.go
generated
vendored
@@ -246,6 +246,18 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
|||||||
return sendfile(outfd, infd, offset, count)
|
return sendfile(outfd, infd, offset, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Dup3(oldfd, newfd, flags int) error {
|
||||||
|
if oldfd == newfd || flags&^O_CLOEXEC != 0 {
|
||||||
|
return EINVAL
|
||||||
|
}
|
||||||
|
how := F_DUP2FD
|
||||||
|
if flags&O_CLOEXEC != 0 {
|
||||||
|
how = F_DUP2FD_CLOEXEC
|
||||||
|
}
|
||||||
|
_, err := fcntl(oldfd, how, newfd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Exposed directly
|
* Exposed directly
|
||||||
*/
|
*/
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/syscall_hurd.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_hurd.go
generated
vendored
@@ -11,6 +11,7 @@ package unix
|
|||||||
int ioctl(int, unsigned long int, uintptr_t);
|
int ioctl(int, unsigned long int, uintptr_t);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
func ioctl(fd int, req uint, arg uintptr) (err error) {
|
func ioctl(fd int, req uint, arg uintptr) (err error) {
|
||||||
r0, er := C.ioctl(C.int(fd), C.ulong(req), C.uintptr_t(arg))
|
r0, er := C.ioctl(C.int(fd), C.ulong(req), C.uintptr_t(arg))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user