Update Vendors (#250)
update go min version Update Vendors: * code.gitea.io/gitea-vet v0.2.0 -> v0.2.1 * code.gitea.io/sdk/gitea v0.13.0 -> v0.13.1 * github.com/AlecAivazis/survey v2.1.1 -> v2.2.2 * github.com/adrg/xdg v0.2.1 -> v0.2.2 * github.com/araddon/dateparse d820a6159ab1 -> 8aadafed4dc4 * github.com/go-git/go-git v5.1.0 -> v5.2.0 * github.com/muesli/termenv v0.7.2 -> v0.7.4 * github.com/stretchr/testify v1.5.1 -> v1.6.1 * github.com/urfave/cli v2.2.0 -> v2.3.0 Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/250 Reviewed-by: Andrew Thornton <art27@cantab.net> Reviewed-by: mrsdizzie <info@mrsdizzie.com> Co-Authored-By: 6543 <6543@noreply.gitea.io> Co-Committed-By: 6543 <6543@noreply.gitea.io>
This commit is contained in:
parent
355fd7aa53
commit
d5058b3b20
41
go.mod
41
go.mod
|
@ -1,25 +1,38 @@
|
||||||
module code.gitea.io/tea
|
module code.gitea.io/tea
|
||||||
|
|
||||||
go 1.12
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/gitea-vet v0.2.0
|
code.gitea.io/gitea-vet v0.2.1
|
||||||
code.gitea.io/sdk/gitea v0.13.0
|
code.gitea.io/sdk/gitea v0.13.1
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
github.com/AlecAivazis/survey/v2 v2.2.2
|
||||||
github.com/adrg/xdg v0.2.1
|
github.com/Microsoft/go-winio v0.4.15 // indirect
|
||||||
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1
|
github.com/adrg/xdg v0.2.2
|
||||||
|
github.com/alecthomas/chroma v0.8.1 // indirect
|
||||||
|
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4
|
||||||
github.com/charmbracelet/glamour v0.2.0
|
github.com/charmbracelet/glamour v0.2.0
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.1.0
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
|
github.com/go-git/go-git/v5 v5.2.0
|
||||||
github.com/hashicorp/go-version v1.2.1 // indirect
|
github.com/hashicorp/go-version v1.2.1 // indirect
|
||||||
github.com/muesli/termenv v0.7.2
|
github.com/imdario/mergo v0.3.11 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4 // indirect
|
||||||
|
github.com/muesli/reflow v0.2.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.7.4
|
||||||
github.com/olekukonko/tablewriter v0.0.4
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a // indirect
|
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf // indirect
|
||||||
|
golang.org/x/text v0.3.4 // indirect
|
||||||
|
golang.org/x/tools v0.0.0-20201105220310-78b158585360 // indirect
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||||
)
|
)
|
||||||
|
|
117
go.sum
117
go.sum
|
@ -1,20 +1,26 @@
|
||||||
code.gitea.io/gitea-vet v0.2.0 h1:xkUePzbHI8e0qp4Aly4GBSd0+6cqEMVTrdZq57fPozo=
|
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
||||||
code.gitea.io/gitea-vet v0.2.0/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||||
code.gitea.io/sdk/gitea v0.13.0 h1:iHognp8ZMhMFLooUUNZFpm8IHaC9qoHJDvAE5vTm5aw=
|
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
|
||||||
code.gitea.io/sdk/gitea v0.13.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
|
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=
|
github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
|
github.com/Microsoft/go-winio v0.4.15 h1:qkLXKzb1QoVatRyd/YlXZ/Kg0m5K3SPuoD82jjSOaBc=
|
||||||
|
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||||
github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=
|
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
||||||
github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ=
|
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
||||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||||
|
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
||||||
|
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||||
|
@ -22,12 +28,16 @@ github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkx
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1 h1:TEBmxO80TM04L8IuMWk77SGL1HomBmKTdzdJLLWznxI=
|
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4 h1:OkS1BqB3CzLtGRznRyvriSY8jeaVk2CrDn2ZiRQgMUI=
|
||||||
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4/go.mod h1:hMAUZFIkk4B1FouGxqlogyMyU6BwY/UiVmmbbzz9Up8=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg=
|
github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg=
|
||||||
github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM=
|
github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
|
@ -40,6 +50,8 @@ 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/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
|
@ -50,14 +62,16 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||||
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
|
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||||
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
|
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
|
||||||
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
|
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||||
|
@ -66,6 +80,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||||
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
@ -73,6 +89,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
@ -87,6 +106,8 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
@ -96,16 +117,22 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM=
|
github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM=
|
||||||
github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
|
github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
|
||||||
|
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0=
|
||||||
|
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||||
github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk=
|
github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk=
|
||||||
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
||||||
github.com/muesli/termenv v0.7.2 h1:r1raklL3uKE7rOvWgSenmEm2px+dnc33OTisZ8YR1fw=
|
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
||||||
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
|
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||||
|
@ -118,30 +145,39 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc=
|
github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc=
|
||||||
github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -149,8 +185,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||||
|
@ -161,38 +197,50 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s=
|
||||||
|
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
|
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a h1:kVMPw4f6EVqYdfGQTedjrpw1dbE2PEMfw4jwXsNdn9s=
|
golang.org/x/tools v0.0.0-20201105220310-78b158585360 h1:/9CzsU8hOpnSUCtem1vfWNgsVeCTgkMdx+VE5YIYxnU=
|
||||||
golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20201105220310-78b158585360/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
@ -201,6 +249,11 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
## [v0.2.1](https://gitea.com/gitea/gitea-vet/releases/tag/v0.2.1) - 2020-08-15
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Split migration check to Deps and Imports (#9)
|
||||||
|
|
||||||
## [0.2.0](https://gitea.com/gitea/gitea-vet/pulls?q=&type=all&state=closed&milestone=1272) - 2020-07-20
|
## [0.2.0](https://gitea.com/gitea/gitea-vet/pulls?q=&type=all&state=closed&milestone=1272) - 2020-07-20
|
||||||
|
|
||||||
* FEATURES
|
* FEATURES
|
||||||
|
|
|
@ -18,10 +18,14 @@ var Migrations = &analysis.Analyzer{
|
||||||
Run: checkMigrations,
|
Run: checkMigrations,
|
||||||
}
|
}
|
||||||
|
|
||||||
var migrationBlacklist = []string{
|
var (
|
||||||
"code.gitea.io/gitea/models",
|
migrationDepBlockList = []string{
|
||||||
"code.gitea.io/gitea/modules/structs",
|
"code.gitea.io/gitea/models",
|
||||||
}
|
}
|
||||||
|
migrationImpBlockList = []string{
|
||||||
|
"code.gitea.io/gitea/modules/structs",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func checkMigrations(pass *analysis.Pass) (interface{}, error) {
|
func checkMigrations(pass *analysis.Pass) (interface{}, error) {
|
||||||
if !strings.EqualFold(pass.Pkg.Path(), "code.gitea.io/gitea/models/migrations") {
|
if !strings.EqualFold(pass.Pkg.Path(), "code.gitea.io/gitea/models/migrations") {
|
||||||
|
@ -40,8 +44,22 @@ func checkMigrations(pass *analysis.Pass) (interface{}, error) {
|
||||||
|
|
||||||
deps := strings.Split(string(depsOut), "\n")
|
deps := strings.Split(string(depsOut), "\n")
|
||||||
for _, dep := range deps {
|
for _, dep := range deps {
|
||||||
if stringInSlice(dep, migrationBlacklist) {
|
if stringInSlice(dep, migrationDepBlockList) {
|
||||||
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot depend on the following packages: %s", migrationBlacklist)
|
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot depend on the following packages: %s", migrationDepBlockList)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impsCmd := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, "code.gitea.io/gitea/models/migrations")
|
||||||
|
impsOut, err := impsCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
imps := strings.Split(string(impsOut), "\n")
|
||||||
|
for _, imp := range imps {
|
||||||
|
if stringInSlice(imp, migrationImpBlockList) {
|
||||||
|
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot import the following packages: %s", migrationImpBlockList)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ type Client struct {
|
||||||
password string
|
password string
|
||||||
otp string
|
otp string
|
||||||
sudo string
|
sudo string
|
||||||
|
debug bool
|
||||||
client *http.Client
|
client *http.Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
serverVersion *version.Version
|
serverVersion *version.Version
|
||||||
|
@ -135,7 +136,17 @@ func (c *Client) SetSudo(sudo string) {
|
||||||
c.sudo = sudo
|
c.sudo = sudo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDebugMode is an option for NewClient to enable debug mode
|
||||||
|
func SetDebugMode() func(client *Client) {
|
||||||
|
return func(client *Client) {
|
||||||
|
client.debug = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body)
|
||||||
|
}
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
|
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -147,10 +158,16 @@ func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *R
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
|
}
|
||||||
return data, &Response{resp}, nil
|
return data, &Response{resp}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body)
|
||||||
|
}
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
|
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -175,6 +192,9 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
|
}
|
||||||
return &Response{resp}, nil
|
return &Response{resp}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +237,7 @@ func (c *Client) getResponse(method, path string, header http.Header, body io.Re
|
||||||
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
|
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
|
||||||
data, resp, err := c.getResponse(method, path, header, body)
|
data, resp, err := c.getResponse(method, path, header, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return resp, err
|
||||||
}
|
}
|
||||||
return resp, json.Unmarshal(data, obj)
|
return resp, json.Unmarshal(data, obj)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,11 @@ type NotificationThread struct {
|
||||||
|
|
||||||
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
||||||
type NotificationSubject struct {
|
type NotificationSubject struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
LatestCommentURL string `json:"latest_comment_url"`
|
LatestCommentURL string `json:"latest_comment_url"`
|
||||||
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
|
Type string `json:"type"`
|
||||||
|
State StateType `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyStatus notification status type
|
// NotifyStatus notification status type
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -45,11 +46,23 @@ func (c *Client) ListReleases(user, repo string, opt ListReleasesOptions) ([]*Re
|
||||||
return releases, resp, err
|
return releases, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRelease get a release of a repository
|
// GetRelease get a release of a repository by id
|
||||||
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) {
|
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) {
|
||||||
r := new(Release)
|
r := new(Release)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
||||||
|
jsonHeader, nil, &r)
|
||||||
|
return r, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReleaseByTag get a release of a repository by tag
|
||||||
|
func (c *Client) GetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) {
|
||||||
|
if c.CheckServerVersionConstraint(">=1.13.0") != nil {
|
||||||
|
return c.fallbackGetReleaseByTag(user, repo, tag)
|
||||||
|
}
|
||||||
|
r := new(Release)
|
||||||
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
|
||||||
nil, nil, &r)
|
nil, nil, &r)
|
||||||
return r, resp, err
|
return r, resp, err
|
||||||
}
|
}
|
||||||
|
@ -118,3 +131,23 @@ func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
|
||||||
nil, nil)
|
nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
|
||||||
|
func (c *Client) fallbackGetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) {
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
rl, resp, err := c.ListReleases(user, repo, ListReleasesOptions{ListOptions{Page: i}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
if len(rl) == 0 {
|
||||||
|
return nil,
|
||||||
|
&Response{&http.Response{StatusCode: 404}},
|
||||||
|
fmt.Errorf("release with tag '%s' not found", tag)
|
||||||
|
}
|
||||||
|
for _, r := range rl {
|
||||||
|
if r.TagName == tag {
|
||||||
|
return r, resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ func main() {
|
||||||
1. [Running the Prompts](#running-the-prompts)
|
1. [Running the Prompts](#running-the-prompts)
|
||||||
1. [Prompts](#prompts)
|
1. [Prompts](#prompts)
|
||||||
1. [Input](#input)
|
1. [Input](#input)
|
||||||
|
1. [Suggestion Options](#suggestion-options)
|
||||||
1. [Multiline](#multiline)
|
1. [Multiline](#multiline)
|
||||||
1. [Password](#password)
|
1. [Password](#password)
|
||||||
1. [Confirm](#confirm)
|
1. [Confirm](#confirm)
|
||||||
|
@ -137,6 +138,23 @@ prompt := &survey.Input{
|
||||||
survey.AskOne(prompt, &name)
|
survey.AskOne(prompt, &name)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Suggestion Options
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
file := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "inform a file to save:",
|
||||||
|
Suggest: func (toComplete string) []string {
|
||||||
|
files, _ := filepath.Glob(toComplete + "*")
|
||||||
|
return files
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &file)
|
||||||
|
```
|
||||||
|
|
||||||
### Multiline
|
### Multiline
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package survey
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Input is a regular text input that prints each character the user types on the screen
|
Input is a regular text input that prints each character the user types on the screen
|
||||||
and accepts the input with the enter key. Response type is a string.
|
and accepts the input with the enter key. Response type is a string.
|
||||||
|
@ -10,18 +15,26 @@ and accepts the input with the enter key. Response type is a string.
|
||||||
*/
|
*/
|
||||||
type Input struct {
|
type Input struct {
|
||||||
Renderer
|
Renderer
|
||||||
Message string
|
Message string
|
||||||
Default string
|
Default string
|
||||||
Help string
|
Help string
|
||||||
|
Suggest func(toComplete string) []string
|
||||||
|
typedAnswer string
|
||||||
|
answer string
|
||||||
|
options []core.OptionAnswer
|
||||||
|
selectedIndex int
|
||||||
|
showingHelp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// data available to the templates when processing
|
// data available to the templates when processing
|
||||||
type InputTemplateData struct {
|
type InputTemplateData struct {
|
||||||
Input
|
Input
|
||||||
Answer string
|
ShowAnswer bool
|
||||||
ShowAnswer bool
|
ShowHelp bool
|
||||||
ShowHelp bool
|
Answer string
|
||||||
Config *PromptConfig
|
PageEntries []core.OptionAnswer
|
||||||
|
SelectedIndex int
|
||||||
|
Config *PromptConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
@ -31,11 +44,92 @@ var InputQuestionTemplate = `
|
||||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
{{- if .ShowAnswer}}
|
{{- if .ShowAnswer}}
|
||||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else if .PageEntries -}}
|
||||||
|
{{- .Answer}} [Use arrows to move, enter to select, type to continue]
|
||||||
|
{{- "\n"}}
|
||||||
|
{{- range $ix, $choice := .PageEntries}}
|
||||||
|
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||||
|
{{- $choice.Value}}
|
||||||
|
{{- color "reset"}}{{"\n"}}
|
||||||
|
{{- end}}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ print .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
{{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[
|
||||||
|
{{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}}
|
||||||
|
{{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}}
|
||||||
|
]{{color "reset"}} {{end}}
|
||||||
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||||
|
{{- .Answer -}}
|
||||||
{{- end}}`
|
{{- end}}`
|
||||||
|
|
||||||
|
func (i *Input) OnChange(key rune, config *PromptConfig) (bool, error) {
|
||||||
|
if key == terminal.KeyEnter || key == '\n' {
|
||||||
|
if i.answer != config.HelpInput || i.Help == "" {
|
||||||
|
// we're done
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
|
i.answer = ""
|
||||||
|
i.showingHelp = true
|
||||||
|
}
|
||||||
|
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||||
|
i.answer = ""
|
||||||
|
} else if key == terminal.KeyEscape && i.Suggest != nil {
|
||||||
|
if len(i.options) > 0 {
|
||||||
|
i.answer = i.typedAnswer
|
||||||
|
}
|
||||||
|
i.options = nil
|
||||||
|
} else if key == terminal.KeyArrowUp && len(i.options) > 0 {
|
||||||
|
if i.selectedIndex == 0 {
|
||||||
|
i.selectedIndex = len(i.options) - 1
|
||||||
|
} else {
|
||||||
|
i.selectedIndex--
|
||||||
|
}
|
||||||
|
i.answer = i.options[i.selectedIndex].Value
|
||||||
|
} else if (key == terminal.KeyArrowDown || key == terminal.KeyTab) && len(i.options) > 0 {
|
||||||
|
if i.selectedIndex == len(i.options)-1 {
|
||||||
|
i.selectedIndex = 0
|
||||||
|
} else {
|
||||||
|
i.selectedIndex++
|
||||||
|
}
|
||||||
|
i.answer = i.options[i.selectedIndex].Value
|
||||||
|
} else if key == terminal.KeyTab && i.Suggest != nil {
|
||||||
|
options := i.Suggest(i.answer)
|
||||||
|
i.selectedIndex = 0
|
||||||
|
i.typedAnswer = i.answer
|
||||||
|
if len(options) > 0 {
|
||||||
|
i.answer = options[0]
|
||||||
|
if len(options) == 1 {
|
||||||
|
i.options = nil
|
||||||
|
} else {
|
||||||
|
i.options = core.OptionAnswerList(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||||
|
if i.answer != "" {
|
||||||
|
i.answer = i.answer[0 : len(i.answer)-1]
|
||||||
|
}
|
||||||
|
} else if key >= terminal.KeySpace {
|
||||||
|
i.answer += string(key)
|
||||||
|
i.typedAnswer = i.answer
|
||||||
|
i.options = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSize := config.PageSize
|
||||||
|
opts, idx := paginate(pageSize, i.options, i.selectedIndex)
|
||||||
|
err := i.Render(
|
||||||
|
InputQuestionTemplate,
|
||||||
|
InputTemplateData{
|
||||||
|
Input: *i,
|
||||||
|
Answer: i.answer,
|
||||||
|
ShowHelp: i.showingHelp,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return err != nil, err
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
// render the template
|
// render the template
|
||||||
err := i.Render(
|
err := i.Render(
|
||||||
|
@ -55,41 +149,39 @@ func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
defer rr.RestoreTermMode()
|
defer rr.RestoreTermMode()
|
||||||
|
|
||||||
cursor := i.NewCursor()
|
cursor := i.NewCursor()
|
||||||
|
cursor.Hide() // hide the cursor
|
||||||
|
defer cursor.Show() // show the cursor when we're done
|
||||||
|
|
||||||
line := []rune{}
|
// start waiting for input
|
||||||
// get the next line
|
|
||||||
for {
|
for {
|
||||||
line, err = rr.ReadLine(0)
|
r, _, err := rr.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return string(line), err
|
return "", err
|
||||||
|
}
|
||||||
|
if r == terminal.KeyInterrupt {
|
||||||
|
return "", terminal.InterruptErr
|
||||||
|
}
|
||||||
|
if r == terminal.KeyEndTransmission {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
// terminal will echo the \n so we need to jump back up one row
|
|
||||||
cursor.PreviousLine(1)
|
|
||||||
|
|
||||||
if string(line) == config.HelpInput && i.Help != "" {
|
b, err := i.OnChange(r, config)
|
||||||
err = i.Render(
|
if err != nil {
|
||||||
InputQuestionTemplate,
|
return "", err
|
||||||
InputTemplateData{
|
}
|
||||||
Input: *i,
|
|
||||||
ShowHelp: true,
|
if b {
|
||||||
Config: config,
|
break
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the line is empty
|
// if the line is empty
|
||||||
if line == nil || len(line) == 0 {
|
if len(i.answer) == 0 {
|
||||||
// use the default value
|
// use the default value
|
||||||
return i.Default, err
|
return i.Default, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lineStr := string(line)
|
lineStr := i.answer
|
||||||
|
|
||||||
i.AppendRenderedText(lineStr)
|
i.AppendRenderedText(lineStr)
|
||||||
|
|
||||||
|
@ -102,9 +194,9 @@ func (i *Input) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
InputQuestionTemplate,
|
InputQuestionTemplate,
|
||||||
InputTemplateData{
|
InputTemplateData{
|
||||||
Input: *i,
|
Input: *i,
|
||||||
Answer: val.(string),
|
|
||||||
ShowAnswer: true,
|
ShowAnswer: true,
|
||||||
Config: config,
|
Config: config,
|
||||||
|
Answer: i.answer,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
|
||||||
// decrement the selected index
|
// decrement the selected index
|
||||||
m.selectedIndex--
|
m.selectedIndex--
|
||||||
}
|
}
|
||||||
} else if key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
|
} else if key == terminal.KeyTab || key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
|
||||||
// if we are at the bottom of the list
|
// if we are at the bottom of the list
|
||||||
if m.selectedIndex == len(options)-1 {
|
if m.selectedIndex == len(options)-1 {
|
||||||
// start at the top
|
// start at the top
|
||||||
|
|
|
@ -78,7 +78,7 @@ func (s *Select) OnChange(key rune, config *PromptConfig) bool {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// if the user pressed the up arrow or 'k' to emulate vim
|
// if the user pressed the up arrow or 'k' to emulate vim
|
||||||
} else if key == terminal.KeyArrowUp || (s.VimMode && key == 'k') && len(options) > 0 {
|
} else if (key == terminal.KeyArrowUp || (s.VimMode && key == 'k')) && len(options) > 0 {
|
||||||
s.useDefault = false
|
s.useDefault = false
|
||||||
|
|
||||||
// if we are at the top of the list
|
// if we are at the top of the list
|
||||||
|
@ -91,7 +91,7 @@ func (s *Select) OnChange(key rune, config *PromptConfig) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user pressed down or 'j' to emulate vim
|
// if the user pressed down or 'j' to emulate vim
|
||||||
} else if key == terminal.KeyArrowDown || (s.VimMode && key == 'j') && len(options) > 0 {
|
} else if (key == terminal.KeyTab || key == terminal.KeyArrowDown || (s.VimMode && key == 'j')) && len(options) > 0 {
|
||||||
s.useDefault = false
|
s.useDefault = false
|
||||||
// if we are at the bottom of the list
|
// if we are at the bottom of the list
|
||||||
if s.selectedIndex == len(options)-1 {
|
if s.selectedIndex == len(options)-1 {
|
||||||
|
|
|
@ -19,8 +19,9 @@ func defaultAskOptions() *AskOptions {
|
||||||
Err: os.Stderr,
|
Err: os.Stderr,
|
||||||
},
|
},
|
||||||
PromptConfig: PromptConfig{
|
PromptConfig: PromptConfig{
|
||||||
PageSize: 7,
|
PageSize: 7,
|
||||||
HelpInput: "?",
|
HelpInput: "?",
|
||||||
|
SuggestInput: "tab",
|
||||||
Icons: IconSet{
|
Icons: IconSet{
|
||||||
Error: Icon{
|
Error: Icon{
|
||||||
Text: "X",
|
Text: "X",
|
||||||
|
@ -107,11 +108,12 @@ type Question struct {
|
||||||
|
|
||||||
// PromptConfig holds the global configuration for a prompt
|
// PromptConfig holds the global configuration for a prompt
|
||||||
type PromptConfig struct {
|
type PromptConfig struct {
|
||||||
PageSize int
|
PageSize int
|
||||||
Icons IconSet
|
Icons IconSet
|
||||||
HelpInput string
|
HelpInput string
|
||||||
Filter func(filter string, option string, index int) bool
|
SuggestInput string
|
||||||
KeepFilter bool
|
Filter func(filter string, option string, index int) bool
|
||||||
|
KeepFilter bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt is the primary interface for the objects that can take user input
|
// Prompt is the primary interface for the objects that can take user input
|
||||||
|
|
|
@ -302,7 +302,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||||
// restore the position of the cursor horizontally
|
// restore the position of the cursor horizontally
|
||||||
cursor.Restore()
|
cursor.Restore()
|
||||||
// restore the position of the cursor vertically
|
// restore the position of the cursor vertically
|
||||||
cursor.Up(1)
|
cursor.PreviousLine(1)
|
||||||
} else {
|
} else {
|
||||||
// restore cursor
|
// restore cursor
|
||||||
cursor.Restore()
|
cursor.Restore()
|
||||||
|
|
|
@ -23,6 +23,7 @@ const (
|
||||||
SpecialKeyEnd = '\x11'
|
SpecialKeyEnd = '\x11'
|
||||||
SpecialKeyDelete = '\x12'
|
SpecialKeyDelete = '\x12'
|
||||||
IgnoreKey = '\000'
|
IgnoreKey = '\000'
|
||||||
|
KeyTab = '\t'
|
||||||
)
|
)
|
||||||
|
|
||||||
func soundBell(out io.Writer) {
|
func soundBell(out io.Writer) {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*.exe
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015 Dmitri Shuralyov
|
Copyright (c) 2015 Microsoft
|
||||||
|
|
||||||
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
|
||||||
|
@ -19,3 +19,4 @@ 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 THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# go-winio
|
||||||
|
|
||||||
|
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||||
|
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||||
|
for using named pipes as a net transport.
|
||||||
|
|
||||||
|
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||||
|
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||||
|
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||||
|
package.
|
||||||
|
|
||||||
|
Please see the LICENSE file for licensing information.
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of
|
||||||
|
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||||
|
see the [Code of Conduct
|
||||||
|
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||||
|
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||||
|
questions or comments.
|
||||||
|
|
||||||
|
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||||
|
for another named pipe implementation.
|
|
@ -0,0 +1,280 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||||
|
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackupData = uint32(iota + 1)
|
||||||
|
BackupEaData
|
||||||
|
BackupSecurity
|
||||||
|
BackupAlternateData
|
||||||
|
BackupLink
|
||||||
|
BackupPropertyData
|
||||||
|
BackupObjectId
|
||||||
|
BackupReparseData
|
||||||
|
BackupSparseBlock
|
||||||
|
BackupTxfsData
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StreamSparseAttributes = uint32(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WRITE_DAC = 0x40000
|
||||||
|
WRITE_OWNER = 0x80000
|
||||||
|
ACCESS_SYSTEM_SECURITY = 0x1000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupHeader represents a backup stream of a file.
|
||||||
|
type BackupHeader struct {
|
||||||
|
Id uint32 // The backup stream ID
|
||||||
|
Attributes uint32 // Stream attributes
|
||||||
|
Size int64 // The size of the stream in bytes
|
||||||
|
Name string // The name of the stream (for BackupAlternateData only).
|
||||||
|
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32StreamId struct {
|
||||||
|
StreamId uint32
|
||||||
|
Attributes uint32
|
||||||
|
Size uint64
|
||||||
|
NameSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||||
|
// of BackupHeader values.
|
||||||
|
type BackupStreamReader struct {
|
||||||
|
r io.Reader
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||||
|
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||||
|
return &BackupStreamReader{r, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
||||||
|
// it was not completely read.
|
||||||
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
|
if r.bytesLeft > 0 {
|
||||||
|
if s, ok := r.r.(io.Seeker); ok {
|
||||||
|
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
||||||
|
// before trying the actual seek.
|
||||||
|
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
||||||
|
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.bytesLeft = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wsi win32StreamId
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr := &BackupHeader{
|
||||||
|
Id: wsi.StreamId,
|
||||||
|
Attributes: wsi.Attributes,
|
||||||
|
Size: int64(wsi.Size),
|
||||||
|
}
|
||||||
|
if wsi.NameSize != 0 {
|
||||||
|
name := make([]uint16, int(wsi.NameSize/2))
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Name = syscall.UTF16ToString(name)
|
||||||
|
}
|
||||||
|
if wsi.StreamId == BackupSparseBlock {
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Size -= 8
|
||||||
|
}
|
||||||
|
r.bytesLeft = hdr.Size
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the current backup stream.
|
||||||
|
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||||
|
if r.bytesLeft == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(b)) > r.bytesLeft {
|
||||||
|
b = b[:r.bytesLeft]
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(b)
|
||||||
|
r.bytesLeft -= int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else if r.bytesLeft == 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||||
|
type BackupStreamWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||||
|
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||||
|
return &BackupStreamWriter{w, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||||
|
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||||
|
if w.bytesLeft != 0 {
|
||||||
|
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||||
|
}
|
||||||
|
name := utf16.Encode([]rune(hdr.Name))
|
||||||
|
wsi := win32StreamId{
|
||||||
|
StreamId: hdr.Id,
|
||||||
|
Attributes: hdr.Attributes,
|
||||||
|
Size: uint64(hdr.Size),
|
||||||
|
NameSize: uint32(len(name) * 2),
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
// Include space for the int64 block offset
|
||||||
|
wsi.Size += 8
|
||||||
|
}
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(name) != 0 {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytesLeft = hdr.Size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current backup stream.
|
||||||
|
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.bytesLeft < int64(len(b)) {
|
||||||
|
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||||
|
}
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
w.bytesLeft -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||||
|
type BackupFileReader struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||||
|
// Read will attempt to read the security descriptor of the file.
|
||||||
|
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||||
|
r := &BackupFileReader{f, includeSecurity, 0}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||||
|
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||||
|
var bytesRead uint32
|
||||||
|
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
if bytesRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return int(bytesRead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||||
|
// the underlying file.
|
||||||
|
func (r *BackupFileReader) Close() error {
|
||||||
|
if r.ctx != 0 {
|
||||||
|
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
r.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||||
|
type BackupFileWriter struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write restores a portion of the file using the provided backup stream.
|
||||||
|
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||||
|
var bytesWritten uint32
|
||||||
|
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
if int(bytesWritten) != len(b) {
|
||||||
|
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||||
|
// close the underlying file.
|
||||||
|
func (w *BackupFileWriter) Close() error {
|
||||||
|
if w.ctx != 0 {
|
||||||
|
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
w.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
||||||
|
// or restore privileges have been acquired.
|
||||||
|
//
|
||||||
|
// If the file opened was a directory, it cannot be used with Readdir().
|
||||||
|
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||||
|
winPath, err := syscall.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(h), path), nil
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileFullEaInformation struct {
|
||||||
|
NextEntryOffset uint32
|
||||||
|
Flags uint8
|
||||||
|
NameLength uint8
|
||||||
|
ValueLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||||
|
|
||||||
|
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||||
|
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||||
|
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedAttribute represents a single Windows EA.
|
||||||
|
type ExtendedAttribute struct {
|
||||||
|
Name string
|
||||||
|
Value []byte
|
||||||
|
Flags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||||
|
var info fileFullEaInformation
|
||||||
|
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nameOffset := fileFullEaInformationSize
|
||||||
|
nameLen := int(info.NameLength)
|
||||||
|
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||||
|
valueLen := int(info.ValueLength)
|
||||||
|
nextOffset := int(info.NextEntryOffset)
|
||||||
|
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||||
|
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||||
|
ea.Flags = info.Flags
|
||||||
|
if info.NextEntryOffset != 0 {
|
||||||
|
nb = b[info.NextEntryOffset:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||||
|
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||||
|
for len(b) != 0 {
|
||||||
|
ea, nb, err := parseEa(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eas = append(eas, ea)
|
||||||
|
b = nb
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||||
|
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||||
|
return errEaNameTooLarge
|
||||||
|
}
|
||||||
|
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||||
|
return errEaValueTooLarge
|
||||||
|
}
|
||||||
|
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||||
|
withPadding := (entrySize + 3) &^ 3
|
||||||
|
nextOffset := uint32(0)
|
||||||
|
if !last {
|
||||||
|
nextOffset = withPadding
|
||||||
|
}
|
||||||
|
info := fileFullEaInformation{
|
||||||
|
NextEntryOffset: nextOffset,
|
||||||
|
Flags: ea.Flags,
|
||||||
|
NameLength: uint8(len(ea.Name)),
|
||||||
|
ValueLength: uint16(len(ea.Value)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte(ea.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write(ea.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||||
|
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := range eas {
|
||||||
|
last := false
|
||||||
|
if i == len(eas)-1 {
|
||||||
|
last = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeEa(&buf, &eas[i], last)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||||
|
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||||
|
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
|
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||||
|
|
||||||
|
type atomicBool int32
|
||||||
|
|
||||||
|
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
|
||||||
|
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
|
||||||
|
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
|
||||||
|
func (b *atomicBool) swap(new bool) bool {
|
||||||
|
var newInt int32
|
||||||
|
if new {
|
||||||
|
newInt = 1
|
||||||
|
}
|
||||||
|
return atomic.SwapInt32((*int32)(b), newInt) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
|
||||||
|
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("file has already been closed")
|
||||||
|
ErrTimeout = &timeoutError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (e *timeoutError) Timeout() bool { return true }
|
||||||
|
func (e *timeoutError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
type timeoutChan chan struct{}
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort syscall.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO
|
||||||
|
type ioOperation struct {
|
||||||
|
o syscall.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIo() {
|
||||||
|
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type win32File struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
wgLock sync.RWMutex
|
||||||
|
closing atomicBool
|
||||||
|
socket bool
|
||||||
|
readDeadline deadlineHandler
|
||||||
|
writeDeadline deadlineHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type deadlineHandler struct {
|
||||||
|
setLock sync.Mutex
|
||||||
|
channel timeoutChan
|
||||||
|
channelLock sync.RWMutex
|
||||||
|
timer *time.Timer
|
||||||
|
timedout atomicBool
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeWin32File makes a new win32File from an existing file handle
|
||||||
|
func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||||
|
f := &win32File{handle: h}
|
||||||
|
ioInitOnce.Do(initIo)
|
||||||
|
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.readDeadline.channel = make(timeoutChan)
|
||||||
|
f.writeDeadline.channel = make(timeoutChan)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
// If we return the result of makeWin32File directly, it can result in an
|
||||||
|
// interface-wrapped nil, rather than a nil interface value.
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
|
func (f *win32File) closeHandle() {
|
||||||
|
f.wgLock.Lock()
|
||||||
|
// Atomically set that we are closing, releasing the resources only once.
|
||||||
|
if !f.closing.swap(true) {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
cancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
syscall.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
} else {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a win32File.
|
||||||
|
func (f *win32File) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIo prepares for a new IO operation.
|
||||||
|
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||||
|
func (f *win32File) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wgLock.RLock()
|
||||||
|
if f.closing.isSet() {
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
return nil, ErrFileClosed
|
||||||
|
}
|
||||||
|
f.wg.Add(1)
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever
|
||||||
|
func ioCompletionProcessor(h syscall.Handle) {
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||||
|
if err != syscall.ERROR_IO_PENDING {
|
||||||
|
return int(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.closing.isSet() {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout timeoutChan
|
||||||
|
if d != nil {
|
||||||
|
d.channelLock.Lock()
|
||||||
|
timeout = d.channel
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ioResult
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
if f.closing.isSet() {
|
||||||
|
err = ErrFileClosed
|
||||||
|
}
|
||||||
|
} else if err != nil && f.socket {
|
||||||
|
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||||
|
var bytes, flags uint32
|
||||||
|
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
r = <-c.ch
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
err = ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime.KeepAlive is needed, as c is passed via native
|
||||||
|
// code to ioCompletionProcessor, c must remain alive
|
||||||
|
// until the channel read is complete.
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *win32File) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.readDeadline.timedout.isSet() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == syscall.ERROR_BROKEN_PIPE {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *win32File) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.writeDeadline.timedout.isSet() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetReadDeadline(deadline time.Time) error {
|
||||||
|
return f.readDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
||||||
|
return f.writeDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Flush() error {
|
||||||
|
return syscall.FlushFileBuffers(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Fd() uintptr {
|
||||||
|
return uintptr(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||||
|
d.setLock.Lock()
|
||||||
|
defer d.setLock.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
if !d.timer.Stop() {
|
||||||
|
<-d.channel
|
||||||
|
}
|
||||||
|
d.timer = nil
|
||||||
|
}
|
||||||
|
d.timedout.setFalse()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-d.channel:
|
||||||
|
d.channelLock.Lock()
|
||||||
|
d.channel = make(chan struct{})
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutIO := func() {
|
||||||
|
d.timedout.setTrue()
|
||||||
|
close(d.channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := deadline.Sub(now)
|
||||||
|
if deadline.After(now) {
|
||||||
|
// Deadline is in the future, set a timer to wait
|
||||||
|
d.timer = time.AfterFunc(duration, timeoutIO)
|
||||||
|
} else {
|
||||||
|
// Deadline is in the past. Cancel all pending IO now.
|
||||||
|
timeoutIO()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||||
|
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileBasicInfo = 0
|
||||||
|
fileIDInfo = 0x12
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileBasicInfo contains file access time and file attributes information.
|
||||||
|
type FileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||||
|
FileAttributes uint32
|
||||||
|
pad uint32 // padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||||
|
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||||
|
bi := &FileBasicInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return bi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileBasicInfo sets times and attributes for a file.
|
||||||
|
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||||
|
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||||
|
// unique on a system.
|
||||||
|
type FileIDInfo struct {
|
||||||
|
VolumeSerialNumber uint64
|
||||||
|
FileID [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||||
|
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||||
|
fileID := &FileIDInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return fileID, nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
module github.com/Microsoft/go-winio
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/sirupsen/logrus v1.4.1
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -0,0 +1,305 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||||
|
|
||||||
|
const (
|
||||||
|
afHvSock = 34 // AF_HYPERV
|
||||||
|
|
||||||
|
socketError = ^uintptr(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||||
|
type HvsockAddr struct {
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawHvsockAddr struct {
|
||||||
|
Family uint16
|
||||||
|
_ uint16
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns the address's network name, "hvsock".
|
||||||
|
func (addr *HvsockAddr) Network() string {
|
||||||
|
return "hvsock"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) String() string {
|
||||||
|
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||||
|
func VsockServiceID(port uint32) guid.GUID {
|
||||||
|
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
||||||
|
g.Data1 = port
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
||||||
|
return rawHvsockAddr{
|
||||||
|
Family: afHvSock,
|
||||||
|
VMID: addr.VMID,
|
||||||
|
ServiceID: addr.ServiceID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
||||||
|
addr.VMID = raw.VMID
|
||||||
|
addr.ServiceID = raw.ServiceID
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||||
|
type HvsockListener struct {
|
||||||
|
sock *win32File
|
||||||
|
addr HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||||
|
type HvsockConn struct {
|
||||||
|
sock *win32File
|
||||||
|
local, remote HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHvSocket() (*win32File, error) {
|
||||||
|
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(fd)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.socket = true
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenHvsock listens for connections on the specified hvsock address.
|
||||||
|
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
||||||
|
l := &HvsockListener{addr: *addr}
|
||||||
|
sock, err := newHvSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", err)
|
||||||
|
}
|
||||||
|
sa := addr.raw()
|
||||||
|
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||||
|
}
|
||||||
|
err = syscall.Listen(sock.handle, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
||||||
|
}
|
||||||
|
return &HvsockListener{sock: sock, addr: *addr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *HvsockListener) opErr(op string, err error) error {
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the listener's network address.
|
||||||
|
func (l *HvsockListener) Addr() net.Addr {
|
||||||
|
return &l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept waits for the next connection and returns it.
|
||||||
|
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
||||||
|
sock, err := newHvSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c, err := l.sock.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer l.sock.wg.Done()
|
||||||
|
|
||||||
|
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||||
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
var addrbuf [addrlen * 2]byte
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
|
||||||
|
_, err = l.sock.asyncIo(c, nil, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||||
|
}
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
}
|
||||||
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||||
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||||
|
sock = nil
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the listener, causing any pending Accept calls to fail.
|
||||||
|
func (l *HvsockListener) Close() error {
|
||||||
|
return l.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Need to finish ConnectEx handling
|
||||||
|
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
|
||||||
|
sock, err := newHvSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c, err := sock.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer sock.wg.Done()
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
|
||||||
|
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
remote: *addr,
|
||||||
|
}
|
||||||
|
sock = nil
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var flags, bytes uint32
|
||||||
|
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(syscall.Errno); ok {
|
||||||
|
err = os.NewSyscallError("wsarecv", err)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
} else if n == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
||||||
|
t := 0
|
||||||
|
for len(b) != 0 {
|
||||||
|
n, err := conn.write(b)
|
||||||
|
if err != nil {
|
||||||
|
return t + n, err
|
||||||
|
}
|
||||||
|
t += n
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) write(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(syscall.Errno); ok {
|
||||||
|
err = os.NewSyscallError("wsasend", err)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the socket connection, failing any pending read or write calls.
|
||||||
|
func (conn *HvsockConn) Close() error {
|
||||||
|
return conn.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) shutdown(how int) error {
|
||||||
|
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("shutdown", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRead shuts down the read end of the socket.
|
||||||
|
func (conn *HvsockConn) CloseRead() error {
|
||||||
|
err := conn.shutdown(syscall.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("close", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||||
|
// no more data will be written.
|
||||||
|
func (conn *HvsockConn) CloseWrite() error {
|
||||||
|
err := conn.shutdown(syscall.SHUT_WR)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("close", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local address of the connection.
|
||||||
|
func (conn *HvsockConn) LocalAddr() net.Addr {
|
||||||
|
return &conn.local
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remote address of the connection.
|
||||||
|
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
||||||
|
return &conn.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline implements the net.Conn SetDeadline method.
|
||||||
|
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
||||||
|
conn.SetReadDeadline(t)
|
||||||
|
conn.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||||
|
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||||
|
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetWriteDeadline(t)
|
||||||
|
}
|
|
@ -0,0 +1,517 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||||
|
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
||||||
|
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile
|
||||||
|
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
|
||||||
|
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U
|
||||||
|
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl
|
||||||
|
|
||||||
|
type ioStatusBlock struct {
|
||||||
|
Status, Information uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributes struct {
|
||||||
|
Length uintptr
|
||||||
|
RootDirectory uintptr
|
||||||
|
ObjectName *unicodeString
|
||||||
|
Attributes uintptr
|
||||||
|
SecurityDescriptor *securityDescriptor
|
||||||
|
SecurityQoS uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type unicodeString struct {
|
||||||
|
Length uint16
|
||||||
|
MaximumLength uint16
|
||||||
|
Buffer uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type securityDescriptor struct {
|
||||||
|
Revision byte
|
||||||
|
Sbz1 byte
|
||||||
|
Control uint16
|
||||||
|
Owner uintptr
|
||||||
|
Group uintptr
|
||||||
|
Sacl uintptr
|
||||||
|
Dacl uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type ntstatus int32
|
||||||
|
|
||||||
|
func (status ntstatus) Err() error {
|
||||||
|
if status >= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rtlNtStatusToDosError(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||||
|
cERROR_NO_DATA = syscall.Errno(232)
|
||||||
|
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||||
|
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||||
|
|
||||||
|
cSECURITY_SQOS_PRESENT = 0x100000
|
||||||
|
cSECURITY_ANONYMOUS = 0
|
||||||
|
|
||||||
|
cPIPE_TYPE_MESSAGE = 4
|
||||||
|
|
||||||
|
cPIPE_READMODE_MESSAGE = 2
|
||||||
|
|
||||||
|
cFILE_OPEN = 1
|
||||||
|
cFILE_CREATE = 2
|
||||||
|
|
||||||
|
cFILE_PIPE_MESSAGE_TYPE = 1
|
||||||
|
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
|
||||||
|
|
||||||
|
cSE_DACL_PRESENT = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
// This error should match net.errClosing since docker takes a dependency on its text.
|
||||||
|
ErrPipeListenerClosed = errors.New("use of closed network connection")
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
f.SetReadDeadline(t)
|
||||||
|
f.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
err := f.win32File.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
} else if err == syscall.ERROR_MORE_DATA {
|
||||||
|
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||||
|
// and the message still has more bytes. Treat this as a success, since
|
||||||
|
// this package presents all named pipes as byte streams.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||||
|
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
|
||||||
|
for {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return syscall.Handle(0), ctx.Err()
|
||||||
|
default:
|
||||||
|
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err == nil {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
if err != cERROR_PIPE_BUSY {
|
||||||
|
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
||||||
|
}
|
||||||
|
// Wait 10 msec and try again. This is a rather simplistic
|
||||||
|
// view, as we always try each 10 milliseconds.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then we use
|
||||||
|
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
} else {
|
||||||
|
absTimeout = time.Now().Add(2 * time.Second)
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||||
|
conn, err := DialPipeContext(ctx, path)
|
||||||
|
if err == context.DeadlineExceeded {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
||||||
|
return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
var h syscall.Handle
|
||||||
|
h, err = tryDialPipe(ctx, &path, access)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&cPIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle syscall.Handle
|
||||||
|
path string
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||||
|
path16, err := syscall.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oa objectAttributes
|
||||||
|
oa.Length = unsafe.Sizeof(oa)
|
||||||
|
|
||||||
|
var ntPath unicodeString
|
||||||
|
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
defer localFree(ntPath.Buffer)
|
||||||
|
oa.ObjectName = &ntPath
|
||||||
|
|
||||||
|
// The security descriptor is only needed for the first pipe.
|
||||||
|
if first {
|
||||||
|
if sd != nil {
|
||||||
|
len := uint32(len(sd))
|
||||||
|
sdb := localAlloc(0, len)
|
||||||
|
defer localFree(sdb)
|
||||||
|
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
|
||||||
|
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
|
||||||
|
} else {
|
||||||
|
// Construct the default named pipe security descriptor.
|
||||||
|
var dacl uintptr
|
||||||
|
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
|
||||||
|
return 0, fmt.Errorf("getting default named pipe ACL: %s", err)
|
||||||
|
}
|
||||||
|
defer localFree(dacl)
|
||||||
|
|
||||||
|
sdb := &securityDescriptor{
|
||||||
|
Revision: 1,
|
||||||
|
Control: cSE_DACL_PRESENT,
|
||||||
|
Dacl: dacl,
|
||||||
|
}
|
||||||
|
oa.SecurityDescriptor = sdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||||
|
if c.MessageMode {
|
||||||
|
typ |= cFILE_PIPE_MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := uint32(cFILE_OPEN)
|
||||||
|
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
|
||||||
|
if first {
|
||||||
|
disposition = cFILE_CREATE
|
||||||
|
// By not asking for read or write access, the named pipe file system
|
||||||
|
// will put this pipe into an initially disconnected state, blocking
|
||||||
|
// client connections until the next call with first == false.
|
||||||
|
access = syscall.SYNCHRONIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := int64(-50 * 10000) // 50ms
|
||||||
|
|
||||||
|
var (
|
||||||
|
h syscall.Handle
|
||||||
|
iosb ioStatusBlock
|
||||||
|
)
|
||||||
|
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err()
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(ntPath)
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func(p *win32File) {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}(p)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed {
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
var (
|
||||||
|
p *win32File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
p, err = l.makeConnectedServerPipe()
|
||||||
|
// If the connection was immediately closed by the client, try
|
||||||
|
// again.
|
||||||
|
if err != cERROR_NO_DATA {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
closed = err == ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syscall.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIo(c, nil, 0, err)
|
||||||
|
if err != nil && err != cERROR_PIPE_CONNECTED {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||||
|
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||||
|
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||||
|
// and the Windows (mixed-endian) encoding. See here for details:
|
||||||
|
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||||
|
// how the entirety of the rest of the GUID is interpreted.
|
||||||
|
type Variant uint8
|
||||||
|
|
||||||
|
// The variants specified by RFC 4122.
|
||||||
|
const (
|
||||||
|
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||||
|
// the variant encodings specified in RFC 4122.
|
||||||
|
VariantUnknown Variant = iota
|
||||||
|
VariantNCS
|
||||||
|
VariantRFC4122
|
||||||
|
VariantMicrosoft
|
||||||
|
VariantFuture
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||||
|
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||||
|
// hash of an input string.
|
||||||
|
type Version uint8
|
||||||
|
|
||||||
|
var _ = (encoding.TextMarshaler)(GUID{})
|
||||||
|
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type so that stringification and
|
||||||
|
// marshaling can be supported. The representation matches that used by native
|
||||||
|
// Windows code.
|
||||||
|
type GUID windows.GUID
|
||||||
|
|
||||||
|
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||||
|
func NewV4() (GUID, error) {
|
||||||
|
var b [16]byte
|
||||||
|
if _, err := rand.Read(b[:]); err != nil {
|
||||||
|
return GUID{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := FromArray(b)
|
||||||
|
g.setVersion(4) // Version 4 means randomly generated.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||||
|
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||||
|
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||||
|
//
|
||||||
|
// Some implementations, such as those found on Windows, treat the name as a
|
||||||
|
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||||
|
// encoded as such before being passed to this function.
|
||||||
|
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
||||||
|
b := sha1.New()
|
||||||
|
namespaceBytes := namespace.ToArray()
|
||||||
|
b.Write(namespaceBytes[:])
|
||||||
|
b.Write(name)
|
||||||
|
|
||||||
|
a := [16]byte{}
|
||||||
|
copy(a[:], b.Sum(nil))
|
||||||
|
|
||||||
|
g := FromArray(a)
|
||||||
|
g.setVersion(5) // Version 5 means generated from a string.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
||||||
|
var g GUID
|
||||||
|
g.Data1 = order.Uint32(b[0:4])
|
||||||
|
g.Data2 = order.Uint16(b[4:6])
|
||||||
|
g.Data3 = order.Uint16(b[6:8])
|
||||||
|
copy(g.Data4[:], b[8:16])
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
||||||
|
b := [16]byte{}
|
||||||
|
order.PutUint32(b[0:4], g.Data1)
|
||||||
|
order.PutUint16(b[4:6], g.Data2)
|
||||||
|
order.PutUint16(b[6:8], g.Data3)
|
||||||
|
copy(b[8:16], g.Data4[:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||||
|
func FromArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToArray() [16]byte {
|
||||||
|
return g.toArray(binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||||
|
func FromWindowsArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToWindowsArray() [16]byte {
|
||||||
|
return g.toArray(binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%08x-%04x-%04x-%04x-%012x",
|
||||||
|
g.Data1,
|
||||||
|
g.Data2,
|
||||||
|
g.Data3,
|
||||||
|
g.Data4[:2],
|
||||||
|
g.Data4[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||||
|
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||||
|
// format.
|
||||||
|
func FromString(s string) (GUID, error) {
|
||||||
|
if len(s) != 36 {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var g GUID
|
||||||
|
|
||||||
|
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data1 = uint32(data1)
|
||||||
|
|
||||||
|
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data2 = uint16(data2)
|
||||||
|
|
||||||
|
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data3 = uint16(data3)
|
||||||
|
|
||||||
|
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||||
|
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data4[i] = uint8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVariant(v Variant) {
|
||||||
|
d := g.Data4[0]
|
||||||
|
switch v {
|
||||||
|
case VariantNCS:
|
||||||
|
d = (d & 0x7f)
|
||||||
|
case VariantRFC4122:
|
||||||
|
d = (d & 0x3f) | 0x80
|
||||||
|
case VariantMicrosoft:
|
||||||
|
d = (d & 0x1f) | 0xc0
|
||||||
|
case VariantFuture:
|
||||||
|
d = (d & 0x0f) | 0xe0
|
||||||
|
case VariantUnknown:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid variant: %d", v))
|
||||||
|
}
|
||||||
|
g.Data4[0] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||||
|
func (g GUID) Variant() Variant {
|
||||||
|
b := g.Data4[0]
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
return VariantNCS
|
||||||
|
} else if b&0xc0 == 0x80 {
|
||||||
|
return VariantRFC4122
|
||||||
|
} else if b&0xe0 == 0xc0 {
|
||||||
|
return VariantMicrosoft
|
||||||
|
} else if b&0xe0 == 0xe0 {
|
||||||
|
return VariantFuture
|
||||||
|
}
|
||||||
|
return VariantUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVersion(v Version) {
|
||||||
|
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the GUID version, as defined in RFC 4122.
|
||||||
|
func (g GUID) Version() Version {
|
||||||
|
return Version((g.Data3 & 0xF000) >> 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText returns the textual representation of the GUID.
|
||||||
|
func (g GUID) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(g.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||||
|
// into this GUID.
|
||||||
|
func (g *GUID) UnmarshalText(text []byte) error {
|
||||||
|
g2, err := FromString(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*g = g2
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
SE_PRIVILEGE_ENABLED = 2
|
||||||
|
|
||||||
|
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
securityAnonymous = iota
|
||||||
|
securityIdentification
|
||||||
|
securityImpersonation
|
||||||
|
securityDelegation
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privNames = make(map[string]uint64)
|
||||||
|
privNameMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivilegeError represents an error enabling privileges.
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := ""
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
} else {
|
||||||
|
s = "Could not enable privilege "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivilege enables a single privilege for a function call.
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivileges enables privileges for a function call.
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPrivileges(names []string) ([]uint64, error) {
|
||||||
|
var privileges []uint64
|
||||||
|
privNameMutex.Lock()
|
||||||
|
defer privNameMutex.Unlock()
|
||||||
|
for _, name := range names {
|
||||||
|
p, ok := privNames[name]
|
||||||
|
if !ok {
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privNames[name] = p
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableProcessPrivileges enables privileges globally for the process.
|
||||||
|
func EnableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableProcessPrivileges disables privileges globally for the process.
|
||||||
|
func DisableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, _ := windows.GetCurrentProcess()
|
||||||
|
var token windows.Token
|
||||||
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer token.Close()
|
||||||
|
return adjustPrivileges(token, privileges, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
binary.Write(&b, binary.LittleEndian, action)
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED {
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langID uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (windows.Token, error) {
|
||||||
|
err := impersonateSelf(securityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token windows.Token
|
||||||
|
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h windows.Token) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
return DecodeReparsePointData(tag, b[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = `\??\` + rp.Target[4:]
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||||
|
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
|
||||||
|
//sys localFree(mem uintptr) = LocalFree
|
||||||
|
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_NONE_MAPPED = syscall.Errno(1332)
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch e.Err {
|
||||||
|
case cERROR_NONE_MAPPED:
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
localFree(uintptr(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
var sdBuffer uintptr
|
||||||
|
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{sddl, err}
|
||||||
|
}
|
||||||
|
defer localFree(sdBuffer)
|
||||||
|
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
|
||||||
|
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
var sddl *uint16
|
||||||
|
// The returned string length seems to including an aribtrary number of terminating NULs.
|
||||||
|
// Don't use it.
|
||||||
|
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer localFree(uintptr(unsafe.Pointer(sddl)))
|
||||||
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
|
@ -0,0 +1,562 @@
|
||||||
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
|
||||||
|
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
||||||
|
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
||||||
|
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
||||||
|
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||||
|
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
|
||||||
|
procLocalFree = modkernel32.NewProc("LocalFree")
|
||||||
|
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
|
||||||
|
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
|
||||||
|
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
procbind = modws2_32.NewProc("bind")
|
||||||
|
)
|
||||||
|
|
||||||
|
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||||
|
newport = syscall.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if wait {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
|
||||||
|
ptr = uintptr(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
|
||||||
|
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
|
||||||
|
if r0 != 0 {
|
||||||
|
winerr = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localFree(mem uintptr) {
|
||||||
|
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
|
||||||
|
len = uint32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h syscall.Handle) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
|
||||||
|
h = syscall.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
|
||||||
|
if r1 == socketError {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
|
||||||
- 1.13.x
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
- windows
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
|
||||||
autocrlf: false
|
|
||||||
before_install:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- go install github.com/golangci/golangci-lint/cmd/golangci-lint
|
|
||||||
script:
|
|
||||||
- golangci-lint run --enable-all -D wsl -D gochecknoinits -D gochecknoglobals -D prealloc
|
|
||||||
- go test -v -race ./...
|
|
|
@ -1,9 +1,10 @@
|
||||||
xdg
|
xdg
|
||||||
===
|
===
|
||||||
[![Build Status](https://travis-ci.org/adrg/xdg.svg?branch=master)](https://travis-ci.org/adrg/xdg)
|
|
||||||
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/adrg/xdg)
|
[![Build Status](https://github.com/adrg/xdg/workflows/CI/badge.svg)](https://github.com/adrg/xdg/actions?query=workflow%3ACI)
|
||||||
[![License: MIT](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](https://opensource.org/licenses/MIT)
|
[![pkg.go.dev documentation](https://pkg.go.dev/badge/github.com/adrg/xdg)](https://pkg.go.dev/github.com/adrg/xdg)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/adrg/xdg)](https://goreportcard.com/report/github.com/adrg/xdg)
|
[![MIT license](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](https://opensource.org/licenses/MIT)
|
||||||
|
[![Go report card](https://goreportcard.com/badge/github.com/adrg/xdg)](https://goreportcard.com/report/github.com/adrg/xdg)
|
||||||
|
|
||||||
Provides an implementation of the [XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html).
|
Provides an implementation of the [XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html).
|
||||||
The specification defines a set of standard paths for storing application files,
|
The specification defines a set of standard paths for storing application files,
|
||||||
|
@ -169,10 +170,9 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## References
|
## Stargazers over time
|
||||||
For more information see the
|
|
||||||
[XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and
|
[![Stargazers over time](https://starchart.cc/adrg/xdg.svg)](https://starchart.cc/adrg/xdg)
|
||||||
[XDG user directories](https://wiki.archlinux.org/index.php/XDG_user_directories).
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -180,7 +180,14 @@ Contributions in the form of pull requests, issues or just general feedback,
|
||||||
are always welcome.
|
are always welcome.
|
||||||
See [CONTRIBUTING.MD](https://github.com/adrg/xdg/blob/master/CONTRIBUTING.md).
|
See [CONTRIBUTING.MD](https://github.com/adrg/xdg/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
For more information see the
|
||||||
|
[XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and
|
||||||
|
[XDG user directories](https://wiki.archlinux.org/index.php/XDG_user_directories).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2014 Adrian-George Bostan.
|
Copyright (c) 2014 Adrian-George Bostan.
|
||||||
|
|
||||||
This project is licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
This project is licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
module github.com/adrg/xdg
|
module github.com/adrg/xdg
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.6.1
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
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=
|
|
@ -20,6 +20,11 @@ linters:
|
||||||
- wsl
|
- wsl
|
||||||
- gomnd
|
- gomnd
|
||||||
- gocognit
|
- gocognit
|
||||||
|
- goerr113
|
||||||
|
- nolintlint
|
||||||
|
- testpackage
|
||||||
|
- godot
|
||||||
|
- nestif
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
|
|
|
@ -4,7 +4,7 @@ go:
|
||||||
- "1.13.x"
|
- "1.13.x"
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.22.2
|
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.26.0
|
||||||
- ./bin/golangci-lint run
|
- ./bin/golangci-lint run
|
||||||
- git clean -fdx .
|
- git clean -fdx .
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -30,14 +30,14 @@ var Awk = internal.Register(MustNewLexer(
|
||||||
"root": {
|
"root": {
|
||||||
{`^(?=\s|/)`, Text, Push("slashstartsregex")},
|
{`^(?=\s|/)`, Text, Push("slashstartsregex")},
|
||||||
Include("commentsandwhitespace"),
|
Include("commentsandwhitespace"),
|
||||||
{`\+\+|--|\|\||&&|in\b|\$|!?~|(\*\*|[-<>+*%\^/!=|])=?`, Operator, Push("slashstartsregex")},
|
{`\+\+|--|\|\||&&|in\b|\$|!?~|\|&|(\*\*|[-<>+*%\^/!=|])=?`, Operator, Push("slashstartsregex")},
|
||||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||||
{`[})\].]`, Punctuation, nil},
|
{`[})\].]`, Punctuation, nil},
|
||||||
{`(break|continue|do|while|exit|for|if|else|return)\b`, Keyword, Push("slashstartsregex")},
|
{`(break|continue|do|while|exit|for|if|else|return|switch|case|default)\b`, Keyword, Push("slashstartsregex")},
|
||||||
{`function\b`, KeywordDeclaration, Push("slashstartsregex")},
|
{`function\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||||
{`(atan2|cos|exp|int|log|rand|sin|sqrt|srand|gensub|gsub|index|length|match|split|sprintf|sub|substr|tolower|toupper|close|fflush|getline|next|nextfile|print|printf|strftime|systime|delete|system)\b`, KeywordReserved, nil},
|
{`(atan2|cos|exp|int|log|rand|sin|sqrt|srand|gensub|gsub|index|length|match|split|patsplit|sprintf|sub|substr|tolower|toupper|close|fflush|getline|next(file)|print|printf|strftime|systime|mktime|delete|system|strtonum|and|compl|lshift|or|rshift|asorti?|isarray|bindtextdomain|dcn?gettext|@(include|load|namespace))\b`, KeywordReserved, nil},
|
||||||
{`(ARGC|ARGIND|ARGV|BEGIN|CONVFMT|ENVIRON|END|ERRNO|FIELDWIDTHS|FILENAME|FNR|FS|IGNORECASE|NF|NR|OFMT|OFS|ORFS|RLENGTH|RS|RSTART|RT|SUBSEP)\b`, NameBuiltin, nil},
|
{`(ARGC|ARGIND|ARGV|BEGIN(FILE)?|BINMODE|CONVFMT|ENVIRON|END(FILE)?|ERRNO|FIELDWIDTHS|FILENAME|FNR|FPAT|FS|IGNORECASE|LINT|NF|NR|OFMT|OFS|ORS|PROCINFO|RLENGTH|RS|RSTART|RT|SUBSEP|TEXTDOMAIN)\b`, NameBuiltin, nil},
|
||||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
{`[@$a-zA-Z_]\w*`, NameOther, nil},
|
||||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||||
|
|
|
@ -36,7 +36,7 @@ var Bash = internal.Register(MustNewLexer(
|
||||||
{`\b(if|fi|else|while|do|done|for|then|return|function|case|select|continue|until|esac|elif)(\s*)\b`, ByGroups(Keyword, Text), nil},
|
{`\b(if|fi|else|while|do|done|for|then|return|function|case|select|continue|until|esac|elif)(\s*)\b`, ByGroups(Keyword, Text), nil},
|
||||||
{"\\b(alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|false|fc|fg|getopts|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|time|times|trap|true|type|typeset|ulimit|umask|unalias|unset|wait)(?=[\\s)`])", NameBuiltin, nil},
|
{"\\b(alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|false|fc|fg|getopts|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|time|times|trap|true|type|typeset|ulimit|umask|unalias|unset|wait)(?=[\\s)`])", NameBuiltin, nil},
|
||||||
{`\A#!.+\n`, CommentPreproc, nil},
|
{`\A#!.+\n`, CommentPreproc, nil},
|
||||||
{`#.*\S`, CommentSingle, nil},
|
{`#.*(\S|$)`, CommentSingle, nil},
|
||||||
{`\\[\w\W]`, LiteralStringEscape, nil},
|
{`\\[\w\W]`, LiteralStringEscape, nil},
|
||||||
{`(\b\w+)(\s*)(\+?=)`, ByGroups(NameVariable, Text, Operator), nil},
|
{`(\b\w+)(\s*)(\+?=)`, ByGroups(NameVariable, Text, Operator), nil},
|
||||||
{`[\[\]{}()=]`, Operator, nil},
|
{`[\[\]{}()=]`, Operator, nil},
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
package c
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// caddyfileCommon are the rules common to both of the lexer variants
|
||||||
|
var caddyfileCommon = Rules{
|
||||||
|
"site_block_common": {
|
||||||
|
// Import keyword
|
||||||
|
{`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
|
||||||
|
// Matcher definition
|
||||||
|
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||||
|
// Matcher token stub for docs
|
||||||
|
{`\[\<matcher\>\]`, NameDecorator, Push("matcher")},
|
||||||
|
// These cannot have matchers but may have things that look like
|
||||||
|
// matchers in their arguments, so we just parse as a subdirective.
|
||||||
|
{`try_files`, Keyword, Push("subdirective")},
|
||||||
|
// These are special, they can nest more directives
|
||||||
|
{`handle_errors|handle|route|handle_path|not`, Keyword, Push("nested_directive")},
|
||||||
|
// Any other directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"matcher": {
|
||||||
|
{`\{`, Punctuation, Push("block")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{`not`, Keyword, Push("deep_not_matcher")},
|
||||||
|
// Any other same-line matcher
|
||||||
|
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||||
|
// Terminators
|
||||||
|
{`\n`, Text, Pop(1)},
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
// Not can be one-liner
|
||||||
|
{`not`, Keyword, Push("not_matcher")},
|
||||||
|
// Any other subdirective
|
||||||
|
{`[^\s#]+`, Keyword, Push("subdirective")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
// Matcher definition
|
||||||
|
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||||
|
// Something that starts with literally < is probably a docs stub
|
||||||
|
{`\<[^#]+\>`, Keyword, Push("nested_directive")},
|
||||||
|
// Any other directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("nested_directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"not_matcher": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"deep_not_matcher": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{`[^\s#]+`, Keyword, Push("deep_subdirective")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"directive": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
Include("matcher_token"),
|
||||||
|
Include("comments_pop_1"),
|
||||||
|
{`\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_directive": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("nested_block")},
|
||||||
|
Include("matcher_token"),
|
||||||
|
Include("comments_pop_1"),
|
||||||
|
{`\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"subdirective": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
Include("comments_pop_1"),
|
||||||
|
{`\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
Include("comments_pop_2"),
|
||||||
|
{`\\\n`, Text, nil}, // Skip escaped newlines
|
||||||
|
{`\n`, Text, Pop(2)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"deep_subdirective": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
Include("comments_pop_3"),
|
||||||
|
{`\n`, Text, Pop(3)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"matcher_token": {
|
||||||
|
{`@[^\s]+`, NameDecorator, Push("arguments")}, // Named matcher
|
||||||
|
{`/[^\s]+`, NameDecorator, Push("arguments")}, // Path matcher
|
||||||
|
{`\*`, NameDecorator, Push("arguments")}, // Wildcard path matcher
|
||||||
|
{`\[\<matcher\>\]`, NameDecorator, Push("arguments")}, // Matcher token stub for docs
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
{`^#.*\n`, CommentSingle, nil}, // Comment at start of line
|
||||||
|
{`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace
|
||||||
|
},
|
||||||
|
"comments_pop_1": {
|
||||||
|
{`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line
|
||||||
|
{`\s+#.*\n`, CommentSingle, Pop(1)}, // Comment preceded by whitespace
|
||||||
|
},
|
||||||
|
"comments_pop_2": {
|
||||||
|
{`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line
|
||||||
|
{`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace
|
||||||
|
},
|
||||||
|
"comments_pop_3": {
|
||||||
|
{`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line
|
||||||
|
{`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
Include("comments"),
|
||||||
|
{`(on|off|first|last|before|after|internal|strip_prefix|strip_suffix|replace)\b`, NameConstant, nil},
|
||||||
|
{`(https?://)?([a-z0-9.-]+)(:)([0-9]+)`, ByGroups(Name, Name, Punctuation, LiteralNumberInteger), nil},
|
||||||
|
{`[a-z-]+/[a-z-+]+`, LiteralString, nil},
|
||||||
|
{`[0-9]+[km]?\b`, LiteralNumberInteger, nil},
|
||||||
|
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, // Placeholder
|
||||||
|
{`\[(?=[^#{}$]+\])`, Punctuation, nil},
|
||||||
|
{`\]|\|`, Punctuation, nil},
|
||||||
|
{`[^\s#{}$\]]+`, LiteralString, nil},
|
||||||
|
{`/[^\s#]*`, Name, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caddyfile lexer.
|
||||||
|
var Caddyfile = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile",
|
||||||
|
Aliases: []string{"caddyfile", "caddy"},
|
||||||
|
Filenames: []string{"Caddyfile*"},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
Include("comments"),
|
||||||
|
// Global options block
|
||||||
|
{`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")},
|
||||||
|
// Snippets
|
||||||
|
{`(\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
|
||||||
|
// Site label
|
||||||
|
{`[^#{(\s,]+`, GenericHeading, Push("label")},
|
||||||
|
// Site label with placeholder
|
||||||
|
{`\{[\w+.\$-]+\}`, LiteralStringEscape, Push("label")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"snippet": {
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
// Matcher definition
|
||||||
|
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||||
|
// Any directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
// Allow multiple labels, comma separated, newlines after
|
||||||
|
// a comma means another label is coming
|
||||||
|
{`,\s*\n?`, Text, nil},
|
||||||
|
{` `, Text, nil},
|
||||||
|
// Site label with placeholder
|
||||||
|
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil},
|
||||||
|
// Site label
|
||||||
|
{`[^#{(\s,]+`, GenericHeading, nil},
|
||||||
|
// Comment after non-block label (hack because comments end in \n)
|
||||||
|
{`#.*\n`, CommentSingle, Push("site_block")},
|
||||||
|
// Note: if \n, we'll never pop out of the site_block, it's valid
|
||||||
|
{`\{(?=\s)|\n`, Punctuation, Push("site_block")},
|
||||||
|
},
|
||||||
|
"site_block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommon),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Caddyfile directive-only lexer.
|
||||||
|
var CaddyfileDirectives = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile Directives",
|
||||||
|
Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"},
|
||||||
|
Filenames: []string{},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
// Same as "site_block" in Caddyfile
|
||||||
|
"root": {
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommon),
|
||||||
|
))
|
|
@ -1,15 +1,12 @@
|
||||||
package circular
|
package circular
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
"github.com/alecthomas/chroma/lexers/h"
|
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PHP lexer.
|
// PHP lexer for pure PHP code (not embedded in HTML).
|
||||||
var PHP = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
var PHP = internal.Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "PHP",
|
Name: "PHP",
|
||||||
Aliases: []string{"php", "php3", "php4", "php5"},
|
Aliases: []string{"php", "php3", "php4", "php5"},
|
||||||
|
@ -19,73 +16,65 @@ var PHP = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
EnsureNL: true,
|
EnsureNL: true,
|
||||||
},
|
},
|
||||||
Rules{
|
phpCommonRules.Rename("php", "root"),
|
||||||
"root": {
|
))
|
||||||
{`<\?(php)?`, CommentPreproc, Push("php")},
|
|
||||||
{`[^<]+`, Other, nil},
|
var phpCommonRules = Rules{
|
||||||
{`<`, Other, nil},
|
"php": {
|
||||||
},
|
{`\?>`, CommentPreproc, Pop(1)},
|
||||||
"php": {
|
{`(<<<)([\'"]?)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)(\2\n.*?\n\s*)(\3)(;?)(\n)`, ByGroups(LiteralString, LiteralString, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, Punctuation, Text), nil},
|
||||||
{`\?>`, CommentPreproc, Pop(1)},
|
{`\s+`, Text, nil},
|
||||||
{`(<<<)([\'"]?)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)(\2\n.*?\n\s*)(\3)(;?)(\n)`, ByGroups(LiteralString, LiteralString, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, Punctuation, Text), nil},
|
{`#.*?\n`, CommentSingle, nil},
|
||||||
{`\s+`, Text, nil},
|
{`//.*?\n`, CommentSingle, nil},
|
||||||
{`#.*?\n`, CommentSingle, nil},
|
{`/\*\*/`, CommentMultiline, nil},
|
||||||
{`//.*?\n`, CommentSingle, nil},
|
{`/\*\*.*?\*/`, LiteralStringDoc, nil},
|
||||||
{`/\*\*/`, CommentMultiline, nil},
|
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||||
{`/\*\*.*?\*/`, LiteralStringDoc, nil},
|
{`(->|::)(\s*)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Operator, Text, NameAttribute), nil},
|
||||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
{`[~!%^&*+=|:.<>/@-]+`, Operator, nil},
|
||||||
{`(->|::)(\s*)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Operator, Text, NameAttribute), nil},
|
{`\?`, Operator, nil},
|
||||||
{`[~!%^&*+=|:.<>/@-]+`, Operator, nil},
|
{`[\[\]{}();,]+`, Punctuation, nil},
|
||||||
{`\?`, Operator, nil},
|
{`(class)(\s+)`, ByGroups(Keyword, Text), Push("classname")},
|
||||||
{`[\[\]{}();,]+`, Punctuation, nil},
|
{`(function)(\s*)(?=\()`, ByGroups(Keyword, Text), nil},
|
||||||
{`(class)(\s+)`, ByGroups(Keyword, Text), Push("classname")},
|
{`(function)(\s+)(&?)(\s*)`, ByGroups(Keyword, Text, Operator, Text), Push("functionname")},
|
||||||
{`(function)(\s*)(?=\()`, ByGroups(Keyword, Text), nil},
|
{`(const)(\s+)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Keyword, Text, NameConstant), nil},
|
||||||
{`(function)(\s+)(&?)(\s*)`, ByGroups(Keyword, Text, Operator, Text), Push("functionname")},
|
{`(and|E_PARSE|old_function|E_ERROR|or|as|E_WARNING|parent|eval|PHP_OS|break|exit|case|extends|PHP_VERSION|cfunction|FALSE|print|for|require|continue|foreach|require_once|declare|return|default|static|do|switch|die|stdClass|echo|else|TRUE|elseif|var|empty|if|xor|enddeclare|include|virtual|endfor|include_once|while|endforeach|global|endif|list|endswitch|new|endwhile|not|array|E_ALL|NULL|final|php_user_filter|interface|implements|public|private|protected|abstract|clone|try|catch|throw|this|use|namespace|trait|yield|finally)\b`, Keyword, nil},
|
||||||
{`(const)(\s+)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Keyword, Text, NameConstant), nil},
|
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||||
{`(and|E_PARSE|old_function|E_ERROR|or|as|E_WARNING|parent|eval|PHP_OS|break|exit|case|extends|PHP_VERSION|cfunction|FALSE|print|for|require|continue|foreach|require_once|declare|return|default|static|do|switch|die|stdClass|echo|else|TRUE|elseif|var|empty|if|xor|enddeclare|include|virtual|endfor|include_once|while|endforeach|global|endif|list|endswitch|new|endwhile|not|array|E_ALL|NULL|final|php_user_filter|interface|implements|public|private|protected|abstract|clone|try|catch|throw|this|use|namespace|trait|yield|finally)\b`, Keyword, nil},
|
Include("magicconstants"),
|
||||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
{`\$\{\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*\}`, NameVariable, nil},
|
||||||
Include("magicconstants"),
|
{`\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameVariable, nil},
|
||||||
{`\$\{\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*\}`, NameVariable, nil},
|
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameOther, nil},
|
||||||
{`\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameVariable, nil},
|
{`(\d+\.\d*|\d*\.\d+)(e[+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameOther, nil},
|
{`\d+e[+-]?[0-9]+`, LiteralNumberFloat, nil},
|
||||||
{`(\d+\.\d*|\d*\.\d+)(e[+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||||
{`\d+e[+-]?[0-9]+`, LiteralNumberFloat, nil},
|
{`0x[a-f0-9]+`, LiteralNumberHex, nil},
|
||||||
{`0[0-7]+`, LiteralNumberOct, nil},
|
{`\d+`, LiteralNumberInteger, nil},
|
||||||
{`0x[a-f0-9]+`, LiteralNumberHex, nil},
|
{`0b[01]+`, LiteralNumberBin, nil},
|
||||||
{`\d+`, LiteralNumberInteger, nil},
|
{`'([^'\\]*(?:\\.[^'\\]*)*)'`, LiteralStringSingle, nil},
|
||||||
{`0b[01]+`, LiteralNumberBin, nil},
|
{"`([^`\\\\]*(?:\\\\.[^`\\\\]*)*)`", LiteralStringBacktick, nil},
|
||||||
{`'([^'\\]*(?:\\.[^'\\]*)*)'`, LiteralStringSingle, nil},
|
{`"`, LiteralStringDouble, Push("string")},
|
||||||
{"`([^`\\\\]*(?:\\\\.[^`\\\\]*)*)`", LiteralStringBacktick, nil},
|
|
||||||
{`"`, LiteralStringDouble, Push("string")},
|
|
||||||
},
|
|
||||||
"magicfuncs": {
|
|
||||||
{Words(``, `\b`, `__construct`, `__destruct`, `__call`, `__callStatic`, `__get`, `__set`, `__isset`, `__unset`, `__sleep`, `__wakeup`, `__toString`, `__invoke`, `__set_state`, `__clone`, `__debugInfo`), NameFunctionMagic, nil},
|
|
||||||
},
|
|
||||||
"magicconstants": {
|
|
||||||
{Words(``, `\b`, `__LINE__`, `__FILE__`, `__DIR__`, `__FUNCTION__`, `__CLASS__`, `__TRAIT__`, `__METHOD__`, `__NAMESPACE__`), NameConstant, nil},
|
|
||||||
},
|
|
||||||
"classname": {
|
|
||||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameClass, Pop(1)},
|
|
||||||
},
|
|
||||||
"functionname": {
|
|
||||||
Include("magicfuncs"),
|
|
||||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameFunction, Pop(1)},
|
|
||||||
Default(Pop(1)),
|
|
||||||
},
|
|
||||||
"string": {
|
|
||||||
{`"`, LiteralStringDouble, Pop(1)},
|
|
||||||
{`[^{$"\\]+`, LiteralStringDouble, nil},
|
|
||||||
{`\\([nrt"$\\]|[0-7]{1,3}|x[0-9a-f]{1,2})`, LiteralStringEscape, nil},
|
|
||||||
{`\$(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*(\[\S+?\]|->(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)?`, LiteralStringInterpol, nil},
|
|
||||||
{`(\{\$\{)(.*?)(\}\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
|
||||||
{`(\{)(\$.*?)(\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
|
||||||
{`(\$\{)(\S+)(\})`, ByGroups(LiteralStringInterpol, NameVariable, LiteralStringInterpol), nil},
|
|
||||||
{`[${\\]`, LiteralStringDouble, nil},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
).SetAnalyser(func(text string) float32 {
|
"magicfuncs": {
|
||||||
if strings.Contains(text, "<?php") {
|
{Words(``, `\b`, `__construct`, `__destruct`, `__call`, `__callStatic`, `__get`, `__set`, `__isset`, `__unset`, `__sleep`, `__wakeup`, `__toString`, `__invoke`, `__set_state`, `__clone`, `__debugInfo`), NameFunctionMagic, nil},
|
||||||
return 0.5
|
},
|
||||||
}
|
"magicconstants": {
|
||||||
return 0.0
|
{Words(``, `\b`, `__LINE__`, `__FILE__`, `__DIR__`, `__FUNCTION__`, `__CLASS__`, `__TRAIT__`, `__METHOD__`, `__NAMESPACE__`), NameConstant, nil},
|
||||||
})))
|
},
|
||||||
|
"classname": {
|
||||||
|
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameClass, Pop(1)},
|
||||||
|
},
|
||||||
|
"functionname": {
|
||||||
|
Include("magicfuncs"),
|
||||||
|
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameFunction, Pop(1)},
|
||||||
|
Default(Pop(1)),
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`"`, LiteralStringDouble, Pop(1)},
|
||||||
|
{`[^{$"\\]+`, LiteralStringDouble, nil},
|
||||||
|
{`\\([nrt"$\\]|[0-7]{1,3}|x[0-9a-f]{1,2})`, LiteralStringEscape, nil},
|
||||||
|
{`\$(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*(\[\S+?\]|->(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)?`, LiteralStringInterpol, nil},
|
||||||
|
{`(\{\$\{)(.*?)(\}\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||||
|
{`(\{)(\$.*?)(\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||||
|
{`(\$\{)(\S+)(\})`, ByGroups(LiteralStringInterpol, NameVariable, LiteralStringInterpol), nil},
|
||||||
|
{`[${\\]`, LiteralStringDouble, nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package circular
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/h"
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PHTML lexer is PHP in HTML.
|
||||||
|
var PHTML = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "PHTML",
|
||||||
|
Aliases: []string{"phtml"},
|
||||||
|
Filenames: []string{"*.phtml"},
|
||||||
|
MimeTypes: []string{"application/x-php", "application/x-httpd-php", "application/x-httpd-php3", "application/x-httpd-php4", "application/x-httpd-php5"},
|
||||||
|
DotAll: true,
|
||||||
|
CaseInsensitive: true,
|
||||||
|
EnsureNL: true,
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`<\?(php)?`, CommentPreproc, Push("php")},
|
||||||
|
{`[^<]+`, Other, nil},
|
||||||
|
{`<`, Other, nil},
|
||||||
|
},
|
||||||
|
}.Merge(phpCommonRules),
|
||||||
|
).SetAnalyser(func(text string) float32 {
|
||||||
|
if strings.Contains(text, "<?php") {
|
||||||
|
return 0.5
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
})))
|
|
@ -28,6 +28,13 @@ var Elixir = internal.Register(MustNewLexer(
|
||||||
{`:"`, LiteralStringSymbol, Push("string_double_atom")},
|
{`:"`, LiteralStringSymbol, Push("string_double_atom")},
|
||||||
{`:'`, LiteralStringSymbol, Push("string_single_atom")},
|
{`:'`, LiteralStringSymbol, Push("string_single_atom")},
|
||||||
{`((?:\.\.\.|<<>>|%\{\}|%|\{\})|(?:(?:\.\.\.|[a-z_]\w*[!?]?)|[A-Z]\w*(?:\.[A-Z]\w*)*|(?:\<\<\<|\>\>\>|\|\|\||\&\&\&|\^\^\^|\~\~\~|\=\=\=|\!\=\=|\~\>\>|\<\~\>|\|\~\>|\<\|\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\<\>|\+\+|\-\-|\|\>|\=\~|\-\>|\<\-|\||\.|\=|\~\>|\<\~|\<|\>|\+|\-|\*|\/|\!|\^|\&)))(:)(?=\s|\n)`, ByGroups(LiteralStringSymbol, Punctuation), nil},
|
{`((?:\.\.\.|<<>>|%\{\}|%|\{\})|(?:(?:\.\.\.|[a-z_]\w*[!?]?)|[A-Z]\w*(?:\.[A-Z]\w*)*|(?:\<\<\<|\>\>\>|\|\|\||\&\&\&|\^\^\^|\~\~\~|\=\=\=|\!\=\=|\~\>\>|\<\~\>|\|\~\>|\<\|\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\<\>|\+\+|\-\-|\|\>|\=\~|\-\>|\<\-|\||\.|\=|\~\>|\<\~|\<|\>|\+|\-|\*|\/|\!|\^|\&)))(:)(?=\s|\n)`, ByGroups(LiteralStringSymbol, Punctuation), nil},
|
||||||
|
{`(fn|do|end|after|else|rescue|catch)\b`, Keyword, nil},
|
||||||
|
{`(not|and|or|when|in)\b`, OperatorWord, nil},
|
||||||
|
{`(case|cond|for|if|unless|try|receive|raise|quote|unquote|unquote_splicing|throw|super|while)\b`, Keyword, nil},
|
||||||
|
{`(def|defp|defmodule|defprotocol|defmacro|defmacrop|defdelegate|defexception|defstruct|defimpl|defcallback)\b`, KeywordDeclaration, nil},
|
||||||
|
{`(import|require|use|alias)\b`, KeywordNamespace, nil},
|
||||||
|
{`(nil|true|false)\b`, NameConstant, nil},
|
||||||
|
{`(_|__MODULE__|__DIR__|__ENV__|__CALLER__)\b`, NamePseudo, nil},
|
||||||
{`@(?:\.\.\.|[a-z_]\w*[!?]?)`, NameAttribute, nil},
|
{`@(?:\.\.\.|[a-z_]\w*[!?]?)`, NameAttribute, nil},
|
||||||
{`(?:\.\.\.|[a-z_]\w*[!?]?)`, Name, nil},
|
{`(?:\.\.\.|[a-z_]\w*[!?]?)`, Name, nil},
|
||||||
{`(%?)([A-Z]\w*(?:\.[A-Z]\w*)*)`, ByGroups(Punctuation, NameClass), nil},
|
{`(%?)([A-Z]\w*(?:\.[A-Z]\w*)*)`, ByGroups(Punctuation, NameClass), nil},
|
||||||
|
|
|
@ -15,6 +15,7 @@ var Go = internal.Register(MustNewLexer(
|
||||||
Aliases: []string{"go", "golang"},
|
Aliases: []string{"go", "golang"},
|
||||||
Filenames: []string{"*.go"},
|
Filenames: []string{"*.go"},
|
||||||
MimeTypes: []string{"text/x-gosrc"},
|
MimeTypes: []string{"text/x-gosrc"},
|
||||||
|
EnsureNL: true,
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -19,8 +19,8 @@ var HTTP = internal.Register(httpBodyContentTypeLexer(MustNewLexer(
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
{`(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|PATCH|CONNECT)( +)([^ ]+)( +)(HTTP)(/)(1\.[01])(\r?\n|\Z)`, ByGroups(NameFunction, Text, NameNamespace, Text, KeywordReserved, Operator, LiteralNumber, Text), Push("headers")},
|
{`(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|PATCH|CONNECT)( +)([^ ]+)( +)(HTTP)(/)([12]\.[01])(\r?\n|\Z)`, ByGroups(NameFunction, Text, NameNamespace, Text, KeywordReserved, Operator, LiteralNumber, Text), Push("headers")},
|
||||||
{`(HTTP)(/)(1\.[01])( +)(\d{3})( +)([^\r\n]+)(\r?\n|\Z)`, ByGroups(KeywordReserved, Operator, LiteralNumber, Text, LiteralNumber, Text, NameException, Text), Push("headers")},
|
{`(HTTP)(/)([12]\.[01])( +)(\d{3})( +)([^\r\n]+)(\r?\n|\Z)`, ByGroups(KeywordReserved, Operator, LiteralNumber, Text, LiteralNumber, Text, NameException, Text), Push("headers")},
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
{`([^\s:]+)( *)(:)( *)([^\r\n]+)(\r?\n|\Z)`, EmitterFunc(httpHeaderBlock), nil},
|
{`([^\s:]+)( *)(:)( *)([^\r\n]+)(\r?\n|\Z)`, EmitterFunc(httpHeaderBlock), nil},
|
||||||
|
|
|
@ -10,7 +10,7 @@ var Ini = internal.Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "INI",
|
Name: "INI",
|
||||||
Aliases: []string{"ini", "cfg", "dosini"},
|
Aliases: []string{"ini", "cfg", "dosini"},
|
||||||
Filenames: []string{"*.ini", "*.cfg", "*.inf", ".gitconfig"},
|
Filenames: []string{"*.ini", "*.cfg", "*.inf", ".gitconfig", ".editorconfig"},
|
||||||
MimeTypes: []string{"text/x-ini", "text/inf"},
|
MimeTypes: []string{"text/x-ini", "text/inf"},
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -24,32 +24,71 @@ var Kotlin = internal.Register(MustNewLexer(
|
||||||
{`//[^\n]*\n?`, CommentSingle, nil},
|
{`//[^\n]*\n?`, CommentSingle, nil},
|
||||||
{`/[*].*?[*]/`, CommentMultiline, nil},
|
{`/[*].*?[*]/`, CommentMultiline, nil},
|
||||||
{`\n`, Text, nil},
|
{`\n`, Text, nil},
|
||||||
{`::|!!|\?[:.]`, Operator, nil},
|
{`!==|!in|!is|===`, Operator, nil},
|
||||||
{`[~!%^&*()+=|\[\]:;,.<>/?-]`, Punctuation, nil},
|
{`%=|&&|\*=|\+\+|\+=|--|-=|->|\.\.|\/=|::|<=|==|>=|!!|!=|\|\||\?[:.]`, Operator, nil},
|
||||||
|
{`[~!%^&*()+=|\[\]:;,.<>\/?-]`, Punctuation, nil},
|
||||||
{`[{}]`, Punctuation, nil},
|
{`[{}]`, Punctuation, nil},
|
||||||
{`"""[^"]*"""`, LiteralString, nil},
|
{`"""`, LiteralString, Push("rawstring")},
|
||||||
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
{`"`, LiteralStringDouble, Push("string")},
|
||||||
|
{`(')(\\u[0-9a-fA-F]{4})(')`, ByGroups(LiteralStringChar, LiteralStringEscape, LiteralStringChar), nil},
|
||||||
{`'\\.'|'[^\\]'`, LiteralStringChar, nil},
|
{`'\\.'|'[^\\]'`, LiteralStringChar, nil},
|
||||||
{`0[xX][0-9a-fA-F]+[Uu]?[Ll]?|[0-9]+(\.[0-9]*)?([eE][+-][0-9]+)?[fF]?[Uu]?[Ll]?`, LiteralNumber, nil},
|
{`0[xX][0-9a-fA-F]+[Uu]?[Ll]?|[0-9]+(\.[0-9]*)?([eE][+-][0-9]+)?[fF]?[Uu]?[Ll]?`, LiteralNumber, nil},
|
||||||
{`(companion)(\s+)(object)`, ByGroups(Keyword, Text, Keyword), nil},
|
{`(companion)(\s+)(object)`, ByGroups(Keyword, Text, Keyword), nil},
|
||||||
{`(class|interface|object)(\s+)`, ByGroups(Keyword, Text), Push("class")},
|
{`(class|interface|object)(\s+)`, ByGroups(Keyword, Text), Push("class")},
|
||||||
{`(package|import)(\s+)`, ByGroups(Keyword, Text), Push("package")},
|
{`(package|import)(\s+)`, ByGroups(Keyword, Text), Push("package")},
|
||||||
{`(val|var)(\s+)`, ByGroups(Keyword, Text), Push("property")},
|
{`(val|var)(\s+)`, ByGroups(Keyword, Text), Push("property")},
|
||||||
{`(fun)(\s+)(<[^>]*>\s+)?`, ByGroups(Keyword, Text, Text), Push("function")},
|
{`(fun)(\s+)`, ByGroups(Keyword, Text), Push("function")},
|
||||||
{`(abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|false|final|finally|for|fun|get|if|import|in|infix|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|true|try|val|var|vararg|when|where|while)\b`, Keyword, nil},
|
{`(abstract|actual|annotation|as|as\?|break|by|catch|class|companion|const|constructor|continue|crossinline|data|delegate|do|dynamic|else|enum|expect|external|false|field|file|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|it|lateinit|noinline|null|object|open|operator|out|override|package|param|private|property|protected|public|receiver|reified|return|sealed|set|setparam|super|suspend|tailrec|this|throw|true|try|typealias|typeof|val|var|vararg|when|where|while)\b`, Keyword, nil},
|
||||||
{"(@?[" + kotlinIdentifier + "]*`)", Name, nil},
|
{`@[` + kotlinIdentifier + `]+`, NameDecorator, nil},
|
||||||
|
{`[` + kotlinIdentifier + `]+`, Name, nil},
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
{`\S+`, NameNamespace, Pop(1)},
|
{`\S+`, NameNamespace, Pop(1)},
|
||||||
},
|
},
|
||||||
"class": {
|
"class": {
|
||||||
{"(@?[" + kotlinIdentifier + "]*`)", NameClass, Pop(1)},
|
// \x60 is the back tick character (`)
|
||||||
|
{`\x60[^\x60]+?\x60`, NameClass, Pop(1)},
|
||||||
|
{`[` + kotlinIdentifier + `]+`, NameClass, Pop(1)},
|
||||||
},
|
},
|
||||||
"property": {
|
"property": {
|
||||||
{"(@?[" + kotlinIdentifier + " ]*`)", NameProperty, Pop(1)},
|
{`\x60[^\x60]+?\x60`, NameProperty, Pop(1)},
|
||||||
|
{`[` + kotlinIdentifier + `]+`, NameProperty, Pop(1)},
|
||||||
|
},
|
||||||
|
"generics-specification": {
|
||||||
|
{`<`, Punctuation, Push("generics-specification")}, // required for generics inside generics e.g. <T : List<Int> >
|
||||||
|
{`>`, Punctuation, Pop(1)},
|
||||||
|
{`[,:*?]`, Punctuation, nil},
|
||||||
|
{`(in|out|reified)`, Keyword, nil},
|
||||||
|
{`\x60[^\x60]+?\x60`, NameClass, nil},
|
||||||
|
{`[` + kotlinIdentifier + `]+`, NameClass, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
},
|
},
|
||||||
"function": {
|
"function": {
|
||||||
{"(@?[" + kotlinIdentifier + " ]*`)", NameFunction, Pop(1)},
|
{`<`, Punctuation, Push("generics-specification")},
|
||||||
|
{`\x60[^\x60]+?\x60`, NameFunction, Pop(1)},
|
||||||
|
{`[` + kotlinIdentifier + `]+`, NameFunction, Pop(1)},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"rawstring": {
|
||||||
|
// raw strings don't allow character escaping
|
||||||
|
{`"""`, LiteralString, Pop(1)},
|
||||||
|
{`(?:[^$"]+|\"{1,2}[^"])+`, LiteralString, nil},
|
||||||
|
Include("string-interpol"),
|
||||||
|
// remaining dollar signs are just a string
|
||||||
|
{`\$`, LiteralString, nil},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`\\[tbnr'"\\\$]`, LiteralStringEscape, nil},
|
||||||
|
{`\\u[0-9a-fA-F]{4}`, LiteralStringEscape, nil},
|
||||||
|
{`"`, LiteralStringDouble, Pop(1)},
|
||||||
|
Include("string-interpol"),
|
||||||
|
{`[^\n\\"$]+`, LiteralStringDouble, nil},
|
||||||
|
// remaining dollar signs are just a string
|
||||||
|
{`\$`, LiteralStringDouble, nil},
|
||||||
|
},
|
||||||
|
"string-interpol": {
|
||||||
|
{`\$[` + kotlinIdentifier + `]+`, LiteralStringInterpol, nil},
|
||||||
|
{`\${[^}\n]*}`, LiteralStringInterpol, nil},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
_ "github.com/alecthomas/chroma/lexers/w"
|
_ "github.com/alecthomas/chroma/lexers/w"
|
||||||
_ "github.com/alecthomas/chroma/lexers/x"
|
_ "github.com/alecthomas/chroma/lexers/x"
|
||||||
_ "github.com/alecthomas/chroma/lexers/y"
|
_ "github.com/alecthomas/chroma/lexers/y"
|
||||||
|
_ "github.com/alecthomas/chroma/lexers/z"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registry of Lexers.
|
// Registry of Lexers.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package p
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pony lexer.
|
||||||
|
var Pony = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Pony",
|
||||||
|
Aliases: []string{"pony"},
|
||||||
|
Filenames: []string{"*.pony"},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
{`[^\S\n]+`, Text, nil},
|
||||||
|
{`//.*\n`, CommentSingle, nil},
|
||||||
|
{`/\*`, CommentMultiline, Push("nested_comment")},
|
||||||
|
{`"""(?:.|\n)*?"""`, LiteralStringDoc, nil},
|
||||||
|
{`"`, LiteralString, Push("string")},
|
||||||
|
{`\'.*\'`, LiteralStringChar, nil},
|
||||||
|
{`=>|[]{}:().~;,|&!^?[]`, Punctuation, nil},
|
||||||
|
{Words(``, `\b`, `addressof`, `and`, `as`, `consume`, `digestof`, `is`, `isnt`, `not`, `or`), OperatorWord, nil},
|
||||||
|
{`!=|==|<<|>>|[-+/*%=<>]`, Operator, nil},
|
||||||
|
{Words(``, `\b`, `box`, `break`, `compile_error`, `compile_intrinsic`, `continue`, `do`, `else`, `elseif`, `embed`, `end`, `error`, `for`, `if`, `ifdef`, `in`, `iso`, `lambda`, `let`, `match`, `object`, `recover`, `ref`, `repeat`, `return`, `tag`, `then`, `this`, `trn`, `try`, `until`, `use`, `var`, `val`, `where`, `while`, `with`, `#any`, `#read`, `#send`, `#share`), Keyword, nil},
|
||||||
|
{`(actor|class|struct|primitive|interface|trait|type)((?:\s)+)`, ByGroups(Keyword, Text), Push("typename")},
|
||||||
|
{`(new|fun|be)((?:\s)+)`, ByGroups(Keyword, Text), Push("methodname")},
|
||||||
|
{Words(``, `\b`, `U8`, `U16`, `U32`, `U64`, `ULong`, `USize`, `U128`, `Unsigned`, `Stringable`, `String`, `StringBytes`, `StringRunes`, `InputNotify`, `InputStream`, `Stdin`, `ByteSeq`, `ByteSeqIter`, `OutStream`, `StdStream`, `SourceLoc`, `I8`, `I16`, `I32`, `I64`, `ILong`, `ISize`, `I128`, `Signed`, `Seq`, `RuntimeOptions`, `Real`, `Integer`, `SignedInteger`, `UnsignedInteger`, `FloatingPoint`, `Number`, `Int`, `ReadSeq`, `ReadElement`, `Pointer`, `Platform`, `NullablePointer`, `None`, `Iterator`, `F32`, `F64`, `Float`, `Env`, `DoNotOptimise`, `DisposableActor`, `Less`, `Equal`, `Greater`, `Compare`, `HasEq`, `Equatable`, `Comparable`, `Bool`, `AsioEventID`, `AsioEventNotify`, `AsioEvent`, `Array`, `ArrayKeys`, `ArrayValues`, `ArrayPairs`, `Any`, `AmbientAuth`), KeywordType, nil},
|
||||||
|
{`_?[A-Z]\w*`, NameClass, nil},
|
||||||
|
{`string\(\)`, NameOther, nil},
|
||||||
|
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+`, LiteralNumberFloat, nil},
|
||||||
|
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||||
|
{`\d+`, LiteralNumberInteger, nil},
|
||||||
|
{`(true|false)\b`, Keyword, nil},
|
||||||
|
{`_\d*`, Name, nil},
|
||||||
|
{`_?[a-z][\w\'_]*`, Name, nil},
|
||||||
|
},
|
||||||
|
"typename": {
|
||||||
|
{`(iso|trn|ref|val|box|tag)?((?:\s)*)(_?[A-Z]\w*)`, ByGroups(Keyword, Text, NameClass), Pop(1)},
|
||||||
|
},
|
||||||
|
"methodname": {
|
||||||
|
{`(iso|trn|ref|val|box|tag)?((?:\s)*)(_?[a-z]\w*)`, ByGroups(Keyword, Text, NameFunction), Pop(1)},
|
||||||
|
},
|
||||||
|
"nested_comment": {
|
||||||
|
{`[^*/]+`, CommentMultiline, nil},
|
||||||
|
{`/\*`, CommentMultiline, Push()},
|
||||||
|
{`\*/`, CommentMultiline, Pop(1)},
|
||||||
|
{`[*/]`, CommentMultiline, nil},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`"`, LiteralString, Pop(1)},
|
||||||
|
{`\\"`, LiteralString, nil},
|
||||||
|
{`[^\\"]+`, LiteralString, nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -22,7 +22,7 @@ var TOML = internal.Register(MustNewLexer(
|
||||||
{`[+-]?[0-9](_?\d)*`, LiteralNumberInteger, nil},
|
{`[+-]?[0-9](_?\d)*`, LiteralNumberInteger, nil},
|
||||||
{`"(\\\\|\\"|[^"])*"`, StringDouble, nil},
|
{`"(\\\\|\\"|[^"])*"`, StringDouble, nil},
|
||||||
{`'(\\\\|\\'|[^'])*'`, StringSingle, nil},
|
{`'(\\\\|\\'|[^'])*'`, StringSingle, nil},
|
||||||
{`[.,=\[\]]`, Punctuation, nil},
|
{`[.,=\[\]{}]`, Punctuation, nil},
|
||||||
{`[^\W\d]\w*`, NameOther, nil},
|
{`[^\W\d]\w*`, NameOther, nil},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,14 +38,14 @@ var TypeScript = internal.Register(MustNewLexer(
|
||||||
{`\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|(<<|>>>?|==?|!=?|[-<>+*%&|^/])=?`, Operator, Push("slashstartsregex")},
|
{`\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|(<<|>>>?|==?|!=?|[-<>+*%&|^/])=?`, Operator, Push("slashstartsregex")},
|
||||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||||
{`[})\].]`, Punctuation, nil},
|
{`[})\].]`, Punctuation, nil},
|
||||||
{`(for|in|while|do|break|return|continue|switch|case|default|if|else|throw|try|catch|finally|new|delete|typeof|instanceof|void|this)\b`, Keyword, Push("slashstartsregex")},
|
{`(for|in|of|while|do|break|return|yield|continue|switch|case|default|if|else|throw|try|catch|finally|new|delete|typeof|instanceof|keyof|asserts|is|infer|await|void|this)\b`, Keyword, Push("slashstartsregex")},
|
||||||
{`(var|let|with|function)\b`, KeywordDeclaration, Push("slashstartsregex")},
|
{`(var|let|with|function)\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||||
{`(abstract|boolean|byte|char|class|const|debugger|double|enum|export|extends|final|float|goto|implements|import|int|interface|long|native|package|private|protected|public|short|static|super|synchronized|throws|transient|volatile)\b`, KeywordReserved, nil},
|
{`(abstract|async|boolean|class|const|debugger|enum|export|extends|from|get|global|goto|implements|import|interface|namespace|package|private|protected|public|readonly|require|set|static|super|type)\b`, KeywordReserved, nil},
|
||||||
{`(true|false|null|NaN|Infinity|undefined)\b`, KeywordConstant, nil},
|
{`(true|false|null|NaN|Infinity|undefined)\b`, KeywordConstant, nil},
|
||||||
{`(Array|Boolean|Date|Error|Function|Math|netscape|Number|Object|Packages|RegExp|String|sun|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|window)\b`, NameBuiltin, nil},
|
{`(Array|Boolean|Date|Error|Function|Math|Number|Object|Packages|RegExp|String|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|document|this|window)\b`, NameBuiltin, nil},
|
||||||
{`\b(module)(\s*)(\s*[\w?.$][\w?.$]*)(\s*)`, ByGroups(KeywordReserved, Text, NameOther, Text), Push("slashstartsregex")},
|
{`\b(module)(\s*)(\s*[\w?.$][\w?.$]*)(\s*)`, ByGroups(KeywordReserved, Text, NameOther, Text), Push("slashstartsregex")},
|
||||||
{`\b(string|bool|number)\b`, KeywordType, nil},
|
{`\b(string|bool|number|any|never|object|symbol|unique|unknown|bigint)\b`, KeywordType, nil},
|
||||||
{`\b(constructor|declare|interface|as|AS)\b`, KeywordReserved, nil},
|
{`\b(constructor|declare|interface|as)\b`, KeywordReserved, nil},
|
||||||
{`(super)(\s*)(\([\w,?.$\s]+\s*\))`, ByGroups(KeywordReserved, Text), Push("slashstartsregex")},
|
{`(super)(\s*)(\([\w,?.$\s]+\s*\))`, ByGroups(KeywordReserved, Text), Push("slashstartsregex")},
|
||||||
{`([a-zA-Z_?.$][\w?.$]*)\(\) \{`, NameOther, Push("slashstartsregex")},
|
{`([a-zA-Z_?.$][\w?.$]*)\(\) \{`, NameOther, Push("slashstartsregex")},
|
||||||
{`([\w?.$][\w?.$]*)(\s*:\s*)([\w?.$][\w?.$]*)`, ByGroups(NameOther, Text, KeywordType), nil},
|
{`([\w?.$][\w?.$]*)(\s*:\s*)([\w?.$][\w?.$]*)`, ByGroups(NameOther, Text, KeywordType), nil},
|
||||||
|
|
|
@ -15,32 +15,36 @@ var YAML = internal.Register(MustNewLexer(
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
Include("whitespace"),
|
Include("whitespace"),
|
||||||
{`^---`, Text, nil},
|
{`^---`, NameNamespace, nil},
|
||||||
|
{`^\.\.\.`, NameNamespace, nil},
|
||||||
{`[\n?]?\s*- `, Text, nil},
|
{`[\n?]?\s*- `, Text, nil},
|
||||||
{`#.*$`, Comment, nil},
|
{`#.*$`, Comment, nil},
|
||||||
{`!![^\s]+`, CommentPreproc, nil},
|
{`!![^\s]+`, CommentPreproc, nil},
|
||||||
{`&[^\s]+`, CommentPreproc, nil},
|
{`&[^\s]+`, CommentPreproc, nil},
|
||||||
{`\*[^\s]+`, CommentPreproc, nil},
|
{`\*[^\s]+`, CommentPreproc, nil},
|
||||||
{`^%include\s+[^\n\r]+`, CommentPreproc, nil},
|
{`^%include\s+[^\n\r]+`, CommentPreproc, nil},
|
||||||
{`([>|+-]\s+)(\s+)((?:(?:.*?$)(?:[\n\r]*?)?)*)`, ByGroups(StringDoc, StringDoc, StringDoc), nil},
|
|
||||||
Include("key"),
|
Include("key"),
|
||||||
Include("value"),
|
Include("value"),
|
||||||
{`[?:,\[\]]`, Punctuation, nil},
|
{`[?:,\[\]]`, Punctuation, nil},
|
||||||
{`.`, Text, nil},
|
{`.`, Text, nil},
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
{Words(``, `\b`, "true", "false", "null"), KeywordConstant, nil},
|
{`([>|](?:[+-])?)(\n(^ {1,})(?:.*\n*(?:^\3 *).*)*)`, ByGroups(Punctuation, StringDoc, Whitespace), nil},
|
||||||
|
{Words(``, `\b`, "true", "True", "TRUE", "false", "False", "FALSE", "null",
|
||||||
|
"y", "Y", "yes", "Yes", "YES", "n", "N", "no", "No", "NO",
|
||||||
|
"on", "On", "ON", "off", "Off", "OFF"), KeywordConstant, nil},
|
||||||
{`"(?:\\.|[^"])*"`, StringDouble, nil},
|
{`"(?:\\.|[^"])*"`, StringDouble, nil},
|
||||||
{`'(?:\\.|[^'])*'`, StringSingle, nil},
|
{`'(?:\\.|[^'])*'`, StringSingle, nil},
|
||||||
{`\d\d\d\d-\d\d-\d\d([T ]\d\d:\d\d:\d\d(\.\d+)?(Z|\s+[-+]\d+)?)?`, LiteralDate, nil},
|
{`\d\d\d\d-\d\d-\d\d([T ]\d\d:\d\d:\d\d(\.\d+)?(Z|\s+[-+]\d+)?)?`, LiteralDate, nil},
|
||||||
{`\b[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)\b`, Number, nil},
|
{`\b[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)\b`, Number, nil},
|
||||||
{`\b[\w]+\b`, Text, nil},
|
{`([^\{\}\[\]\?,\:\!\-\*&\@].*)( )+(#.*)`, ByGroups(Literal, Whitespace, Comment), nil},
|
||||||
|
{`[^\{\}\[\]\?,\:\!\-\*&\@].*`, Literal, nil},
|
||||||
},
|
},
|
||||||
"key": {
|
"key": {
|
||||||
{`"[^"\n].*": `, Keyword, nil},
|
{`"[^"\n].*": `, NameTag, nil},
|
||||||
{`(-)( )([^"\n{]*)(:)( )`, ByGroups(Punctuation, Whitespace, Keyword, Punctuation, Whitespace), nil},
|
{`(-)( )([^"\n{]*)(:)( )`, ByGroups(Punctuation, Whitespace, NameTag, Punctuation, Whitespace), nil},
|
||||||
{`([^"\n{]*)(:)( )`, ByGroups(Keyword, Punctuation, Whitespace), nil},
|
{`([^"\n{]*)(:)( )`, ByGroups(NameTag, Punctuation, Whitespace), nil},
|
||||||
{`([^"\n{]*)(:)(\n)`, ByGroups(Keyword, Punctuation, Whitespace), nil},
|
{`([^"\n{]*)(:)(\n)`, ByGroups(NameTag, Punctuation, Whitespace), nil},
|
||||||
},
|
},
|
||||||
"whitespace": {
|
"whitespace": {
|
||||||
{`\s+`, Whitespace, nil},
|
{`\s+`, Whitespace, nil},
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package z
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zig lexer.
|
||||||
|
var Zig = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Zig",
|
||||||
|
Aliases: []string{"zig"},
|
||||||
|
Filenames: []string{"*.zig"},
|
||||||
|
MimeTypes: []string{"text/zig"},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`\n`, TextWhitespace, nil},
|
||||||
|
{`\s+`, TextWhitespace, nil},
|
||||||
|
{`//.*?\n`, CommentSingle, nil},
|
||||||
|
{Words(``, `\b`, `break`, `return`, `continue`, `asm`, `defer`, `errdefer`, `unreachable`, `try`, `catch`, `async`, `await`, `suspend`, `resume`, `cancel`), Keyword, nil},
|
||||||
|
{Words(``, `\b`, `const`, `var`, `extern`, `packed`, `export`, `pub`, `noalias`, `inline`, `comptime`, `nakedcc`, `stdcallcc`, `volatile`, `allowzero`, `align`, `linksection`, `threadlocal`), KeywordReserved, nil},
|
||||||
|
{Words(``, `\b`, `struct`, `enum`, `union`, `error`), Keyword, nil},
|
||||||
|
{Words(``, `\b`, `while`, `for`), Keyword, nil},
|
||||||
|
{Words(``, `\b`, `bool`, `f16`, `f32`, `f64`, `f128`, `void`, `noreturn`, `type`, `anyerror`, `promise`, `i0`, `u0`, `isize`, `usize`, `comptime_int`, `comptime_float`, `c_short`, `c_ushort`, `c_int`, `c_uint`, `c_long`, `c_ulong`, `c_longlong`, `c_ulonglong`, `c_longdouble`, `c_voidi8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`), KeywordType, nil},
|
||||||
|
{Words(``, `\b`, `true`, `false`, `null`, `undefined`), KeywordConstant, nil},
|
||||||
|
{Words(``, `\b`, `if`, `else`, `switch`, `and`, `or`, `orelse`), Keyword, nil},
|
||||||
|
{Words(``, `\b`, `fn`, `usingnamespace`, `test`), Keyword, nil},
|
||||||
|
{`0x[0-9a-fA-F]+\.[0-9a-fA-F]+([pP][\-+]?[0-9a-fA-F]+)?`, LiteralNumberFloat, nil},
|
||||||
|
{`0x[0-9a-fA-F]+\.?[pP][\-+]?[0-9a-fA-F]+`, LiteralNumberFloat, nil},
|
||||||
|
{`[0-9]+\.[0-9]+([eE][-+]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||||
|
{`[0-9]+\.?[eE][-+]?[0-9]+`, LiteralNumberFloat, nil},
|
||||||
|
{`0b[01]+`, LiteralNumberBin, nil},
|
||||||
|
{`0o[0-7]+`, LiteralNumberOct, nil},
|
||||||
|
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||||
|
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||||
|
{`@[a-zA-Z_]\w*`, NameBuiltin, nil},
|
||||||
|
{`[a-zA-Z_]\w*`, Name, nil},
|
||||||
|
{`\'\\\'\'`, LiteralStringEscape, nil},
|
||||||
|
{`\'\\(|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\'"])\'`, LiteralStringEscape, nil},
|
||||||
|
{`\'[^\\\']\'`, LiteralString, nil},
|
||||||
|
{`\\\\[^\n]*`, LiteralStringHeredoc, nil},
|
||||||
|
{`c\\\\[^\n]*`, LiteralStringHeredoc, nil},
|
||||||
|
{`c?"`, LiteralString, Push("string")},
|
||||||
|
{`[+%=><|^!?/\-*&~:]`, Operator, nil},
|
||||||
|
{`[{}()\[\],.;]`, Punctuation, nil},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`\\(x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\'"])`, LiteralStringEscape, nil},
|
||||||
|
{`[^\\"\n]+`, LiteralString, nil},
|
||||||
|
{`"`, LiteralString, Pop(1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
|
@ -160,6 +161,14 @@ func Tokenise(lexer Lexer, options *TokeniseOptions, text string) ([]Token, erro
|
||||||
// Rules maps from state to a sequence of Rules.
|
// Rules maps from state to a sequence of Rules.
|
||||||
type Rules map[string][]Rule
|
type Rules map[string][]Rule
|
||||||
|
|
||||||
|
// Rename clones rules then a rule.
|
||||||
|
func (r Rules) Rename(old, new string) Rules {
|
||||||
|
r = r.Clone()
|
||||||
|
r[new] = r[old]
|
||||||
|
delete(r, old)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Clone returns a clone of the Rules.
|
// Clone returns a clone of the Rules.
|
||||||
func (r Rules) Clone() Rules {
|
func (r Rules) Clone() Rules {
|
||||||
out := map[string][]Rule{}
|
out := map[string][]Rule{}
|
||||||
|
@ -170,6 +179,15 @@ func (r Rules) Clone() Rules {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge creates a clone of "r" then merges "rules" into the clone.
|
||||||
|
func (r Rules) Merge(rules Rules) Rules {
|
||||||
|
out := r.Clone()
|
||||||
|
for k, v := range rules.Clone() {
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// MustNewLexer creates a new Lexer or panics.
|
// MustNewLexer creates a new Lexer or panics.
|
||||||
func MustNewLexer(config *Config, rules Rules) *RegexLexer {
|
func MustNewLexer(config *Config, rules Rules) *RegexLexer {
|
||||||
lexer, err := NewLexer(config, rules)
|
lexer, err := NewLexer(config, rules)
|
||||||
|
@ -376,6 +394,7 @@ func (r *RegexLexer) maybeCompile() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
|
return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
|
||||||
}
|
}
|
||||||
|
rule.Regexp.MatchTimeout = time.Millisecond * 250
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- 1.13.x
|
||||||
- 1.11.x
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- go get -t -v ./...
|
- go get -t -v ./...
|
||||||
|
|
|
@ -66,7 +66,9 @@ var examples = []string{
|
||||||
"Tue, 11 Jul 2017 16:28:13 +0200 (CEST)",
|
"Tue, 11 Jul 2017 16:28:13 +0200 (CEST)",
|
||||||
"Mon, 02 Jan 2006 15:04:05 -0700",
|
"Mon, 02 Jan 2006 15:04:05 -0700",
|
||||||
"Thu, 4 Jan 2018 17:53:36 +0000",
|
"Thu, 4 Jan 2018 17:53:36 +0000",
|
||||||
|
"Mon 30 Sep 2018 09:09:09 PM UTC",
|
||||||
"Mon Aug 10 15:44:11 UTC+0100 2015",
|
"Mon Aug 10 15:44:11 UTC+0100 2015",
|
||||||
|
"Thu, 4 Jan 2018 17:53:36 +0000",
|
||||||
"Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)",
|
"Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)",
|
||||||
"September 17, 2012 10:09am",
|
"September 17, 2012 10:09am",
|
||||||
"September 17, 2012 at 10:09am PST-08",
|
"September 17, 2012 at 10:09am PST-08",
|
||||||
|
@ -106,6 +108,15 @@ var examples = []string{
|
||||||
"2014/4/02 03:00:51",
|
"2014/4/02 03:00:51",
|
||||||
"2012/03/19 10:11:59",
|
"2012/03/19 10:11:59",
|
||||||
"2012/03/19 10:11:59.3186369",
|
"2012/03/19 10:11:59.3186369",
|
||||||
|
// yyyy:mm:dd
|
||||||
|
"2014:3:31",
|
||||||
|
"2014:03:31",
|
||||||
|
"2014:4:8 22:05",
|
||||||
|
"2014:04:08 22:05",
|
||||||
|
"2014:04:2 03:00:51",
|
||||||
|
"2014:4:02 03:00:51",
|
||||||
|
"2012:03:19 10:11:59",
|
||||||
|
"2012:03:19 10:11:59.3186369",
|
||||||
// Chinese
|
// Chinese
|
||||||
"2014年04月08日",
|
"2014年04月08日",
|
||||||
// yyyy-mm-ddThh
|
// yyyy-mm-ddThh
|
||||||
|
@ -199,8 +210,9 @@ func main() {
|
||||||
| Mon, 02 Jan 2006 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST |
|
| Mon, 02 Jan 2006 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST |
|
||||||
| Tue, 11 Jul 2017 16:28:13 +0200 (CEST) | 2017-07-11 16:28:13 +0200 +0200 |
|
| Tue, 11 Jul 2017 16:28:13 +0200 (CEST) | 2017-07-11 16:28:13 +0200 +0200 |
|
||||||
| Mon, 02 Jan 2006 15:04:05 -0700 | 2006-01-02 15:04:05 -0700 -0700 |
|
| Mon, 02 Jan 2006 15:04:05 -0700 | 2006-01-02 15:04:05 -0700 -0700 |
|
||||||
| Thu, 4 Jan 2018 17:53:36 +0000 | 2018-01-04 17:53:36 +0000 UTC |
|
| Mon 30 Sep 2018 09:09:09 PM UTC | 2018-09-30 21:09:09 +0000 UTC |
|
||||||
| Mon Aug 10 15:44:11 UTC+0100 2015 | 2015-08-10 15:44:11 +0000 UTC |
|
| Mon Aug 10 15:44:11 UTC+0100 2015 | 2015-08-10 15:44:11 +0000 UTC |
|
||||||
|
| Thu, 4 Jan 2018 17:53:36 +0000 | 2018-01-04 17:53:36 +0000 UTC |
|
||||||
| Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT |
|
| Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT |
|
||||||
| September 17, 2012 10:09am | 2012-09-17 10:09:00 +0000 UTC |
|
| September 17, 2012 10:09am | 2012-09-17 10:09:00 +0000 UTC |
|
||||||
| September 17, 2012 at 10:09am PST-08 | 2012-09-17 10:09:00 -0800 PST |
|
| September 17, 2012 at 10:09am PST-08 | 2012-09-17 10:09:00 -0800 PST |
|
||||||
|
@ -238,7 +250,7 @@ func main() {
|
||||||
| 2014/4/02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC |
|
| 2014/4/02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC |
|
||||||
| 2012/03/19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC |
|
| 2012/03/19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC |
|
||||||
| 2012/03/19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC |
|
| 2012/03/19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC |
|
||||||
| 2014年04月08日 | 2014-04-08 00:00:00 +0000 UTC |
|
| 2014年04月08日 | 2014-04-08 00:00:00 +0000 UTC |
|
||||||
| 2006-01-02T15:04:05+0000 | 2006-01-02 15:04:05 +0000 UTC |
|
| 2006-01-02T15:04:05+0000 | 2006-01-02 15:04:05 +0000 UTC |
|
||||||
| 2009-08-12T22:15:09-07:00 | 2009-08-12 22:15:09 -0700 -0700 |
|
| 2009-08-12T22:15:09-07:00 | 2009-08-12 22:15:09 -0700 -0700 |
|
||||||
| 2009-08-12T22:15:09 | 2009-08-12 22:15:09 +0000 UTC |
|
| 2009-08-12T22:15:09 | 2009-08-12 22:15:09 +0000 UTC |
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module github.com/araddon/dateparse
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4 h1:8qmTC5ByIXO3GP/IzBkxcZ/99VITvnIETDhdFz/om7A=
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
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=
|
|
@ -17,6 +17,23 @@ import (
|
||||||
// gou.SetColorOutput()
|
// gou.SetColorOutput()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
var days = []string{
|
||||||
|
"mon",
|
||||||
|
"tue",
|
||||||
|
"wed",
|
||||||
|
"thu",
|
||||||
|
"fri",
|
||||||
|
"sat",
|
||||||
|
"sun",
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday",
|
||||||
|
"sunday",
|
||||||
|
}
|
||||||
|
|
||||||
var months = []string{
|
var months = []string{
|
||||||
"january",
|
"january",
|
||||||
"february",
|
"february",
|
||||||
|
@ -49,22 +66,24 @@ const (
|
||||||
dateDigitDot // 10
|
dateDigitDot // 10
|
||||||
dateDigitDotDot
|
dateDigitDotDot
|
||||||
dateDigitSlash
|
dateDigitSlash
|
||||||
|
dateDigitColon
|
||||||
dateDigitChineseYear
|
dateDigitChineseYear
|
||||||
dateDigitChineseYearWs
|
dateDigitChineseYearWs // 15
|
||||||
dateDigitWs // 15
|
dateDigitWs
|
||||||
dateDigitWsMoYear
|
dateDigitWsMoYear
|
||||||
dateDigitWsMolong
|
dateDigitWsMolong
|
||||||
dateAlpha
|
dateAlpha
|
||||||
dateAlphaWs
|
dateAlphaWs // 20
|
||||||
dateAlphaWsDigit // 20
|
dateAlphaWsDigit
|
||||||
dateAlphaWsDigitMore
|
dateAlphaWsDigitMore
|
||||||
dateAlphaWsDigitMoreWs
|
dateAlphaWsDigitMoreWs
|
||||||
dateAlphaWsDigitMoreWsYear
|
dateAlphaWsDigitMoreWsYear
|
||||||
dateAlphaWsMonth
|
dateAlphaWsMonth // 25
|
||||||
|
dateAlphaWsDigitYearmaybe
|
||||||
dateAlphaWsMonthMore
|
dateAlphaWsMonthMore
|
||||||
dateAlphaWsMonthSuffix
|
dateAlphaWsMonthSuffix
|
||||||
dateAlphaWsMore
|
dateAlphaWsMore
|
||||||
dateAlphaWsAtTime
|
dateAlphaWsAtTime // 30
|
||||||
dateAlphaWsAlpha
|
dateAlphaWsAlpha
|
||||||
dateAlphaWsAlphaYearmaybe
|
dateAlphaWsAlphaYearmaybe
|
||||||
dateAlphaPeriodWsDigit
|
dateAlphaPeriodWsDigit
|
||||||
|
@ -120,8 +139,8 @@ func unknownErr(datestr string) error {
|
||||||
// ParseAny parse an unknown date format, detect the layout.
|
// ParseAny parse an unknown date format, detect the layout.
|
||||||
// Normal parse. Equivalent Timezone rules as time.Parse().
|
// Normal parse. Equivalent Timezone rules as time.Parse().
|
||||||
// NOTE: please see readme on mmdd vs ddmm ambiguous dates.
|
// NOTE: please see readme on mmdd vs ddmm ambiguous dates.
|
||||||
func ParseAny(datestr string) (time.Time, error) {
|
func ParseAny(datestr string, opts ...ParserOption) (time.Time, error) {
|
||||||
p, err := parseTime(datestr, nil)
|
p, err := parseTime(datestr, nil, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
@ -133,8 +152,8 @@ func ParseAny(datestr string) (time.Time, error) {
|
||||||
// datestring, it uses the given location rules for any zone interpretation.
|
// datestring, it uses the given location rules for any zone interpretation.
|
||||||
// That is, MST means one thing when using America/Denver and something else
|
// That is, MST means one thing when using America/Denver and something else
|
||||||
// in other locations.
|
// in other locations.
|
||||||
func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
|
func ParseIn(datestr string, loc *time.Location, opts ...ParserOption) (time.Time, error) {
|
||||||
p, err := parseTime(datestr, loc)
|
p, err := parseTime(datestr, loc, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
@ -156,8 +175,8 @@ func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
|
||||||
//
|
//
|
||||||
// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
|
// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
|
||||||
//
|
//
|
||||||
func ParseLocal(datestr string) (time.Time, error) {
|
func ParseLocal(datestr string, opts ...ParserOption) (time.Time, error) {
|
||||||
p, err := parseTime(datestr, time.Local)
|
p, err := parseTime(datestr, time.Local, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
@ -166,8 +185,8 @@ func ParseLocal(datestr string) (time.Time, error) {
|
||||||
|
|
||||||
// MustParse parse a date, and panic if it can't be parsed. Used for testing.
|
// MustParse parse a date, and panic if it can't be parsed. Used for testing.
|
||||||
// Not recommended for most use-cases.
|
// Not recommended for most use-cases.
|
||||||
func MustParse(datestr string) time.Time {
|
func MustParse(datestr string, opts ...ParserOption) time.Time {
|
||||||
p, err := parseTime(datestr, nil)
|
p, err := parseTime(datestr, nil, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -184,8 +203,8 @@ func MustParse(datestr string) time.Time {
|
||||||
// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
|
// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
|
||||||
// // layout = "2006-01-02 15:04:05"
|
// // layout = "2006-01-02 15:04:05"
|
||||||
//
|
//
|
||||||
func ParseFormat(datestr string) (string, error) {
|
func ParseFormat(datestr string, opts ...ParserOption) (string, error) {
|
||||||
p, err := parseTime(datestr, nil)
|
p, err := parseTime(datestr, nil, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -198,8 +217,8 @@ func ParseFormat(datestr string) (string, error) {
|
||||||
|
|
||||||
// ParseStrict parse an unknown date format. IF the date is ambigous
|
// ParseStrict parse an unknown date format. IF the date is ambigous
|
||||||
// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
|
// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
|
||||||
func ParseStrict(datestr string) (time.Time, error) {
|
func ParseStrict(datestr string, opts ...ParserOption) (time.Time, error) {
|
||||||
p, err := parseTime(datestr, nil)
|
p, err := parseTime(datestr, nil, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
@ -209,9 +228,31 @@ func ParseStrict(datestr string) (time.Time, error) {
|
||||||
return p.parse()
|
return p.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTime(datestr string, loc *time.Location) (*parser, error) {
|
func parseTime(datestr string, loc *time.Location, opts ...ParserOption) (p *parser, err error) {
|
||||||
|
|
||||||
|
p = newParser(datestr, loc, opts...)
|
||||||
|
if p.retryAmbiguousDateWithSwap {
|
||||||
|
// month out of range signifies that a day/month swap is the correct solution to an ambiguous date
|
||||||
|
// this is because it means that a day is being interpreted as a month and overflowing the valid value for that
|
||||||
|
// by retrying in this case, we can fix a common situation with no assumptions
|
||||||
|
defer func() {
|
||||||
|
if p.ambiguousMD {
|
||||||
|
// if it errors out with the following error, swap before we
|
||||||
|
// get out of this function to reduce scope it needs to be applied on
|
||||||
|
_, err := p.parse()
|
||||||
|
if err != nil && strings.Contains(err.Error(), "month out of range") {
|
||||||
|
// create the option to reverse the preference
|
||||||
|
preferMonthFirst := PreferMonthFirst(!p.preferMonthFirst)
|
||||||
|
// turn off the retry to avoid endless recursion
|
||||||
|
retryAmbiguousDateWithSwap := RetryAmbiguousDateWithSwap(false)
|
||||||
|
modifiedOpts := append(opts, preferMonthFirst, retryAmbiguousDateWithSwap)
|
||||||
|
p, err = parseTime(datestr, time.Local, modifiedOpts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
p := newParser(datestr, loc)
|
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
// General strategy is to read rune by rune through the date looking for
|
// General strategy is to read rune by rune through the date looking for
|
||||||
|
@ -257,6 +298,31 @@ iterRunes:
|
||||||
// 03/31/2005
|
// 03/31/2005
|
||||||
// 2014/02/24
|
// 2014/02/24
|
||||||
p.stateDate = dateDigitSlash
|
p.stateDate = dateDigitSlash
|
||||||
|
if i == 4 {
|
||||||
|
p.yearlen = i
|
||||||
|
p.moi = i + 1
|
||||||
|
p.setYear()
|
||||||
|
} else {
|
||||||
|
p.ambiguousMD = true
|
||||||
|
if p.preferMonthFirst {
|
||||||
|
if p.molen == 0 {
|
||||||
|
p.molen = i
|
||||||
|
p.setMonth()
|
||||||
|
p.dayi = i + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if p.daylen == 0 {
|
||||||
|
p.daylen = i
|
||||||
|
p.setDay()
|
||||||
|
p.moi = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
// 03/31/2005
|
||||||
|
// 2014/02/24
|
||||||
|
p.stateDate = dateDigitColon
|
||||||
if i == 4 {
|
if i == 4 {
|
||||||
p.yearlen = i
|
p.yearlen = i
|
||||||
p.moi = i + 1
|
p.moi = i + 1
|
||||||
|
@ -446,6 +512,51 @@ iterRunes:
|
||||||
p.setDay()
|
p.setDay()
|
||||||
p.yeari = i + 1
|
p.yeari = i + 1
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if p.molen == 0 {
|
||||||
|
p.molen = i - p.moi
|
||||||
|
p.setMonth()
|
||||||
|
p.yeari = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case dateDigitColon:
|
||||||
|
// 2014:07:10 06:55:38.156283
|
||||||
|
// 03:19:2012 10:11:59
|
||||||
|
// 04:2:2014 03:00:37
|
||||||
|
// 3:1:2012 10:11:59
|
||||||
|
// 4:8:2014 22:05
|
||||||
|
// 3:1:2014
|
||||||
|
// 10:13:2014
|
||||||
|
// 01:02:2006
|
||||||
|
// 1:2:06
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case ' ':
|
||||||
|
p.stateTime = timeStart
|
||||||
|
if p.yearlen == 0 {
|
||||||
|
p.yearlen = i - p.yeari
|
||||||
|
p.setYear()
|
||||||
|
} else if p.daylen == 0 {
|
||||||
|
p.daylen = i - p.dayi
|
||||||
|
p.setDay()
|
||||||
|
}
|
||||||
|
break iterRunes
|
||||||
|
case ':':
|
||||||
|
if p.yearlen > 0 {
|
||||||
|
// 2014:07:10 06:55:38.156283
|
||||||
|
if p.molen == 0 {
|
||||||
|
p.molen = i - p.moi
|
||||||
|
p.setMonth()
|
||||||
|
p.dayi = i + 1
|
||||||
|
}
|
||||||
|
} else if p.preferMonthFirst {
|
||||||
|
if p.daylen == 0 {
|
||||||
|
p.daylen = i - p.dayi
|
||||||
|
p.setDay()
|
||||||
|
p.yeari = i + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,9 +698,21 @@ iterRunes:
|
||||||
} else {
|
} else {
|
||||||
// This is possibly ambiguous? May will parse as either though.
|
// This is possibly ambiguous? May will parse as either though.
|
||||||
// So, it could return in-correct format.
|
// So, it could return in-correct format.
|
||||||
// May 05, 2005, 05:05:05
|
// dateAlphaWs
|
||||||
// May 05 2005, 05:05:05
|
// May 05, 2005, 05:05:05
|
||||||
// Jul 05, 2005, 05:05:05
|
// May 05 2005, 05:05:05
|
||||||
|
// Jul 05, 2005, 05:05:05
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// skip & return to dateStart
|
||||||
|
// Tue 05 May 2020, 05:05:05
|
||||||
|
// Mon Jan 2 15:04:05 2006
|
||||||
|
|
||||||
|
maybeDay := strings.ToLower(datestr[0:i])
|
||||||
|
if isDay(maybeDay) {
|
||||||
|
// using skip throws off indices used by other code; saner to restart
|
||||||
|
return parseTime(datestr[i+1:], loc)
|
||||||
|
}
|
||||||
p.stateDate = dateAlphaWs
|
p.stateDate = dateAlphaWs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,7 +741,7 @@ iterRunes:
|
||||||
} else if i == 4 {
|
} else if i == 4 {
|
||||||
// gross
|
// gross
|
||||||
datestr = datestr[0:i-1] + datestr[i:]
|
datestr = datestr[0:i-1] + datestr[i:]
|
||||||
return parseTime(datestr, loc)
|
return parseTime(datestr, loc, opts...)
|
||||||
} else {
|
} else {
|
||||||
return nil, unknownErr(datestr)
|
return nil, unknownErr(datestr)
|
||||||
}
|
}
|
||||||
|
@ -631,11 +754,14 @@ iterRunes:
|
||||||
// Mon Jan 02 15:04:05 -0700 2006
|
// Mon Jan 02 15:04:05 -0700 2006
|
||||||
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
||||||
// Mon Aug 10 15:44:11 UTC+0100 2015
|
// Mon Aug 10 15:44:11 UTC+0100 2015
|
||||||
// dateAlphaWsDigit
|
// dateAlphaWsDigit
|
||||||
// May 8, 2009 5:57:51 PM
|
// May 8, 2009 5:57:51 PM
|
||||||
// May 8 2009 5:57:51 PM
|
// May 8 2009 5:57:51 PM
|
||||||
// oct 1, 1970
|
// May 8 17:57:51 2009
|
||||||
// oct 7, '70
|
// May 8 17:57:51 2009
|
||||||
|
// May 08 17:57:51 2009
|
||||||
|
// oct 1, 1970
|
||||||
|
// oct 7, '70
|
||||||
switch {
|
switch {
|
||||||
case unicode.IsLetter(r):
|
case unicode.IsLetter(r):
|
||||||
p.set(0, "Mon")
|
p.set(0, "Mon")
|
||||||
|
@ -653,6 +779,9 @@ iterRunes:
|
||||||
// oct 1, 1970
|
// oct 1, 1970
|
||||||
// oct 7, '70
|
// oct 7, '70
|
||||||
// oct. 7, 1970
|
// oct. 7, 1970
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// May 08 17:57:51 2009
|
||||||
if r == ',' {
|
if r == ',' {
|
||||||
p.daylen = i - p.dayi
|
p.daylen = i - p.dayi
|
||||||
p.setDay()
|
p.setDay()
|
||||||
|
@ -661,11 +790,31 @@ iterRunes:
|
||||||
p.daylen = i - p.dayi
|
p.daylen = i - p.dayi
|
||||||
p.setDay()
|
p.setDay()
|
||||||
p.yeari = i + 1
|
p.yeari = i + 1
|
||||||
p.stateDate = dateAlphaWsDigitMoreWs
|
p.stateDate = dateAlphaWsDigitYearmaybe
|
||||||
|
p.stateTime = timeStart
|
||||||
} else if unicode.IsLetter(r) {
|
} else if unicode.IsLetter(r) {
|
||||||
p.stateDate = dateAlphaWsMonthSuffix
|
p.stateDate = dateAlphaWsMonthSuffix
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
|
case dateAlphaWsDigitYearmaybe:
|
||||||
|
// x
|
||||||
|
// May 8 2009 5:57:51 PM
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// May 8 17:57:51 2009
|
||||||
|
// May 08 17:57:51 2009
|
||||||
|
// Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
||||||
|
if r == ':' {
|
||||||
|
// Guessed wrong; was not a year
|
||||||
|
i = i - 3
|
||||||
|
p.stateDate = dateAlphaWsDigit
|
||||||
|
p.yeari = 0
|
||||||
|
break iterRunes
|
||||||
|
} else if r == ' ' {
|
||||||
|
// must be year format, not 15:04
|
||||||
|
p.yearlen = i - p.yeari
|
||||||
|
p.setYear()
|
||||||
|
break iterRunes
|
||||||
|
}
|
||||||
case dateAlphaWsDigitMore:
|
case dateAlphaWsDigitMore:
|
||||||
// x
|
// x
|
||||||
// May 8, 2009 5:57:51 PM
|
// May 8, 2009 5:57:51 PM
|
||||||
|
@ -698,42 +847,6 @@ iterRunes:
|
||||||
break iterRunes
|
break iterRunes
|
||||||
}
|
}
|
||||||
|
|
||||||
case dateAlphaWsAlpha:
|
|
||||||
// Mon Jan _2 15:04:05 2006
|
|
||||||
// Mon Jan 02 15:04:05 -0700 2006
|
|
||||||
// Mon Jan _2 15:04:05 MST 2006
|
|
||||||
// Mon Aug 10 15:44:11 UTC+0100 2015
|
|
||||||
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
||||||
if r == ' ' {
|
|
||||||
if p.dayi > 0 {
|
|
||||||
p.daylen = i - p.dayi
|
|
||||||
p.setDay()
|
|
||||||
p.yeari = i + 1
|
|
||||||
p.stateDate = dateAlphaWsAlphaYearmaybe
|
|
||||||
p.stateTime = timeStart
|
|
||||||
}
|
|
||||||
} else if unicode.IsDigit(r) {
|
|
||||||
if p.dayi == 0 {
|
|
||||||
p.dayi = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case dateAlphaWsAlphaYearmaybe:
|
|
||||||
// x
|
|
||||||
// Mon Jan _2 15:04:05 2006
|
|
||||||
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
||||||
if r == ':' {
|
|
||||||
i = i - 3
|
|
||||||
p.stateDate = dateAlphaWsAlpha
|
|
||||||
p.yeari = 0
|
|
||||||
break iterRunes
|
|
||||||
} else if r == ' ' {
|
|
||||||
// must be year format, not 15:04
|
|
||||||
p.yearlen = i - p.yeari
|
|
||||||
p.setYear()
|
|
||||||
break iterRunes
|
|
||||||
}
|
|
||||||
|
|
||||||
case dateAlphaWsMonth:
|
case dateAlphaWsMonth:
|
||||||
// April 8, 2009
|
// April 8, 2009
|
||||||
// April 8 2009
|
// April 8 2009
|
||||||
|
@ -783,25 +896,25 @@ iterRunes:
|
||||||
case 't', 'T':
|
case 't', 'T':
|
||||||
if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
|
if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
|
||||||
if len(datestr) > i+2 {
|
if len(datestr) > i+2 {
|
||||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'n', 'N':
|
case 'n', 'N':
|
||||||
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
||||||
if len(datestr) > i+2 {
|
if len(datestr) > i+2 {
|
||||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 's', 'S':
|
case 's', 'S':
|
||||||
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
||||||
if len(datestr) > i+2 {
|
if len(datestr) > i+2 {
|
||||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'r', 'R':
|
case 'r', 'R':
|
||||||
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
||||||
if len(datestr) > i+2 {
|
if len(datestr) > i+2 {
|
||||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -975,7 +1088,7 @@ iterRunes:
|
||||||
// 2014-05-11 08:20:13,787
|
// 2014-05-11 08:20:13,787
|
||||||
ds := []byte(p.datestr)
|
ds := []byte(p.datestr)
|
||||||
ds[i] = '.'
|
ds[i] = '.'
|
||||||
return parseTime(string(ds), loc)
|
return parseTime(string(ds), loc, opts...)
|
||||||
case '-', '+':
|
case '-', '+':
|
||||||
// 03:21:51+00:00
|
// 03:21:51+00:00
|
||||||
p.stateTime = timeOffset
|
p.stateTime = timeOffset
|
||||||
|
@ -997,6 +1110,8 @@ iterRunes:
|
||||||
} else {
|
} else {
|
||||||
p.seclen = i - p.seci
|
p.seclen = i - p.seci
|
||||||
}
|
}
|
||||||
|
// (Z)ulu time
|
||||||
|
p.loc = time.UTC
|
||||||
case 'a', 'A':
|
case 'a', 'A':
|
||||||
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
||||||
// x
|
// x
|
||||||
|
@ -1139,7 +1254,9 @@ iterRunes:
|
||||||
switch r {
|
switch r {
|
||||||
case ' ':
|
case ' ':
|
||||||
p.set(p.offseti, "-0700")
|
p.set(p.offseti, "-0700")
|
||||||
p.yeari = i + 1
|
if p.yeari == 0 {
|
||||||
|
p.yeari = i + 1
|
||||||
|
}
|
||||||
p.stateTime = timeWsAlphaZoneOffsetWs
|
p.stateTime = timeWsAlphaZoneOffsetWs
|
||||||
}
|
}
|
||||||
case timeWsAlphaZoneOffsetWs:
|
case timeWsAlphaZoneOffsetWs:
|
||||||
|
@ -1630,7 +1747,10 @@ iterRunes:
|
||||||
case dateAlphaWsAlpha:
|
case dateAlphaWsAlpha:
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|
||||||
case dateAlphaWsAlphaYearmaybe:
|
case dateAlphaWsDigit:
|
||||||
|
return p, nil
|
||||||
|
|
||||||
|
case dateAlphaWsDigitYearmaybe:
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|
||||||
case dateDigitSlash:
|
case dateDigitSlash:
|
||||||
|
@ -1640,6 +1760,13 @@ iterRunes:
|
||||||
// 2014/10/13
|
// 2014/10/13
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|
||||||
|
case dateDigitColon:
|
||||||
|
// 3:1:2014
|
||||||
|
// 10:13:2014
|
||||||
|
// 01:02:2006
|
||||||
|
// 2014:10:13
|
||||||
|
return p, nil
|
||||||
|
|
||||||
case dateDigitChineseYear:
|
case dateDigitChineseYear:
|
||||||
// dateDigitChineseYear
|
// dateDigitChineseYear
|
||||||
// 2014年04月08日
|
// 2014年04月08日
|
||||||
|
@ -1667,48 +1794,75 @@ iterRunes:
|
||||||
}
|
}
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
loc *time.Location
|
loc *time.Location
|
||||||
preferMonthFirst bool
|
preferMonthFirst bool
|
||||||
ambiguousMD bool
|
retryAmbiguousDateWithSwap bool
|
||||||
stateDate dateState
|
ambiguousMD bool
|
||||||
stateTime timeState
|
stateDate dateState
|
||||||
format []byte
|
stateTime timeState
|
||||||
datestr string
|
format []byte
|
||||||
fullMonth string
|
datestr string
|
||||||
skip int
|
fullMonth string
|
||||||
extra int
|
skip int
|
||||||
part1Len int
|
extra int
|
||||||
yeari int
|
part1Len int
|
||||||
yearlen int
|
yeari int
|
||||||
moi int
|
yearlen int
|
||||||
molen int
|
moi int
|
||||||
dayi int
|
molen int
|
||||||
daylen int
|
dayi int
|
||||||
houri int
|
daylen int
|
||||||
hourlen int
|
houri int
|
||||||
mini int
|
hourlen int
|
||||||
minlen int
|
mini int
|
||||||
seci int
|
minlen int
|
||||||
seclen int
|
seci int
|
||||||
msi int
|
seclen int
|
||||||
mslen int
|
msi int
|
||||||
offseti int
|
mslen int
|
||||||
offsetlen int
|
offseti int
|
||||||
tzi int
|
offsetlen int
|
||||||
tzlen int
|
tzi int
|
||||||
t *time.Time
|
tzlen int
|
||||||
|
t *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParser(dateStr string, loc *time.Location) *parser {
|
// ParserOption defines a function signature implemented by options
|
||||||
p := parser{
|
// Options defined like this accept the parser and operate on the data within
|
||||||
stateDate: dateStart,
|
type ParserOption func(*parser) error
|
||||||
stateTime: timeIgnore,
|
|
||||||
datestr: dateStr,
|
// PreferMonthFirst is an option that allows preferMonthFirst to be changed from its default
|
||||||
loc: loc,
|
func PreferMonthFirst(preferMonthFirst bool) ParserOption {
|
||||||
preferMonthFirst: true,
|
return func(p *parser) error {
|
||||||
|
p.preferMonthFirst = preferMonthFirst
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryAmbiguousDateWithSwap is an option that allows retryAmbiguousDateWithSwap to be changed from its default
|
||||||
|
func RetryAmbiguousDateWithSwap(retryAmbiguousDateWithSwap bool) ParserOption {
|
||||||
|
return func(p *parser) error {
|
||||||
|
p.retryAmbiguousDateWithSwap = retryAmbiguousDateWithSwap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(dateStr string, loc *time.Location, opts ...ParserOption) *parser {
|
||||||
|
p := &parser{
|
||||||
|
stateDate: dateStart,
|
||||||
|
stateTime: timeIgnore,
|
||||||
|
datestr: dateStr,
|
||||||
|
loc: loc,
|
||||||
|
preferMonthFirst: true,
|
||||||
|
retryAmbiguousDateWithSwap: false,
|
||||||
}
|
}
|
||||||
p.format = []byte(dateStr)
|
p.format = []byte(dateStr)
|
||||||
return &p
|
|
||||||
|
// allow the options to mutate the parser fields from their defaults
|
||||||
|
for _, option := range opts {
|
||||||
|
option(p)
|
||||||
|
}
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) nextIs(i int, b byte) bool {
|
func (p *parser) nextIs(i int, b byte) bool {
|
||||||
|
@ -1854,6 +2008,14 @@ func (p *parser) parse() (time.Time, error) {
|
||||||
}
|
}
|
||||||
return time.ParseInLocation(string(p.format), p.datestr, p.loc)
|
return time.ParseInLocation(string(p.format), p.datestr, p.loc)
|
||||||
}
|
}
|
||||||
|
func isDay(alpha string) bool {
|
||||||
|
for _, day := range days {
|
||||||
|
if alpha == day {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
func isMonthFull(alpha string) bool {
|
func isMonthFull(alpha string) bool {
|
||||||
for _, month := range months {
|
for _, month := range months {
|
||||||
if alpha == month {
|
if alpha == month {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Aymerick JEHANNE
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Declaration represents a parsed style property
|
||||||
|
type Declaration struct {
|
||||||
|
Property string
|
||||||
|
Value string
|
||||||
|
Important bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeclaration instanciates a new Declaration
|
||||||
|
func NewDeclaration() *Declaration {
|
||||||
|
return &Declaration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of the Declaration
|
||||||
|
func (decl *Declaration) String() string {
|
||||||
|
return decl.StringWithImportant(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringWithImportant returns string representation with optional !important part
|
||||||
|
func (decl *Declaration) StringWithImportant(option bool) string {
|
||||||
|
result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
|
||||||
|
|
||||||
|
if option && decl.Important {
|
||||||
|
result += " !important"
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ";"
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if both Declarations are equals
|
||||||
|
func (decl *Declaration) Equal(other *Declaration) bool {
|
||||||
|
return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// DeclarationsByProperty
|
||||||
|
//
|
||||||
|
|
||||||
|
// DeclarationsByProperty represents sortable style declarations
|
||||||
|
type DeclarationsByProperty []*Declaration
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Len() int {
|
||||||
|
return len(declarations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Swap(i, j int) {
|
||||||
|
declarations[i], declarations[j] = declarations[j], declarations[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements sort.Interface
|
||||||
|
func (declarations DeclarationsByProperty) Less(i, j int) bool {
|
||||||
|
return declarations[i].Property < declarations[j].Property
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
indentSpace = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleKind represents a Rule kind
|
||||||
|
type RuleKind int
|
||||||
|
|
||||||
|
// Rule kinds
|
||||||
|
const (
|
||||||
|
QualifiedRule RuleKind = iota
|
||||||
|
AtRule
|
||||||
|
)
|
||||||
|
|
||||||
|
// At Rules than have Rules inside their block instead of Declarations
|
||||||
|
var atRulesWithRulesBlock = []string{
|
||||||
|
"@document", "@font-feature-values", "@keyframes", "@media", "@supports",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule represents a parsed CSS rule
|
||||||
|
type Rule struct {
|
||||||
|
Kind RuleKind
|
||||||
|
|
||||||
|
// At Rule name (eg: "@media")
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Raw prelude
|
||||||
|
Prelude string
|
||||||
|
|
||||||
|
// Qualified Rule selectors parsed from prelude
|
||||||
|
Selectors []string
|
||||||
|
|
||||||
|
// Style properties
|
||||||
|
Declarations []*Declaration
|
||||||
|
|
||||||
|
// At Rule embedded rules
|
||||||
|
Rules []*Rule
|
||||||
|
|
||||||
|
// Current rule embedding level
|
||||||
|
EmbedLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRule instanciates a new Rule
|
||||||
|
func NewRule(kind RuleKind) *Rule {
|
||||||
|
return &Rule{
|
||||||
|
Kind: kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of rule kind
|
||||||
|
func (kind RuleKind) String() string {
|
||||||
|
switch kind {
|
||||||
|
case QualifiedRule:
|
||||||
|
return "Qualified Rule"
|
||||||
|
case AtRule:
|
||||||
|
return "At Rule"
|
||||||
|
default:
|
||||||
|
return "WAT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedsRules returns true if this rule embeds another rules
|
||||||
|
func (rule *Rule) EmbedsRules() bool {
|
||||||
|
if rule.Kind == AtRule {
|
||||||
|
for _, atRuleName := range atRulesWithRulesBlock {
|
||||||
|
if rule.Name == atRuleName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if both rules are equals
|
||||||
|
func (rule *Rule) Equal(other *Rule) bool {
|
||||||
|
if (rule.Kind != other.Kind) ||
|
||||||
|
(rule.Prelude != other.Prelude) ||
|
||||||
|
(rule.Name != other.Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(rule.Selectors) != len(other.Selectors)) ||
|
||||||
|
(len(rule.Declarations) != len(other.Declarations)) ||
|
||||||
|
(len(rule.Rules) != len(other.Rules)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if sel != other.Selectors[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, decl := range rule.Declarations {
|
||||||
|
if !decl.Equal(other.Declarations[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rule := range rule.Rules {
|
||||||
|
if !rule.Equal(other.Rules[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff returns a string representation of rules differences
|
||||||
|
func (rule *Rule) Diff(other *Rule) []string {
|
||||||
|
result := []string{}
|
||||||
|
|
||||||
|
if rule.Kind != other.Kind {
|
||||||
|
result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Prelude != other.Prelude {
|
||||||
|
result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Name != other.Name {
|
||||||
|
result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Selectors) != len(other.Selectors) {
|
||||||
|
result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
|
||||||
|
} else {
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if sel != other.Selectors[i] {
|
||||||
|
result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Declarations) != len(other.Declarations) {
|
||||||
|
result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
|
||||||
|
} else {
|
||||||
|
for i, decl := range rule.Declarations {
|
||||||
|
if !decl.Equal(other.Declarations[i]) {
|
||||||
|
result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule.Rules) != len(other.Rules) {
|
||||||
|
result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for i, rule := range rule.Rules {
|
||||||
|
if !rule.Equal(other.Rules[i]) {
|
||||||
|
result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation of a rule
|
||||||
|
func (rule *Rule) String() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
if rule.Kind == QualifiedRule {
|
||||||
|
for i, sel := range rule.Selectors {
|
||||||
|
if i != 0 {
|
||||||
|
result += ", "
|
||||||
|
}
|
||||||
|
result += sel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AtRule
|
||||||
|
result += fmt.Sprintf("%s", rule.Name)
|
||||||
|
|
||||||
|
if rule.Prelude != "" {
|
||||||
|
if result != "" {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("%s", rule.Prelude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
|
||||||
|
result += ";"
|
||||||
|
} else {
|
||||||
|
result += " {\n"
|
||||||
|
|
||||||
|
if rule.EmbedsRules() {
|
||||||
|
for _, subRule := range rule.Rules {
|
||||||
|
result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, decl := range rule.Declarations {
|
||||||
|
result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += fmt.Sprintf("%s}", rule.indentEndBlock())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns identation spaces for declarations and rules
|
||||||
|
func (rule *Rule) indent() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns identation spaces for end of block character
|
||||||
|
func (rule *Rule) indentEndBlock() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package css
|
||||||
|
|
||||||
|
// Stylesheet represents a parsed stylesheet
|
||||||
|
type Stylesheet struct {
|
||||||
|
Rules []*Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStylesheet instanciate a new Stylesheet
|
||||||
|
func NewStylesheet() *Stylesheet {
|
||||||
|
return &Stylesheet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns string representation of the Stylesheet
|
||||||
|
func (sheet *Stylesheet) String() string {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for _, rule := range sheet.Rules {
|
||||||
|
if result != "" {
|
||||||
|
result += "\n"
|
||||||
|
}
|
||||||
|
result += rule.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Aymerick JEHANNE
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,409 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
|
||||||
|
"github.com/aymerick/douceur/css"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
importantSuffixRegexp = `(?i)\s*!important\s*$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
importantRegexp *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser represents a CSS parser
|
||||||
|
type Parser struct {
|
||||||
|
scan *scanner.Scanner // Tokenizer
|
||||||
|
|
||||||
|
// Tokens parsed but not consumed yet
|
||||||
|
tokens []*scanner.Token
|
||||||
|
|
||||||
|
// Rule embedding level
|
||||||
|
embedLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
importantRegexp = regexp.MustCompile(importantSuffixRegexp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser instanciates a new parser
|
||||||
|
func NewParser(txt string) *Parser {
|
||||||
|
return &Parser{
|
||||||
|
scan: scanner.New(txt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a whole stylesheet
|
||||||
|
func Parse(text string) (*css.Stylesheet, error) {
|
||||||
|
result, err := NewParser(text).ParseStylesheet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDeclarations parses CSS declarations
|
||||||
|
func ParseDeclarations(text string) ([]*css.Declaration, error) {
|
||||||
|
result, err := NewParser(text).ParseDeclarations()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStylesheet parses a stylesheet
|
||||||
|
func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
|
||||||
|
result := css.NewStylesheet()
|
||||||
|
|
||||||
|
// Parse BOM
|
||||||
|
if _, err := parser.parseBOM(); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse list of rules
|
||||||
|
rules, err := parser.ParseRules()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Rules = rules
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRules parses a list of rules
|
||||||
|
func (parser *Parser) ParseRules() ([]*css.Rule, error) {
|
||||||
|
result := []*css.Rule{}
|
||||||
|
|
||||||
|
inBlock := false
|
||||||
|
if parser.tokenChar("{") {
|
||||||
|
// parsing a block of rules
|
||||||
|
inBlock = true
|
||||||
|
parser.embedLevel++
|
||||||
|
|
||||||
|
parser.shiftToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
for parser.tokenParsable() {
|
||||||
|
if parser.tokenIgnorable() {
|
||||||
|
parser.shiftToken()
|
||||||
|
} else if parser.tokenChar("}") {
|
||||||
|
if !inBlock {
|
||||||
|
errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
|
||||||
|
return result, errors.New(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.shiftToken()
|
||||||
|
parser.embedLevel--
|
||||||
|
|
||||||
|
// finished
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
rule, err := parser.ParseRule()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.EmbedLevel = parser.embedLevel
|
||||||
|
result = append(result, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRule parses a rule
|
||||||
|
func (parser *Parser) ParseRule() (*css.Rule, error) {
|
||||||
|
if parser.tokenAtKeyword() {
|
||||||
|
return parser.parseAtRule()
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser.parseQualifiedRule()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDeclarations parses a list of declarations
|
||||||
|
func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
|
||||||
|
result := []*css.Declaration{}
|
||||||
|
|
||||||
|
if parser.tokenChar("{") {
|
||||||
|
parser.shiftToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
for parser.tokenParsable() {
|
||||||
|
if parser.tokenIgnorable() {
|
||||||
|
parser.shiftToken()
|
||||||
|
} else if parser.tokenChar("}") {
|
||||||
|
// end of block
|
||||||
|
parser.shiftToken()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
declaration, err := parser.ParseDeclaration()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, declaration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDeclaration parses a declaration
|
||||||
|
func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
|
||||||
|
result := css.NewDeclaration()
|
||||||
|
curValue := ""
|
||||||
|
|
||||||
|
for parser.tokenParsable() {
|
||||||
|
if parser.tokenChar(":") {
|
||||||
|
result.Property = strings.TrimSpace(curValue)
|
||||||
|
curValue = ""
|
||||||
|
|
||||||
|
parser.shiftToken()
|
||||||
|
} else if parser.tokenChar(";") || parser.tokenChar("}") {
|
||||||
|
if result.Property == "" {
|
||||||
|
errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
|
||||||
|
return result, errors.New(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if importantRegexp.MatchString(curValue) {
|
||||||
|
result.Important = true
|
||||||
|
curValue = importantRegexp.ReplaceAllString(curValue, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Value = strings.TrimSpace(curValue)
|
||||||
|
|
||||||
|
if parser.tokenChar(";") {
|
||||||
|
parser.shiftToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
// finished
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
token := parser.shiftToken()
|
||||||
|
curValue += token.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("[parsed] Declaration: %s", result.String())
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse an At Rule
|
||||||
|
func (parser *Parser) parseAtRule() (*css.Rule, error) {
|
||||||
|
// parse rule name (eg: "@import")
|
||||||
|
token := parser.shiftToken()
|
||||||
|
|
||||||
|
result := css.NewRule(css.AtRule)
|
||||||
|
result.Name = token.Value
|
||||||
|
|
||||||
|
for parser.tokenParsable() {
|
||||||
|
if parser.tokenChar(";") {
|
||||||
|
parser.shiftToken()
|
||||||
|
|
||||||
|
// finished
|
||||||
|
break
|
||||||
|
} else if parser.tokenChar("{") {
|
||||||
|
if result.EmbedsRules() {
|
||||||
|
// parse rules block
|
||||||
|
rules, err := parser.ParseRules()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Rules = rules
|
||||||
|
} else {
|
||||||
|
// parse declarations block
|
||||||
|
declarations, err := parser.ParseDeclarations()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Declarations = declarations
|
||||||
|
}
|
||||||
|
|
||||||
|
// finished
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// parse prelude
|
||||||
|
prelude, err := parser.parsePrelude()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Prelude = prelude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("[parsed] Rule: %s", result.String())
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a Qualified Rule
|
||||||
|
func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
|
||||||
|
result := css.NewRule(css.QualifiedRule)
|
||||||
|
|
||||||
|
for parser.tokenParsable() {
|
||||||
|
if parser.tokenChar("{") {
|
||||||
|
if result.Prelude == "" {
|
||||||
|
errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
|
||||||
|
return result, errors.New(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse declarations block
|
||||||
|
declarations, err := parser.ParseDeclarations()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Declarations = declarations
|
||||||
|
|
||||||
|
// finished
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// parse prelude
|
||||||
|
prelude, err := parser.parsePrelude()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Prelude = prelude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Selectors = strings.Split(result.Prelude, ",")
|
||||||
|
for i, sel := range result.Selectors {
|
||||||
|
result.Selectors[i] = strings.TrimSpace(sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("[parsed] Rule: %s", result.String())
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Rule prelude
|
||||||
|
func (parser *Parser) parsePrelude() (string, error) {
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
|
||||||
|
token := parser.shiftToken()
|
||||||
|
result += token.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
result = strings.TrimSpace(result)
|
||||||
|
|
||||||
|
// log.Printf("[parsed] prelude: %s", result)
|
||||||
|
|
||||||
|
return result, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse BOM
|
||||||
|
func (parser *Parser) parseBOM() (bool, error) {
|
||||||
|
if parser.nextToken().Type == scanner.TokenBOM {
|
||||||
|
parser.shiftToken()
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, parser.err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns next token without removing it from tokens buffer
|
||||||
|
func (parser *Parser) nextToken() *scanner.Token {
|
||||||
|
if len(parser.tokens) == 0 {
|
||||||
|
// fetch next token
|
||||||
|
nextToken := parser.scan.Next()
|
||||||
|
|
||||||
|
// log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
|
||||||
|
|
||||||
|
// queue it
|
||||||
|
parser.tokens = append(parser.tokens, nextToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser.tokens[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns next token and remove it from the tokens buffer
|
||||||
|
func (parser *Parser) shiftToken() *scanner.Token {
|
||||||
|
var result *scanner.Token
|
||||||
|
|
||||||
|
result, parser.tokens = parser.tokens[0], parser.tokens[1:]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns tokenizer error, or nil if no error
|
||||||
|
func (parser *Parser) err() error {
|
||||||
|
if parser.tokenError() {
|
||||||
|
token := parser.nextToken()
|
||||||
|
return fmt.Errorf("Tokenizer error: %s", token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is Error
|
||||||
|
func (parser *Parser) tokenError() bool {
|
||||||
|
return parser.nextToken().Type == scanner.TokenError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is EOF
|
||||||
|
func (parser *Parser) tokenEOF() bool {
|
||||||
|
return parser.nextToken().Type == scanner.TokenEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is a whitespace
|
||||||
|
func (parser *Parser) tokenWS() bool {
|
||||||
|
return parser.nextToken().Type == scanner.TokenS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is a comment
|
||||||
|
func (parser *Parser) tokenComment() bool {
|
||||||
|
return parser.nextToken().Type == scanner.TokenComment
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is a CDO or a CDC
|
||||||
|
func (parser *Parser) tokenCDOorCDC() bool {
|
||||||
|
switch parser.nextToken().Type {
|
||||||
|
case scanner.TokenCDO, scanner.TokenCDC:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is ignorable
|
||||||
|
func (parser *Parser) tokenIgnorable() bool {
|
||||||
|
return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is parsable
|
||||||
|
func (parser *Parser) tokenParsable() bool {
|
||||||
|
return !parser.tokenEOF() && !parser.tokenError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is an At Rule keyword
|
||||||
|
func (parser *Parser) tokenAtKeyword() bool {
|
||||||
|
return parser.nextToken().Type == scanner.TokenAtKeyword
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token is given character
|
||||||
|
func (parser *Parser) tokenChar(value string) bool {
|
||||||
|
token := parser.nextToken()
|
||||||
|
return (token.Type == scanner.TokenChar) && (token.Value == value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if next token marks the end of a prelude
|
||||||
|
func (parser *Parser) tokenEndOfPrelude() bool {
|
||||||
|
return parser.tokenChar(";") || parser.tokenChar("{")
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.5
|
- 1.9
|
||||||
- tip
|
- tip
|
|
@ -43,8 +43,8 @@ The __last__ capture is embedded in each group, so `g.String()` will return the
|
||||||
| Category | regexp | regexp2 |
|
| Category | regexp | regexp2 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Catastrophic backtracking possible | no, constant execution time guarantees | yes, if your pattern is at risk you can use the `re.MatchTimeout` field |
|
| Catastrophic backtracking possible | no, constant execution time guarantees | yes, if your pattern is at risk you can use the `re.MatchTimeout` field |
|
||||||
| Python-style capture groups `(P<name>re)` | yes | no |
|
| Python-style capture groups `(?P<name>re)` | yes | no (yes in RE2 compat mode) |
|
||||||
| .NET-style capture groups `(<name>re)` or `('name're)` | no | yes |
|
| .NET-style capture groups `(?<name>re)` or `(?'name're)` | no | yes |
|
||||||
| comments `(?#comment)` | no | yes |
|
| comments `(?#comment)` | no | yes |
|
||||||
| branch numbering reset `(?\|a\|b)` | no | no |
|
| branch numbering reset `(?\|a\|b)` | no | no |
|
||||||
| possessive match `(?>re)` | no | yes |
|
| possessive match `(?>re)` | no | yes |
|
||||||
|
@ -54,13 +54,14 @@ The __last__ capture is embedded in each group, so `g.String()` will return the
|
||||||
| negative lookbehind `(?<!re)` | no | yes |
|
| negative lookbehind `(?<!re)` | no | yes |
|
||||||
| back reference `\1` | no | yes |
|
| back reference `\1` | no | yes |
|
||||||
| named back reference `\k'name'` | no | yes |
|
| named back reference `\k'name'` | no | yes |
|
||||||
| named ascii character class `[[:foo:]]`| yes | no |
|
| named ascii character class `[[:foo:]]`| yes | no (yes in RE2 compat mode) |
|
||||||
| conditionals `((expr)yes\|no)` | no | yes |
|
| conditionals `(?(expr)yes\|no)` | no | yes |
|
||||||
|
|
||||||
## RE2 compatibility mode
|
## RE2 compatibility mode
|
||||||
The default behavior of `regexp2` is to match the .NET regexp engine, however the `RE2` option is provided to change the parsing to increase compatibility with RE2. Using the `RE2` option when compiling a regexp will not take away any features, but will change the following behaviors:
|
The default behavior of `regexp2` is to match the .NET regexp engine, however the `RE2` option is provided to change the parsing to increase compatibility with RE2. Using the `RE2` option when compiling a regexp will not take away any features, but will change the following behaviors:
|
||||||
* add support for named ascii character classes (e.g. `[[:foo:]]`)
|
* add support for named ascii character classes (e.g. `[[:foo:]]`)
|
||||||
* add support for python-style capture groups (e.g. `(P<name>re)`)
|
* add support for python-style capture groups (e.g. `(P<name>re)`)
|
||||||
|
* change singleline behavior for `$` to only match end of string (like RE2) (see [#24](https://github.com/dlclark/regexp2/issues/24))
|
||||||
|
|
||||||
```go
|
```go
|
||||||
re := regexp2.MustCompile(`Your RE2-compatible pattern`, regexp2.RE2)
|
re := regexp2.MustCompile(`Your RE2-compatible pattern`, regexp2.RE2)
|
||||||
|
|
|
@ -235,17 +235,14 @@ func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) {
|
||||||
ret[i] = r
|
ret[i] = r
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
if startAt == len(s) {
|
||||||
|
runeIdx = i
|
||||||
|
}
|
||||||
return ret[:i], runeIdx
|
return ret[:i], runeIdx
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRunes(s string) []rune {
|
func getRunes(s string) []rune {
|
||||||
ret := make([]rune, len(s))
|
return []rune(s)
|
||||||
i := 0
|
|
||||||
for _, r := range s {
|
|
||||||
ret[i] = r
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return ret[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchRunes return true if the runes matches the regex
|
// MatchRunes return true if the runes matches the regex
|
||||||
|
|
|
@ -566,9 +566,22 @@ func (r *runner) execute() error {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case syntax.EndZ:
|
case syntax.EndZ:
|
||||||
if r.rightchars() > 1 || r.rightchars() == 1 && r.charAt(r.textPos()) != '\n' {
|
rchars := r.rightchars()
|
||||||
|
if rchars > 1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// RE2 and EcmaScript define $ as "asserts position at the end of the string"
|
||||||
|
// PCRE/.NET adds "or before the line terminator right at the end of the string (if any)"
|
||||||
|
if (r.re.options & (RE2 | ECMAScript)) != 0 {
|
||||||
|
// RE2/Ecmascript mode
|
||||||
|
if rchars > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if rchars == 1 && r.charAt(r.textPos()) != '\n' {
|
||||||
|
// "regular" mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
r.advance(0)
|
r.advance(0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -938,8 +951,8 @@ func (r *runner) advance(i int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) goTo(newpos int) {
|
func (r *runner) goTo(newpos int) {
|
||||||
// when branching backward, ensure storage
|
// when branching backward or in place, ensure storage
|
||||||
if newpos < r.codepos {
|
if newpos <= r.codepos {
|
||||||
r.ensureStorage()
|
r.ensureStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1250,10 +1250,10 @@ func (p *parser) scanBasicBackslash(scanOnly bool) (*regexNode, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.useOptionE() || p.isCaptureSlot(capnum) {
|
if p.isCaptureSlot(capnum) {
|
||||||
return newRegexNodeM(ntRef, p.options, capnum), nil
|
return newRegexNodeM(ntRef, p.options, capnum), nil
|
||||||
}
|
}
|
||||||
if capnum <= 9 {
|
if capnum <= 9 && !p.useOptionE() {
|
||||||
return nil, p.getErr(ErrUndefinedBackRef, capnum)
|
return nil, p.getErr(ErrUndefinedBackRef, capnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1648,7 +1648,7 @@ func (p *parser) scanOptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scans \ code for escape codes that map to single unicode chars.
|
// Scans \ code for escape codes that map to single unicode chars.
|
||||||
func (p *parser) scanCharEscape() (rune, error) {
|
func (p *parser) scanCharEscape() (r rune, err error) {
|
||||||
|
|
||||||
ch := p.moveRightGetChar()
|
ch := p.moveRightGetChar()
|
||||||
|
|
||||||
|
@ -1657,16 +1657,22 @@ func (p *parser) scanCharEscape() (rune, error) {
|
||||||
return p.scanOctal(), nil
|
return p.scanOctal(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pos := p.textpos()
|
||||||
|
|
||||||
switch ch {
|
switch ch {
|
||||||
case 'x':
|
case 'x':
|
||||||
// support for \x{HEX} syntax from Perl and PCRE
|
// support for \x{HEX} syntax from Perl and PCRE
|
||||||
if p.charsRight() > 0 && p.rightChar(0) == '{' {
|
if p.charsRight() > 0 && p.rightChar(0) == '{' {
|
||||||
|
if p.useOptionE() {
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
p.moveRight(1)
|
p.moveRight(1)
|
||||||
return p.scanHexUntilBrace()
|
return p.scanHexUntilBrace()
|
||||||
|
} else {
|
||||||
|
r, err = p.scanHex(2)
|
||||||
}
|
}
|
||||||
return p.scanHex(2)
|
|
||||||
case 'u':
|
case 'u':
|
||||||
return p.scanHex(4)
|
r, err = p.scanHex(4)
|
||||||
case 'a':
|
case 'a':
|
||||||
return '\u0007', nil
|
return '\u0007', nil
|
||||||
case 'b':
|
case 'b':
|
||||||
|
@ -1684,13 +1690,18 @@ func (p *parser) scanCharEscape() (rune, error) {
|
||||||
case 'v':
|
case 'v':
|
||||||
return '\u000B', nil
|
return '\u000B', nil
|
||||||
case 'c':
|
case 'c':
|
||||||
return p.scanControl()
|
r, err = p.scanControl()
|
||||||
default:
|
default:
|
||||||
if !p.useOptionE() && IsWordChar(ch) {
|
if !p.useOptionE() && IsWordChar(ch) {
|
||||||
return 0, p.getErr(ErrUnrecognizedEscape, string(ch))
|
return 0, p.getErr(ErrUnrecognizedEscape, string(ch))
|
||||||
}
|
}
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
if err != nil && p.useOptionE() {
|
||||||
|
p.textto(pos)
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grabs and converts an ascii control character
|
// Grabs and converts an ascii control character
|
||||||
|
@ -1807,12 +1818,12 @@ func (p *parser) scanOctal() rune {
|
||||||
//we know the first char is good because the caller had to check
|
//we know the first char is good because the caller had to check
|
||||||
i := 0
|
i := 0
|
||||||
d := int(p.rightChar(0) - '0')
|
d := int(p.rightChar(0) - '0')
|
||||||
for c > 0 && d <= 7 {
|
for c > 0 && d <= 7 && d >= 0 {
|
||||||
i *= 8
|
if i >= 0x20 && p.useOptionE() {
|
||||||
i += d
|
|
||||||
if p.useOptionE() && i >= 0x20 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
i *= 8
|
||||||
|
i += d
|
||||||
c--
|
c--
|
||||||
|
|
||||||
p.moveRight(1)
|
p.moveRight(1)
|
||||||
|
|
|
@ -8,7 +8,7 @@ require (
|
||||||
github.com/gliderlabs/ssh v0.2.2
|
github.com/gliderlabs/ssh v0.2.2
|
||||||
github.com/go-git/gcfg v1.5.0
|
github.com/go-git/gcfg v1.5.0
|
||||||
github.com/go-git/go-billy/v5 v5.0.0
|
github.com/go-git/go-billy/v5 v5.0.0
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1
|
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12
|
||||||
github.com/google/go-cmp v0.3.0
|
github.com/google/go-cmp v0.3.0
|
||||||
github.com/imdario/mergo v0.3.9
|
github.com/imdario/mergo v0.3.9
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||||
|
|
|
@ -20,6 +20,8 @@ github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agR
|
||||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
|
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (e *ErrInvalidRevision) Error() string {
|
||||||
type Revisioner interface {
|
type Revisioner interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ref represents a reference name : HEAD, master
|
// Ref represents a reference name : HEAD, master, <hash>
|
||||||
type Ref string
|
type Ref string
|
||||||
|
|
||||||
// TildePath represents ~, ~{n}
|
// TildePath represents ~, ~{n}
|
||||||
|
@ -297,7 +297,7 @@ func (p *Parser) parseAt() (Revisioner, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if t != cbrace {
|
if t != cbrace {
|
||||||
return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)}
|
return nil, &ErrInvalidRevision{s: `missing "}" in @{-n} structure`}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AtCheckout{n}, nil
|
return AtCheckout{n}, nil
|
||||||
|
@ -419,7 +419,7 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) {
|
||||||
case re == "" && tok == emark && nextTok == minus:
|
case re == "" && tok == emark && nextTok == minus:
|
||||||
negate = true
|
negate = true
|
||||||
case re == "" && tok == emark:
|
case re == "" && tok == emark:
|
||||||
return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)}
|
return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`}
|
||||||
case re == "" && tok == slash:
|
case re == "" && tok == slash:
|
||||||
p.unscan()
|
p.unscan()
|
||||||
case tok != slash && start:
|
case tok != slash && start:
|
||||||
|
@ -490,7 +490,7 @@ func (p *Parser) parseColonSlash() (Revisioner, error) {
|
||||||
case re == "" && tok == emark && nextTok == minus:
|
case re == "" && tok == emark && nextTok == minus:
|
||||||
negate = true
|
negate = true
|
||||||
case re == "" && tok == emark:
|
case re == "" && tok == emark:
|
||||||
return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)}
|
return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`}
|
||||||
case tok == eof:
|
case tok == eof:
|
||||||
p.unscan()
|
p.unscan()
|
||||||
reg, err := regexp.Compile(re)
|
reg, err := regexp.Compile(re)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -373,6 +374,30 @@ var (
|
||||||
ErrMissingAuthor = errors.New("author field is required")
|
ErrMissingAuthor = errors.New("author field is required")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AddOptions describes how a add operation should be performed
|
||||||
|
type AddOptions struct {
|
||||||
|
// All equivalent to `git add -A`, update the index not only where the
|
||||||
|
// working tree has a file matching `Path` but also where the index already
|
||||||
|
// has an entry. This adds, modifies, and removes index entries to match the
|
||||||
|
// working tree. If no `Path` nor `Glob` is given when `All` option is
|
||||||
|
// used, all files in the entire working tree are updated.
|
||||||
|
All bool
|
||||||
|
// Path is the exact filepath to a the file or directory to be added.
|
||||||
|
Path string
|
||||||
|
// Glob adds all paths, matching pattern, to the index. If pattern matches a
|
||||||
|
// directory path, all directory contents are added to the index recursively.
|
||||||
|
Glob string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the fields and sets the default values.
|
||||||
|
func (o *AddOptions) Validate(r *Repository) error {
|
||||||
|
if o.Path != "" && o.Glob != "" {
|
||||||
|
return fmt.Errorf("fields Path and Glob are mutual exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CommitOptions describes how a commit operation should be performed.
|
// CommitOptions describes how a commit operation should be performed.
|
||||||
type CommitOptions struct {
|
type CommitOptions struct {
|
||||||
// All automatically stage files that have been modified and deleted, but
|
// All automatically stage files that have been modified and deleted, but
|
||||||
|
@ -464,7 +489,8 @@ var (
|
||||||
|
|
||||||
// CreateTagOptions describes how a tag object should be created.
|
// CreateTagOptions describes how a tag object should be created.
|
||||||
type CreateTagOptions struct {
|
type CreateTagOptions struct {
|
||||||
// Tagger defines the signature of the tag creator.
|
// Tagger defines the signature of the tag creator. If Tagger is empty the
|
||||||
|
// Name and Email is read from the config, and time.Now it's used as When.
|
||||||
Tagger *object.Signature
|
Tagger *object.Signature
|
||||||
// Message defines the annotation of the tag. It is canonicalized during
|
// Message defines the annotation of the tag. It is canonicalized during
|
||||||
// validation into the format expected by git - no leading whitespace and
|
// validation into the format expected by git - no leading whitespace and
|
||||||
|
@ -478,7 +504,9 @@ type CreateTagOptions struct {
|
||||||
// Validate validates the fields and sets the default values.
|
// Validate validates the fields and sets the default values.
|
||||||
func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
|
func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
|
||||||
if o.Tagger == nil {
|
if o.Tagger == nil {
|
||||||
return ErrMissingTagger
|
if err := o.loadConfigTagger(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Message == "" {
|
if o.Message == "" {
|
||||||
|
@ -491,6 +519,35 @@ func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CreateTagOptions) loadConfigTagger(r *Repository) error {
|
||||||
|
cfg, err := r.ConfigScoped(config.SystemScope)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Tagger == nil && cfg.Author.Email != "" && cfg.Author.Name != "" {
|
||||||
|
o.Tagger = &object.Signature{
|
||||||
|
Name: cfg.Author.Name,
|
||||||
|
Email: cfg.Author.Email,
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Tagger == nil && cfg.User.Email != "" && cfg.User.Name != "" {
|
||||||
|
o.Tagger = &object.Signature{
|
||||||
|
Name: cfg.User.Name,
|
||||||
|
Email: cfg.User.Email,
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Tagger == nil {
|
||||||
|
return ErrMissingTagger
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListOptions describes how a remote list should be performed.
|
// ListOptions describes how a remote list should be performed.
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
// Auth credentials, if required, to use with the remote repository.
|
// Auth credentials, if required, to use with the remote repository.
|
||||||
|
@ -545,6 +602,9 @@ type PlainOpenOptions struct {
|
||||||
// DetectDotGit defines whether parent directories should be
|
// DetectDotGit defines whether parent directories should be
|
||||||
// walked until a .git directory or file is found.
|
// walked until a .git directory or file is found.
|
||||||
DetectDotGit bool
|
DetectDotGit bool
|
||||||
|
// Enable .git/commondir support (see https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt).
|
||||||
|
// NOTE: This option will only work with the filesystem storage.
|
||||||
|
EnableDotGitCommonDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields and sets the default values.
|
// Validate validates the fields and sets the default values.
|
||||||
|
|
|
@ -118,7 +118,7 @@ func isSetSymLink(m os.FileMode) bool {
|
||||||
func (m FileMode) Bytes() []byte {
|
func (m FileMode) Bytes() []byte {
|
||||||
ret := make([]byte, 4)
|
ret := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(ret, uint32(m))
|
binary.LittleEndian.PutUint32(ret, uint32(m))
|
||||||
return ret[:]
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMalformed returns if the FileMode should not appear in a git packfile,
|
// IsMalformed returns if the FileMode should not appear in a git packfile,
|
||||||
|
|
|
@ -44,6 +44,46 @@ func (c *Config) Section(name string) *Section {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasSection checks if the Config has a section with the specified name.
|
||||||
|
func (c *Config) HasSection(name string) bool {
|
||||||
|
for _, s := range c.Sections {
|
||||||
|
if s.IsName(name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSection removes a section from a config file.
|
||||||
|
func (c *Config) RemoveSection(name string) *Config {
|
||||||
|
result := Sections{}
|
||||||
|
for _, s := range c.Sections {
|
||||||
|
if !s.IsName(name) {
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Sections = result
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSubsection remove a subsection from a config file.
|
||||||
|
func (c *Config) RemoveSubsection(section string, subsection string) *Config {
|
||||||
|
for _, s := range c.Sections {
|
||||||
|
if s.IsName(section) {
|
||||||
|
result := Subsections{}
|
||||||
|
for _, ss := range s.Subsections {
|
||||||
|
if !ss.IsName(subsection) {
|
||||||
|
result = append(result, ss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Subsections = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// AddOption adds an option to a given section and subsection. Use the
|
// AddOption adds an option to a given section and subsection. Use the
|
||||||
// NoSubsection constant for the subsection argument if no subsection is wanted.
|
// NoSubsection constant for the subsection argument if no subsection is wanted.
|
||||||
func (c *Config) AddOption(section string, subsection string, key string, value string) *Config {
|
func (c *Config) AddOption(section string, subsection string, key string, value string) *Config {
|
||||||
|
@ -67,33 +107,3 @@ func (c *Config) SetOption(section string, subsection string, key string, value
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSection removes a section from a config file.
|
|
||||||
func (c *Config) RemoveSection(name string) *Config {
|
|
||||||
result := Sections{}
|
|
||||||
for _, s := range c.Sections {
|
|
||||||
if !s.IsName(name) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Sections = result
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveSubsection remove s a subsection from a config file.
|
|
||||||
func (c *Config) RemoveSubsection(section string, subsection string) *Config {
|
|
||||||
for _, s := range c.Sections {
|
|
||||||
if s.IsName(section) {
|
|
||||||
result := Subsections{}
|
|
||||||
for _, ss := range s.Subsections {
|
|
||||||
if !ss.IsName(subsection) {
|
|
||||||
result = append(result, ss)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Subsections = result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Options []*Option
|
||||||
// IsKey returns true if the given key matches
|
// IsKey returns true if the given key matches
|
||||||
// this option's key in a case-insensitive comparison.
|
// this option's key in a case-insensitive comparison.
|
||||||
func (o *Option) IsKey(key string) bool {
|
func (o *Option) IsKey(key string) bool {
|
||||||
return strings.ToLower(o.Key) == strings.ToLower(key)
|
return strings.EqualFold(o.Key, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts Options) GoString() string {
|
func (opts Options) GoString() string {
|
||||||
|
@ -54,6 +54,16 @@ func (opts Options) Get(key string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has checks if an Option exist with the given key.
|
||||||
|
func (opts Options) Has(key string) bool {
|
||||||
|
for _, o := range opts {
|
||||||
|
if o.IsKey(key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetAll returns all possible values for the same key.
|
// GetAll returns all possible values for the same key.
|
||||||
func (opts Options) GetAll(key string) []string {
|
func (opts Options) GetAll(key string) []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
|
|
|
@ -61,32 +61,7 @@ func (s Subsections) GoString() string {
|
||||||
|
|
||||||
// IsName checks if the name provided is equals to the Section name, case insensitive.
|
// IsName checks if the name provided is equals to the Section name, case insensitive.
|
||||||
func (s *Section) IsName(name string) bool {
|
func (s *Section) IsName(name string) bool {
|
||||||
return strings.ToLower(s.Name) == strings.ToLower(name)
|
return strings.EqualFold(s.Name, name)
|
||||||
}
|
|
||||||
|
|
||||||
// Option return the value for the specified key. Empty string is returned if
|
|
||||||
// key does not exists.
|
|
||||||
func (s *Section) Option(key string) string {
|
|
||||||
return s.Options.Get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOption adds a new Option to the Section. The updated Section is returned.
|
|
||||||
func (s *Section) AddOption(key string, value string) *Section {
|
|
||||||
s.Options = s.Options.withAddedOption(key, value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOption adds a new Option to the Section. If the option already exists, is replaced.
|
|
||||||
// The updated Section is returned.
|
|
||||||
func (s *Section) SetOption(key string, value string) *Section {
|
|
||||||
s.Options = s.Options.withSettedOption(key, value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove an option with the specified key. The updated Section is returned.
|
|
||||||
func (s *Section) RemoveOption(key string) *Section {
|
|
||||||
s.Options = s.Options.withoutOption(key)
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsection returns a Subsection from the specified Section. If the
|
// Subsection returns a Subsection from the specified Section. If the
|
||||||
|
@ -115,6 +90,55 @@ func (s *Section) HasSubsection(name string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveSubsection removes a subsection from a Section.
|
||||||
|
func (s *Section) RemoveSubsection(name string) *Section {
|
||||||
|
result := Subsections{}
|
||||||
|
for _, s := range s.Subsections {
|
||||||
|
if !s.IsName(name) {
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Subsections = result
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option return the value for the specified key. Empty string is returned if
|
||||||
|
// key does not exists.
|
||||||
|
func (s *Section) Option(key string) string {
|
||||||
|
return s.Options.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionAll returns all possible values for an option with the specified key.
|
||||||
|
// If the option does not exists, an empty slice will be returned.
|
||||||
|
func (s *Section) OptionAll(key string) []string {
|
||||||
|
return s.Options.GetAll(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasOption checks if the Section has an Option with the given key.
|
||||||
|
func (s *Section) HasOption(key string) bool {
|
||||||
|
return s.Options.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOption adds a new Option to the Section. The updated Section is returned.
|
||||||
|
func (s *Section) AddOption(key string, value string) *Section {
|
||||||
|
s.Options = s.Options.withAddedOption(key, value)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption adds a new Option to the Section. If the option already exists, is replaced.
|
||||||
|
// The updated Section is returned.
|
||||||
|
func (s *Section) SetOption(key string, value string) *Section {
|
||||||
|
s.Options = s.Options.withSettedOption(key, value)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an option with the specified key. The updated Section is returned.
|
||||||
|
func (s *Section) RemoveOption(key string) *Section {
|
||||||
|
s.Options = s.Options.withoutOption(key)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// IsName checks if the name of the subsection is exactly the specified name.
|
// IsName checks if the name of the subsection is exactly the specified name.
|
||||||
func (s *Subsection) IsName(name string) bool {
|
func (s *Subsection) IsName(name string) bool {
|
||||||
return s.Name == name
|
return s.Name == name
|
||||||
|
@ -126,6 +150,17 @@ func (s *Subsection) Option(key string) string {
|
||||||
return s.Options.Get(key)
|
return s.Options.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptionAll returns all possible values for an option with the specified key.
|
||||||
|
// If the option does not exists, an empty slice will be returned.
|
||||||
|
func (s *Subsection) OptionAll(key string) []string {
|
||||||
|
return s.Options.GetAll(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasOption checks if the Subsection has an Option with the given key.
|
||||||
|
func (s *Subsection) HasOption(key string) bool {
|
||||||
|
return s.Options.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
// AddOption adds a new Option to the Subsection. The updated Subsection is returned.
|
// AddOption adds a new Option to the Subsection. The updated Subsection is returned.
|
||||||
func (s *Subsection) AddOption(key string, value string) *Subsection {
|
func (s *Subsection) AddOption(key string, value string) *Subsection {
|
||||||
s.Options = s.Options.withAddedOption(key, value)
|
s.Options = s.Options.withAddedOption(key, value)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package gitignore
|
package gitignore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -15,7 +16,6 @@ import (
|
||||||
const (
|
const (
|
||||||
commentPrefix = "#"
|
commentPrefix = "#"
|
||||||
coreSection = "core"
|
coreSection = "core"
|
||||||
eol = "\n"
|
|
||||||
excludesfile = "excludesfile"
|
excludesfile = "excludesfile"
|
||||||
gitDir = ".git"
|
gitDir = ".git"
|
||||||
gitignoreFile = ".gitignore"
|
gitignoreFile = ".gitignore"
|
||||||
|
@ -29,11 +29,11 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
if data, err := ioutil.ReadAll(f); err == nil {
|
scanner := bufio.NewScanner(f)
|
||||||
for _, s := range strings.Split(string(data), eol) {
|
for scanner.Scan() {
|
||||||
if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
|
s := scanner.Text()
|
||||||
ps = append(ps, ParsePattern(s, path))
|
if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
|
||||||
}
|
ps = append(ps, ParsePattern(s, path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
|
|
|
@ -188,7 +188,7 @@ func (d *Decoder) doReadEntryNameV4() (string, error) {
|
||||||
|
|
||||||
func (d *Decoder) doReadEntryName(len uint16) (string, error) {
|
func (d *Decoder) doReadEntryName(len uint16) (string, error) {
|
||||||
name := make([]byte, len)
|
name := make([]byte, len)
|
||||||
_, err := io.ReadFull(d.r, name[:])
|
_, err := io.ReadFull(d.r, name)
|
||||||
|
|
||||||
return string(name), err
|
return string(name), err
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,9 @@ func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) {
|
||||||
|
|
||||||
e.Trees = i
|
e.Trees = i
|
||||||
_, err = io.ReadFull(d.r, e.Hash[:])
|
_, err = io.ReadFull(d.r, e.Hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
target.SetSize(int64(dst.Len()))
|
target.SetSize(int64(dst.Len()))
|
||||||
|
|
||||||
b := byteSlicePool.Get().([]byte)
|
b := byteSlicePool.Get().([]byte)
|
||||||
|
@ -113,7 +112,7 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
|
||||||
invalidOffsetSize(offset, sz, srcSz) {
|
invalidOffsetSize(offset, sz, srcSz) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
dst.Write(src[offset:offset+sz])
|
dst.Write(src[offset : offset+sz])
|
||||||
remainingTargetSz -= sz
|
remainingTargetSz -= sz
|
||||||
} else if isCopyFromDelta(cmd) {
|
} else if isCopyFromDelta(cmd) {
|
||||||
sz := uint(cmd) // cmd is the size itself
|
sz := uint(cmd) // cmd is the size itself
|
||||||
|
|
|
@ -3,7 +3,6 @@ package plumbing
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MemoryObject on memory Object implementation
|
// MemoryObject on memory Object implementation
|
||||||
|
@ -39,9 +38,11 @@ func (o *MemoryObject) Size() int64 { return o.sz }
|
||||||
// afterwards
|
// afterwards
|
||||||
func (o *MemoryObject) SetSize(s int64) { o.sz = s }
|
func (o *MemoryObject) SetSize(s int64) { o.sz = s }
|
||||||
|
|
||||||
// Reader returns a ObjectReader used to read the object's content.
|
// Reader returns an io.ReadCloser used to read the object's content.
|
||||||
|
//
|
||||||
|
// For a MemoryObject, this reader is seekable.
|
||||||
func (o *MemoryObject) Reader() (io.ReadCloser, error) {
|
func (o *MemoryObject) Reader() (io.ReadCloser, error) {
|
||||||
return ioutil.NopCloser(bytes.NewBuffer(o.cont)), nil
|
return nopCloser{bytes.NewReader(o.cont)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer returns a ObjectWriter used to write the object's content.
|
// Writer returns a ObjectWriter used to write the object's content.
|
||||||
|
@ -59,3 +60,13 @@ func (o *MemoryObject) Write(p []byte) (n int, err error) {
|
||||||
// Close releases any resources consumed by the object when it is acting as a
|
// Close releases any resources consumed by the object when it is acting as a
|
||||||
// ObjectWriter.
|
// ObjectWriter.
|
||||||
func (o *MemoryObject) Close() error { return nil }
|
func (o *MemoryObject) Close() error { return nil }
|
||||||
|
|
||||||
|
// nopCloser exposes the extra methods of bytes.Reader while nopping Close().
|
||||||
|
//
|
||||||
|
// This allows clients to attempt seeking in a cached Blob's Reader.
|
||||||
|
type nopCloser struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close does nothing.
|
||||||
|
func (nc nopCloser) Close() error { return nil }
|
||||||
|
|
|
@ -75,7 +75,7 @@ func (c *Change) Files() (from, to *File, err error) {
|
||||||
func (c *Change) String() string {
|
func (c *Change) String() string {
|
||||||
action, err := c.Action()
|
action, err := c.Action()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("malformed change")
|
return "malformed change"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name())
|
return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name())
|
||||||
|
|
|
@ -243,16 +243,16 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode transforms a Commit into a plumbing.EncodedObject.
|
// Encode transforms a Commit into a plumbing.EncodedObject.
|
||||||
func (b *Commit) Encode(o plumbing.EncodedObject) error {
|
func (c *Commit) Encode(o plumbing.EncodedObject) error {
|
||||||
return b.encode(o, true)
|
return c.encode(o, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
|
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
|
||||||
func (b *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
||||||
return b.encode(o, false)
|
return c.encode(o, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
o.SetType(plumbing.CommitObject)
|
o.SetType(plumbing.CommitObject)
|
||||||
w, err := o.Writer()
|
w, err := o.Writer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -261,11 +261,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
|
|
||||||
defer ioutil.CheckClose(w, &err)
|
defer ioutil.CheckClose(w, &err)
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(w, "tree %s\n", b.TreeHash.String()); err != nil {
|
if _, err = fmt.Fprintf(w, "tree %s\n", c.TreeHash.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, parent := range b.ParentHashes {
|
for _, parent := range c.ParentHashes {
|
||||||
if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
|
if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = b.Author.Encode(w); err != nil {
|
if err = c.Author.Encode(w); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,11 +283,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = b.Committer.Encode(w); err != nil {
|
if err = c.Committer.Encode(w); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.PGPSignature != "" && includeSig {
|
if c.PGPSignature != "" && includeSig {
|
||||||
if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
|
if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -296,14 +296,14 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
// newline. Use join for this so it's clear that a newline should not be
|
// newline. Use join for this so it's clear that a newline should not be
|
||||||
// added after this section, as it will be added when the message is
|
// added after this section, as it will be added when the message is
|
||||||
// printed.
|
// printed.
|
||||||
signature := strings.TrimSuffix(b.PGPSignature, "\n")
|
signature := strings.TrimSuffix(c.PGPSignature, "\n")
|
||||||
lines := strings.Split(signature, "\n")
|
lines := strings.Split(signature, "\n")
|
||||||
if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
|
if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil {
|
if _, err = fmt.Fprintf(w, "\n\n%s", c.Message); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
vendor/github.com/go-git/go-git/v5/plumbing/object/commit_walker_bfs_filtered.go
generated
vendored
1
vendor/github.com/go-git/go-git/v5/plumbing/object/commit_walker_bfs_filtered.go
generated
vendored
|
@ -173,4 +173,3 @@ func (w *filterCommitIter) addToQueue(
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/storer"
|
"github.com/go-git/go-git/v5/plumbing/storer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ func NewCommitPathIterFromIter(pathFilter func(string) bool, commitIter CommitIt
|
||||||
return iterator
|
return iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function is kept for compatibilty, can be replaced with NewCommitPathIterFromIter
|
// NewCommitFileIterFromIter is kept for compatibility, can be replaced with NewCommitPathIterFromIter
|
||||||
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter {
|
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter {
|
||||||
return NewCommitPathIterFromIter(
|
return NewCommitPathIterFromIter(
|
||||||
func(path string) bool {
|
func(path string) bool {
|
||||||
|
|
|
@ -121,12 +121,12 @@ type Patch struct {
|
||||||
filePatches []fdiff.FilePatch
|
filePatches []fdiff.FilePatch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Patch) FilePatches() []fdiff.FilePatch {
|
func (p *Patch) FilePatches() []fdiff.FilePatch {
|
||||||
return t.filePatches
|
return p.filePatches
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Patch) Message() string {
|
func (p *Patch) Message() string {
|
||||||
return t.message
|
return p.message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Patch) Encode(w io.Writer) error {
|
func (p *Patch) Encode(w io.Writer) error {
|
||||||
|
@ -198,12 +198,12 @@ func (tf *textFilePatch) Files() (from fdiff.File, to fdiff.File) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *textFilePatch) IsBinary() bool {
|
func (tf *textFilePatch) IsBinary() bool {
|
||||||
return len(t.chunks) == 0
|
return len(tf.chunks) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *textFilePatch) Chunks() []fdiff.Chunk {
|
func (tf *textFilePatch) Chunks() []fdiff.Chunk {
|
||||||
return t.chunks
|
return tf.chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
// textChunk is an implementation of fdiff.Chunk interface
|
// textChunk is an implementation of fdiff.Chunk interface
|
||||||
|
|
|
@ -536,7 +536,7 @@ var errIndexFull = errors.New("index is full")
|
||||||
// between two files.
|
// between two files.
|
||||||
// To save space in memory, this index uses a space efficient encoding which
|
// To save space in memory, this index uses a space efficient encoding which
|
||||||
// will not exceed 1MiB per instance. The index starts out at a smaller size
|
// will not exceed 1MiB per instance. The index starts out at a smaller size
|
||||||
// (closer to 2KiB), but may grow as more distinct blocks withing the scanned
|
// (closer to 2KiB), but may grow as more distinct blocks within the scanned
|
||||||
// file are discovered.
|
// file are discovered.
|
||||||
// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java
|
// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java
|
||||||
type similarityIndex struct {
|
type similarityIndex struct {
|
||||||
|
@ -709,7 +709,7 @@ func (i *similarityIndex) common(dst *similarityIndex) uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *similarityIndex) add(key int, cnt uint64) error {
|
func (i *similarityIndex) add(key int, cnt uint64) error {
|
||||||
key = int(uint32(key)*0x9e370001 >> 1)
|
key = int(uint32(key) * 0x9e370001 >> 1)
|
||||||
|
|
||||||
j := i.slot(key)
|
j := i.slot(key)
|
||||||
for {
|
for {
|
||||||
|
@ -769,7 +769,7 @@ func (i *similarityIndex) slot(key int) int {
|
||||||
// We use 31 - hashBits because the upper bit was already forced
|
// We use 31 - hashBits because the upper bit was already forced
|
||||||
// to be 0 and we want the remaining high bits to be used as the
|
// to be 0 and we want the remaining high bits to be used as the
|
||||||
// table slot.
|
// table slot.
|
||||||
return int(uint32(key) >> uint(31 - i.hashBits))
|
return int(uint32(key) >> uint(31-i.hashBits))
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldGrowAt(hashBits int) int {
|
func shouldGrowAt(hashBits int) int {
|
||||||
|
|
|
@ -86,10 +86,7 @@ func (l *List) Get(capability Capability) []string {
|
||||||
|
|
||||||
// Set sets a capability removing the previous values
|
// Set sets a capability removing the previous values
|
||||||
func (l *List) Set(capability Capability, values ...string) error {
|
func (l *List) Set(capability Capability, values ...string) error {
|
||||||
if _, ok := l.m[capability]; ok {
|
delete(l.m, capability)
|
||||||
delete(l.m, capability)
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.Add(capability, values...)
|
return l.Add(capability, values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,42 +109,42 @@ func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest {
|
||||||
// - is a DepthReference is given capability.DeepenNot MUST be present
|
// - is a DepthReference is given capability.DeepenNot MUST be present
|
||||||
// - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k
|
// - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k
|
||||||
// - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed
|
// - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed
|
||||||
func (r *UploadRequest) Validate() error {
|
func (req *UploadRequest) Validate() error {
|
||||||
if len(r.Wants) == 0 {
|
if len(req.Wants) == 0 {
|
||||||
return fmt.Errorf("want can't be empty")
|
return fmt.Errorf("want can't be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.validateRequiredCapabilities(); err != nil {
|
if err := req.validateRequiredCapabilities(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.validateConflictCapabilities(); err != nil {
|
if err := req.validateConflictCapabilities(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UploadRequest) validateRequiredCapabilities() error {
|
func (req *UploadRequest) validateRequiredCapabilities() error {
|
||||||
msg := "missing capability %s"
|
msg := "missing capability %s"
|
||||||
|
|
||||||
if len(r.Shallows) != 0 && !r.Capabilities.Supports(capability.Shallow) {
|
if len(req.Shallows) != 0 && !req.Capabilities.Supports(capability.Shallow) {
|
||||||
return fmt.Errorf(msg, capability.Shallow)
|
return fmt.Errorf(msg, capability.Shallow)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Depth.(type) {
|
switch req.Depth.(type) {
|
||||||
case DepthCommits:
|
case DepthCommits:
|
||||||
if r.Depth != DepthCommits(0) {
|
if req.Depth != DepthCommits(0) {
|
||||||
if !r.Capabilities.Supports(capability.Shallow) {
|
if !req.Capabilities.Supports(capability.Shallow) {
|
||||||
return fmt.Errorf(msg, capability.Shallow)
|
return fmt.Errorf(msg, capability.Shallow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case DepthSince:
|
case DepthSince:
|
||||||
if !r.Capabilities.Supports(capability.DeepenSince) {
|
if !req.Capabilities.Supports(capability.DeepenSince) {
|
||||||
return fmt.Errorf(msg, capability.DeepenSince)
|
return fmt.Errorf(msg, capability.DeepenSince)
|
||||||
}
|
}
|
||||||
case DepthReference:
|
case DepthReference:
|
||||||
if !r.Capabilities.Supports(capability.DeepenNot) {
|
if !req.Capabilities.Supports(capability.DeepenNot) {
|
||||||
return fmt.Errorf(msg, capability.DeepenNot)
|
return fmt.Errorf(msg, capability.DeepenNot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,15 +152,15 @@ func (r *UploadRequest) validateRequiredCapabilities() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UploadRequest) validateConflictCapabilities() error {
|
func (req *UploadRequest) validateConflictCapabilities() error {
|
||||||
msg := "capabilities %s and %s are mutually exclusive"
|
msg := "capabilities %s and %s are mutually exclusive"
|
||||||
if r.Capabilities.Supports(capability.Sideband) &&
|
if req.Capabilities.Supports(capability.Sideband) &&
|
||||||
r.Capabilities.Supports(capability.Sideband64k) {
|
req.Capabilities.Supports(capability.Sideband64k) {
|
||||||
return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k)
|
return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Capabilities.Supports(capability.MultiACK) &&
|
if req.Capabilities.Supports(capability.MultiACK) &&
|
||||||
r.Capabilities.Supports(capability.MultiACKDetailed) {
|
req.Capabilities.Supports(capability.MultiACKDetailed) {
|
||||||
return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed)
|
return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ import (
|
||||||
|
|
||||||
// Decode reads the next upload-request form its input and
|
// Decode reads the next upload-request form its input and
|
||||||
// stores it in the UploadRequest.
|
// stores it in the UploadRequest.
|
||||||
func (u *UploadRequest) Decode(r io.Reader) error {
|
func (req *UploadRequest) Decode(r io.Reader) error {
|
||||||
d := newUlReqDecoder(r)
|
d := newUlReqDecoder(r)
|
||||||
return d.Decode(u)
|
return d.Decode(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ulReqDecoder struct {
|
type ulReqDecoder struct {
|
||||||
|
|
|
@ -15,9 +15,9 @@ import (
|
||||||
// All the payloads will end with a newline character. Wants and
|
// All the payloads will end with a newline character. Wants and
|
||||||
// shallows are sorted alphabetically. A depth of 0 means no depth
|
// shallows are sorted alphabetically. A depth of 0 means no depth
|
||||||
// request is sent.
|
// request is sent.
|
||||||
func (u *UploadRequest) Encode(w io.Writer) error {
|
func (req *UploadRequest) Encode(w io.Writer) error {
|
||||||
e := newUlReqEncoder(w)
|
e := newUlReqEncoder(w)
|
||||||
return e.Encode(u)
|
return e.Encode(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ulReqEncoder struct {
|
type ulReqEncoder struct {
|
||||||
|
|
|
@ -68,12 +68,12 @@ func NewReferenceUpdateRequestFromCapabilities(adv *capability.List) *ReferenceU
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReferenceUpdateRequest) validate() error {
|
func (req *ReferenceUpdateRequest) validate() error {
|
||||||
if len(r.Commands) == 0 {
|
if len(req.Commands) == 0 {
|
||||||
return ErrEmptyCommands
|
return ErrEmptyCommands
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range r.Commands {
|
for _, c := range req.Commands {
|
||||||
if err := c.validate(); err != nil {
|
if err := c.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,33 +14,33 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encode writes the ReferenceUpdateRequest encoding to the stream.
|
// Encode writes the ReferenceUpdateRequest encoding to the stream.
|
||||||
func (r *ReferenceUpdateRequest) Encode(w io.Writer) error {
|
func (req *ReferenceUpdateRequest) Encode(w io.Writer) error {
|
||||||
if err := r.validate(); err != nil {
|
if err := req.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e := pktline.NewEncoder(w)
|
e := pktline.NewEncoder(w)
|
||||||
|
|
||||||
if err := r.encodeShallow(e, r.Shallow); err != nil {
|
if err := req.encodeShallow(e, req.Shallow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.encodeCommands(e, r.Commands, r.Capabilities); err != nil {
|
if err := req.encodeCommands(e, req.Commands, req.Capabilities); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Packfile != nil {
|
if req.Packfile != nil {
|
||||||
if _, err := io.Copy(w, r.Packfile); err != nil {
|
if _, err := io.Copy(w, req.Packfile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Packfile.Close()
|
return req.Packfile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
|
func (req *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
|
||||||
h *plumbing.Hash) error {
|
h *plumbing.Hash) error {
|
||||||
|
|
||||||
if h == nil {
|
if h == nil {
|
||||||
|
@ -51,7 +51,7 @@ func (r *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder,
|
||||||
return e.Encodef("%s%s", shallow, objId)
|
return e.Encodef("%s%s", shallow, objId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder,
|
func (req *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder,
|
||||||
cmds []*Command, cap *capability.List) error {
|
cmds []*Command, cap *capability.List) error {
|
||||||
|
|
||||||
if err := e.Encodef("%s\x00%s",
|
if err := e.Encodef("%s\x00%s",
|
||||||
|
|
|
@ -32,6 +32,19 @@ var (
|
||||||
ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
|
ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NoMatchingRefSpecError struct {
|
||||||
|
refSpec config.RefSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NoMatchingRefSpecError) Error() string {
|
||||||
|
return fmt.Sprintf("couldn't find remote ref %q", e.refSpec.Src())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NoMatchingRefSpecError) Is(target error) bool {
|
||||||
|
_, ok := target.(NoMatchingRefSpecError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// This describes the maximum number of commits to walk when
|
// This describes the maximum number of commits to walk when
|
||||||
// computing the haves to send to a server, for each ref in the
|
// computing the haves to send to a server, for each ref in the
|
||||||
|
@ -126,7 +139,7 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
|
||||||
if o.Force {
|
if o.Force {
|
||||||
for i := 0; i < len(o.RefSpecs); i++ {
|
for i := 0; i < len(o.RefSpecs); i++ {
|
||||||
rs := &o.RefSpecs[i]
|
rs := &o.RefSpecs[i]
|
||||||
if !rs.IsForceUpdate() {
|
if !rs.IsForceUpdate() && !rs.IsDelete() {
|
||||||
o.RefSpecs[i] = config.RefSpec("+" + rs.String())
|
o.RefSpecs[i] = config.RefSpec("+" + rs.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,9 +231,9 @@ func (r *Remote) newReferenceUpdateRequest(
|
||||||
if o.Progress != nil {
|
if o.Progress != nil {
|
||||||
req.Progress = o.Progress
|
req.Progress = o.Progress
|
||||||
if ar.Capabilities.Supports(capability.Sideband64k) {
|
if ar.Capabilities.Supports(capability.Sideband64k) {
|
||||||
req.Capabilities.Set(capability.Sideband64k)
|
_ = req.Capabilities.Set(capability.Sideband64k)
|
||||||
} else if ar.Capabilities.Supports(capability.Sideband) {
|
} else if ar.Capabilities.Supports(capability.Sideband) {
|
||||||
req.Capabilities.Set(capability.Sideband)
|
_ = req.Capabilities.Set(capability.Sideband)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,10 +511,8 @@ func (r *Remote) deleteReferences(rs config.RefSpec,
|
||||||
if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok {
|
if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else if rs.Dst("") != ref.Name() {
|
||||||
if rs.Dst("") != ref.Name() {
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &packp.Command{
|
cmd := &packp.Command{
|
||||||
|
@ -753,7 +764,7 @@ func doCalculateRefs(
|
||||||
})
|
})
|
||||||
|
|
||||||
if !matched && !s.IsWildcard() {
|
if !matched && !s.IsWildcard() {
|
||||||
return fmt.Errorf("couldn't find remote ref %q", s.Src())
|
return NoMatchingRefSpecError{refSpec: s}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -1037,21 +1048,22 @@ func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultRefs []*plumbing.Reference
|
var resultRefs []*plumbing.Reference
|
||||||
refs.ForEach(func(ref *plumbing.Reference) error {
|
err = refs.ForEach(func(ref *plumbing.Reference) error {
|
||||||
resultRefs = append(resultRefs, ref)
|
resultRefs = append(resultRefs, ref)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return resultRefs, nil
|
return resultRefs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func objectsToPush(commands []*packp.Command) []plumbing.Hash {
|
func objectsToPush(commands []*packp.Command) []plumbing.Hash {
|
||||||
var objects []plumbing.Hash
|
objects := make([]plumbing.Hash, 0, len(commands))
|
||||||
for _, cmd := range commands {
|
for _, cmd := range commands {
|
||||||
if cmd.New == plumbing.ZeroHash {
|
if cmd.New == plumbing.ZeroHash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
objects = append(objects, cmd.New)
|
objects = append(objects, cmd.New)
|
||||||
}
|
}
|
||||||
return objects
|
return objects
|
||||||
|
|
|
@ -3,6 +3,7 @@ package git
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -13,6 +14,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/storage/filesystem/dotgit"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/internal/revision"
|
"github.com/go-git/go-git/v5/internal/revision"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
@ -47,6 +50,7 @@ var (
|
||||||
|
|
||||||
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
|
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
|
||||||
ErrRepositoryNotExists = errors.New("repository does not exist")
|
ErrRepositoryNotExists = errors.New("repository does not exist")
|
||||||
|
ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist")
|
||||||
ErrRepositoryAlreadyExists = errors.New("repository already exists")
|
ErrRepositoryAlreadyExists = errors.New("repository already exists")
|
||||||
ErrRemoteNotFound = errors.New("remote not found")
|
ErrRemoteNotFound = errors.New("remote not found")
|
||||||
ErrRemoteExists = errors.New("remote already exists")
|
ErrRemoteExists = errors.New("remote already exists")
|
||||||
|
@ -89,7 +93,7 @@ func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if worktree == nil {
|
if worktree == nil {
|
||||||
r.setIsBare(true)
|
_ = r.setIsBare(true)
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +257,19 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
var repositoryFs billy.Filesystem
|
||||||
|
|
||||||
|
if o.EnableDotGitCommonDir {
|
||||||
|
dotGitCommon, err := dotGitCommonDirectory(dot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repositoryFs = dotgit.NewRepositoryFilesystem(dot, dotGitCommon)
|
||||||
|
} else {
|
||||||
|
repositoryFs = dot
|
||||||
|
}
|
||||||
|
|
||||||
|
s := filesystem.NewStorage(repositoryFs, cache.NewObjectLRUDefault())
|
||||||
|
|
||||||
return Open(s, wt)
|
return Open(s, wt)
|
||||||
}
|
}
|
||||||
|
@ -262,6 +278,14 @@ func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem,
|
||||||
if path, err = filepath.Abs(path); err != nil {
|
if path, err = filepath.Abs(path); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pathinfo, err := os.Stat(path)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
if !pathinfo.IsDir() && detect {
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var fs billy.Filesystem
|
var fs billy.Filesystem
|
||||||
var fi os.FileInfo
|
var fi os.FileInfo
|
||||||
for {
|
for {
|
||||||
|
@ -328,6 +352,38 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files
|
||||||
return osfs.New(fs.Join(path, gitdir)), nil
|
return osfs.New(fs.Join(path, gitdir)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) {
|
||||||
|
f, err := fs.Open("commondir")
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := stdioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b) > 0 {
|
||||||
|
path := strings.TrimSpace(string(b))
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
commonDir = osfs.New(path)
|
||||||
|
} else {
|
||||||
|
commonDir = osfs.New(filepath.Join(fs.Root(), path))
|
||||||
|
}
|
||||||
|
if _, err := commonDir.Stat(""); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrRepositoryIncomplete
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commonDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PlainClone a repository into the path with the given options, isBare defines
|
// PlainClone a repository into the path with the given options, isBare defines
|
||||||
// if the new repository will be bare or normal. If the path is not empty
|
// if the new repository will be bare or normal. If the path is not empty
|
||||||
// ErrRepositoryAlreadyExists is returned.
|
// ErrRepositoryAlreadyExists is returned.
|
||||||
|
@ -361,7 +417,7 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp
|
||||||
err = r.clone(ctx, o)
|
err = r.clone(ctx, o)
|
||||||
if err != nil && err != ErrRepositoryAlreadyExists {
|
if err != nil && err != ErrRepositoryAlreadyExists {
|
||||||
if cleanup {
|
if cleanup {
|
||||||
cleanUpDir(path, cleanupParent)
|
_ = cleanUpDir(path, cleanupParent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1379,7 +1435,7 @@ func (r *Repository) Worktree() (*Worktree, error) {
|
||||||
// resolve to a commit hash, not a tree or annotated tag.
|
// resolve to a commit hash, not a tree or annotated tag.
|
||||||
//
|
//
|
||||||
// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch,
|
// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch,
|
||||||
// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug})
|
// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}), hash (prefix and full)
|
||||||
func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) {
|
func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) {
|
||||||
p := revision.NewParserFromString(string(rev))
|
p := revision.NewParserFromString(string(rev))
|
||||||
|
|
||||||
|
@ -1392,17 +1448,13 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
|
||||||
var commit *object.Commit
|
var commit *object.Commit
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
switch item.(type) {
|
switch item := item.(type) {
|
||||||
case revision.Ref:
|
case revision.Ref:
|
||||||
revisionRef := item.(revision.Ref)
|
revisionRef := item
|
||||||
|
|
||||||
var tryHashes []plumbing.Hash
|
var tryHashes []plumbing.Hash
|
||||||
|
|
||||||
maybeHash := plumbing.NewHash(string(revisionRef))
|
tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...)
|
||||||
|
|
||||||
if !maybeHash.IsZero() {
|
|
||||||
tryHashes = append(tryHashes, maybeHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
|
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
|
||||||
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
|
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))
|
||||||
|
@ -1447,7 +1499,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
|
||||||
}
|
}
|
||||||
|
|
||||||
case revision.CaretPath:
|
case revision.CaretPath:
|
||||||
depth := item.(revision.CaretPath).Depth
|
depth := item.Depth
|
||||||
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
break
|
break
|
||||||
|
@ -1475,7 +1527,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
|
||||||
|
|
||||||
commit = c
|
commit = c
|
||||||
case revision.TildePath:
|
case revision.TildePath:
|
||||||
for i := 0; i < item.(revision.TildePath).Depth; i++ {
|
for i := 0; i < item.Depth; i++ {
|
||||||
c, err := commit.Parents().Next()
|
c, err := commit.Parents().Next()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1487,8 +1539,8 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
|
||||||
case revision.CaretReg:
|
case revision.CaretReg:
|
||||||
history := object.NewCommitPreorderIter(commit, nil, nil)
|
history := object.NewCommitPreorderIter(commit, nil, nil)
|
||||||
|
|
||||||
re := item.(revision.CaretReg).Regexp
|
re := item.Regexp
|
||||||
negate := item.(revision.CaretReg).Negate
|
negate := item.Negate
|
||||||
|
|
||||||
var c *object.Commit
|
var c *object.Commit
|
||||||
|
|
||||||
|
@ -1520,6 +1572,49 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
|
||||||
return &commit.Hash, nil
|
return &commit.Hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveHashPrefix returns a list of potential hashes that the given string
|
||||||
|
// is a prefix of. It quietly swallows errors, returning nil.
|
||||||
|
func (r *Repository) resolveHashPrefix(hashStr string) []plumbing.Hash {
|
||||||
|
// Handle complete and partial hashes.
|
||||||
|
// plumbing.NewHash forces args into a full 20 byte hash, which isn't suitable
|
||||||
|
// for partial hashes since they will become zero-filled.
|
||||||
|
|
||||||
|
if hashStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(hashStr) == len(plumbing.ZeroHash)*2 {
|
||||||
|
// Only a full hash is possible.
|
||||||
|
hexb, err := hex.DecodeString(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var h plumbing.Hash
|
||||||
|
copy(h[:], hexb)
|
||||||
|
return []plumbing.Hash{h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial hash.
|
||||||
|
// hex.DecodeString only decodes to complete bytes, so only works with pairs of hex digits.
|
||||||
|
evenHex := hashStr[:len(hashStr)&^1]
|
||||||
|
hexb, err := hex.DecodeString(evenHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
candidates := expandPartialHash(r.Storer, hexb)
|
||||||
|
if len(evenHex) == len(hashStr) {
|
||||||
|
// The prefix was an exact number of bytes.
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
// Do another prefix check to ensure the dangling nybble is correct.
|
||||||
|
var hashes []plumbing.Hash
|
||||||
|
for _, h := range candidates {
|
||||||
|
if strings.HasPrefix(h.String(), hashStr) {
|
||||||
|
hashes = append(hashes, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashes
|
||||||
|
}
|
||||||
|
|
||||||
type RepackConfig struct {
|
type RepackConfig struct {
|
||||||
// UseRefDeltas configures whether packfile encoder will use reference deltas.
|
// UseRefDeltas configures whether packfile encoder will use reference deltas.
|
||||||
// By default OFSDeltaObject is used.
|
// By default OFSDeltaObject is used.
|
||||||
|
@ -1612,3 +1707,31 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
|
||||||
|
|
||||||
return h, err
|
return h, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expandPartialHash(st storer.EncodedObjectStorer, prefix []byte) (hashes []plumbing.Hash) {
|
||||||
|
// The fast version is implemented by storage/filesystem.ObjectStorage.
|
||||||
|
type fastIter interface {
|
||||||
|
HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error)
|
||||||
|
}
|
||||||
|
if fi, ok := st.(fastIter); ok {
|
||||||
|
h, err := fi.HashesWithPrefix(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path.
|
||||||
|
iter, err := st.IterEncodedObjects(plumbing.AnyObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
iter.ForEach(func(obj plumbing.EncodedObject) error {
|
||||||
|
h := obj.Hash()
|
||||||
|
if bytes.HasPrefix(h[:], prefix) {
|
||||||
|
hashes = append(hashes, h)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ package dotgit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
stdioutil "io/ioutil"
|
stdioutil "io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -30,6 +32,12 @@ const (
|
||||||
objectsPath = "objects"
|
objectsPath = "objects"
|
||||||
packPath = "pack"
|
packPath = "pack"
|
||||||
refsPath = "refs"
|
refsPath = "refs"
|
||||||
|
branchesPath = "branches"
|
||||||
|
hooksPath = "hooks"
|
||||||
|
infoPath = "info"
|
||||||
|
remotesPath = "remotes"
|
||||||
|
logsPath = "logs"
|
||||||
|
worktreesPath = "worktrees"
|
||||||
|
|
||||||
tmpPackedRefsPrefix = "._packed-refs"
|
tmpPackedRefsPrefix = "._packed-refs"
|
||||||
|
|
||||||
|
@ -82,7 +90,7 @@ type DotGit struct {
|
||||||
incomingChecked bool
|
incomingChecked bool
|
||||||
incomingDirName string
|
incomingDirName string
|
||||||
|
|
||||||
objectList []plumbing.Hash
|
objectList []plumbing.Hash // sorted
|
||||||
objectMap map[plumbing.Hash]struct{}
|
objectMap map[plumbing.Hash]struct{}
|
||||||
packList []plumbing.Hash
|
packList []plumbing.Hash
|
||||||
packMap map[plumbing.Hash]struct{}
|
packMap map[plumbing.Hash]struct{}
|
||||||
|
@ -330,6 +338,53 @@ func (d *DotGit) NewObject() (*ObjectWriter, error) {
|
||||||
return newObjectWriter(d.fs)
|
return newObjectWriter(d.fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectsWithPrefix returns the hashes of objects that have the given prefix.
|
||||||
|
func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
|
||||||
|
// Handle edge cases.
|
||||||
|
if len(prefix) < 1 {
|
||||||
|
return d.Objects()
|
||||||
|
} else if len(prefix) > len(plumbing.ZeroHash) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.options.ExclusiveAccess {
|
||||||
|
err := d.genObjectList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rely on d.objectList being sorted.
|
||||||
|
// Figure out the half-open interval defined by the prefix.
|
||||||
|
first := sort.Search(len(d.objectList), func(i int) bool {
|
||||||
|
// Same as plumbing.HashSlice.Less.
|
||||||
|
return bytes.Compare(d.objectList[i][:], prefix) >= 0
|
||||||
|
})
|
||||||
|
lim := len(d.objectList)
|
||||||
|
if limPrefix, overflow := incBytes(prefix); !overflow {
|
||||||
|
lim = sort.Search(len(d.objectList), func(i int) bool {
|
||||||
|
// Same as plumbing.HashSlice.Less.
|
||||||
|
return bytes.Compare(d.objectList[i][:], limPrefix) >= 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return d.objectList[first:lim], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the slow path.
|
||||||
|
var objects []plumbing.Hash
|
||||||
|
var n int
|
||||||
|
err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
|
||||||
|
n++
|
||||||
|
if bytes.HasPrefix(hash[:], prefix) {
|
||||||
|
objects = append(objects, hash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Objects returns a slice with the hashes of objects found under the
|
// Objects returns a slice with the hashes of objects found under the
|
||||||
// .git/objects/ directory.
|
// .git/objects/ directory.
|
||||||
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
|
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
|
||||||
|
@ -421,12 +476,17 @@ func (d *DotGit) genObjectList() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.objectMap = make(map[plumbing.Hash]struct{})
|
d.objectMap = make(map[plumbing.Hash]struct{})
|
||||||
return d.forEachObjectHash(func(h plumbing.Hash) error {
|
populate := func(h plumbing.Hash) error {
|
||||||
d.objectList = append(d.objectList, h)
|
d.objectList = append(d.objectList, h)
|
||||||
d.objectMap[h] = struct{}{}
|
d.objectMap[h] = struct{}{}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
|
if err := d.forEachObjectHash(populate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plumbing.HashesSort(d.objectList)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DotGit) hasObject(h plumbing.Hash) error {
|
func (d *DotGit) hasObject(h plumbing.Hash) error {
|
||||||
|
@ -1109,3 +1169,20 @@ func isNum(b byte) bool {
|
||||||
func isHexAlpha(b byte) bool {
|
func isHexAlpha(b byte) bool {
|
||||||
return b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F'
|
return b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// incBytes increments a byte slice, which involves incrementing the
|
||||||
|
// right-most byte, and following carry leftward.
|
||||||
|
// It makes a copy so that the provided slice's underlying array is not modified.
|
||||||
|
// If the overall operation overflows (e.g. incBytes(0xff, 0xff)), the second return parameter indicates that.
|
||||||
|
func incBytes(in []byte) (out []byte, overflow bool) {
|
||||||
|
out = make([]byte, len(in))
|
||||||
|
copy(out, in)
|
||||||
|
for i := len(out) - 1; i >= 0; i-- {
|
||||||
|
out[i]++
|
||||||
|
if out[i] != 0 {
|
||||||
|
return // Didn't overflow.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overflow = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
111
vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/repository_filesystem.go
generated
vendored
Normal file
111
vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/repository_filesystem.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package dotgit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-billy/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepositoryFilesystem is a billy.Filesystem compatible object wrapper
|
||||||
|
// which handles dot-git filesystem operations and supports commondir according to git scm layout:
|
||||||
|
// https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt
|
||||||
|
type RepositoryFilesystem struct {
|
||||||
|
dotGitFs billy.Filesystem
|
||||||
|
commonDotGitFs billy.Filesystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepositoryFilesystem(dotGitFs, commonDotGitFs billy.Filesystem) *RepositoryFilesystem {
|
||||||
|
return &RepositoryFilesystem{
|
||||||
|
dotGitFs: dotGitFs,
|
||||||
|
commonDotGitFs: commonDotGitFs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) mapToRepositoryFsByPath(path string) billy.Filesystem {
|
||||||
|
// Nothing to decide if commondir not defined
|
||||||
|
if fs.commonDotGitFs == nil {
|
||||||
|
return fs.dotGitFs
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPath := filepath.Clean(path)
|
||||||
|
|
||||||
|
// Check exceptions for commondir (https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt)
|
||||||
|
switch cleanPath {
|
||||||
|
case fs.dotGitFs.Join(logsPath, "HEAD"):
|
||||||
|
return fs.dotGitFs
|
||||||
|
case fs.dotGitFs.Join(refsPath, "bisect"), fs.dotGitFs.Join(refsPath, "rewritten"), fs.dotGitFs.Join(refsPath, "worktree"):
|
||||||
|
return fs.dotGitFs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine dot-git root by first path element.
|
||||||
|
// There are some elements which should always use commondir when commondir defined.
|
||||||
|
// Usual dot-git root will be used for the rest of files.
|
||||||
|
switch strings.Split(cleanPath, string(filepath.Separator))[0] {
|
||||||
|
case objectsPath, refsPath, packedRefsPath, configPath, branchesPath, hooksPath, infoPath, remotesPath, logsPath, shallowPath, worktreesPath:
|
||||||
|
return fs.commonDotGitFs
|
||||||
|
default:
|
||||||
|
return fs.dotGitFs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Create(filename string) (billy.File, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).Create(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Open(filename string) (billy.File, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).Open(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).OpenFile(filename, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Stat(filename string) (os.FileInfo, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).Stat(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Rename(oldpath, newpath string) error {
|
||||||
|
return fs.mapToRepositoryFsByPath(oldpath).Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Remove(filename string) error {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).Remove(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Join(elem ...string) string {
|
||||||
|
return fs.dotGitFs.Join(elem...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) TempFile(dir, prefix string) (billy.File, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(dir).TempFile(dir, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(path).ReadDir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) MkdirAll(filename string, perm os.FileMode) error {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).MkdirAll(filename, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Lstat(filename string) (os.FileInfo, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(filename).Lstat(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Symlink(target, link string) error {
|
||||||
|
return fs.mapToRepositoryFsByPath(target).Symlink(target, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Readlink(link string) (string, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(link).Readlink(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Chroot(path string) (billy.Filesystem, error) {
|
||||||
|
return fs.mapToRepositoryFsByPath(path).Chroot(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *RepositoryFilesystem) Root() string {
|
||||||
|
return fs.dotGitFs.Root()
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue