未验证 提交 d5cd6b8e 编写于 作者: S stormgbs 提交者: GitHub

Merge pull request #148 from hustliyilin/lyl-dev

shim/runtime: Add the attestation package to process remote attestation requests
...@@ -17,10 +17,11 @@ require ( ...@@ -17,10 +17,11 @@ require (
github.com/gogo/googleapis v1.4.0 // indirect github.com/gogo/googleapis v1.4.0 // indirect
github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf v1.3.1
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/protobuf v1.3.5
github.com/imdario/mergo v0.3.9 // indirect github.com/imdario/mergo v0.3.9 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v0.1.1 // indirect github.com/opencontainers/runc v0.1.1
github.com/opencontainers/runtime-spec v1.0.2 github.com/opencontainers/runtime-spec v1.0.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.5.0 github.com/sirupsen/logrus v1.5.0
...@@ -29,10 +30,12 @@ require ( ...@@ -29,10 +30,12 @@ require (
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
go.etcd.io/bbolt v1.3.4 // indirect go.etcd.io/bbolt v1.3.4 // indirect
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d golang.org/x/sys v0.0.0-20200331124033-c3d80250170d
google.golang.org/grpc v1.28.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect
k8s.io/apimachinery v0.18.2 k8s.io/apimachinery v0.18.2
) )
replace github.com/docker/distribution => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible replace (
github.com/docker/distribution => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
github.com/opencontainers/runc => github.com/alibaba/inclavare-containers/rune v0.0.0-20200903043353-e35cb5b583ad
)
...@@ -11,12 +11,22 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt ...@@ -11,12 +11,22 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200824174404-0364b2ac42dc h1:US5z8CeA+srHrHcCgO0jvbWOpbAtRGNy5eEK29lBtBo=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200824174404-0364b2ac42dc/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200828143903-196cf5ad4180 h1:PgB4TeF8anzUxDsZupY1CmDc+9PyL3lioGvyw4YzlnQ=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200828143903-196cf5ad4180/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200903043353-e35cb5b583ad h1:bYJltsHZjDaWrnTASSMRTzdT86WYlCjMkTJkmXovYf4=
github.com/alibaba/inclavare-containers/rune v0.0.0-20200903043353-e35cb5b583ad/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5 h1:950diauOSoCNaQfvLE0WaFadyI/ilKIjb6jEUQnm+hE=
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f h1:W1RQPz3nR8RxUw/Uqk71GU3JlZ7pNa1pXrHs98h0o9U=
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s=
...@@ -24,6 +34,8 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh ...@@ -24,6 +34,8 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e h1:GdiIYd8ZDOrT++e1NjhSD4rGt9zaJukHm4rt5F4mRQc= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e h1:GdiIYd8ZDOrT++e1NjhSD4rGt9zaJukHm4rt5F4mRQc=
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc= github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
...@@ -46,8 +58,14 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc ...@@ -46,8 +58,14 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
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/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
...@@ -89,9 +107,13 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 ...@@ -89,9 +107,13 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 h1:LoN2wx/aN8JPGebG+2DaUyk4M+xRcqJXfuIbs8AWHdE=
github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
...@@ -109,6 +131,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y ...@@ -109,6 +131,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
...@@ -128,6 +152,20 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+ ...@@ -128,6 +152,20 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200512065524-c9751df2e241 h1:9gTeevwZZIlAHHMxgx1cGhxEcReLwI+GYB6f71zPB5Q=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200512065524-c9751df2e241/go.mod h1:J0bOgJ9XXn/Q5kOys2btdg+YRAY2XWuWlwisNwjKANg=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830150828-52ab51d389fb h1:V1AqIFEpSDCOB6g0x5nBkCkBptQIAO/hWJ4ddoa6ky8=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830150828-52ab51d389fb/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830151934-729383d9725a h1:N2/1HVzTTe5Pv0C9dB7SybwMFcwYqmnRSeAinw2oBRQ=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830151934-729383d9725a/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830154759-b23096689797 h1:5bm34PuD2x6OWZKK6Xz00axjnkyYhX5Gbc7nbQtaxFo=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830154759-b23096689797/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830160541-3154e2e49306 h1:92phd2uEs70IP6U3h4Zr/Jm90v1DuupdShCrqH2/y5A=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200830160541-3154e2e49306/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200831081149-5c5099afc16e h1:HkXaLqH2GTBgKDhz8CR1uBQawZd/h1IFaeCAWdb/OFE=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200831081149-5c5099afc16e/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200831083357-49f2a43ff9b5 h1:dVi1VRHzrKju9i9SZ7TC/hRew8mcv21jyzB7qm0eS6Y=
github.com/hustliyilin/inclavare-containers/rune v0.0.0-20200831083357-49f2a43ff9b5/go.mod h1:/CbCa9fzRq4l68afAO4pcpNdgNkQFQOAhdwSFWz7S1s=
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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
...@@ -158,12 +196,16 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky ...@@ -158,12 +196,16 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
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/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
...@@ -178,14 +220,13 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i ...@@ -178,14 +220,13 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo=
github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
...@@ -206,7 +247,11 @@ github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQl ...@@ -206,7 +247,11 @@ github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQl
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
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/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
...@@ -232,6 +277,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy ...@@ -232,6 +277,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
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/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
...@@ -240,6 +287,12 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs ...@@ -240,6 +287,12 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5 h1:MCfT24H3f//U5+UCrZp1/riVO3B50BovxtDiNn0XKkk= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5 h1:MCfT24H3f//U5+UCrZp1/riVO3B50BovxtDiNn0XKkk=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
...@@ -275,6 +328,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG ...@@ -275,6 +328,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
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=
...@@ -288,12 +342,16 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h ...@@ -288,12 +342,16 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/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-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
...@@ -309,19 +367,23 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm ...@@ -309,19 +367,23 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
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=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
......
...@@ -4,5 +4,10 @@ const ( ...@@ -4,5 +4,10 @@ const (
ConfigurationPath = "/etc/inclavare-containers/config.toml" ConfigurationPath = "/etc/inclavare-containers/config.toml"
RuneOCIRuntime = "rune" RuneOCIRuntime = "rune"
EnvKeyRuneCarrier = "RUNE_CARRIER" EnvKeyRuneCarrier = "RUNE_CARRIER"
EnvKeyRaType = "ENCLAVE_IS_RA_TYPE_EPID"
EnvKeyIsProductEnclave = "ENCLAVE_IS_PRODUCT_ENCLAVE"
EnvKeyRaEpidSpid = "ENCLAVE_RA_EPID_SPID"
EnvKeyRaEpidSubKey = "ENCLAVE_RA_EPID_SUB_KEY"
EnvKeyRaEpidIsLinkable = "ENCLAVE_RA_EPID_IS_LINKABLE"
RuneDefaultWorkDirectory = "/run/rune" RuneDefaultWorkDirectory = "/run/rune"
) )
package attestation
import (
"context"
"fmt"
"github.com/alibaba/inclavare-containers/shim/runtime/config"
"github.com/alibaba/inclavare-containers/shim/runtime/v2/rune/constants"
"github.com/opencontainers/runc/libenclave/attestation/sgx"
pb "github.com/opencontainers/runc/libenclave/proto"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"net"
"path"
"path/filepath"
"strings"
)
const (
agentSocket = "agent.sock"
)
const (
QuoteSignatureTypeUnlinkable = iota
QuoteSignatureTypeLinkable
InvalidQuoteSignatureType
)
func dialAgentSocket(root string, containerId string) (*net.UnixConn, error) {
agentSock := filepath.Join(root, containerId, agentSocket)
addr, err := net.ResolveUnixAddr("unix", agentSock)
if err != nil {
return nil, err
}
conn, err := net.DialUnix("unix", nil, addr)
if err != nil {
return nil, err
}
return conn, nil
}
func GetRaParameters(bundlePath string) (raParameters map[string]string, err error) {
configPath := path.Join(bundlePath, "config.json")
p := make(map[string]string)
var spec *specs.Spec
spec, err = config.LoadSpec(configPath)
if err != nil {
return nil, fmt.Errorf("Load Spec:%s error:%s", configPath, err)
}
v, ok := config.GetEnv(spec, constants.EnvKeyRaType)
if !ok {
logrus.Infof("remote attestation parameters aren't set")
return nil, nil
}
p[constants.EnvKeyRaType] = v
v, ok = config.GetEnv(spec, constants.EnvKeyIsProductEnclave)
if !ok {
return nil, fmt.Errorf("Env:%s isn't set", constants.EnvKeyIsProductEnclave)
}
p[constants.EnvKeyIsProductEnclave] = v
v, ok = config.GetEnv(spec, constants.EnvKeyRaEpidSpid)
if !ok {
return nil, fmt.Errorf("Env:%s isn't set", constants.EnvKeyRaEpidSpid)
}
p[constants.EnvKeyRaEpidSpid] = v
v, ok = config.GetEnv(spec, constants.EnvKeyRaEpidSubKey)
if !ok {
return nil, fmt.Errorf("Env:%s isn't set", constants.EnvKeyRaEpidSubKey)
}
p[constants.EnvKeyRaEpidSubKey] = v
v, ok = config.GetEnv(spec, constants.EnvKeyRaEpidIsLinkable)
if !ok {
return nil, fmt.Errorf("Env:%s isn't set", constants.EnvKeyRaEpidIsLinkable)
}
p[constants.EnvKeyRaEpidIsLinkable] = v
return p, nil
}
func Attest(ctx context.Context, raParameters map[string]string, containerId string, root string) (map[string]string, error) {
if raParameters == nil {
return nil, nil
}
if raParameters[constants.EnvKeyRaType] == "" {
return nil, nil
}
if !strings.EqualFold(raParameters[constants.EnvKeyRaType], "true") {
return nil, fmt.Errorf("Unsupported ra type:%s!\n", raParameters[constants.EnvKeyRaType])
}
/* spid and subscriptionKey is checked in
* package github.com/opencontainers/runc/libenclave/attestation/sgx/ias.
* so we only need to check containerId, product and linkable here.
*/
if containerId == "" {
return nil, fmt.Errorf("Invalid container ID!\n")
}
if root == "" {
return nil, fmt.Errorf("Invalid rune global options --root")
}
conn, err := dialAgentSocket(root, containerId)
if err != nil {
return nil, err
}
isProductEnclave := sgx.DebugEnclave
if strings.EqualFold(raParameters[constants.EnvKeyIsProductEnclave], "true") {
isProductEnclave = sgx.ProductEnclave
}
raEpidQuoteType := QuoteSignatureTypeUnlinkable
if strings.EqualFold(raParameters[constants.EnvKeyRaEpidIsLinkable], "true") {
raEpidQuoteType = QuoteSignatureTypeLinkable
}
req := &pb.AgentServiceRequest{}
req.Attest = &pb.AgentServiceRequest_Attest{
Spid: raParameters[constants.EnvKeyRaEpidSpid],
SubscriptionKey: raParameters[constants.EnvKeyRaEpidSubKey],
Product: (uint32)(isProductEnclave),
QuoteType: (uint32)(raEpidQuoteType),
}
if err = protoBufWrite(conn, req); err != nil {
return nil, err
}
logrus.Infof("Begin remote attestation")
resp := &pb.AgentServiceResponse{}
if err = protoBufRead(conn, resp); err != nil {
return nil, err
}
logrus.Infof("End remote attestation")
if resp.Attest.Error != "" {
err = fmt.Errorf(resp.Attest.Error)
return nil, err
}
iasReport := make(map[string]string)
iasReport["StatusCode"] = resp.Attest.StatusCode
iasReport["Request-ID"] = resp.Attest.RequestID
iasReport["X-Iasreport-Signature"] = resp.Attest.XIasreportSignature
iasReport["X-Iasreport-Signing-Certificate"] = resp.Attest.XIasreportSigningCertificate
iasReport["ContentLength"] = resp.Attest.ContentLength
iasReport["Content-Type"] = resp.Attest.ContentType
iasReport["Body"] = resp.Attest.Body
return iasReport, nil
}
package attestation
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/golang/protobuf/proto"
pb "github.com/opencontainers/runc/libenclave/proto"
"io"
"unsafe"
)
func protoBufWrite(conn io.Writer, marshaled interface{}) (err error) {
var data []byte
switch marshaled := marshaled.(type) {
case *pb.AgentServiceRequest:
data, err = proto.Marshal(marshaled)
case *pb.AgentServiceResponse:
data, err = proto.Marshal(marshaled)
default:
return fmt.Errorf("invalid type of marshaled data")
}
if err != nil {
return err
}
sz := uint32(len(data))
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.LittleEndian, &sz)
if _, err := conn.Write(buf.Bytes()); err != nil {
return err
}
if _, err := conn.Write(data); err != nil {
return err
}
return nil
}
func protoBufRead(conn io.Reader, unmarshaled interface{}) error {
var sz uint32
data := make([]byte, unsafe.Sizeof(sz))
_, err := conn.Read(data)
if err != nil {
return err
}
buf := bytes.NewBuffer(data)
sz = uint32(len(data))
if err := binary.Read(buf, binary.LittleEndian, &sz); err != nil {
return err
}
data = make([]byte, sz)
if _, err := conn.Read(data); err != nil {
return err
}
switch unmarshaled := unmarshaled.(type) {
case *pb.AgentServiceRequest:
err = proto.Unmarshal(data, unmarshaled)
case *pb.AgentServiceResponse:
err = proto.Unmarshal(data, unmarshaled)
default:
return fmt.Errorf("invalid type of unmarshaled data")
}
return nil
}
...@@ -58,6 +58,7 @@ type Epoller struct { ...@@ -58,6 +58,7 @@ type Epoller struct {
efd int efd int
mu sync.Mutex mu sync.Mutex
fdMapping map[int]*EpollConsole fdMapping map[int]*EpollConsole
closeOnce sync.Once
} }
// NewEpoller returns an instance of epoller with a valid epoll fd. // NewEpoller returns an instance of epoller with a valid epoll fd.
...@@ -151,7 +152,11 @@ func (e *Epoller) getConsole(sysfd int) *EpollConsole { ...@@ -151,7 +152,11 @@ func (e *Epoller) getConsole(sysfd int) *EpollConsole {
// Close closes the epoll fd // Close closes the epoll fd
func (e *Epoller) Close() error { func (e *Epoller) Close() error {
return unix.Close(e.efd) closeErr := os.ErrClosed // default to "file already closed"
e.closeOnce.Do(func() {
closeErr = unix.Close(e.efd)
})
return closeErr
} }
// EpollConsole acts like a console but registers its file descriptor with an // EpollConsole acts like a console but registers its file descriptor with an
......
...@@ -74,6 +74,3 @@ func (m *defaultMonitor) Wait(c *exec.Cmd, ec chan Exit) (int, error) { ...@@ -74,6 +74,3 @@ func (m *defaultMonitor) Wait(c *exec.Cmd, ec chan Exit) (int, error) {
e := <-ec e := <-ec
return e.Status, nil return e.Status, nil
} }
The MIT License (MIT)
Copyright (c) 2014 Brian Goff
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.
package md2man
import (
"github.com/russross/blackfriday/v2"
)
// Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte {
renderer := NewRoffRenderer()
return blackfriday.Run(doc,
[]blackfriday.Option{blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(renderer.GetExtensions())}...)
}
package md2man
import (
"fmt"
"io"
"os"
"strings"
"github.com/russross/blackfriday/v2"
)
// roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text
type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int
firstHeader bool
defineTerm bool
listDepth int
}
const (
titleHeader = ".TH "
topLevelHeader = "\n\n.SH "
secondLevelHdr = "\n.SH "
otherHeader = "\n.SS "
crTag = "\n"
emphTag = "\\fI"
emphCloseTag = "\\fP"
strongTag = "\\fB"
strongCloseTag = "\\fP"
breakTag = "\n.br\n"
paraTag = "\n.PP\n"
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
linkTag = "\n\\[la]"
linkCloseTag = "\\[ra]"
codespanTag = "\\fB\\fC"
codespanCloseTag = "\\fR"
codeTag = "\n.PP\n.RS\n\n.nf\n"
codeCloseTag = "\n.fi\n.RE\n"
quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n"
listCloseTag = "\n.RE\n"
arglistTag = "\n.TP\n"
tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n"
tableCellStart = "T{\n"
tableCellEnd = "\nT}\n"
)
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint
var extensions blackfriday.Extensions
extensions |= blackfriday.NoIntraEmphasis
extensions |= blackfriday.Tables
extensions |= blackfriday.FencedCode
extensions |= blackfriday.SpaceHeadings
extensions |= blackfriday.Footnotes
extensions |= blackfriday.Titleblock
extensions |= blackfriday.DefinitionLists
return &roffRenderer{
extensions: extensions,
}
}
// GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions
}
// RenderHeader handles outputting the header at document start
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
// disable hyphenation
out(w, ".nh\n")
}
// RenderFooter handles outputting the footer at the document end; the roff
// renderer has no footer information
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
}
// RenderNode is called for each node in a markdown document; based on the node
// type the equivalent roff output is sent to the writer
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
var walkAction = blackfriday.GoToNext
switch node.Type {
case blackfriday.Text:
r.handleText(w, node, entering)
case blackfriday.Softbreak:
out(w, crTag)
case blackfriday.Hardbreak:
out(w, breakTag)
case blackfriday.Emph:
if entering {
out(w, emphTag)
} else {
out(w, emphCloseTag)
}
case blackfriday.Strong:
if entering {
out(w, strongTag)
} else {
out(w, strongCloseTag)
}
case blackfriday.Link:
if !entering {
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
}
case blackfriday.Image:
// ignore images
walkAction = blackfriday.SkipChildren
case blackfriday.Code:
out(w, codespanTag)
escapeSpecialChars(w, node.Literal)
out(w, codespanCloseTag)
case blackfriday.Document:
break
case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering {
out(w, paraTag)
} else {
out(w, crTag)
}
case blackfriday.BlockQuote:
if entering {
out(w, quoteTag)
} else {
out(w, quoteCloseTag)
}
case blackfriday.Heading:
r.handleHeading(w, node, entering)
case blackfriday.HorizontalRule:
out(w, hruleTag)
case blackfriday.List:
r.handleList(w, node, entering)
case blackfriday.Item:
r.handleItem(w, node, entering)
case blackfriday.CodeBlock:
out(w, codeTag)
escapeSpecialChars(w, node.Literal)
out(w, codeCloseTag)
case blackfriday.Table:
r.handleTable(w, node, entering)
case blackfriday.TableCell:
r.handleTableCell(w, node, entering)
case blackfriday.TableHead:
case blackfriday.TableBody:
case blackfriday.TableRow:
// no action as cell entries do all the nroff formatting
return blackfriday.GoToNext
default:
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
}
return walkAction
}
func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) {
var (
start, end string
)
// handle special roff table cell text encapsulation
if node.Parent.Type == blackfriday.TableCell {
if len(node.Literal) > 30 {
start = tableCellStart
end = tableCellEnd
} else {
// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
if node.Parent.Next == nil && !node.Parent.IsHeader {
end = crTag
}
}
}
out(w, start)
escapeSpecialChars(w, node.Literal)
out(w, end)
}
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
switch node.Level {
case 1:
if !r.firstHeader {
out(w, titleHeader)
r.firstHeader = true
break
}
out(w, topLevelHeader)
case 2:
out(w, secondLevelHdr)
default:
out(w, otherHeader)
}
}
}
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag
closeTag := listCloseTag
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node
openTag = ""
closeTag = ""
}
if entering {
r.listDepth++
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = append(r.listCounters, 1)
}
out(w, openTag)
} else {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = r.listCounters[:len(r.listCounters)-1]
}
out(w, closeTag)
r.listDepth--
}
}
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
r.listCounters[len(r.listCounters)-1]++
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// state machine for handling terms and following definitions
// since blackfriday does not distinguish them properly, nor
// does it seperate them into separate lists as it should
if !r.defineTerm {
out(w, arglistTag)
r.defineTerm = true
} else {
r.defineTerm = false
}
} else {
out(w, ".IP \\(bu 2\n")
}
} else {
out(w, "\n")
}
}
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
out(w, tableStart)
//call walker to count cells (and rows?) so format section can be produced
columns := countColumns(node)
out(w, strings.Repeat("l ", columns)+"\n")
out(w, strings.Repeat("l ", columns)+".\n")
} else {
out(w, tableEnd)
}
}
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
var (
start, end string
)
if node.IsHeader {
start = codespanTag
end = codespanCloseTag
}
if entering {
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
out(w, "\t"+start)
} else {
out(w, start)
}
} else {
// need to carriage return if we are at the end of the header row
if node.IsHeader && node.Next == nil {
end = end + crTag
}
out(w, end)
}
}
// because roff format requires knowing the column count before outputting any table
// data we need to walk a table tree and count the columns
func countColumns(node *blackfriday.Node) int {
var columns int
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.TableRow:
if !entering {
return blackfriday.Terminate
}
case blackfriday.TableCell:
if entering {
columns++
}
default:
}
return blackfriday.GoToNext
})
return columns
}
func out(w io.Writer, output string) {
io.WriteString(w, output) // nolint: errcheck
}
func needsBackslash(c byte) bool {
for _, r := range []byte("-_&\\~") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(w io.Writer, text []byte) {
for i := 0; i < len(text); i++ {
// escape initial apostrophe or period
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
out(w, "\\&")
}
// directly copy normal characters
org := i
for i < len(text) && !needsBackslash(text[i]) {
i++
}
if i > org {
w.Write(text[org:i]) // nolint: errcheck
}
// escape a character
if i >= len(text) {
break
}
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
}
}
...@@ -102,7 +102,8 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package ...@@ -102,7 +102,8 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// //
type Any struct { type Any struct {
// A URL/resource name that uniquely identifies the type of the serialized // A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. The last segment of the URL's path must represent // protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
// the fully qualified name of the type (as in // the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form // `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted). // (e.g., leading "." is not accepted).
...@@ -181,7 +182,9 @@ func init() { ...@@ -181,7 +182,9 @@ func init() {
proto.RegisterType((*Any)(nil), "google.protobuf.Any") proto.RegisterType((*Any)(nil), "google.protobuf.Any")
} }
func init() { proto.RegisterFile("google/protobuf/any.proto", fileDescriptor_b53526c13ae22eb4) } func init() {
proto.RegisterFile("google/protobuf/any.proto", fileDescriptor_b53526c13ae22eb4)
}
var fileDescriptor_b53526c13ae22eb4 = []byte{ var fileDescriptor_b53526c13ae22eb4 = []byte{
// 185 bytes of a gzipped FileDescriptorProto // 185 bytes of a gzipped FileDescriptorProto
......
...@@ -121,7 +121,8 @@ option objc_class_prefix = "GPB"; ...@@ -121,7 +121,8 @@ option objc_class_prefix = "GPB";
// //
message Any { message Any {
// A URL/resource name that uniquely identifies the type of the serialized // A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. The last segment of the URL's path must represent // protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
// the fully qualified name of the type (as in // the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form // `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted). // (e.g., leading "." is not accepted).
......
...@@ -41,7 +41,7 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package ...@@ -41,7 +41,7 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// if (duration.seconds < 0 && duration.nanos > 0) { // if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1; // duration.seconds += 1;
// duration.nanos -= 1000000000; // duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) { // } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1; // duration.seconds -= 1;
// duration.nanos += 1000000000; // duration.nanos += 1000000000;
// } // }
...@@ -142,7 +142,9 @@ func init() { ...@@ -142,7 +142,9 @@ func init() {
proto.RegisterType((*Duration)(nil), "google.protobuf.Duration") proto.RegisterType((*Duration)(nil), "google.protobuf.Duration")
} }
func init() { proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor_23597b2ebd7ac6c5) } func init() {
proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor_23597b2ebd7ac6c5)
}
var fileDescriptor_23597b2ebd7ac6c5 = []byte{ var fileDescriptor_23597b2ebd7ac6c5 = []byte{
// 190 bytes of a gzipped FileDescriptorProto // 190 bytes of a gzipped FileDescriptorProto
......
...@@ -61,7 +61,7 @@ option objc_class_prefix = "GPB"; ...@@ -61,7 +61,7 @@ option objc_class_prefix = "GPB";
// if (duration.seconds < 0 && duration.nanos > 0) { // if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1; // duration.seconds += 1;
// duration.nanos -= 1000000000; // duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) { // } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1; // duration.seconds -= 1;
// duration.nanos += 1000000000; // duration.nanos += 1000000000;
// } // }
...@@ -101,7 +101,6 @@ option objc_class_prefix = "GPB"; ...@@ -101,7 +101,6 @@ option objc_class_prefix = "GPB";
// //
// //
message Duration { message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000 // Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from: // to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
......
...@@ -20,17 +20,19 @@ var _ = math.Inf ...@@ -20,17 +20,19 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// A Timestamp represents a point in time independent of any time zone // A Timestamp represents a point in time independent of any time zone or local
// or calendar, represented as seconds and fractions of seconds at // calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the // nanosecond resolution. The count is relative to an epoch at UTC midnight on
// Proleptic Gregorian Calendar which extends the Gregorian calendar // January 1, 1970, in the proleptic Gregorian calendar which extends the
// backwards to year one. It is encoded assuming all minutes are 60 // Gregorian calendar backwards to year one.
// seconds long, i.e. leap seconds are "smeared" so that no leap second //
// table is needed for interpretation. Range is from // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. // second table is needed for interpretation, using a [24-hour linear
// By restricting to that range, we ensure that we can convert to // smear](https://developers.google.com/time/smear).
// and from RFC 3339 date strings. //
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
// //
// # Examples // # Examples
// //
...@@ -91,12 +93,14 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package ...@@ -91,12 +93,14 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// 01:30 UTC on January 15, 2017. // 01:30 UTC on January 15, 2017.
// //
// In JavaScript, one can convert a Date object to this format using the // In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] // standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted // method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) // to this format using
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- // the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format. // ) to obtain a formatter capable of generating timestamps in this format.
// //
// //
...@@ -160,7 +164,9 @@ func init() { ...@@ -160,7 +164,9 @@ func init() {
proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp") proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp")
} }
func init() { proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor_292007bbfe81227e) } func init() {
proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor_292007bbfe81227e)
}
var fileDescriptor_292007bbfe81227e = []byte{ var fileDescriptor_292007bbfe81227e = []byte{
// 191 bytes of a gzipped FileDescriptorProto // 191 bytes of a gzipped FileDescriptorProto
......
...@@ -40,17 +40,19 @@ option java_outer_classname = "TimestampProto"; ...@@ -40,17 +40,19 @@ option java_outer_classname = "TimestampProto";
option java_multiple_files = true; option java_multiple_files = true;
option objc_class_prefix = "GPB"; option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone // A Timestamp represents a point in time independent of any time zone or local
// or calendar, represented as seconds and fractions of seconds at // calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the // nanosecond resolution. The count is relative to an epoch at UTC midnight on
// Proleptic Gregorian Calendar which extends the Gregorian calendar // January 1, 1970, in the proleptic Gregorian calendar which extends the
// backwards to year one. It is encoded assuming all minutes are 60 // Gregorian calendar backwards to year one.
// seconds long, i.e. leap seconds are "smeared" so that no leap second //
// table is needed for interpretation. Range is from // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. // second table is needed for interpretation, using a [24-hour linear
// By restricting to that range, we ensure that we can convert to // smear](https://developers.google.com/time/smear).
// and from RFC 3339 date strings. //
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
// //
// # Examples // # Examples
// //
...@@ -111,17 +113,18 @@ option objc_class_prefix = "GPB"; ...@@ -111,17 +113,18 @@ option objc_class_prefix = "GPB";
// 01:30 UTC on January 15, 2017. // 01:30 UTC on January 15, 2017.
// //
// In JavaScript, one can convert a Date object to this format using the // In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] // standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted // method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) // to this format using
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- // the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format. // ) to obtain a formatter capable of generating timestamps in this format.
// //
// //
message Timestamp { message Timestamp {
// Represents seconds of UTC time since Unix epoch // Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive. // 9999-12-31T23:59:59Z inclusive.
......
...@@ -3,26 +3,13 @@ ...@@ -3,26 +3,13 @@
package system package system
import ( import (
"bufio"
"fmt"
"os" "os"
"os/exec" "os/exec"
"syscall"
"unsafe" "unsafe"
)
// If arg2 is nonzero, set the "child subreaper" attribute of the "github.com/opencontainers/runc/libcontainer/user"
// calling process; if arg2 is zero, unset the attribute. When a "golang.org/x/sys/unix"
// process is marked as a child subreaper, all of the children )
// that it creates, and their descendants, will be marked as
// having a subreaper. In effect, a subreaper fulfills the role
// of init(1) for its descendant processes. Upon termination of
// a process that is orphaned (i.e., its immediate parent has
// already terminated) and marked as having a subreaper, the
// nearest still living ancestor subreaper will receive a SIGCHLD
// signal and be able to wait(2) on the process to discover its
// termination status.
const PR_SET_CHILD_SUBREAPER = 36
type ParentDeathSignal int type ParentDeathSignal int
...@@ -50,11 +37,11 @@ func Execv(cmd string, args []string, env []string) error { ...@@ -50,11 +37,11 @@ func Execv(cmd string, args []string, env []string) error {
return err return err
} }
return syscall.Exec(name, args, env) return unix.Exec(name, args, env)
} }
func Prlimit(pid, resource int, limit syscall.Rlimit) error { func Prlimit(pid, resource int, limit unix.Rlimit) error {
_, _, err := syscall.RawSyscall6(syscall.SYS_PRLIMIT64, uintptr(pid), uintptr(resource), uintptr(unsafe.Pointer(&limit)), uintptr(unsafe.Pointer(&limit)), 0, 0) _, _, err := unix.RawSyscall6(unix.SYS_PRLIMIT64, uintptr(pid), uintptr(resource), uintptr(unsafe.Pointer(&limit)), uintptr(unsafe.Pointer(&limit)), 0, 0)
if err != 0 { if err != 0 {
return err return err
} }
...@@ -62,7 +49,7 @@ func Prlimit(pid, resource int, limit syscall.Rlimit) error { ...@@ -62,7 +49,7 @@ func Prlimit(pid, resource int, limit syscall.Rlimit) error {
} }
func SetParentDeathSignal(sig uintptr) error { func SetParentDeathSignal(sig uintptr) error {
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 { if err := unix.Prctl(unix.PR_SET_PDEATHSIG, sig, 0, 0, 0); err != nil {
return err return err
} }
return nil return nil
...@@ -70,15 +57,14 @@ func SetParentDeathSignal(sig uintptr) error { ...@@ -70,15 +57,14 @@ func SetParentDeathSignal(sig uintptr) error {
func GetParentDeathSignal() (ParentDeathSignal, error) { func GetParentDeathSignal() (ParentDeathSignal, error) {
var sig int var sig int
_, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0) if err := unix.Prctl(unix.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0, 0, 0); err != nil {
if err != 0 {
return -1, err return -1, err
} }
return ParentDeathSignal(sig), nil return ParentDeathSignal(sig), nil
} }
func SetKeepCaps() error { func SetKeepCaps() error {
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 { if err := unix.Prctl(unix.PR_SET_KEEPCAPS, 1, 0, 0, 0); err != nil {
return err return err
} }
...@@ -86,7 +72,7 @@ func SetKeepCaps() error { ...@@ -86,7 +72,7 @@ func SetKeepCaps() error {
} }
func ClearKeepCaps() error { func ClearKeepCaps() error {
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 { if err := unix.Prctl(unix.PR_SET_KEEPCAPS, 0, 0, 0, 0); err != nil {
return err return err
} }
...@@ -94,55 +80,62 @@ func ClearKeepCaps() error { ...@@ -94,55 +80,62 @@ func ClearKeepCaps() error {
} }
func Setctty() error { func Setctty() error {
if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { if err := unix.IoctlSetInt(0, unix.TIOCSCTTY, 0); err != nil {
return err return err
} }
return nil return nil
} }
/* // RunningInUserNS detects whether we are currently running in a user namespace.
* Detect whether we are currently running in a user namespace. // Originally copied from github.com/lxc/lxd/shared/util.go
* Copied from github.com/lxc/lxd/shared/util.go
*/
func RunningInUserNS() bool { func RunningInUserNS() bool {
file, err := os.Open("/proc/self/uid_map") uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
/*
* This kernel-provided file only exists if user namespaces are
* supported
*/
return false
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil { if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return false return false
} }
return UIDMapInUserNS(uidmap)
}
line := string(l) func UIDMapInUserNS(uidmap []user.IDMap) bool {
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/* /*
* We assume we are in the initial user namespace if we have a full * We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0. * range - 4294967295 uids starting at uid 0.
*/ */
if a == 0 && b == 0 && c == 4294967295 { if len(uidmap) == 1 && uidmap[0].ID == 0 && uidmap[0].ParentID == 0 && uidmap[0].Count == 4294967295 {
return false return false
} }
return true return true
} }
// GetParentNSeuid returns the euid within the parent user namespace
func GetParentNSeuid() int64 {
euid := int64(os.Geteuid())
uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return euid
}
for _, um := range uidmap {
if um.ID <= euid && euid <= um.ID+um.Count-1 {
return um.ParentID + euid - um.ID
}
}
return euid
}
// SetSubreaper sets the value i as the subreaper setting for the calling process // SetSubreaper sets the value i as the subreaper setting for the calling process
func SetSubreaper(i int) error { func SetSubreaper(i int) error {
return Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0) return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
} }
func Prctl(option int, arg2, arg3, arg4, arg5 uintptr) (err error) { // GetSubreaper returns the subreaper setting for the calling process
_, _, e1 := syscall.Syscall6(syscall.SYS_PRCTL, uintptr(option), arg2, arg3, arg4, arg5, 0) func GetSubreaper() (int, error) {
if e1 != 0 { var i uintptr
err = e1
if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil {
return -1, err
} }
return
return int(i), nil
} }
package system package system
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
) )
// look in /proc to find the process start time so that we can verify // State is the status of a process.
// that this pid has started after ourself type State rune
const ( // Only values for Linux 3.14 and later are listed here
Dead State = 'X'
DiskSleep State = 'D'
Running State = 'R'
Sleeping State = 'S'
Stopped State = 'T'
TracingStop State = 't'
Zombie State = 'Z'
)
// String forms of the state from proc(5)'s documentation for
// /proc/[pid]/status' "State" field.
func (s State) String() string {
switch s {
case Dead:
return "dead"
case DiskSleep:
return "disk sleep"
case Running:
return "running"
case Sleeping:
return "sleeping"
case Stopped:
return "stopped"
case TracingStop:
return "tracing stop"
case Zombie:
return "zombie"
default:
return fmt.Sprintf("unknown (%c)", s)
}
}
// Stat_t represents the information from /proc/[pid]/stat, as
// described in proc(5) with names based on the /proc/[pid]/status
// fields.
type Stat_t struct {
// PID is the process ID.
PID uint
// Name is the command run by the process.
Name string
// State is the state of the process.
State State
// StartTime is the number of clock ticks after system boot (since
// Linux 2.6).
StartTime uint64
}
// Stat returns a Stat_t instance for the specified process.
func Stat(pid int) (stat Stat_t, err error) {
bytes, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
if err != nil {
return stat, err
}
return parseStat(string(bytes))
}
// GetProcessStartTime is deprecated. Use Stat(pid) and
// Stat_t.StartTime instead.
func GetProcessStartTime(pid int) (string, error) { func GetProcessStartTime(pid int) (string, error) {
data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat")) stat, err := Stat(pid)
if err != nil { if err != nil {
return "", err return "", err
} }
return fmt.Sprintf("%d", stat.StartTime), nil
}
func parseStat(data string) (stat Stat_t, err error) {
// From proc(5), field 2 could contain space and is inside `(` and `)`.
// The following is an example:
// 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
i := strings.LastIndex(data, ")")
if i <= 2 || i >= len(data)-1 {
return stat, fmt.Errorf("invalid stat data: %q", data)
}
parts := strings.SplitN(data[:i], "(", 2)
if len(parts) != 2 {
return stat, fmt.Errorf("invalid stat data: %q", data)
}
stat.Name = parts[1]
_, err = fmt.Sscanf(parts[0], "%d", &stat.PID)
if err != nil {
return stat, err
}
parts := strings.Split(string(data), " ") // parts indexes should be offset by 3 from the field number given
// the starttime is located at pos 22 // proc(5), because parts is zero-indexed and we've removed fields
// from the man page // one (PID) and two (Name) in the paren-split.
// parts = strings.Split(data[i+2:], " ")
// starttime %llu (was %lu before Linux 2.6) var state int
// (22) The time the process started after system boot. In kernels before Linux 2.6, this fmt.Sscanf(parts[3-3], "%c", &state)
// value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks stat.State = State(state)
// (divide by sysconf(_SC_CLK_TCK)). fmt.Sscanf(parts[22-3], "%d", &stat.StartTime)
return parts[22-1], nil // starts at 1 return stat, nil
} }
package system
import (
"fmt"
"runtime"
"syscall"
)
// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092
//
// We need different setns values for the different platforms and arch
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
var setNsMap = map[string]uintptr{
"linux/386": 346,
"linux/arm64": 268,
"linux/amd64": 308,
"linux/arm": 375,
"linux/ppc": 350,
"linux/ppc64": 350,
"linux/ppc64le": 350,
"linux/s390x": 339,
}
var sysSetns = setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
func SysSetns() uint32 {
return uint32(sysSetns)
}
func Setns(fd uintptr, flags uintptr) error {
ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
if !exists {
return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
}
_, _, err := syscall.RawSyscall(ns, fd, flags, 0)
if err != 0 {
return err
}
return nil
}
// +build linux,386 // +build linux
// +build 386 arm
package system package system
import ( import (
"syscall" "golang.org/x/sys/unix"
) )
// Setuid sets the uid of the calling thread to the specified uid. // Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) { func Setuid(uid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) _, _, e1 := unix.RawSyscall(unix.SYS_SETUID32, uintptr(uid), 0, 0)
if e1 != 0 { if e1 != 0 {
err = e1 err = e1
} }
...@@ -17,7 +18,7 @@ func Setuid(uid int) (err error) { ...@@ -17,7 +18,7 @@ func Setuid(uid int) (err error) {
// Setgid sets the gid of the calling thread to the specified gid. // Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) { func Setgid(gid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0) _, _, e1 := unix.RawSyscall(unix.SYS_SETGID32, uintptr(gid), 0, 0)
if e1 != 0 { if e1 != 0 {
err = e1 err = e1
} }
......
// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x // +build linux
// +build arm64 amd64 mips mipsle mips64 mips64le ppc ppc64 ppc64le riscv64 s390x
package system package system
import ( import (
"syscall" "golang.org/x/sys/unix"
) )
// Setuid sets the uid of the calling thread to the specified uid. // Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) { func Setuid(uid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) _, _, e1 := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0)
if e1 != 0 { if e1 != 0 {
err = e1 err = e1
} }
...@@ -17,7 +18,7 @@ func Setuid(uid int) (err error) { ...@@ -17,7 +18,7 @@ func Setuid(uid int) (err error) {
// Setgid sets the gid of the calling thread to the specified gid. // Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) { func Setgid(gid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(gid), 0, 0) _, _, e1 := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0)
if e1 != 0 { if e1 != 0 {
err = e1 err = e1
} }
......
// +build linux,arm
package system
import (
"syscall"
)
// Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0)
if e1 != 0 {
err = e1
}
return
}
// Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) {
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
if e1 != 0 {
err = e1
}
return
}
// +build cgo,linux cgo,freebsd // +build cgo,linux
package system package system
......
...@@ -2,8 +2,26 @@ ...@@ -2,8 +2,26 @@
package system package system
import (
"os"
"github.com/opencontainers/runc/libcontainer/user"
)
// RunningInUserNS is a stub for non-Linux systems // RunningInUserNS is a stub for non-Linux systems
// Always returns false // Always returns false
func RunningInUserNS() bool { func RunningInUserNS() bool {
return false return false
} }
// UIDMapInUserNS is a stub for non-Linux systems
// Always returns false
func UIDMapInUserNS(uidmap []user.IDMap) bool {
return false
}
// GetParentNSeuid returns the euid within the parent user namespace
// Always returns os.Geteuid on non-linux
func GetParentNSeuid() int {
return os.Geteuid()
}
package system package system
import ( import "golang.org/x/sys/unix"
"syscall"
"unsafe"
)
var _zero uintptr
// Returns the size of xattrs and nil error
// Requires path, takes allocated []byte or nil as last argument
func Llistxattr(path string, dest []byte) (size int, err error) {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return -1, err
}
var newpathBytes unsafe.Pointer
if len(dest) > 0 {
newpathBytes = unsafe.Pointer(&dest[0])
} else {
newpathBytes = unsafe.Pointer(&_zero)
}
_size, _, errno := syscall.Syscall6(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(newpathBytes), uintptr(len(dest)), 0, 0, 0)
size = int(_size)
if errno != 0 {
return -1, errno
}
return size, nil
}
// Returns a []byte slice if the xattr is set and nil otherwise // Returns a []byte slice if the xattr is set and nil otherwise
// Requires path and its attribute as arguments // Requires path and its attribute as arguments
func Lgetxattr(path string, attr string) ([]byte, error) { func Lgetxattr(path string, attr string) ([]byte, error) {
var sz int var sz int
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return nil, err
}
attrBytes, err := syscall.BytePtrFromString(attr)
if err != nil {
return nil, err
}
// Start with a 128 length byte array // Start with a 128 length byte array
sz = 128 dest := make([]byte, 128)
dest := make([]byte, sz) sz, errno := unix.Lgetxattr(path, attr, dest)
destBytes := unsafe.Pointer(&dest[0])
_sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
switch { switch {
case errno == syscall.ENODATA: case errno == unix.ENODATA:
return nil, errno return nil, errno
case errno == syscall.ENOTSUP: case errno == unix.ENOTSUP:
return nil, errno return nil, errno
case errno == syscall.ERANGE: case errno == unix.ERANGE:
// 128 byte array might just not be good enough, // 128 byte array might just not be good enough,
// A dummy buffer is used ``uintptr(0)`` to get real size // A dummy buffer is used to get the real size
// of the xattrs on disk // of the xattrs on disk
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0) sz, errno = unix.Lgetxattr(path, attr, []byte{})
sz = int(_sz) if errno != nil {
if sz < 0 {
return nil, errno return nil, errno
} }
dest = make([]byte, sz) dest = make([]byte, sz)
destBytes := unsafe.Pointer(&dest[0]) sz, errno = unix.Lgetxattr(path, attr, dest)
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) if errno != nil {
if errno != 0 {
return nil, errno return nil, errno
} }
case errno != 0: case errno != nil:
return nil, errno return nil, errno
} }
sz = int(_sz)
return dest[:sz], nil return dest[:sz], nil
} }
func Lsetxattr(path string, attr string, data []byte, flags int) error {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
attrBytes, err := syscall.BytePtrFromString(attr)
if err != nil {
return err
}
var dataBytes unsafe.Pointer
if len(data) > 0 {
dataBytes = unsafe.Pointer(&data[0])
} else {
dataBytes = unsafe.Pointer(&_zero)
}
_, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0)
if errno != 0 {
return errno
}
return nil
}
...@@ -2,7 +2,6 @@ package user ...@@ -2,7 +2,6 @@ package user
import ( import (
"errors" "errors"
"syscall"
) )
var ( var (
...@@ -13,98 +12,30 @@ var ( ...@@ -13,98 +12,30 @@ var (
ErrNoGroupEntries = errors.New("no matching entries in group file") ErrNoGroupEntries = errors.New("no matching entries in group file")
) )
func lookupUser(filter func(u User) bool) (User, error) {
// Get operating system-specific passwd reader-closer.
passwd, err := GetPasswd()
if err != nil {
return User{}, err
}
defer passwd.Close()
// Get the users.
users, err := ParsePasswdFilter(passwd, filter)
if err != nil {
return User{}, err
}
// No user entries found.
if len(users) == 0 {
return User{}, ErrNoPasswdEntries
}
// Assume the first entry is the "correct" one.
return users[0], nil
}
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
// user cannot be found (or there is no /etc/passwd file on the filesystem),
// then CurrentUser returns an error.
func CurrentUser() (User, error) {
return LookupUid(syscall.Getuid())
}
// LookupUser looks up a user by their username in /etc/passwd. If the user // LookupUser looks up a user by their username in /etc/passwd. If the user
// cannot be found (or there is no /etc/passwd file on the filesystem), then // cannot be found (or there is no /etc/passwd file on the filesystem), then
// LookupUser returns an error. // LookupUser returns an error.
func LookupUser(username string) (User, error) { func LookupUser(username string) (User, error) {
return lookupUser(func(u User) bool { return lookupUser(username)
return u.Name == username
})
} }
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot // LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
// be found (or there is no /etc/passwd file on the filesystem), then LookupId // be found (or there is no /etc/passwd file on the filesystem), then LookupId
// returns an error. // returns an error.
func LookupUid(uid int) (User, error) { func LookupUid(uid int) (User, error) {
return lookupUser(func(u User) bool { return lookupUid(uid)
return u.Uid == uid
})
}
func lookupGroup(filter func(g Group) bool) (Group, error) {
// Get operating system-specific group reader-closer.
group, err := GetGroup()
if err != nil {
return Group{}, err
}
defer group.Close()
// Get the users.
groups, err := ParseGroupFilter(group, filter)
if err != nil {
return Group{}, err
}
// No user entries found.
if len(groups) == 0 {
return Group{}, ErrNoGroupEntries
}
// Assume the first entry is the "correct" one.
return groups[0], nil
}
// CurrentGroup looks up the current user's group by their primary group id's
// entry in /etc/passwd. If the group cannot be found (or there is no
// /etc/group file on the filesystem), then CurrentGroup returns an error.
func CurrentGroup() (Group, error) {
return LookupGid(syscall.Getgid())
} }
// LookupGroup looks up a group by its name in /etc/group. If the group cannot // LookupGroup looks up a group by its name in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGroup // be found (or there is no /etc/group file on the filesystem), then LookupGroup
// returns an error. // returns an error.
func LookupGroup(groupname string) (Group, error) { func LookupGroup(groupname string) (Group, error) {
return lookupGroup(func(g Group) bool { return lookupGroup(groupname)
return g.Name == groupname
})
} }
// LookupGid looks up a group by its group id in /etc/group. If the group cannot // LookupGid looks up a group by its group id in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGid // be found (or there is no /etc/group file on the filesystem), then LookupGid
// returns an error. // returns an error.
func LookupGid(gid int) (Group, error) { func LookupGid(gid int) (Group, error) {
return lookupGroup(func(g Group) bool { return lookupGid(gid)
return g.Gid == gid
})
} }
...@@ -5,6 +5,9 @@ package user ...@@ -5,6 +5,9 @@ package user
import ( import (
"io" "io"
"os" "os"
"strconv"
"golang.org/x/sys/unix"
) )
// Unix-specific path to the passwd and group formatted files. // Unix-specific path to the passwd and group formatted files.
...@@ -13,6 +16,76 @@ const ( ...@@ -13,6 +16,76 @@ const (
unixGroupPath = "/etc/group" unixGroupPath = "/etc/group"
) )
func lookupUser(username string) (User, error) {
return lookupUserFunc(func(u User) bool {
return u.Name == username
})
}
func lookupUid(uid int) (User, error) {
return lookupUserFunc(func(u User) bool {
return u.Uid == uid
})
}
func lookupUserFunc(filter func(u User) bool) (User, error) {
// Get operating system-specific passwd reader-closer.
passwd, err := GetPasswd()
if err != nil {
return User{}, err
}
defer passwd.Close()
// Get the users.
users, err := ParsePasswdFilter(passwd, filter)
if err != nil {
return User{}, err
}
// No user entries found.
if len(users) == 0 {
return User{}, ErrNoPasswdEntries
}
// Assume the first entry is the "correct" one.
return users[0], nil
}
func lookupGroup(groupname string) (Group, error) {
return lookupGroupFunc(func(g Group) bool {
return g.Name == groupname
})
}
func lookupGid(gid int) (Group, error) {
return lookupGroupFunc(func(g Group) bool {
return g.Gid == gid
})
}
func lookupGroupFunc(filter func(g Group) bool) (Group, error) {
// Get operating system-specific group reader-closer.
group, err := GetGroup()
if err != nil {
return Group{}, err
}
defer group.Close()
// Get the users.
groups, err := ParseGroupFilter(group, filter)
if err != nil {
return Group{}, err
}
// No user entries found.
if len(groups) == 0 {
return Group{}, ErrNoGroupEntries
}
// Assume the first entry is the "correct" one.
return groups[0], nil
}
func GetPasswdPath() (string, error) { func GetPasswdPath() (string, error) {
return unixPasswdPath, nil return unixPasswdPath, nil
} }
...@@ -28,3 +101,44 @@ func GetGroupPath() (string, error) { ...@@ -28,3 +101,44 @@ func GetGroupPath() (string, error) {
func GetGroup() (io.ReadCloser, error) { func GetGroup() (io.ReadCloser, error) {
return os.Open(unixGroupPath) return os.Open(unixGroupPath)
} }
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
// user cannot be found (or there is no /etc/passwd file on the filesystem),
// then CurrentUser returns an error.
func CurrentUser() (User, error) {
return LookupUid(unix.Getuid())
}
// CurrentGroup looks up the current user's group by their primary group id's
// entry in /etc/passwd. If the group cannot be found (or there is no
// /etc/group file on the filesystem), then CurrentGroup returns an error.
func CurrentGroup() (Group, error) {
return LookupGid(unix.Getgid())
}
func currentUserSubIDs(fileName string) ([]SubID, error) {
u, err := CurrentUser()
if err != nil {
return nil, err
}
filter := func(entry SubID) bool {
return entry.Name == u.Name || entry.Name == strconv.Itoa(u.Uid)
}
return ParseSubIDFileFilter(fileName, filter)
}
func CurrentUserSubUIDs() ([]SubID, error) {
return currentUserSubIDs("/etc/subuid")
}
func CurrentUserSubGIDs() ([]SubID, error) {
return currentUserSubIDs("/etc/subgid")
}
func CurrentProcessUIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/uid_map")
}
func CurrentProcessGIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/gid_map")
}
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package user
import "io"
func GetPasswdPath() (string, error) {
return "", ErrUnsupported
}
func GetPasswd() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
func GetGroupPath() (string, error) {
return "", ErrUnsupported
}
func GetGroup() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
// +build windows
package user
import (
"fmt"
"os/user"
)
func lookupUser(username string) (User, error) {
u, err := user.Lookup(username)
if err != nil {
return User{}, err
}
return userFromOS(u)
}
func lookupUid(uid int) (User, error) {
u, err := user.LookupId(fmt.Sprintf("%d", uid))
if err != nil {
return User{}, err
}
return userFromOS(u)
}
func lookupGroup(groupname string) (Group, error) {
g, err := user.LookupGroup(groupname)
if err != nil {
return Group{}, err
}
return groupFromOS(g)
}
func lookupGid(gid int) (Group, error) {
g, err := user.LookupGroupId(fmt.Sprintf("%d", gid))
if err != nil {
return Group{}, err
}
return groupFromOS(g)
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/user"
"strconv" "strconv"
"strings" "strings"
) )
...@@ -28,6 +29,28 @@ type User struct { ...@@ -28,6 +29,28 @@ type User struct {
Shell string Shell string
} }
// userFromOS converts an os/user.(*User) to local User
//
// (This does not include Pass, Shell or Gecos)
func userFromOS(u *user.User) (User, error) {
newUser := User{
Name: u.Username,
Home: u.HomeDir,
}
id, err := strconv.Atoi(u.Uid)
if err != nil {
return newUser, err
}
newUser.Uid = id
id, err = strconv.Atoi(u.Gid)
if err != nil {
return newUser, err
}
newUser.Gid = id
return newUser, nil
}
type Group struct { type Group struct {
Name string Name string
Pass string Pass string
...@@ -35,12 +58,46 @@ type Group struct { ...@@ -35,12 +58,46 @@ type Group struct {
List []string List []string
} }
// groupFromOS converts an os/user.(*Group) to local Group
//
// (This does not include Pass, Shell or Gecos)
func groupFromOS(g *user.Group) (Group, error) {
newGroup := Group{
Name: g.Name,
}
id, err := strconv.Atoi(g.Gid)
if err != nil {
return newGroup, err
}
newGroup.Gid = id
return newGroup, nil
}
// SubID represents an entry in /etc/sub{u,g}id
type SubID struct {
Name string
SubID int64
Count int64
}
// IDMap represents an entry in /proc/PID/{u,g}id_map
type IDMap struct {
ID int64
ParentID int64
Count int64
}
func parseLine(line string, v ...interface{}) { func parseLine(line string, v ...interface{}) {
if line == "" { parseParts(strings.Split(line, ":"), v...)
}
func parseParts(parts []string, v ...interface{}) {
if len(parts) == 0 {
return return
} }
parts := strings.Split(line, ":")
for i, p := range parts { for i, p := range parts {
// Ignore cases where we don't have enough fields to populate the arguments. // Ignore cases where we don't have enough fields to populate the arguments.
// Some configuration files like to misbehave. // Some configuration files like to misbehave.
...@@ -56,6 +113,8 @@ func parseLine(line string, v ...interface{}) { ...@@ -56,6 +113,8 @@ func parseLine(line string, v ...interface{}) {
case *int: case *int:
// "numbers", with conversion errors ignored because of some misbehaving configuration files. // "numbers", with conversion errors ignored because of some misbehaving configuration files.
*e, _ = strconv.Atoi(p) *e, _ = strconv.Atoi(p)
case *int64:
*e, _ = strconv.ParseInt(p, 10, 64)
case *[]string: case *[]string:
// Comma-separated lists. // Comma-separated lists.
if p != "" { if p != "" {
...@@ -65,7 +124,7 @@ func parseLine(line string, v ...interface{}) { ...@@ -65,7 +124,7 @@ func parseLine(line string, v ...interface{}) {
} }
default: default:
// Someone goof'd when writing code using this function. Scream so they can hear us. // Someone goof'd when writing code using this function. Scream so they can hear us.
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e)) panic(fmt.Sprintf("parseLine only accepts {*string, *int, *int64, *[]string} as arguments! %#v is not a pointer!", e))
} }
} }
} }
...@@ -103,10 +162,6 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { ...@@ -103,10 +162,6 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text()) line := strings.TrimSpace(s.Text())
if line == "" { if line == "" {
continue continue
...@@ -124,6 +179,9 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { ...@@ -124,6 +179,9 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }
...@@ -162,10 +220,6 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { ...@@ -162,10 +220,6 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text() text := s.Text()
if text == "" { if text == "" {
continue continue
...@@ -183,6 +237,9 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { ...@@ -183,6 +237,9 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }
...@@ -199,18 +256,16 @@ type ExecUser struct { ...@@ -199,18 +256,16 @@ type ExecUser struct {
// files cannot be opened for any reason, the error is ignored and a nil // files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead. // io.Reader is passed instead.
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
passwd, err := os.Open(passwdPath) var passwd, group io.Reader
if err != nil {
passwd = nil if passwdFile, err := os.Open(passwdPath); err == nil {
} else { passwd = passwdFile
defer passwd.Close() defer passwdFile.Close()
} }
group, err := os.Open(groupPath) if groupFile, err := os.Open(groupPath); err == nil {
if err != nil { group = groupFile
group = nil defer groupFile.Close()
} else {
defer group.Close()
} }
return GetExecUser(userSpec, defaults, passwd, group) return GetExecUser(userSpec, defaults, passwd, group)
...@@ -343,7 +398,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( ...@@ -343,7 +398,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
if len(groups) > 0 { if len(groups) > 0 {
// First match wins, even if there's more than one matching entry. // First match wins, even if there's more than one matching entry.
user.Gid = groups[0].Gid user.Gid = groups[0].Gid
} else if groupArg != "" { } else {
// If we can't find a group with the given name, the only other valid // If we can't find a group with the given name, the only other valid
// option is if it's a numeric group name with no associated entry in group. // option is if it's a numeric group name with no associated entry in group.
...@@ -433,9 +488,117 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err ...@@ -433,9 +488,117 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err
// that opens the groupPath given and gives it as an argument to // that opens the groupPath given and gives it as an argument to
// GetAdditionalGroups. // GetAdditionalGroups.
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) { func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
group, err := os.Open(groupPath) var group io.Reader
if err == nil {
defer group.Close() if groupFile, err := os.Open(groupPath); err == nil {
group = groupFile
defer groupFile.Close()
} }
return GetAdditionalGroups(additionalGroups, group) return GetAdditionalGroups(additionalGroups, group)
} }
func ParseSubIDFile(path string) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubID(subid)
}
func ParseSubID(subid io.Reader) ([]SubID, error) {
return ParseSubIDFilter(subid, nil)
}
func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubIDFilter(subid, filter)
}
func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
if r == nil {
return nil, fmt.Errorf("nil source for subid-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []SubID{}
)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
// see: man 5 subuid
p := SubID{}
parseLine(line, &p.Name, &p.SubID, &p.Count)
if filter == nil || filter(p) {
out = append(out, p)
}
}
if err := s.Err(); err != nil {
return nil, err
}
return out, nil
}
func ParseIDMapFile(path string) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMap(r)
}
func ParseIDMap(r io.Reader) ([]IDMap, error) {
return ParseIDMapFilter(r, nil)
}
func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMapFilter(r, filter)
}
func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
if r == nil {
return nil, fmt.Errorf("nil source for idmap-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []IDMap{}
)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
// see: man 7 user_namespaces
p := IDMap{}
parseParts(strings.Fields(line), &p.ID, &p.ParentID, &p.Count)
if filter == nil || filter(p) {
out = append(out, p)
}
}
if err := s.Err(); err != nil {
return nil, err
}
return out, nil
}
package sgx // import "github.com/opencontainers/runc/libenclave/attestation/sgx"
// RA Type
const (
UnknownRaType = iota
EPID
DCAP
)
// RA Enclave Type
const (
InvalidEnclaveType = iota
DebugEnclave
ProductEnclave
)
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: agent-service.proto
package libenclave_proto
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type AgentServiceRequest struct {
Exec *AgentServiceRequest_Execute `protobuf:"bytes,1,opt,name=exec,proto3" json:"exec,omitempty"`
Kill *AgentServiceRequest_Kill `protobuf:"bytes,2,opt,name=kill,proto3" json:"kill,omitempty"`
Attest *AgentServiceRequest_Attest `protobuf:"bytes,3,opt,name=attest,proto3" json:"attest,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceRequest) Reset() { *m = AgentServiceRequest{} }
func (m *AgentServiceRequest) String() string { return proto.CompactTextString(m) }
func (*AgentServiceRequest) ProtoMessage() {}
func (*AgentServiceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{0}
}
func (m *AgentServiceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceRequest.Unmarshal(m, b)
}
func (m *AgentServiceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceRequest.Marshal(b, m, deterministic)
}
func (m *AgentServiceRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceRequest.Merge(m, src)
}
func (m *AgentServiceRequest) XXX_Size() int {
return xxx_messageInfo_AgentServiceRequest.Size(m)
}
func (m *AgentServiceRequest) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceRequest.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceRequest proto.InternalMessageInfo
func (m *AgentServiceRequest) GetExec() *AgentServiceRequest_Execute {
if m != nil {
return m.Exec
}
return nil
}
func (m *AgentServiceRequest) GetKill() *AgentServiceRequest_Kill {
if m != nil {
return m.Kill
}
return nil
}
func (m *AgentServiceRequest) GetAttest() *AgentServiceRequest_Attest {
if m != nil {
return m.Attest
}
return nil
}
type AgentServiceRequest_Execute struct {
Argv string `protobuf:"bytes,1,opt,name=argv,proto3" json:"argv,omitempty"`
Envp string `protobuf:"bytes,2,opt,name=envp,proto3" json:"envp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceRequest_Execute) Reset() { *m = AgentServiceRequest_Execute{} }
func (m *AgentServiceRequest_Execute) String() string { return proto.CompactTextString(m) }
func (*AgentServiceRequest_Execute) ProtoMessage() {}
func (*AgentServiceRequest_Execute) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{0, 0}
}
func (m *AgentServiceRequest_Execute) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceRequest_Execute.Unmarshal(m, b)
}
func (m *AgentServiceRequest_Execute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceRequest_Execute.Marshal(b, m, deterministic)
}
func (m *AgentServiceRequest_Execute) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceRequest_Execute.Merge(m, src)
}
func (m *AgentServiceRequest_Execute) XXX_Size() int {
return xxx_messageInfo_AgentServiceRequest_Execute.Size(m)
}
func (m *AgentServiceRequest_Execute) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceRequest_Execute.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceRequest_Execute proto.InternalMessageInfo
func (m *AgentServiceRequest_Execute) GetArgv() string {
if m != nil {
return m.Argv
}
return ""
}
func (m *AgentServiceRequest_Execute) GetEnvp() string {
if m != nil {
return m.Envp
}
return ""
}
type AgentServiceRequest_Kill struct {
Sig int32 `protobuf:"varint,1,opt,name=sig,proto3" json:"sig,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceRequest_Kill) Reset() { *m = AgentServiceRequest_Kill{} }
func (m *AgentServiceRequest_Kill) String() string { return proto.CompactTextString(m) }
func (*AgentServiceRequest_Kill) ProtoMessage() {}
func (*AgentServiceRequest_Kill) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{0, 1}
}
func (m *AgentServiceRequest_Kill) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceRequest_Kill.Unmarshal(m, b)
}
func (m *AgentServiceRequest_Kill) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceRequest_Kill.Marshal(b, m, deterministic)
}
func (m *AgentServiceRequest_Kill) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceRequest_Kill.Merge(m, src)
}
func (m *AgentServiceRequest_Kill) XXX_Size() int {
return xxx_messageInfo_AgentServiceRequest_Kill.Size(m)
}
func (m *AgentServiceRequest_Kill) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceRequest_Kill.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceRequest_Kill proto.InternalMessageInfo
func (m *AgentServiceRequest_Kill) GetSig() int32 {
if m != nil {
return m.Sig
}
return 0
}
type AgentServiceRequest_Attest struct {
Spid string `protobuf:"bytes,1,opt,name=spid,proto3" json:"spid,omitempty"`
SubscriptionKey string `protobuf:"bytes,2,opt,name=subscriptionKey,proto3" json:"subscriptionKey,omitempty"`
Product uint32 `protobuf:"varint,3,opt,name=product,proto3" json:"product,omitempty"`
QuoteType uint32 `protobuf:"varint,4,opt,name=quoteType,proto3" json:"quoteType,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceRequest_Attest) Reset() { *m = AgentServiceRequest_Attest{} }
func (m *AgentServiceRequest_Attest) String() string { return proto.CompactTextString(m) }
func (*AgentServiceRequest_Attest) ProtoMessage() {}
func (*AgentServiceRequest_Attest) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{0, 2}
}
func (m *AgentServiceRequest_Attest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceRequest_Attest.Unmarshal(m, b)
}
func (m *AgentServiceRequest_Attest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceRequest_Attest.Marshal(b, m, deterministic)
}
func (m *AgentServiceRequest_Attest) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceRequest_Attest.Merge(m, src)
}
func (m *AgentServiceRequest_Attest) XXX_Size() int {
return xxx_messageInfo_AgentServiceRequest_Attest.Size(m)
}
func (m *AgentServiceRequest_Attest) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceRequest_Attest.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceRequest_Attest proto.InternalMessageInfo
func (m *AgentServiceRequest_Attest) GetSpid() string {
if m != nil {
return m.Spid
}
return ""
}
func (m *AgentServiceRequest_Attest) GetSubscriptionKey() string {
if m != nil {
return m.SubscriptionKey
}
return ""
}
func (m *AgentServiceRequest_Attest) GetProduct() uint32 {
if m != nil {
return m.Product
}
return 0
}
func (m *AgentServiceRequest_Attest) GetQuoteType() uint32 {
if m != nil {
return m.QuoteType
}
return 0
}
type AgentServiceResponse struct {
Exec *AgentServiceResponse_Execute `protobuf:"bytes,1,opt,name=exec,proto3" json:"exec,omitempty"`
Attest *AgentServiceResponse_Attest `protobuf:"bytes,2,opt,name=attest,proto3" json:"attest,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceResponse) Reset() { *m = AgentServiceResponse{} }
func (m *AgentServiceResponse) String() string { return proto.CompactTextString(m) }
func (*AgentServiceResponse) ProtoMessage() {}
func (*AgentServiceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{1}
}
func (m *AgentServiceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceResponse.Unmarshal(m, b)
}
func (m *AgentServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceResponse.Marshal(b, m, deterministic)
}
func (m *AgentServiceResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceResponse.Merge(m, src)
}
func (m *AgentServiceResponse) XXX_Size() int {
return xxx_messageInfo_AgentServiceResponse.Size(m)
}
func (m *AgentServiceResponse) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceResponse.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceResponse proto.InternalMessageInfo
func (m *AgentServiceResponse) GetExec() *AgentServiceResponse_Execute {
if m != nil {
return m.Exec
}
return nil
}
func (m *AgentServiceResponse) GetAttest() *AgentServiceResponse_Attest {
if m != nil {
return m.Attest
}
return nil
}
type AgentServiceResponse_Execute struct {
ExitCode int32 `protobuf:"varint,1,opt,name=exitCode,proto3" json:"exitCode,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceResponse_Execute) Reset() { *m = AgentServiceResponse_Execute{} }
func (m *AgentServiceResponse_Execute) String() string { return proto.CompactTextString(m) }
func (*AgentServiceResponse_Execute) ProtoMessage() {}
func (*AgentServiceResponse_Execute) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{1, 0}
}
func (m *AgentServiceResponse_Execute) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceResponse_Execute.Unmarshal(m, b)
}
func (m *AgentServiceResponse_Execute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceResponse_Execute.Marshal(b, m, deterministic)
}
func (m *AgentServiceResponse_Execute) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceResponse_Execute.Merge(m, src)
}
func (m *AgentServiceResponse_Execute) XXX_Size() int {
return xxx_messageInfo_AgentServiceResponse_Execute.Size(m)
}
func (m *AgentServiceResponse_Execute) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceResponse_Execute.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceResponse_Execute proto.InternalMessageInfo
func (m *AgentServiceResponse_Execute) GetExitCode() int32 {
if m != nil {
return m.ExitCode
}
return 0
}
func (m *AgentServiceResponse_Execute) GetError() string {
if m != nil {
return m.Error
}
return ""
}
type AgentServiceResponse_Attest struct {
ExitCode int32 `protobuf:"varint,1,opt,name=exitCode,proto3" json:"exitCode,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
StatusCode string `protobuf:"bytes,3,opt,name=statusCode,proto3" json:"statusCode,omitempty"`
RequestID string `protobuf:"bytes,4,opt,name=requestID,proto3" json:"requestID,omitempty"`
XIasreportSignature string `protobuf:"bytes,5,opt,name=xIasreportSignature,proto3" json:"xIasreportSignature,omitempty"`
XIasreportSigningCertificate string `protobuf:"bytes,6,opt,name=xIasreportSigningCertificate,proto3" json:"xIasreportSigningCertificate,omitempty"`
ContentLength string `protobuf:"bytes,7,opt,name=contentLength,proto3" json:"contentLength,omitempty"`
ContentType string `protobuf:"bytes,8,opt,name=contentType,proto3" json:"contentType,omitempty"`
Body string `protobuf:"bytes,9,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AgentServiceResponse_Attest) Reset() { *m = AgentServiceResponse_Attest{} }
func (m *AgentServiceResponse_Attest) String() string { return proto.CompactTextString(m) }
func (*AgentServiceResponse_Attest) ProtoMessage() {}
func (*AgentServiceResponse_Attest) Descriptor() ([]byte, []int) {
return fileDescriptor_145c985d4df28d67, []int{1, 1}
}
func (m *AgentServiceResponse_Attest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AgentServiceResponse_Attest.Unmarshal(m, b)
}
func (m *AgentServiceResponse_Attest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AgentServiceResponse_Attest.Marshal(b, m, deterministic)
}
func (m *AgentServiceResponse_Attest) XXX_Merge(src proto.Message) {
xxx_messageInfo_AgentServiceResponse_Attest.Merge(m, src)
}
func (m *AgentServiceResponse_Attest) XXX_Size() int {
return xxx_messageInfo_AgentServiceResponse_Attest.Size(m)
}
func (m *AgentServiceResponse_Attest) XXX_DiscardUnknown() {
xxx_messageInfo_AgentServiceResponse_Attest.DiscardUnknown(m)
}
var xxx_messageInfo_AgentServiceResponse_Attest proto.InternalMessageInfo
func (m *AgentServiceResponse_Attest) GetExitCode() int32 {
if m != nil {
return m.ExitCode
}
return 0
}
func (m *AgentServiceResponse_Attest) GetError() string {
if m != nil {
return m.Error
}
return ""
}
func (m *AgentServiceResponse_Attest) GetStatusCode() string {
if m != nil {
return m.StatusCode
}
return ""
}
func (m *AgentServiceResponse_Attest) GetRequestID() string {
if m != nil {
return m.RequestID
}
return ""
}
func (m *AgentServiceResponse_Attest) GetXIasreportSignature() string {
if m != nil {
return m.XIasreportSignature
}
return ""
}
func (m *AgentServiceResponse_Attest) GetXIasreportSigningCertificate() string {
if m != nil {
return m.XIasreportSigningCertificate
}
return ""
}
func (m *AgentServiceResponse_Attest) GetContentLength() string {
if m != nil {
return m.ContentLength
}
return ""
}
func (m *AgentServiceResponse_Attest) GetContentType() string {
if m != nil {
return m.ContentType
}
return ""
}
func (m *AgentServiceResponse_Attest) GetBody() string {
if m != nil {
return m.Body
}
return ""
}
func init() {
proto.RegisterType((*AgentServiceRequest)(nil), "libenclave_proto.AgentServiceRequest")
proto.RegisterType((*AgentServiceRequest_Execute)(nil), "libenclave_proto.AgentServiceRequest.Execute")
proto.RegisterType((*AgentServiceRequest_Kill)(nil), "libenclave_proto.AgentServiceRequest.Kill")
proto.RegisterType((*AgentServiceRequest_Attest)(nil), "libenclave_proto.AgentServiceRequest.Attest")
proto.RegisterType((*AgentServiceResponse)(nil), "libenclave_proto.AgentServiceResponse")
proto.RegisterType((*AgentServiceResponse_Execute)(nil), "libenclave_proto.AgentServiceResponse.Execute")
proto.RegisterType((*AgentServiceResponse_Attest)(nil), "libenclave_proto.AgentServiceResponse.Attest")
}
func init() {
proto.RegisterFile("agent-service.proto", fileDescriptor_145c985d4df28d67)
}
var fileDescriptor_145c985d4df28d67 = []byte{
// 455 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x6a, 0xdb, 0x40,
0x10, 0xc6, 0x89, 0x2d, 0xdb, 0xd1, 0x84, 0xd0, 0xb0, 0xce, 0x61, 0x11, 0xa1, 0x84, 0xd0, 0x43,
0x28, 0x8d, 0xe8, 0x9f, 0x63, 0xa1, 0xe0, 0xfc, 0x39, 0x84, 0xf4, 0xb4, 0xe9, 0xbd, 0xc8, 0xf2,
0x54, 0x5d, 0x2a, 0x76, 0x95, 0xdd, 0x91, 0xb1, 0x2f, 0x7d, 0xa1, 0xbc, 0x49, 0x9f, 0xaa, 0xec,
0x48, 0xb1, 0x63, 0x61, 0x5a, 0xe7, 0x36, 0xfb, 0xed, 0xfc, 0x3e, 0x76, 0xbf, 0x19, 0x18, 0x67,
0x05, 0x1a, 0xba, 0xf0, 0xe8, 0xe6, 0x3a, 0xc7, 0xb4, 0x72, 0x96, 0xac, 0x38, 0x2a, 0xf5, 0x14,
0x4d, 0x5e, 0x66, 0x73, 0xfc, 0xce, 0xca, 0xd9, 0x63, 0x1f, 0xc6, 0x93, 0xd0, 0x79, 0xdf, 0x34,
0x2a, 0x7c, 0xa8, 0xd1, 0x93, 0x98, 0x40, 0x84, 0x0b, 0xcc, 0xe5, 0xde, 0xe9, 0xde, 0xf9, 0xc1,
0xc7, 0x8b, 0xb4, 0x0b, 0xa6, 0x5b, 0xa0, 0xf4, 0x66, 0x81, 0x79, 0x4d, 0xa8, 0x18, 0x15, 0x5f,
0x20, 0xfa, 0xa5, 0xcb, 0x52, 0xf6, 0xd8, 0xe2, 0xed, 0x6e, 0x16, 0x77, 0xba, 0x2c, 0x15, 0x73,
0xe2, 0x1a, 0x86, 0x19, 0x11, 0x7a, 0x92, 0x7d, 0x76, 0x78, 0xb7, 0x9b, 0xc3, 0x84, 0x19, 0xd5,
0xb2, 0xc9, 0x07, 0x18, 0xb5, 0xcf, 0x12, 0x02, 0xa2, 0xcc, 0x15, 0x73, 0xfe, 0x53, 0xac, 0xb8,
0x0e, 0x1a, 0x9a, 0x79, 0xc5, 0x8f, 0x8c, 0x15, 0xd7, 0x89, 0x84, 0x28, 0x3c, 0x43, 0x1c, 0x41,
0xdf, 0xeb, 0x82, 0xdb, 0x07, 0x2a, 0x94, 0xc9, 0x6f, 0x18, 0x36, 0xf6, 0x81, 0xf3, 0x95, 0x9e,
0x3d, 0x79, 0x85, 0x5a, 0x9c, 0xc3, 0x2b, 0x5f, 0x4f, 0x7d, 0xee, 0x74, 0x45, 0xda, 0x9a, 0x3b,
0x5c, 0xb6, 0xb6, 0x5d, 0x59, 0x48, 0x18, 0x55, 0xce, 0xce, 0xea, 0xbc, 0xf9, 0xdb, 0xa1, 0x7a,
0x3a, 0x8a, 0x13, 0x88, 0x1f, 0x6a, 0x4b, 0xf8, 0x6d, 0x59, 0xa1, 0x8c, 0xf8, 0x6e, 0x2d, 0x9c,
0x3d, 0x46, 0x70, 0xbc, 0xf9, 0x67, 0x5f, 0x59, 0xe3, 0x51, 0x5c, 0x6e, 0x8c, 0x2b, 0xfd, 0x5f,
0x52, 0x0d, 0xd5, 0x99, 0xd7, 0xcd, 0x2a, 0xef, 0xde, 0x6e, 0x43, 0x6f, 0x5d, 0x3a, 0x81, 0x7f,
0x5e, 0x07, 0x9e, 0xc0, 0x3e, 0x2e, 0x34, 0x5d, 0xd9, 0x19, 0xb6, 0x29, 0xae, 0xce, 0xe2, 0x18,
0x06, 0xe8, 0x9c, 0x75, 0x6d, 0x44, 0xcd, 0x21, 0xf9, 0xd3, 0x5b, 0x25, 0xfc, 0x62, 0x58, 0xbc,
0x06, 0xf0, 0x94, 0x51, 0xed, 0x99, 0xe9, 0xf3, 0xd5, 0x33, 0x25, 0x64, 0xeb, 0x9a, 0x25, 0xb9,
0xbd, 0xe6, 0x6c, 0x63, 0xb5, 0x16, 0xc4, 0x7b, 0x18, 0x2f, 0x6e, 0x33, 0xef, 0xb0, 0xb2, 0x8e,
0xee, 0x75, 0x61, 0x32, 0xaa, 0x1d, 0xca, 0x01, 0xf7, 0x6d, 0xbb, 0x12, 0x97, 0x70, 0xb2, 0x29,
0x6b, 0x53, 0x5c, 0xa1, 0x23, 0xfd, 0x43, 0xe7, 0x19, 0xa1, 0x1c, 0x32, 0xfa, 0xcf, 0x1e, 0xf1,
0x06, 0x0e, 0x73, 0x6b, 0x08, 0x0d, 0x7d, 0x45, 0x53, 0xd0, 0x4f, 0x39, 0x62, 0x68, 0x53, 0x14,
0xa7, 0x70, 0xd0, 0x0a, 0xbc, 0x17, 0xfb, 0xdc, 0xf3, 0x5c, 0x0a, 0xfb, 0x38, 0xb5, 0xb3, 0xa5,
0x8c, 0x9b, 0x7d, 0x0c, 0xf5, 0x74, 0xc8, 0x43, 0xfb, 0xf4, 0x37, 0x00, 0x00, 0xff, 0xff, 0xd5,
0xb7, 0xb8, 0x57, 0x0b, 0x04, 0x00, 0x00,
}
syntax = "proto3";
package libenclave_proto;
message AgentServiceRequest{
message Execute {
string argv = 1;
string envp = 2;
}
message Kill {
int32 sig = 1;
}
message Attest {
string spid = 1;
string subscriptionKey = 2;
uint32 product = 3;
uint32 quoteType = 4;
}
Execute exec = 1;
Kill kill = 2;
Attest attest = 3;
}
message AgentServiceResponse {
message Execute {
int32 exitCode = 1;
string error = 2;
}
message Attest {
int32 exitCode = 1;
string error = 2;
string statusCode = 3;
string requestID = 4;
string xIasreportSignature = 5;
string xIasreportSigningCertificate = 6;
string contentLength = 7;
string contentType = 8;
string body = 9;
}
Execute exec = 1;
Attest attest = 2;
}
syntax = "proto2";
package libenclave_proto;
message AgentServiceRequest{
message Execute {
required string argv = 1;
required string envp = 2;
}
message Kill {
required int32 sig = 1;
}
optional Execute exec = 1;
optional Kill kill = 2;
}
message AgentServiceResponse {
message Execute {
required int32 exitCode = 1;
required string error = 2;
}
optional Execute exec = 1;
}
*.out
*.swp
*.8
*.6
_obj
_test*
markdown
tags
sudo: false
language: go
go:
- "1.10.x"
- "1.11.x"
- tip
matrix:
fast_finish: true
allow_failures:
- go: tip
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v ./...
Blackfriday is distributed under the Simplified BSD License:
> Copyright © 2011 Russ Ross
> All rights reserved.
>
> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions
> are met:
>
> 1. Redistributions of source code must retain the above copyright
> notice, this list of conditions and the following disclaimer.
>
> 2. Redistributions in binary form must reproduce the above
> copyright notice, this list of conditions and the following
> disclaimer in the documentation and/or other materials provided with
> the distribution.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> POSSIBILITY OF SUCH DAMAGE.
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday)
===========
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
is paranoid about its input (so you can safely feed it user-supplied
data), it is fast, it supports common extensions (tables, smart
punctuation substitutions, etc.), and it is safe for all utf-8
(unicode) input.
HTML output is currently supported, along with Smartypants
extensions.
It started as a translation from C of [Sundown][3].
Installation
------------
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
installed:
go get gopkg.in/russross/blackfriday.v2
will download, compile, and install the package into your `$GOPATH`
directory hierarchy. Alternatively, you can achieve the same if you
import it into a project:
import "gopkg.in/russross/blackfriday.v2"
and `go get` without parameters.
Versions
--------
Currently maintained and recommended version of Blackfriday is `v2`. It's being
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
documentation is available at
https://godoc.org/gopkg.in/russross/blackfriday.v2.
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
but we highly recommend using package management tool like [dep][7] or
[Glide][8] and make use of semantic versioning. With package management you
should import `github.com/russross/blackfriday` and specify that you're using
version 2.0.0.
Version 2 offers a number of improvements over v1:
* Cleaned up API
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
the document
* Latest bug fixes
* Flexibility to easily add your own rendering extensions
Potential drawbacks:
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
ballpark of around 15%.
* API breakage. If you can't afford modifying your code to adhere to the new API
and don't care too much about the new features, v2 is probably not for you.
* Several bug fixes are trailing behind and still need to be forward-ported to
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
tracking.
Usage
-----
For the most sensible markdown processing, it is as simple as getting your input
into a byte slice and calling:
```go
output := blackfriday.Run(input)
```
Your input will be parsed and the output rendered with a set of most popular
extensions enabled. If you want the most basic feature set, corresponding with
the bare Markdown specification, use:
```go
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
```
### Sanitize untrusted content
Blackfriday itself does nothing to protect against malicious content. If you are
dealing with user-supplied markdown, we recommend running Blackfriday's output
through HTML sanitizer such as [Bluemonday][5].
Here's an example of simple usage of Blackfriday together with Bluemonday:
```go
import (
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
)
// ...
unsafe := blackfriday.Run(input)
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
```
### Custom options
If you want to customize the set of options, use `blackfriday.WithExtensions`,
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
You can also check out `blackfriday-tool` for a more complete example
of how to use it. Download and install it using:
go get github.com/russross/blackfriday-tool
This is a simple command-line tool that allows you to process a
markdown file using a standalone program. You can also browse the
source directly on github if you are just looking for some example
code:
* <http://github.com/russross/blackfriday-tool>
Note that if you have not already done so, installing
`blackfriday-tool` will be sufficient to download and install
blackfriday in addition to the tool itself. The tool binary will be
installed in `$GOPATH/bin`. This is a statically-linked binary that
can be copied to wherever you need it without worrying about
dependencies and library versions.
Features
--------
All features of Sundown are supported, including:
* **Compatibility**. The Markdown v1.0.3 test suite passes with
the `--tidy` option. Without `--tidy`, the differences are
mostly in whitespace and entity escaping, where blackfriday is
more consistent and cleaner.
* **Common extensions**, including table support, fenced code
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
* **Safety**. Blackfriday is paranoid when parsing, making it safe
to feed untrusted user input without fear of bad things
happening. The test suite stress tests this and there are no
known inputs that make it crash. If you find one, please let me
know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to
protect yourself against JavaScript injection in untrusted content, see
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in
most web applications without having to cache the output.
* **Thread safety**. You can run multiple parsers in different
goroutines without ill effect. There is no dependence on global
shared state.
* **Minimal dependencies**. Blackfriday only depends on standard
library packages in Go. The source code is pretty
self-contained, so it is easy to add to any project, including
Google App Engine projects.
* **Standards compliant**. Output successfully validates using the
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
Extensions
----------
In addition to the standard markdown syntax, this package
implements the following extensions:
* **Intra-word emphasis supression**. The `_` character is
commonly used inside words when discussing code, so having
markdown interpret it as an emphasis command is usually the
wrong thing. Blackfriday lets you treat all emphasis markers as
normal characters when they occur inside a word.
* **Tables**. Tables can be created by drawing them in the input
using a simple syntax:
```
Name | Age
--------|------
Bob | 27
Alice | 23
```
* **Fenced code blocks**. In addition to the normal 4-space
indentation to mark code blocks, you can explicitly mark them
and supply a language (to make syntax highlighting simple). Just
mark it like this:
```go
func getTrue() bool {
return true
}
```
You can use 3 or more backticks to mark the beginning of the
block, and the same number to mark the end of the block.
* **Definition lists**. A simple definition list is made of a single-line
term followed by a colon and the definition for that term.
Cat
: Fluffy animal everyone likes
Internet
: Vector of transmission for pictures of cats
Terms must be separated from the previous definition by a blank line.
* **Footnotes**. A marker in the text that will become a superscript number;
a footnote definition that will be placed in a list of footnotes at the
end of the document. A footnote looks like this:
This is a footnote.[^1]
[^1]: the footnote text.
* **Autolinking**. Blackfriday can find URLs that have not been
explicitly marked as links and turn them into links.
* **Strikethrough**. Use two tildes (`~~`) to mark text that
should be crossed out.
* **Hard line breaks**. With this extension enabled newlines in the input
translate into line breaks in the output. This extension is off by default.
* **Smart quotes**. Smartypants-style punctuation substitution is
supported, turning normal double- and single-quote marks into
curly quotes, etc.
* **LaTeX-style dash parsing** is an additional option, where `--`
is translated into `&ndash;`, and `---` is translated into
`&mdash;`. This differs from most smartypants processors, which
turn a single hyphen into an ndash and a double hyphen into an
mdash.
* **Smart fractions**, where anything that looks like a fraction
is translated into suitable HTML (instead of just a few special
cases like most smartypant processors). For example, `4/5`
becomes `<sup>4</sup>&frasl;<sub>5</sub>`, which renders as
<sup>4</sup>&frasl;<sub>5</sub>.
Other renderers
---------------
Blackfriday is structured to allow alternative rendering engines. Here
are a few of note:
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
provides a GitHub Flavored Markdown renderer with fenced code block
highlighting, clickable heading anchor links.
It's not customizable, and its goal is to produce HTML output
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
except the rendering is performed locally.
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
renders output as LaTeX.
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
Todo
----
* More unit testing
* Improve unicode support. It does not understand all unicode
rules (about what constitutes a letter, a punctuation symbol,
etc.), so it may fail to detect word boundaries correctly in
some instances. It is safe on all utf-8 input.
License
-------
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
[2]: https://golang.org/ "Go Language"
[3]: https://github.com/vmg/sundown "Sundown"
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
[6]: https://labix.org/gopkg.in "gopkg.in"
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
// Functions to parse block-level elements.
//
package blackfriday
import (
"bytes"
"html"
"regexp"
"strings"
"github.com/shurcooL/sanitized_anchor_name"
)
const (
charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
)
var (
reBackslashOrAmp = regexp.MustCompile("[\\&]")
reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
)
// Parse block-level data.
// Note: this function and many that it calls assume that
// the input buffer ends with a newline.
func (p *Markdown) block(data []byte) {
// this is called recursively: enforce a maximum depth
if p.nesting >= p.maxNesting {
return
}
p.nesting++
// parse out one block-level construct at a time
for len(data) > 0 {
// prefixed heading:
//
// # Heading 1
// ## Heading 2
// ...
// ###### Heading 6
if p.isPrefixHeading(data) {
data = data[p.prefixHeading(data):]
continue
}
// block of preformatted HTML:
//
// <div>
// ...
// </div>
if data[0] == '<' {
if i := p.html(data, true); i > 0 {
data = data[i:]
continue
}
}
// title block
//
// % stuff
// % more stuff
// % even more stuff
if p.extensions&Titleblock != 0 {
if data[0] == '%' {
if i := p.titleBlock(data, true); i > 0 {
data = data[i:]
continue
}
}
}
// blank lines. note: returns the # of bytes to skip
if i := p.isEmpty(data); i > 0 {
data = data[i:]
continue
}
// indented code block:
//
// func max(a, b int) int {
// if a > b {
// return a
// }
// return b
// }
if p.codePrefix(data) > 0 {
data = data[p.code(data):]
continue
}
// fenced code block:
//
// ``` go
// func fact(n int) int {
// if n <= 1 {
// return n
// }
// return n * fact(n-1)
// }
// ```
if p.extensions&FencedCode != 0 {
if i := p.fencedCodeBlock(data, true); i > 0 {
data = data[i:]
continue
}
}
// horizontal rule:
//
// ------
// or
// ******
// or
// ______
if p.isHRule(data) {
p.addBlock(HorizontalRule, nil)
var i int
for i = 0; i < len(data) && data[i] != '\n'; i++ {
}
data = data[i:]
continue
}
// block quote:
//
// > A big quote I found somewhere
// > on the web
if p.quotePrefix(data) > 0 {
data = data[p.quote(data):]
continue
}
// table:
//
// Name | Age | Phone
// ------|-----|---------
// Bob | 31 | 555-1234
// Alice | 27 | 555-4321
if p.extensions&Tables != 0 {
if i := p.table(data); i > 0 {
data = data[i:]
continue
}
}
// an itemized/unordered list:
//
// * Item 1
// * Item 2
//
// also works with + or -
if p.uliPrefix(data) > 0 {
data = data[p.list(data, 0):]
continue
}
// a numbered/ordered list:
//
// 1. Item 1
// 2. Item 2
if p.oliPrefix(data) > 0 {
data = data[p.list(data, ListTypeOrdered):]
continue
}
// definition lists:
//
// Term 1
// : Definition a
// : Definition b
//
// Term 2
// : Definition c
if p.extensions&DefinitionLists != 0 {
if p.dliPrefix(data) > 0 {
data = data[p.list(data, ListTypeDefinition):]
continue
}
}
// anything else must look like a normal paragraph
// note: this finds underlined headings, too
data = data[p.paragraph(data):]
}
p.nesting--
}
func (p *Markdown) addBlock(typ NodeType, content []byte) *Node {
p.closeUnmatchedBlocks()
container := p.addChild(typ, 0)
container.content = content
return container
}
func (p *Markdown) isPrefixHeading(data []byte) bool {
if data[0] != '#' {
return false
}
if p.extensions&SpaceHeadings != 0 {
level := 0
for level < 6 && level < len(data) && data[level] == '#' {
level++
}
if level == len(data) || data[level] != ' ' {
return false
}
}
return true
}
func (p *Markdown) prefixHeading(data []byte) int {
level := 0
for level < 6 && level < len(data) && data[level] == '#' {
level++
}
i := skipChar(data, level, ' ')
end := skipUntilChar(data, i, '\n')
skip := end
id := ""
if p.extensions&HeadingIDs != 0 {
j, k := 0, 0
// find start/end of heading id
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
}
for k = j + 1; k < end && data[k] != '}'; k++ {
}
// extract heading id iff found
if j < end && k < end {
id = string(data[j+2 : k])
end = j
skip = k + 1
for end > 0 && data[end-1] == ' ' {
end--
}
}
}
for end > 0 && data[end-1] == '#' {
if isBackslashEscaped(data, end-1) {
break
}
end--
}
for end > 0 && data[end-1] == ' ' {
end--
}
if end > i {
if id == "" && p.extensions&AutoHeadingIDs != 0 {
id = sanitized_anchor_name.Create(string(data[i:end]))
}
block := p.addBlock(Heading, data[i:end])
block.HeadingID = id
block.Level = level
}
return skip
}
func (p *Markdown) isUnderlinedHeading(data []byte) int {
// test of level 1 heading
if data[0] == '=' {
i := skipChar(data, 1, '=')
i = skipChar(data, i, ' ')
if i < len(data) && data[i] == '\n' {
return 1
}
return 0
}
// test of level 2 heading
if data[0] == '-' {
i := skipChar(data, 1, '-')
i = skipChar(data, i, ' ')
if i < len(data) && data[i] == '\n' {
return 2
}
return 0
}
return 0
}
func (p *Markdown) titleBlock(data []byte, doRender bool) int {
if data[0] != '%' {
return 0
}
splitData := bytes.Split(data, []byte("\n"))
var i int
for idx, b := range splitData {
if !bytes.HasPrefix(b, []byte("%")) {
i = idx // - 1
break
}
}
data = bytes.Join(splitData[0:i], []byte("\n"))
consumed := len(data)
data = bytes.TrimPrefix(data, []byte("% "))
data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
block := p.addBlock(Heading, data)
block.Level = 1
block.IsTitleblock = true
return consumed
}
func (p *Markdown) html(data []byte, doRender bool) int {
var i, j int
// identify the opening tag
if data[0] != '<' {
return 0
}
curtag, tagfound := p.htmlFindTag(data[1:])
// handle special cases
if !tagfound {
// check for an HTML comment
if size := p.htmlComment(data, doRender); size > 0 {
return size
}
// check for an <hr> tag
if size := p.htmlHr(data, doRender); size > 0 {
return size
}
// no special case recognized
return 0
}
// look for an unindented matching closing tag
// followed by a blank line
found := false
/*
closetag := []byte("\n</" + curtag + ">")
j = len(curtag) + 1
for !found {
// scan for a closing tag at the beginning of a line
if skip := bytes.Index(data[j:], closetag); skip >= 0 {
j += skip + len(closetag)
} else {
break
}
// see if it is the only thing on the line
if skip := p.isEmpty(data[j:]); skip > 0 {
// see if it is followed by a blank line/eof
j += skip
if j >= len(data) {
found = true
i = j
} else {
if skip := p.isEmpty(data[j:]); skip > 0 {
j += skip
found = true
i = j
}
}
}
}
*/
// if not found, try a second pass looking for indented match
// but not if tag is "ins" or "del" (following original Markdown.pl)
if !found && curtag != "ins" && curtag != "del" {
i = 1
for i < len(data) {
i++
for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
i++
}
if i+2+len(curtag) >= len(data) {
break
}
j = p.htmlFindEnd(curtag, data[i-1:])
if j > 0 {
i += j - 1
found = true
break
}
}
}
if !found {
return 0
}
// the end of the block has been found
if doRender {
// trim newlines
end := i
for end > 0 && data[end-1] == '\n' {
end--
}
finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
}
return i
}
func finalizeHTMLBlock(block *Node) {
block.Literal = block.content
block.content = nil
}
// HTML comment, lax form
func (p *Markdown) htmlComment(data []byte, doRender bool) int {
i := p.inlineHTMLComment(data)
// needs to end with a blank line
if j := p.isEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim trailing newlines
end := size
for end > 0 && data[end-1] == '\n' {
end--
}
block := p.addBlock(HTMLBlock, data[:end])
finalizeHTMLBlock(block)
}
return size
}
return 0
}
// HR, which is the only self-closing block tag considered
func (p *Markdown) htmlHr(data []byte, doRender bool) int {
if len(data) < 4 {
return 0
}
if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
return 0
}
if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
// not an <hr> tag after all; at least not a valid one
return 0
}
i := 3
for i < len(data) && data[i] != '>' && data[i] != '\n' {
i++
}
if i < len(data) && data[i] == '>' {
i++
if j := p.isEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim newlines
end := size
for end > 0 && data[end-1] == '\n' {
end--
}
finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
}
return size
}
}
return 0
}
func (p *Markdown) htmlFindTag(data []byte) (string, bool) {
i := 0
for i < len(data) && isalnum(data[i]) {
i++
}
key := string(data[:i])
if _, ok := blockTags[key]; ok {
return key, true
}
return "", false
}
func (p *Markdown) htmlFindEnd(tag string, data []byte) int {
// assume data[0] == '<' && data[1] == '/' already tested
if tag == "hr" {
return 2
}
// check if tag is a match
closetag := []byte("</" + tag + ">")
if !bytes.HasPrefix(data, closetag) {
return 0
}
i := len(closetag)
// check that the rest of the line is blank
skip := 0
if skip = p.isEmpty(data[i:]); skip == 0 {
return 0
}
i += skip
skip = 0
if i >= len(data) {
return i
}
if p.extensions&LaxHTMLBlocks != 0 {
return i
}
if skip = p.isEmpty(data[i:]); skip == 0 {
// following line must be blank
return 0
}
return i + skip
}
func (*Markdown) isEmpty(data []byte) int {
// it is okay to call isEmpty on an empty buffer
if len(data) == 0 {
return 0
}
var i int
for i = 0; i < len(data) && data[i] != '\n'; i++ {
if data[i] != ' ' && data[i] != '\t' {
return 0
}
}
if i < len(data) && data[i] == '\n' {
i++
}
return i
}
func (*Markdown) isHRule(data []byte) bool {
i := 0
// skip up to three spaces
for i < 3 && data[i] == ' ' {
i++
}
// look at the hrule char
if data[i] != '*' && data[i] != '-' && data[i] != '_' {
return false
}
c := data[i]
// the whole line must be the char or whitespace
n := 0
for i < len(data) && data[i] != '\n' {
switch {
case data[i] == c:
n++
case data[i] != ' ':
return false
}
i++
}
return n >= 3
}
// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
// If info is not nil, it gets set to the syntax specified in the fence line.
func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
i, size := 0, 0
// skip up to three spaces
for i < len(data) && i < 3 && data[i] == ' ' {
i++
}
// check for the marker characters: ~ or `
if i >= len(data) {
return 0, ""
}
if data[i] != '~' && data[i] != '`' {
return 0, ""
}
c := data[i]
// the whole line must be the same char or whitespace
for i < len(data) && data[i] == c {
size++
i++
}
// the marker char must occur at least 3 times
if size < 3 {
return 0, ""
}
marker = string(data[i-size : i])
// if this is the end marker, it must match the beginning marker
if oldmarker != "" && marker != oldmarker {
return 0, ""
}
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
// into one, always get the info string, and discard it if the caller doesn't care.
if info != nil {
infoLength := 0
i = skipChar(data, i, ' ')
if i >= len(data) {
if i == len(data) {
return i, marker
}
return 0, ""
}
infoStart := i
if data[i] == '{' {
i++
infoStart++
for i < len(data) && data[i] != '}' && data[i] != '\n' {
infoLength++
i++
}
if i >= len(data) || data[i] != '}' {
return 0, ""
}
// strip all whitespace at the beginning and the end
// of the {} block
for infoLength > 0 && isspace(data[infoStart]) {
infoStart++
infoLength--
}
for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
infoLength--
}
i++
i = skipChar(data, i, ' ')
} else {
for i < len(data) && !isverticalspace(data[i]) {
infoLength++
i++
}
}
*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
}
if i == len(data) {
return i, marker
}
if i > len(data) || data[i] != '\n' {
return 0, ""
}
return i + 1, marker // Take newline into account.
}
// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
var info string
beg, marker := isFenceLine(data, &info, "")
if beg == 0 || beg >= len(data) {
return 0
}
var work bytes.Buffer
work.Write([]byte(info))
work.WriteByte('\n')
for {
// safe to assume beg < len(data)
// check for the end of the code block
fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
if fenceEnd != 0 {
beg += fenceEnd
break
}
// copy the current line
end := skipUntilChar(data, beg, '\n') + 1
// did we reach the end of the buffer without a closing marker?
if end >= len(data) {
return 0
}
// verbatim copy to the working buffer
if doRender {
work.Write(data[beg:end])
}
beg = end
}
if doRender {
block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
block.IsFenced = true
finalizeCodeBlock(block)
}
return beg
}
func unescapeChar(str []byte) []byte {
if str[0] == '\\' {
return []byte{str[1]}
}
return []byte(html.UnescapeString(string(str)))
}
func unescapeString(str []byte) []byte {
if reBackslashOrAmp.Match(str) {
return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
}
return str
}
func finalizeCodeBlock(block *Node) {
if block.IsFenced {
newlinePos := bytes.IndexByte(block.content, '\n')
firstLine := block.content[:newlinePos]
rest := block.content[newlinePos+1:]
block.Info = unescapeString(bytes.Trim(firstLine, "\n"))
block.Literal = rest
} else {
block.Literal = block.content
}
block.content = nil
}
func (p *Markdown) table(data []byte) int {
table := p.addBlock(Table, nil)
i, columns := p.tableHeader(data)
if i == 0 {
p.tip = table.Parent
table.Unlink()
return 0
}
p.addBlock(TableBody, nil)
for i < len(data) {
pipes, rowStart := 0, i
for ; i < len(data) && data[i] != '\n'; i++ {
if data[i] == '|' {
pipes++
}
}
if pipes == 0 {
i = rowStart
break
}
// include the newline in data sent to tableRow
if i < len(data) && data[i] == '\n' {
i++
}
p.tableRow(data[rowStart:i], columns, false)
}
return i
}
// check if the specified position is preceded by an odd number of backslashes
func isBackslashEscaped(data []byte, i int) bool {
backslashes := 0
for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
backslashes++
}
return backslashes&1 == 1
}
func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
i := 0
colCount := 1
for i = 0; i < len(data) && data[i] != '\n'; i++ {
if data[i] == '|' && !isBackslashEscaped(data, i) {
colCount++
}
}
// doesn't look like a table header
if colCount == 1 {
return
}
// include the newline in the data sent to tableRow
j := i
if j < len(data) && data[j] == '\n' {
j++
}
header := data[:j]
// column count ignores pipes at beginning or end of line
if data[0] == '|' {
colCount--
}
if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
colCount--
}
columns = make([]CellAlignFlags, colCount)
// move on to the header underline
i++
if i >= len(data) {
return
}
if data[i] == '|' && !isBackslashEscaped(data, i) {
i++
}
i = skipChar(data, i, ' ')
// each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
// and trailing | optional on last column
col := 0
for i < len(data) && data[i] != '\n' {
dashes := 0
if data[i] == ':' {
i++
columns[col] |= TableAlignmentLeft
dashes++
}
for i < len(data) && data[i] == '-' {
i++
dashes++
}
if i < len(data) && data[i] == ':' {
i++
columns[col] |= TableAlignmentRight
dashes++
}
for i < len(data) && data[i] == ' ' {
i++
}
if i == len(data) {
return
}
// end of column test is messy
switch {
case dashes < 3:
// not a valid column
return
case data[i] == '|' && !isBackslashEscaped(data, i):
// marker found, now skip past trailing whitespace
col++
i++
for i < len(data) && data[i] == ' ' {
i++
}
// trailing junk found after last column
if col >= colCount && i < len(data) && data[i] != '\n' {
return
}
case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
// something else found where marker was required
return
case data[i] == '\n':
// marker is optional for the last column
col++
default:
// trailing junk found after last column
return
}
}
if col != colCount {
return
}
p.addBlock(TableHead, nil)
p.tableRow(header, columns, true)
size = i
if size < len(data) && data[size] == '\n' {
size++
}
return
}
func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) {
p.addBlock(TableRow, nil)
i, col := 0, 0
if data[i] == '|' && !isBackslashEscaped(data, i) {
i++
}
for col = 0; col < len(columns) && i < len(data); col++ {
for i < len(data) && data[i] == ' ' {
i++
}
cellStart := i
for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
i++
}
cellEnd := i
// skip the end-of-cell marker, possibly taking us past end of buffer
i++
for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' {
cellEnd--
}
cell := p.addBlock(TableCell, data[cellStart:cellEnd])
cell.IsHeader = header
cell.Align = columns[col]
}
// pad it out with empty columns to get the right number
for ; col < len(columns); col++ {
cell := p.addBlock(TableCell, nil)
cell.IsHeader = header
cell.Align = columns[col]
}
// silently ignore rows with too many cells
}
// returns blockquote prefix length
func (p *Markdown) quotePrefix(data []byte) int {
i := 0
for i < 3 && i < len(data) && data[i] == ' ' {
i++
}
if i < len(data) && data[i] == '>' {
if i+1 < len(data) && data[i+1] == ' ' {
return i + 2
}
return i + 1
}
return 0
}
// blockquote ends with at least one blank line
// followed by something without a blockquote prefix
func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool {
if p.isEmpty(data[beg:]) <= 0 {
return false
}
if end >= len(data) {
return true
}
return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
}
// parse a blockquote fragment
func (p *Markdown) quote(data []byte) int {
block := p.addBlock(BlockQuote, nil)
var raw bytes.Buffer
beg, end := 0, 0
for beg < len(data) {
end = beg
// Step over whole lines, collecting them. While doing that, check for
// fenced code and if one's found, incorporate it altogether,
// irregardless of any contents inside it
for end < len(data) && data[end] != '\n' {
if p.extensions&FencedCode != 0 {
if i := p.fencedCodeBlock(data[end:], false); i > 0 {
// -1 to compensate for the extra end++ after the loop:
end += i - 1
break
}
}
end++
}
if end < len(data) && data[end] == '\n' {
end++
}
if pre := p.quotePrefix(data[beg:]); pre > 0 {
// skip the prefix
beg += pre
} else if p.terminateBlockquote(data, beg, end) {
break
}
// this line is part of the blockquote
raw.Write(data[beg:end])
beg = end
}
p.block(raw.Bytes())
p.finalize(block)
return end
}
// returns prefix length for block code
func (p *Markdown) codePrefix(data []byte) int {
if len(data) >= 1 && data[0] == '\t' {
return 1
}
if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
return 4
}
return 0
}
func (p *Markdown) code(data []byte) int {
var work bytes.Buffer
i := 0
for i < len(data) {
beg := i
for i < len(data) && data[i] != '\n' {
i++
}
if i < len(data) && data[i] == '\n' {
i++
}
blankline := p.isEmpty(data[beg:i]) > 0
if pre := p.codePrefix(data[beg:i]); pre > 0 {
beg += pre
} else if !blankline {
// non-empty, non-prefixed line breaks the pre
i = beg
break
}
// verbatim copy to the working buffer
if blankline {
work.WriteByte('\n')
} else {
work.Write(data[beg:i])
}
}
// trim all the \n off the end of work
workbytes := work.Bytes()
eol := len(workbytes)
for eol > 0 && workbytes[eol-1] == '\n' {
eol--
}
if eol != len(workbytes) {
work.Truncate(eol)
}
work.WriteByte('\n')
block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
block.IsFenced = false
finalizeCodeBlock(block)
return i
}
// returns unordered list item prefix
func (p *Markdown) uliPrefix(data []byte) int {
i := 0
// start with up to 3 spaces
for i < len(data) && i < 3 && data[i] == ' ' {
i++
}
if i >= len(data)-1 {
return 0
}
// need one of {'*', '+', '-'} followed by a space or a tab
if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
(data[i+1] != ' ' && data[i+1] != '\t') {
return 0
}
return i + 2
}
// returns ordered list item prefix
func (p *Markdown) oliPrefix(data []byte) int {
i := 0
// start with up to 3 spaces
for i < 3 && i < len(data) && data[i] == ' ' {
i++
}
// count the digits
start := i
for i < len(data) && data[i] >= '0' && data[i] <= '9' {
i++
}
if start == i || i >= len(data)-1 {
return 0
}
// we need >= 1 digits followed by a dot and a space or a tab
if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
return 0
}
return i + 2
}
// returns definition list item prefix
func (p *Markdown) dliPrefix(data []byte) int {
if len(data) < 2 {
return 0
}
i := 0
// need a ':' followed by a space or a tab
if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
return 0
}
for i < len(data) && data[i] == ' ' {
i++
}
return i + 2
}
// parse ordered or unordered list block
func (p *Markdown) list(data []byte, flags ListType) int {
i := 0
flags |= ListItemBeginningOfList
block := p.addBlock(List, nil)
block.ListFlags = flags
block.Tight = true
for i < len(data) {
skip := p.listItem(data[i:], &flags)
if flags&ListItemContainsBlock != 0 {
block.ListData.Tight = false
}
i += skip
if skip == 0 || flags&ListItemEndOfList != 0 {
break
}
flags &= ^ListItemBeginningOfList
}
above := block.Parent
finalizeList(block)
p.tip = above
return i
}
// Returns true if the list item is not the same type as its parent list
func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
return true
} else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
return true
} else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
return true
}
return false
}
// Returns true if block ends with a blank line, descending if needed
// into lists and sublists.
func endsWithBlankLine(block *Node) bool {
// TODO: figure this out. Always false now.
for block != nil {
//if block.lastLineBlank {
//return true
//}
t := block.Type
if t == List || t == Item {
block = block.LastChild
} else {
break
}
}
return false
}
func finalizeList(block *Node) {
block.open = false
item := block.FirstChild
for item != nil {
// check for non-final list item ending with blank line:
if endsWithBlankLine(item) && item.Next != nil {
block.ListData.Tight = false
break
}
// recurse into children of list item, to see if there are spaces
// between any of them:
subItem := item.FirstChild
for subItem != nil {
if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) {
block.ListData.Tight = false
break
}
subItem = subItem.Next
}
item = item.Next
}
}
// Parse a single list item.
// Assumes initial prefix is already removed if this is a sublist.
func (p *Markdown) listItem(data []byte, flags *ListType) int {
// keep track of the indentation of the first line
itemIndent := 0
if data[0] == '\t' {
itemIndent += 4
} else {
for itemIndent < 3 && data[itemIndent] == ' ' {
itemIndent++
}
}
var bulletChar byte = '*'
i := p.uliPrefix(data)
if i == 0 {
i = p.oliPrefix(data)
} else {
bulletChar = data[i-2]
}
if i == 0 {
i = p.dliPrefix(data)
// reset definition term flag
if i > 0 {
*flags &= ^ListTypeTerm
}
}
if i == 0 {
// if in definition list, set term flag and continue
if *flags&ListTypeDefinition != 0 {
*flags |= ListTypeTerm
} else {
return 0
}
}
// skip leading whitespace on first line
for i < len(data) && data[i] == ' ' {
i++
}
// find the end of the line
line := i
for i > 0 && i < len(data) && data[i-1] != '\n' {
i++
}
// get working buffer
var raw bytes.Buffer
// put the first line into the working buffer
raw.Write(data[line:i])
line = i
// process the following lines
containsBlankLine := false
sublist := 0
codeBlockMarker := ""
gatherlines:
for line < len(data) {
i++
// find the end of this line
for i < len(data) && data[i-1] != '\n' {
i++
}
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[line:i]) > 0 {
containsBlankLine = true
line = i
continue
}
// calculate the indentation
indent := 0
indentIndex := 0
if data[line] == '\t' {
indentIndex++
indent += 4
} else {
for indent < 4 && line+indent < i && data[line+indent] == ' ' {
indent++
indentIndex++
}
}
chunk := data[line+indentIndex : i]
if p.extensions&FencedCode != 0 {
// determine if in or out of codeblock
// if in codeblock, ignore normal list processing
_, marker := isFenceLine(chunk, nil, codeBlockMarker)
if marker != "" {
if codeBlockMarker == "" {
// start of codeblock
codeBlockMarker = marker
} else {
// end of codeblock.
codeBlockMarker = ""
}
}
// we are in a codeblock, write line, and continue
if codeBlockMarker != "" || marker != "" {
raw.Write(data[line+indentIndex : i])
line = i
continue gatherlines
}
}
// evaluate how this line fits in
switch {
// is this a nested list item?
case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
p.oliPrefix(chunk) > 0 ||
p.dliPrefix(chunk) > 0:
// to be a nested list, it must be indented more
// if not, it is either a different kind of list
// or the next item in the same list
if indent <= itemIndent {
if p.listTypeChanged(chunk, flags) {
*flags |= ListItemEndOfList
} else if containsBlankLine {
*flags |= ListItemContainsBlock
}
break gatherlines
}
if containsBlankLine {
*flags |= ListItemContainsBlock
}
// is this the first item in the nested list?
if sublist == 0 {
sublist = raw.Len()
}
// is this a nested prefix heading?
case p.isPrefixHeading(chunk):
// if the heading is not indented, it is not nested in the list
// and thus ends the list
if containsBlankLine && indent < 4 {
*flags |= ListItemEndOfList
break gatherlines
}
*flags |= ListItemContainsBlock
// anything following an empty line is only part
// of this item if it is indented 4 spaces
// (regardless of the indentation of the beginning of the item)
case containsBlankLine && indent < 4:
if *flags&ListTypeDefinition != 0 && i < len(data)-1 {
// is the next item still a part of this list?
next := i
for next < len(data) && data[next] != '\n' {
next++
}
for next < len(data)-1 && data[next] == '\n' {
next++
}
if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
*flags |= ListItemEndOfList
}
} else {
*flags |= ListItemEndOfList
}
break gatherlines
// a blank line means this should be parsed as a block
case containsBlankLine:
raw.WriteByte('\n')
*flags |= ListItemContainsBlock
}
// if this line was preceded by one or more blanks,
// re-introduce the blank into the buffer
if containsBlankLine {
containsBlankLine = false
raw.WriteByte('\n')
}
// add the line into the working buffer without prefix
raw.Write(data[line+indentIndex : i])
line = i
}
rawBytes := raw.Bytes()
block := p.addBlock(Item, nil)
block.ListFlags = *flags
block.Tight = false
block.BulletChar = bulletChar
block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
// render the contents of the list item
if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 {
// intermediate render of block item, except for definition term
if sublist > 0 {
p.block(rawBytes[:sublist])
p.block(rawBytes[sublist:])
} else {
p.block(rawBytes)
}
} else {
// intermediate render of inline item
if sublist > 0 {
child := p.addChild(Paragraph, 0)
child.content = rawBytes[:sublist]
p.block(rawBytes[sublist:])
} else {
child := p.addChild(Paragraph, 0)
child.content = rawBytes
}
}
return line
}
// render a single paragraph that has already been parsed out
func (p *Markdown) renderParagraph(data []byte) {
if len(data) == 0 {
return
}
// trim leading spaces
beg := 0
for data[beg] == ' ' {
beg++
}
end := len(data)
// trim trailing newline
if data[len(data)-1] == '\n' {
end--
}
// trim trailing spaces
for end > beg && data[end-1] == ' ' {
end--
}
p.addBlock(Paragraph, data[beg:end])
}
func (p *Markdown) paragraph(data []byte) int {
// prev: index of 1st char of previous line
// line: index of 1st char of current line
// i: index of cursor/end of current line
var prev, line, i int
tabSize := TabSizeDefault
if p.extensions&TabSizeEight != 0 {
tabSize = TabSizeDouble
}
// keep going until we find something to mark the end of the paragraph
for i < len(data) {
// mark the beginning of the current line
prev = line
current := data[i:]
line = i
// did we find a reference or a footnote? If so, end a paragraph
// preceding it and report that we have consumed up to the end of that
// reference:
if refEnd := isReference(p, current, tabSize); refEnd > 0 {
p.renderParagraph(data[:i])
return i + refEnd
}
// did we find a blank line marking the end of the paragraph?
if n := p.isEmpty(current); n > 0 {
// did this blank line followed by a definition list item?
if p.extensions&DefinitionLists != 0 {
if i < len(data)-1 && data[i+1] == ':' {
return p.list(data[prev:], ListTypeDefinition)
}
}
p.renderParagraph(data[:i])
return i + n
}
// an underline under some text marks a heading, so our paragraph ended on prev line
if i > 0 {
if level := p.isUnderlinedHeading(current); level > 0 {
// render the paragraph
p.renderParagraph(data[:prev])
// ignore leading and trailing whitespace
eol := i - 1
for prev < eol && data[prev] == ' ' {
prev++
}
for eol > prev && data[eol-1] == ' ' {
eol--
}
id := ""
if p.extensions&AutoHeadingIDs != 0 {
id = sanitized_anchor_name.Create(string(data[prev:eol]))
}
block := p.addBlock(Heading, data[prev:eol])
block.Level = level
block.HeadingID = id
// find the end of the underline
for i < len(data) && data[i] != '\n' {
i++
}
return i
}
}
// if the next line starts a block of HTML, then the paragraph ends here
if p.extensions&LaxHTMLBlocks != 0 {
if data[i] == '<' && p.html(current, false) > 0 {
// rewind to before the HTML block
p.renderParagraph(data[:i])
return i
}
}
// if there's a prefixed heading or a horizontal rule after this, paragraph is over
if p.isPrefixHeading(current) || p.isHRule(current) {
p.renderParagraph(data[:i])
return i
}
// if there's a fenced code block, paragraph is over
if p.extensions&FencedCode != 0 {
if p.fencedCodeBlock(current, false) > 0 {
p.renderParagraph(data[:i])
return i
}
}
// if there's a definition list item, prev line is a definition term
if p.extensions&DefinitionLists != 0 {
if p.dliPrefix(current) != 0 {
ret := p.list(data[prev:], ListTypeDefinition)
return ret
}
}
// if there's a list after this, paragraph is over
if p.extensions&NoEmptyLineBeforeBlock != 0 {
if p.uliPrefix(current) != 0 ||
p.oliPrefix(current) != 0 ||
p.quotePrefix(current) != 0 ||
p.codePrefix(current) != 0 {
p.renderParagraph(data[:i])
return i
}
}
// otherwise, scan to the beginning of the next line
nl := bytes.IndexByte(data[i:], '\n')
if nl >= 0 {
i += nl + 1
} else {
i += len(data[i:])
}
}
p.renderParagraph(data[:i])
return i
}
func skipChar(data []byte, start int, char byte) int {
i := start
for i < len(data) && data[i] == char {
i++
}
return i
}
func skipUntilChar(text []byte, start int, char byte) int {
i := start
for i < len(text) && text[i] != char {
i++
}
return i
}
// Package blackfriday is a markdown processor.
//
// It translates plain text with simple formatting rules into an AST, which can
// then be further processed to HTML (provided by Blackfriday itself) or other
// formats (provided by the community).
//
// The simplest way to invoke Blackfriday is to call the Run function. It will
// take a text input and produce a text output in HTML (or other format).
//
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
// processor and to call Parse, which returns a syntax tree for the input
// document. You can leverage Blackfriday's parsing for content extraction from
// markdown documents. You can assign a custom renderer and set various options
// to the Markdown processor.
//
// If you're interested in calling Blackfriday from command line, see
// https://github.com/russross/blackfriday-tool.
package blackfriday
package blackfriday
import (
"html"
"io"
)
var htmlEscaper = [256][]byte{
'&': []byte("&amp;"),
'<': []byte("&lt;"),
'>': []byte("&gt;"),
'"': []byte("&quot;"),
}
func escapeHTML(w io.Writer, s []byte) {
var start, end int
for end < len(s) {
escSeq := htmlEscaper[s[end]]
if escSeq != nil {
w.Write(s[start:end])
w.Write(escSeq)
start = end + 1
}
end++
}
if start < len(s) && end <= len(s) {
w.Write(s[start:end])
}
}
func escLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
escapeHTML(w, []byte(unesc))
}
module github.com/russross/blackfriday/v2
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
//
// HTML rendering backend
//
//
package blackfriday
import (
"bytes"
"fmt"
"io"
"regexp"
"strings"
)
// HTMLFlags control optional behavior of HTML renderer.
type HTMLFlags int
// HTML renderer configuration options.
const (
HTMLFlagsNone HTMLFlags = 0
SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
SkipImages // Skip embedded images
SkipLinks // Skip all links
Safelink // Only link to trusted protocols
NofollowLinks // Only link with rel="nofollow"
NoreferrerLinks // Only link with rel="noreferrer"
NoopenerLinks // Only link with rel="noopener"
HrefTargetBlank // Add a blank target
CompletePage // Generate a complete HTML page
UseXHTML // Generate XHTML output instead of HTML
FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
Smartypants // Enable smart punctuation substitutions
SmartypantsFractions // Enable smart fractions (with Smartypants)
SmartypantsDashes // Enable smart dashes (with Smartypants)
SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
TOC // Generate a table of contents
)
var (
htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
)
const (
htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
processingInstruction + "|" + declaration + "|" + cdata + ")"
closeTag = "</" + tagName + "\\s*[>]"
openTag = "<" + tagName + attribute + "*" + "\\s*/?>"
attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
declaration = "<![A-Z]+" + "\\s+[^>]*>"
doubleQuotedValue = "\"[^\"]*\""
htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
processingInstruction = "[<][?].*?[?][>]"
singleQuotedValue = "'[^']*'"
tagName = "[A-Za-z][A-Za-z0-9-]*"
unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
)
// HTMLRendererParameters is a collection of supplementary parameters tweaking
// the behavior of various parts of HTML renderer.
type HTMLRendererParameters struct {
// Prepend this text to each relative URL.
AbsolutePrefix string
// Add this text to each footnote anchor, to ensure uniqueness.
FootnoteAnchorPrefix string
// Show this text inside the <a> tag for a footnote return link, if the
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
// <sup>[return]</sup> is used.
FootnoteReturnLinkContents string
// If set, add this text to the front of each Heading ID, to ensure
// uniqueness.
HeadingIDPrefix string
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
HeadingIDSuffix string
// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
// Negative offset is also valid.
// Resulting levels are clipped between 1 and 6.
HeadingLevelOffset int
Title string // Document title (used if CompletePage is set)
CSS string // Optional CSS file URL (used if CompletePage is set)
Icon string // Optional icon file URL (used if CompletePage is set)
Flags HTMLFlags // Flags allow customizing this renderer's behavior
}
// HTMLRenderer is a type that implements the Renderer interface for HTML output.
//
// Do not create this directly, instead use the NewHTMLRenderer function.
type HTMLRenderer struct {
HTMLRendererParameters
closeTag string // how to end singleton tags: either " />" or ">"
// Track heading IDs to prevent ID collision in a single generation.
headingIDs map[string]int
lastOutputLen int
disableTags int
sr *SPRenderer
}
const (
xhtmlClose = " />"
htmlClose = ">"
)
// NewHTMLRenderer creates and configures an HTMLRenderer object, which
// satisfies the Renderer interface.
func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
// configure the rendering engine
closeTag := htmlClose
if params.Flags&UseXHTML != 0 {
closeTag = xhtmlClose
}
if params.FootnoteReturnLinkContents == "" {
params.FootnoteReturnLinkContents = `<sup>[return]</sup>`
}
return &HTMLRenderer{
HTMLRendererParameters: params,
closeTag: closeTag,
headingIDs: make(map[string]int),
sr: NewSmartypantsRenderer(params.Flags),
}
}
func isHTMLTag(tag []byte, tagname string) bool {
found, _ := findHTMLTagPos(tag, tagname)
return found
}
// Look for a character, but ignore it when it's in any kind of quotes, it
// might be JavaScript
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
inSingleQuote := false
inDoubleQuote := false
inGraveQuote := false
i := start
for i < len(html) {
switch {
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
return i
case html[i] == '\'':
inSingleQuote = !inSingleQuote
case html[i] == '"':
inDoubleQuote = !inDoubleQuote
case html[i] == '`':
inGraveQuote = !inGraveQuote
}
i++
}
return start
}
func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
i := 0
if i < len(tag) && tag[0] != '<' {
return false, -1
}
i++
i = skipSpace(tag, i)
if i < len(tag) && tag[i] == '/' {
i++
}
i = skipSpace(tag, i)
j := 0
for ; i < len(tag); i, j = i+1, j+1 {
if j >= len(tagname) {
break
}
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
return false, -1
}
}
if i == len(tag) {
return false, -1
}
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
if rightAngle >= i {
return true, rightAngle
}
return false, -1
}
func skipSpace(tag []byte, i int) int {
for i < len(tag) && isspace(tag[i]) {
i++
}
return i
}
func isRelativeLink(link []byte) (yes bool) {
// a tag begin with '#'
if link[0] == '#' {
return true
}
// link begin with '/' but not '//', the second maybe a protocol relative link
if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
return true
}
// only the root '/'
if len(link) == 1 && link[0] == '/' {
return true
}
// current directory : begin with "./"
if bytes.HasPrefix(link, []byte("./")) {
return true
}
// parent directory : begin with "../"
if bytes.HasPrefix(link, []byte("../")) {
return true
}
return false
}
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, count+1)
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
r.headingIDs[id] = count + 1
id = tmp
} else {
id = id + "-1"
}
}
if _, found := r.headingIDs[id]; !found {
r.headingIDs[id] = 0
}
return id
}
func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
newDest := r.AbsolutePrefix
if link[0] != '/' {
newDest += "/"
}
newDest += string(link)
return []byte(newDest)
}
return link
}
func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
if isRelativeLink(link) {
return attrs
}
val := []string{}
if flags&NofollowLinks != 0 {
val = append(val, "nofollow")
}
if flags&NoreferrerLinks != 0 {
val = append(val, "noreferrer")
}
if flags&NoopenerLinks != 0 {
val = append(val, "noopener")
}
if flags&HrefTargetBlank != 0 {
attrs = append(attrs, "target=\"_blank\"")
}
if len(val) == 0 {
return attrs
}
attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
return append(attrs, attr)
}
func isMailto(link []byte) bool {
return bytes.HasPrefix(link, []byte("mailto:"))
}
func needSkipLink(flags HTMLFlags, dest []byte) bool {
if flags&SkipLinks != 0 {
return true
}
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
}
func isSmartypantable(node *Node) bool {
pt := node.Parent.Type
return pt != Link && pt != CodeBlock && pt != Code
}
func appendLanguageAttr(attrs []string, info []byte) []string {
if len(info) == 0 {
return attrs
}
endOfLang := bytes.IndexAny(info, "\t ")
if endOfLang < 0 {
endOfLang = len(info)
}
return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
}
func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
w.Write(name)
if len(attrs) > 0 {
w.Write(spaceBytes)
w.Write([]byte(strings.Join(attrs, " ")))
}
w.Write(gtBytes)
r.lastOutputLen = 1
}
func footnoteRef(prefix string, node *Node) []byte {
urlFrag := prefix + string(slugify(node.Destination))
anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
}
func footnoteItem(prefix string, slug []byte) []byte {
return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
}
func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
}
func itemOpenCR(node *Node) bool {
if node.Prev == nil {
return false
}
ld := node.Parent.ListData
return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
}
func skipParagraphTags(node *Node) bool {
grandparent := node.Parent.Parent
if grandparent == nil || grandparent.Type != List {
return false
}
tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
return grandparent.Type == List && tightOrTerm
}
func cellAlignment(align CellAlignFlags) string {
switch align {
case TableAlignmentLeft:
return "left"
case TableAlignmentRight:
return "right"
case TableAlignmentCenter:
return "center"
default:
return ""
}
}
func (r *HTMLRenderer) out(w io.Writer, text []byte) {
if r.disableTags > 0 {
w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
} else {
w.Write(text)
}
r.lastOutputLen = len(text)
}
func (r *HTMLRenderer) cr(w io.Writer) {
if r.lastOutputLen > 0 {
r.out(w, nlBytes)
}
}
var (
nlBytes = []byte{'\n'}
gtBytes = []byte{'>'}
spaceBytes = []byte{' '}
)
var (
brTag = []byte("<br>")
brXHTMLTag = []byte("<br />")
emTag = []byte("<em>")
emCloseTag = []byte("</em>")
strongTag = []byte("<strong>")
strongCloseTag = []byte("</strong>")
delTag = []byte("<del>")
delCloseTag = []byte("</del>")
ttTag = []byte("<tt>")
ttCloseTag = []byte("</tt>")
aTag = []byte("<a")
aCloseTag = []byte("</a>")
preTag = []byte("<pre>")
preCloseTag = []byte("</pre>")
codeTag = []byte("<code>")
codeCloseTag = []byte("</code>")
pTag = []byte("<p>")
pCloseTag = []byte("</p>")
blockquoteTag = []byte("<blockquote>")
blockquoteCloseTag = []byte("</blockquote>")
hrTag = []byte("<hr>")
hrXHTMLTag = []byte("<hr />")
ulTag = []byte("<ul>")
ulCloseTag = []byte("</ul>")
olTag = []byte("<ol>")
olCloseTag = []byte("</ol>")
dlTag = []byte("<dl>")
dlCloseTag = []byte("</dl>")
liTag = []byte("<li>")
liCloseTag = []byte("</li>")
ddTag = []byte("<dd>")
ddCloseTag = []byte("</dd>")
dtTag = []byte("<dt>")
dtCloseTag = []byte("</dt>")
tableTag = []byte("<table>")
tableCloseTag = []byte("</table>")
tdTag = []byte("<td")
tdCloseTag = []byte("</td>")
thTag = []byte("<th")
thCloseTag = []byte("</th>")
theadTag = []byte("<thead>")
theadCloseTag = []byte("</thead>")
tbodyTag = []byte("<tbody>")
tbodyCloseTag = []byte("</tbody>")
trTag = []byte("<tr>")
trCloseTag = []byte("</tr>")
h1Tag = []byte("<h1")
h1CloseTag = []byte("</h1>")
h2Tag = []byte("<h2")
h2CloseTag = []byte("</h2>")
h3Tag = []byte("<h3")
h3CloseTag = []byte("</h3>")
h4Tag = []byte("<h4")
h4CloseTag = []byte("</h4>")
h5Tag = []byte("<h5")
h5CloseTag = []byte("</h5>")
h6Tag = []byte("<h6")
h6CloseTag = []byte("</h6>")
footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n")
footnotesCloseDivBytes = []byte("\n</div>\n")
)
func headingTagsFromLevel(level int) ([]byte, []byte) {
if level <= 1 {
return h1Tag, h1CloseTag
}
switch level {
case 2:
return h2Tag, h2CloseTag
case 3:
return h3Tag, h3CloseTag
case 4:
return h4Tag, h4CloseTag
case 5:
return h5Tag, h5CloseTag
}
return h6Tag, h6CloseTag
}
func (r *HTMLRenderer) outHRTag(w io.Writer) {
if r.Flags&UseXHTML == 0 {
r.out(w, hrTag)
} else {
r.out(w, hrXHTMLTag)
}
}
// RenderNode is a default renderer of a single node of a syntax tree. For
// block nodes it will be called twice: first time with entering=true, second
// time with entering=false, so that it could know when it's working on an open
// tag and when on close. It writes the result to w.
//
// The return value is a way to tell the calling walker to adjust its walk
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
// can ask the walker to skip a subtree of this node by returning SkipChildren.
// The typical behavior is to return GoToNext, which asks for the usual
// traversal to the next node.
func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
attrs := []string{}
switch node.Type {
case Text:
if r.Flags&Smartypants != 0 {
var tmp bytes.Buffer
escapeHTML(&tmp, node.Literal)
r.sr.Process(w, tmp.Bytes())
} else {
if node.Parent.Type == Link {
escLink(w, node.Literal)
} else {
escapeHTML(w, node.Literal)
}
}
case Softbreak:
r.cr(w)
// TODO: make it configurable via out(renderer.softbreak)
case Hardbreak:
if r.Flags&UseXHTML == 0 {
r.out(w, brTag)
} else {
r.out(w, brXHTMLTag)
}
r.cr(w)
case Emph:
if entering {
r.out(w, emTag)
} else {
r.out(w, emCloseTag)
}
case Strong:
if entering {
r.out(w, strongTag)
} else {
r.out(w, strongCloseTag)
}
case Del:
if entering {
r.out(w, delTag)
} else {
r.out(w, delCloseTag)
}
case HTMLSpan:
if r.Flags&SkipHTML != 0 {
break
}
r.out(w, node.Literal)
case Link:
// mark it but don't link it if it is not a safe link: no smartypants
dest := node.LinkData.Destination
if needSkipLink(r.Flags, dest) {
if entering {
r.out(w, ttTag)
} else {
r.out(w, ttCloseTag)
}
} else {
if entering {
dest = r.addAbsPrefix(dest)
var hrefBuf bytes.Buffer
hrefBuf.WriteString("href=\"")
escLink(&hrefBuf, dest)
hrefBuf.WriteByte('"')
attrs = append(attrs, hrefBuf.String())
if node.NoteID != 0 {
r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
break
}
attrs = appendLinkAttrs(attrs, r.Flags, dest)
if len(node.LinkData.Title) > 0 {
var titleBuff bytes.Buffer
titleBuff.WriteString("title=\"")
escapeHTML(&titleBuff, node.LinkData.Title)
titleBuff.WriteByte('"')
attrs = append(attrs, titleBuff.String())
}
r.tag(w, aTag, attrs)
} else {
if node.NoteID != 0 {
break
}
r.out(w, aCloseTag)
}
}
case Image:
if r.Flags&SkipImages != 0 {
return SkipChildren
}
if entering {
dest := node.LinkData.Destination
dest = r.addAbsPrefix(dest)
if r.disableTags == 0 {
//if options.safe && potentiallyUnsafe(dest) {
//out(w, `<img src="" alt="`)
//} else {
r.out(w, []byte(`<img src="`))
escLink(w, dest)
r.out(w, []byte(`" alt="`))
//}
}
r.disableTags++
} else {
r.disableTags--
if r.disableTags == 0 {
if node.LinkData.Title != nil {
r.out(w, []byte(`" title="`))
escapeHTML(w, node.LinkData.Title)
}
r.out(w, []byte(`" />`))
}
}
case Code:
r.out(w, codeTag)
escapeHTML(w, node.Literal)
r.out(w, codeCloseTag)
case Document:
break
case Paragraph:
if skipParagraphTags(node) {
break
}
if entering {
// TODO: untangle this clusterfuck about when the newlines need
// to be added and when not.
if node.Prev != nil {
switch node.Prev.Type {
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
r.cr(w)
}
}
if node.Parent.Type == BlockQuote && node.Prev == nil {
r.cr(w)
}
r.out(w, pTag)
} else {
r.out(w, pCloseTag)
if !(node.Parent.Type == Item && node.Next == nil) {
r.cr(w)
}
}
case BlockQuote:
if entering {
r.cr(w)
r.out(w, blockquoteTag)
} else {
r.out(w, blockquoteCloseTag)
r.cr(w)
}
case HTMLBlock:
if r.Flags&SkipHTML != 0 {
break
}
r.cr(w)
r.out(w, node.Literal)
r.cr(w)
case Heading:
headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
openTag, closeTag := headingTagsFromLevel(headingLevel)
if entering {
if node.IsTitleblock {
attrs = append(attrs, `class="title"`)
}
if node.HeadingID != "" {
id := r.ensureUniqueHeadingID(node.HeadingID)
if r.HeadingIDPrefix != "" {
id = r.HeadingIDPrefix + id
}
if r.HeadingIDSuffix != "" {
id = id + r.HeadingIDSuffix
}
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
}
r.cr(w)
r.tag(w, openTag, attrs)
} else {
r.out(w, closeTag)
if !(node.Parent.Type == Item && node.Next == nil) {
r.cr(w)
}
}
case HorizontalRule:
r.cr(w)
r.outHRTag(w)
r.cr(w)
case List:
openTag := ulTag
closeTag := ulCloseTag
if node.ListFlags&ListTypeOrdered != 0 {
openTag = olTag
closeTag = olCloseTag
}
if node.ListFlags&ListTypeDefinition != 0 {
openTag = dlTag
closeTag = dlCloseTag
}
if entering {
if node.IsFootnotesList {
r.out(w, footnotesDivBytes)
r.outHRTag(w)
r.cr(w)
}
r.cr(w)
if node.Parent.Type == Item && node.Parent.Parent.Tight {
r.cr(w)
}
r.tag(w, openTag[:len(openTag)-1], attrs)
r.cr(w)
} else {
r.out(w, closeTag)
//cr(w)
//if node.parent.Type != Item {
// cr(w)
//}
if node.Parent.Type == Item && node.Next != nil {
r.cr(w)
}
if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
r.cr(w)
}
if node.IsFootnotesList {
r.out(w, footnotesCloseDivBytes)
}
}
case Item:
openTag := liTag
closeTag := liCloseTag
if node.ListFlags&ListTypeDefinition != 0 {
openTag = ddTag
closeTag = ddCloseTag
}
if node.ListFlags&ListTypeTerm != 0 {
openTag = dtTag
closeTag = dtCloseTag
}
if entering {
if itemOpenCR(node) {
r.cr(w)
}
if node.ListData.RefLink != nil {
slug := slugify(node.ListData.RefLink)
r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
break
}
r.out(w, openTag)
} else {
if node.ListData.RefLink != nil {
slug := slugify(node.ListData.RefLink)
if r.Flags&FootnoteReturnLinks != 0 {
r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
}
}
r.out(w, closeTag)
r.cr(w)
}
case CodeBlock:
attrs = appendLanguageAttr(attrs, node.Info)
r.cr(w)
r.out(w, preTag)
r.tag(w, codeTag[:len(codeTag)-1], attrs)
escapeHTML(w, node.Literal)
r.out(w, codeCloseTag)
r.out(w, preCloseTag)
if node.Parent.Type != Item {
r.cr(w)
}
case Table:
if entering {
r.cr(w)
r.out(w, tableTag)
} else {
r.out(w, tableCloseTag)
r.cr(w)
}
case TableCell:
openTag := tdTag
closeTag := tdCloseTag
if node.IsHeader {
openTag = thTag
closeTag = thCloseTag
}
if entering {
align := cellAlignment(node.Align)
if align != "" {
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
}
if node.Prev == nil {
r.cr(w)
}
r.tag(w, openTag, attrs)
} else {
r.out(w, closeTag)
r.cr(w)
}
case TableHead:
if entering {
r.cr(w)
r.out(w, theadTag)
} else {
r.out(w, theadCloseTag)
r.cr(w)
}
case TableBody:
if entering {
r.cr(w)
r.out(w, tbodyTag)
// XXX: this is to adhere to a rather silly test. Should fix test.
if node.FirstChild == nil {
r.cr(w)
}
} else {
r.out(w, tbodyCloseTag)
r.cr(w)
}
case TableRow:
if entering {
r.cr(w)
r.out(w, trTag)
} else {
r.out(w, trCloseTag)
r.cr(w)
}
default:
panic("Unknown node type " + node.Type.String())
}
return GoToNext
}
// RenderHeader writes HTML document preamble and TOC if requested.
func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
r.writeDocumentHeader(w)
if r.Flags&TOC != 0 {
r.writeTOC(w, ast)
}
}
// RenderFooter writes HTML document footer.
func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
if r.Flags&CompletePage == 0 {
return
}
io.WriteString(w, "\n</body>\n</html>\n")
}
func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
if r.Flags&CompletePage == 0 {
return
}
ending := ""
if r.Flags&UseXHTML != 0 {
io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
ending = " /"
} else {
io.WriteString(w, "<!DOCTYPE html>\n")
io.WriteString(w, "<html>\n")
}
io.WriteString(w, "<head>\n")
io.WriteString(w, " <title>")
if r.Flags&Smartypants != 0 {
r.sr.Process(w, []byte(r.Title))
} else {
escapeHTML(w, []byte(r.Title))
}
io.WriteString(w, "</title>\n")
io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
io.WriteString(w, Version)
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
io.WriteString(w, " <meta charset=\"utf-8\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
if r.CSS != "" {
io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
escapeHTML(w, []byte(r.CSS))
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
if r.Icon != "" {
io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
escapeHTML(w, []byte(r.Icon))
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
io.WriteString(w, "</head>\n")
io.WriteString(w, "<body>\n\n")
}
func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
buf := bytes.Buffer{}
inHeading := false
tocLevel := 0
headingCount := 0
ast.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Heading && !node.HeadingData.IsTitleblock {
inHeading = entering
if entering {
node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
if node.Level == tocLevel {
buf.WriteString("</li>\n\n<li>")
} else if node.Level < tocLevel {
for node.Level < tocLevel {
tocLevel--
buf.WriteString("</li>\n</ul>")
}
buf.WriteString("</li>\n\n<li>")
} else {
for node.Level > tocLevel {
tocLevel++
buf.WriteString("\n<ul>\n<li>")
}
}
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
headingCount++
} else {
buf.WriteString("</a>")
}
return GoToNext
}
if inHeading {
return r.RenderNode(&buf, node, entering)
}
return GoToNext
})
for ; tocLevel > 0; tocLevel-- {
buf.WriteString("</li>\n</ul>")
}
if buf.Len() > 0 {
io.WriteString(w, "<nav>\n")
w.Write(buf.Bytes())
io.WriteString(w, "\n\n</nav>\n")
}
r.lastOutputLen = buf.Len()
}
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
// Functions to parse inline elements.
//
package blackfriday
import (
"bytes"
"regexp"
"strconv"
)
var (
urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
// https://www.w3.org/TR/html5/syntax.html#character-references
// highest unicode code point in 17 planes (2^20): 1,114,112d =
// 7 dec digits or 6 hex digits
// named entity references can be 2-31 characters with stuff like &lt;
// at one end and &CounterClockwiseContourIntegral; at the other. There
// are also sometimes numbers at the end, although this isn't inherent
// in the specification; there are never numbers anywhere else in
// current character references, though; see &frac34; and &blk12;, etc.
// https://www.w3.org/TR/html5/syntax.html#named-character-references
//
// entity := "&" (named group | number ref) ";"
// named group := [a-zA-Z]{2,31}[0-9]{0,2}
// number ref := "#" (dec ref | hex ref)
// dec ref := [0-9]{1,7}
// hex ref := ("x" | "X") [0-9a-fA-F]{1,6}
htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`)
)
// Functions to parse text within a block
// Each function returns the number of chars taken care of
// data is the complete block being rendered
// offset is the number of valid chars before the current cursor
func (p *Markdown) inline(currBlock *Node, data []byte) {
// handlers might call us recursively: enforce a maximum depth
if p.nesting >= p.maxNesting || len(data) == 0 {
return
}
p.nesting++
beg, end := 0, 0
for end < len(data) {
handler := p.inlineCallback[data[end]]
if handler != nil {
if consumed, node := handler(p, data, end); consumed == 0 {
// No action from the callback.
end++
} else {
// Copy inactive chars into the output.
currBlock.AppendChild(text(data[beg:end]))
if node != nil {
currBlock.AppendChild(node)
}
// Skip past whatever the callback used.
beg = end + consumed
end = beg
}
} else {
end++
}
}
if beg < len(data) {
if data[end-1] == '\n' {
end--
}
currBlock.AppendChild(text(data[beg:end]))
}
p.nesting--
}
// single and double emphasis parsing
func emphasis(p *Markdown, data []byte, offset int) (int, *Node) {
data = data[offset:]
c := data[0]
if len(data) > 2 && data[1] != c {
// whitespace cannot follow an opening emphasis;
// strikethrough only takes two characters '~~'
if c == '~' || isspace(data[1]) {
return 0, nil
}
ret, node := helperEmphasis(p, data[1:], c)
if ret == 0 {
return 0, nil
}
return ret + 1, node
}
if len(data) > 3 && data[1] == c && data[2] != c {
if isspace(data[2]) {
return 0, nil
}
ret, node := helperDoubleEmphasis(p, data[2:], c)
if ret == 0 {
return 0, nil
}
return ret + 2, node
}
if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c {
if c == '~' || isspace(data[3]) {
return 0, nil
}
ret, node := helperTripleEmphasis(p, data, 3, c)
if ret == 0 {
return 0, nil
}
return ret + 3, node
}
return 0, nil
}
func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) {
data = data[offset:]
nb := 0
// count the number of backticks in the delimiter
for nb < len(data) && data[nb] == '`' {
nb++
}
// find the next delimiter
i, end := 0, 0
for end = nb; end < len(data) && i < nb; end++ {
if data[end] == '`' {
i++
} else {
i = 0
}
}
// no matching delimiter?
if i < nb && end >= len(data) {
return 0, nil
}
// trim outside whitespace
fBegin := nb
for fBegin < end && data[fBegin] == ' ' {
fBegin++
}
fEnd := end - nb
for fEnd > fBegin && data[fEnd-1] == ' ' {
fEnd--
}
// render the code span
if fBegin != fEnd {
code := NewNode(Code)
code.Literal = data[fBegin:fEnd]
return end, code
}
return end, nil
}
// newline preceded by two spaces becomes <br>
func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) {
origOffset := offset
for offset < len(data) && data[offset] == ' ' {
offset++
}
if offset < len(data) && data[offset] == '\n' {
if offset-origOffset >= 2 {
return offset - origOffset + 1, NewNode(Hardbreak)
}
return offset - origOffset, nil
}
return 0, nil
}
// newline without two spaces works when HardLineBreak is enabled
func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) {
if p.extensions&HardLineBreak != 0 {
return 1, NewNode(Hardbreak)
}
return 0, nil
}
type linkType int
const (
linkNormal linkType = iota
linkImg
linkDeferredFootnote
linkInlineFootnote
)
func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
if t == linkDeferredFootnote {
return false
}
return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
}
func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) {
if offset < len(data)-1 && data[offset+1] == '[' {
return link(p, data, offset)
}
return 0, nil
}
func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) {
if offset < len(data)-1 && data[offset+1] == '[' {
return link(p, data, offset)
}
return 0, nil
}
// '[': parse a link or an image or a footnote
func link(p *Markdown, data []byte, offset int) (int, *Node) {
// no links allowed inside regular links, footnote, and deferred footnotes
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
return 0, nil
}
var t linkType
switch {
// special case: ![^text] == deferred footnote (that follows something with
// an exclamation point)
case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^':
t = linkDeferredFootnote
// ![alt] == image
case offset >= 0 && data[offset] == '!':
t = linkImg
offset++
// ^[text] == inline footnote
// [^refId] == deferred footnote
case p.extensions&Footnotes != 0:
if offset >= 0 && data[offset] == '^' {
t = linkInlineFootnote
offset++
} else if len(data)-1 > offset && data[offset+1] == '^' {
t = linkDeferredFootnote
}
// [text] == regular link
default:
t = linkNormal
}
data = data[offset:]
var (
i = 1
noteID int
title, link, altContent []byte
textHasNl = false
)
if t == linkDeferredFootnote {
i++
}
// look for the matching closing bracket
for level := 1; level > 0 && i < len(data); i++ {
switch {
case data[i] == '\n':
textHasNl = true
case data[i-1] == '\\':
continue
case data[i] == '[':
level++
case data[i] == ']':
level--
if level <= 0 {
i-- // compensate for extra i++ in for loop
}
}
}
if i >= len(data) {
return 0, nil
}
txtE := i
i++
var footnoteNode *Node
// skip any amount of whitespace or newline
// (this is much more lax than original markdown syntax)
for i < len(data) && isspace(data[i]) {
i++
}
// inline style link
switch {
case i < len(data) && data[i] == '(':
// skip initial whitespace
i++
for i < len(data) && isspace(data[i]) {
i++
}
linkB := i
// look for link end: ' " )
findlinkend:
for i < len(data) {
switch {
case data[i] == '\\':
i += 2
case data[i] == ')' || data[i] == '\'' || data[i] == '"':
break findlinkend
default:
i++
}
}
if i >= len(data) {
return 0, nil
}
linkE := i
// look for title end if present
titleB, titleE := 0, 0
if data[i] == '\'' || data[i] == '"' {
i++
titleB = i
findtitleend:
for i < len(data) {
switch {
case data[i] == '\\':
i += 2
case data[i] == ')':
break findtitleend
default:
i++
}
}
if i >= len(data) {
return 0, nil
}
// skip whitespace after title
titleE = i - 1
for titleE > titleB && isspace(data[titleE]) {
titleE--
}
// check for closing quote presence
if data[titleE] != '\'' && data[titleE] != '"' {
titleB, titleE = 0, 0
linkE = i
}
}
// remove whitespace at the end of the link
for linkE > linkB && isspace(data[linkE-1]) {
linkE--
}
// remove optional angle brackets around the link
if data[linkB] == '<' {
linkB++
}
if data[linkE-1] == '>' {
linkE--
}
// build escaped link and title
if linkE > linkB {
link = data[linkB:linkE]
}
if titleE > titleB {
title = data[titleB:titleE]
}
i++
// reference style link
case isReferenceStyleLink(data, i, t):
var id []byte
altContentConsidered := false
// look for the id
i++
linkB := i
for i < len(data) && data[i] != ']' {
i++
}
if i >= len(data) {
return 0, nil
}
linkE := i
// find the reference
if linkB == linkE {
if textHasNl {
var b bytes.Buffer
for j := 1; j < txtE; j++ {
switch {
case data[j] != '\n':
b.WriteByte(data[j])
case data[j-1] != ' ':
b.WriteByte(' ')
}
}
id = b.Bytes()
} else {
id = data[1:txtE]
altContentConsidered = true
}
} else {
id = data[linkB:linkE]
}
// find the reference with matching id
lr, ok := p.getRef(string(id))
if !ok {
return 0, nil
}
// keep link and title from reference
link = lr.link
title = lr.title
if altContentConsidered {
altContent = lr.text
}
i++
// shortcut reference style link or reference or inline footnote
default:
var id []byte
// craft the id
if textHasNl {
var b bytes.Buffer
for j := 1; j < txtE; j++ {
switch {
case data[j] != '\n':
b.WriteByte(data[j])
case data[j-1] != ' ':
b.WriteByte(' ')
}
}
id = b.Bytes()
} else {
if t == linkDeferredFootnote {
id = data[2:txtE] // get rid of the ^
} else {
id = data[1:txtE]
}
}
footnoteNode = NewNode(Item)
if t == linkInlineFootnote {
// create a new reference
noteID = len(p.notes) + 1
var fragment []byte
if len(id) > 0 {
if len(id) < 16 {
fragment = make([]byte, len(id))
} else {
fragment = make([]byte, 16)
}
copy(fragment, slugify(id))
} else {
fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...)
}
ref := &reference{
noteID: noteID,
hasBlock: false,
link: fragment,
title: id,
footnote: footnoteNode,
}
p.notes = append(p.notes, ref)
link = ref.link
title = ref.title
} else {
// find the reference with matching id
lr, ok := p.getRef(string(id))
if !ok {
return 0, nil
}
if t == linkDeferredFootnote {
lr.noteID = len(p.notes) + 1
lr.footnote = footnoteNode
p.notes = append(p.notes, lr)
}
// keep link and title from reference
link = lr.link
// if inline footnote, title == footnote contents
title = lr.title
noteID = lr.noteID
}
// rewind the whitespace
i = txtE + 1
}
var uLink []byte
if t == linkNormal || t == linkImg {
if len(link) > 0 {
var uLinkBuf bytes.Buffer
unescapeText(&uLinkBuf, link)
uLink = uLinkBuf.Bytes()
}
// links need something to click on and somewhere to go
if len(uLink) == 0 || (t == linkNormal && txtE <= 1) {
return 0, nil
}
}
// call the relevant rendering function
var linkNode *Node
switch t {
case linkNormal:
linkNode = NewNode(Link)
linkNode.Destination = normalizeURI(uLink)
linkNode.Title = title
if len(altContent) > 0 {
linkNode.AppendChild(text(altContent))
} else {
// links cannot contain other links, so turn off link parsing
// temporarily and recurse
insideLink := p.insideLink
p.insideLink = true
p.inline(linkNode, data[1:txtE])
p.insideLink = insideLink
}
case linkImg:
linkNode = NewNode(Image)
linkNode.Destination = uLink
linkNode.Title = title
linkNode.AppendChild(text(data[1:txtE]))
i++
case linkInlineFootnote, linkDeferredFootnote:
linkNode = NewNode(Link)
linkNode.Destination = link
linkNode.Title = title
linkNode.NoteID = noteID
linkNode.Footnote = footnoteNode
if t == linkInlineFootnote {
i++
}
default:
return 0, nil
}
return i, linkNode
}
func (p *Markdown) inlineHTMLComment(data []byte) int {
if len(data) < 5 {
return 0
}
if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
return 0
}
i := 5
// scan for an end-of-comment marker, across lines if necessary
for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
i++
}
// no end-of-comment marker
if i >= len(data) {
return 0
}
return i + 1
}
func stripMailto(link []byte) []byte {
if bytes.HasPrefix(link, []byte("mailto://")) {
return link[9:]
} else if bytes.HasPrefix(link, []byte("mailto:")) {
return link[7:]
} else {
return link
}
}
// autolinkType specifies a kind of autolink that gets detected.
type autolinkType int
// These are the possible flag values for the autolink renderer.
const (
notAutolink autolinkType = iota
normalAutolink
emailAutolink
)
// '<' when tags or autolinks are allowed
func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) {
data = data[offset:]
altype, end := tagLength(data)
if size := p.inlineHTMLComment(data); size > 0 {
end = size
}
if end > 2 {
if altype != notAutolink {
var uLink bytes.Buffer
unescapeText(&uLink, data[1:end+1-2])
if uLink.Len() > 0 {
link := uLink.Bytes()
node := NewNode(Link)
node.Destination = link
if altype == emailAutolink {
node.Destination = append([]byte("mailto:"), link...)
}
node.AppendChild(text(stripMailto(link)))
return end, node
}
} else {
htmlTag := NewNode(HTMLSpan)
htmlTag.Literal = data[:end]
return end, htmlTag
}
}
return end, nil
}
// '\\' backslash escape
var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
func escape(p *Markdown, data []byte, offset int) (int, *Node) {
data = data[offset:]
if len(data) > 1 {
if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' {
return 2, NewNode(Hardbreak)
}
if bytes.IndexByte(escapeChars, data[1]) < 0 {
return 0, nil
}
return 2, text(data[1:2])
}
return 2, nil
}
func unescapeText(ob *bytes.Buffer, src []byte) {
i := 0
for i < len(src) {
org := i
for i < len(src) && src[i] != '\\' {
i++
}
if i > org {
ob.Write(src[org:i])
}
if i+1 >= len(src) {
break
}
ob.WriteByte(src[i+1])
i += 2
}
}
// '&' escaped when it doesn't belong to an entity
// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
func entity(p *Markdown, data []byte, offset int) (int, *Node) {
data = data[offset:]
end := 1
if end < len(data) && data[end] == '#' {
end++
}
for end < len(data) && isalnum(data[end]) {
end++
}
if end < len(data) && data[end] == ';' {
end++ // real entity
} else {
return 0, nil // lone '&'
}
ent := data[:end]
// undo &amp; escaping or it will be converted to &amp;amp; by another
// escaper in the renderer
if bytes.Equal(ent, []byte("&amp;")) {
ent = []byte{'&'}
}
return end, text(ent)
}
func linkEndsWithEntity(data []byte, linkEnd int) bool {
entityRanges := htmlEntityRe.FindAllIndex(data[:linkEnd], -1)
return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
}
// hasPrefixCaseInsensitive is a custom implementation of
// strings.HasPrefix(strings.ToLower(s), prefix)
// we rolled our own because ToLower pulls in a huge machinery of lowercasing
// anything from Unicode and that's very slow. Since this func will only be
// used on ASCII protocol prefixes, we can take shortcuts.
func hasPrefixCaseInsensitive(s, prefix []byte) bool {
if len(s) < len(prefix) {
return false
}
delta := byte('a' - 'A')
for i, b := range prefix {
if b != s[i] && b != s[i]+delta {
return false
}
}
return true
}
var protocolPrefixes = [][]byte{
[]byte("http://"),
[]byte("https://"),
[]byte("ftp://"),
[]byte("file://"),
[]byte("mailto:"),
}
const shortestPrefix = 6 // len("ftp://"), the shortest of the above
func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) {
// quick check to rule out most false hits
if p.insideLink || len(data) < offset+shortestPrefix {
return 0, nil
}
for _, prefix := range protocolPrefixes {
endOfHead := offset + 8 // 8 is the len() of the longest prefix
if endOfHead > len(data) {
endOfHead = len(data)
}
if hasPrefixCaseInsensitive(data[offset:endOfHead], prefix) {
return autoLink(p, data, offset)
}
}
return 0, nil
}
func autoLink(p *Markdown, data []byte, offset int) (int, *Node) {
// Now a more expensive check to see if we're not inside an anchor element
anchorStart := offset
offsetFromAnchor := 0
for anchorStart > 0 && data[anchorStart] != '<' {
anchorStart--
offsetFromAnchor++
}
anchorStr := anchorRe.Find(data[anchorStart:])
if anchorStr != nil {
anchorClose := NewNode(HTMLSpan)
anchorClose.Literal = anchorStr[offsetFromAnchor:]
return len(anchorStr) - offsetFromAnchor, anchorClose
}
// scan backward for a word boundary
rewind := 0
for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
rewind++
}
if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
return 0, nil
}
origData := data
data = data[offset-rewind:]
if !isSafeLink(data) {
return 0, nil
}
linkEnd := 0
for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
linkEnd++
}
// Skip punctuation at the end of the link
if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
linkEnd--
}
// But don't skip semicolon if it's a part of escaped entity:
if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
linkEnd--
}
// See if the link finishes with a punctuation sign that can be closed.
var copen byte
switch data[linkEnd-1] {
case '"':
copen = '"'
case '\'':
copen = '\''
case ')':
copen = '('
case ']':
copen = '['
case '}':
copen = '{'
default:
copen = 0
}
if copen != 0 {
bufEnd := offset - rewind + linkEnd - 2
openDelim := 1
/* Try to close the final punctuation sign in this same line;
* if we managed to close it outside of the URL, that means that it's
* not part of the URL. If it closes inside the URL, that means it
* is part of the URL.
*
* Examples:
*
* foo http://www.pokemon.com/Pikachu_(Electric) bar
* => http://www.pokemon.com/Pikachu_(Electric)
*
* foo (http://www.pokemon.com/Pikachu_(Electric)) bar
* => http://www.pokemon.com/Pikachu_(Electric)
*
* foo http://www.pokemon.com/Pikachu_(Electric)) bar
* => http://www.pokemon.com/Pikachu_(Electric))
*
* (foo http://www.pokemon.com/Pikachu_(Electric)) bar
* => foo http://www.pokemon.com/Pikachu_(Electric)
*/
for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
if origData[bufEnd] == data[linkEnd-1] {
openDelim++
}
if origData[bufEnd] == copen {
openDelim--
}
bufEnd--
}
if openDelim == 0 {
linkEnd--
}
}
var uLink bytes.Buffer
unescapeText(&uLink, data[:linkEnd])
if uLink.Len() > 0 {
node := NewNode(Link)
node.Destination = uLink.Bytes()
node.AppendChild(text(uLink.Bytes()))
return linkEnd, node
}
return linkEnd, nil
}
func isEndOfLink(char byte) bool {
return isspace(char) || char == '<'
}
var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
func isSafeLink(link []byte) bool {
for _, path := range validPaths {
if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
if len(link) == len(path) {
return true
} else if isalnum(link[len(path)]) {
return true
}
}
}
for _, prefix := range validUris {
// TODO: handle unicode here
// case-insensitive prefix test
if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
return true
}
}
return false
}
// return the length of the given tag, or 0 is it's not valid
func tagLength(data []byte) (autolink autolinkType, end int) {
var i, j int
// a valid tag can't be shorter than 3 chars
if len(data) < 3 {
return notAutolink, 0
}
// begins with a '<' optionally followed by '/', followed by letter or number
if data[0] != '<' {
return notAutolink, 0
}
if data[1] == '/' {
i = 2
} else {
i = 1
}
if !isalnum(data[i]) {
return notAutolink, 0
}
// scheme test
autolink = notAutolink
// try to find the beginning of an URI
for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
i++
}
if i > 1 && i < len(data) && data[i] == '@' {
if j = isMailtoAutoLink(data[i:]); j != 0 {
return emailAutolink, i + j
}
}
if i > 2 && i < len(data) && data[i] == ':' {
autolink = normalAutolink
i++
}
// complete autolink test: no whitespace or ' or "
switch {
case i >= len(data):
autolink = notAutolink
case autolink != notAutolink:
j = i
for i < len(data) {
if data[i] == '\\' {
i += 2
} else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
break
} else {
i++
}
}
if i >= len(data) {
return autolink, 0
}
if i > j && data[i] == '>' {
return autolink, i + 1
}
// one of the forbidden chars has been found
autolink = notAutolink
}
i += bytes.IndexByte(data[i:], '>')
if i < 0 {
return autolink, 0
}
return autolink, i + 1
}
// look for the address part of a mail autolink and '>'
// this is less strict than the original markdown e-mail address matching
func isMailtoAutoLink(data []byte) int {
nb := 0
// address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
for i := 0; i < len(data); i++ {
if isalnum(data[i]) {
continue
}
switch data[i] {
case '@':
nb++
case '-', '.', '_':
break
case '>':
if nb == 1 {
return i + 1
}
return 0
default:
return 0
}
}
return 0
}
// look for the next emph char, skipping other constructs
func helperFindEmphChar(data []byte, c byte) int {
i := 0
for i < len(data) {
for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
i++
}
if i >= len(data) {
return 0
}
// do not count escaped chars
if i != 0 && data[i-1] == '\\' {
i++
continue
}
if data[i] == c {
return i
}
if data[i] == '`' {
// skip a code span
tmpI := 0
i++
for i < len(data) && data[i] != '`' {
if tmpI == 0 && data[i] == c {
tmpI = i
}
i++
}
if i >= len(data) {
return tmpI
}
i++
} else if data[i] == '[' {
// skip a link
tmpI := 0
i++
for i < len(data) && data[i] != ']' {
if tmpI == 0 && data[i] == c {
tmpI = i
}
i++
}
i++
for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
i++
}
if i >= len(data) {
return tmpI
}
if data[i] != '[' && data[i] != '(' { // not a link
if tmpI > 0 {
return tmpI
}
continue
}
cc := data[i]
i++
for i < len(data) && data[i] != cc {
if tmpI == 0 && data[i] == c {
return i
}
i++
}
if i >= len(data) {
return tmpI
}
i++
}
}
return 0
}
func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) {
i := 0
// skip one symbol if coming from emph3
if len(data) > 1 && data[0] == c && data[1] == c {
i = 1
}
for i < len(data) {
length := helperFindEmphChar(data[i:], c)
if length == 0 {
return 0, nil
}
i += length
if i >= len(data) {
return 0, nil
}
if i+1 < len(data) && data[i+1] == c {
i++
continue
}
if data[i] == c && !isspace(data[i-1]) {
if p.extensions&NoIntraEmphasis != 0 {
if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
continue
}
}
emph := NewNode(Emph)
p.inline(emph, data[:i])
return i + 1, emph
}
}
return 0, nil
}
func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) {
i := 0
for i < len(data) {
length := helperFindEmphChar(data[i:], c)
if length == 0 {
return 0, nil
}
i += length
if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
nodeType := Strong
if c == '~' {
nodeType = Del
}
node := NewNode(nodeType)
p.inline(node, data[:i])
return i + 2, node
}
i++
}
return 0, nil
}
func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) {
i := 0
origData := data
data = data[offset:]
for i < len(data) {
length := helperFindEmphChar(data[i:], c)
if length == 0 {
return 0, nil
}
i += length
// skip whitespace preceded symbols
if data[i] != c || isspace(data[i-1]) {
continue
}
switch {
case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
// triple symbol found
strong := NewNode(Strong)
em := NewNode(Emph)
strong.AppendChild(em)
p.inline(em, data[:i])
return i + 3, strong
case (i+1 < len(data) && data[i+1] == c):
// double symbol found, hand over to emph1
length, node := helperEmphasis(p, origData[offset-2:], c)
if length == 0 {
return 0, nil
}
return length - 2, node
default:
// single symbol found, hand over to emph2
length, node := helperDoubleEmphasis(p, origData[offset-1:], c)
if length == 0 {
return 0, nil
}
return length - 1, node
}
}
return 0, nil
}
func text(s []byte) *Node {
node := NewNode(Text)
node.Literal = s
return node
}
func normalizeURI(s []byte) []byte {
return s // TODO: implement
}
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
package blackfriday
import (
"bytes"
"fmt"
"io"
"strings"
"unicode/utf8"
)
//
// Markdown parsing and processing
//
// Version string of the package. Appears in the rendered document when
// CompletePage flag is on.
const Version = "2.0"
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
// extensions.
type Extensions int
// These are the supported markdown parsing extensions.
// OR these values together to select multiple extensions.
const (
NoExtensions Extensions = 0
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
Tables // Render tables
FencedCode // Render fenced code blocks
Autolink // Detect embedded URLs that are not explicitly marked
Strikethrough // Strikethrough text using ~~test~~
LaxHTMLBlocks // Loosen up HTML block parsing rules
SpaceHeadings // Be strict about prefix heading rules
HardLineBreak // Translate newlines into line breaks
TabSizeEight // Expand tabs to eight spaces instead of four
Footnotes // Pandoc-style footnotes
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
HeadingIDs // specify heading IDs with {#id}
Titleblock // Titleblock ala pandoc
AutoHeadingIDs // Create the heading ID from the text
BackslashLineBreak // Translate trailing backslashes into line breaks
DefinitionLists // Render definition lists
CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
BackslashLineBreak | DefinitionLists
)
// ListType contains bitwise or'ed flags for list and list item objects.
type ListType int
// These are the possible flag values for the ListItem renderer.
// Multiple flag values may be ORed together.
// These are mostly of interest if you are writing a new output format.
const (
ListTypeOrdered ListType = 1 << iota
ListTypeDefinition
ListTypeTerm
ListItemContainsBlock
ListItemBeginningOfList // TODO: figure out if this is of any use now
ListItemEndOfList
)
// CellAlignFlags holds a type of alignment in a table cell.
type CellAlignFlags int
// These are the possible flag values for the table cell renderer.
// Only a single one of these values will be used; they are not ORed together.
// These are mostly of interest if you are writing a new output format.
const (
TableAlignmentLeft CellAlignFlags = 1 << iota
TableAlignmentRight
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
)
// The size of a tab stop.
const (
TabSizeDefault = 4
TabSizeDouble = 8
)
// blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{
"blockquote": {},
"del": {},
"div": {},
"dl": {},
"fieldset": {},
"form": {},
"h1": {},
"h2": {},
"h3": {},
"h4": {},
"h5": {},
"h6": {},
"iframe": {},
"ins": {},
"math": {},
"noscript": {},
"ol": {},
"pre": {},
"p": {},
"script": {},
"style": {},
"table": {},
"ul": {},
// HTML5
"address": {},
"article": {},
"aside": {},
"canvas": {},
"figcaption": {},
"figure": {},
"footer": {},
"header": {},
"hgroup": {},
"main": {},
"nav": {},
"output": {},
"progress": {},
"section": {},
"video": {},
}
// Renderer is the rendering interface. This is mostly of interest if you are
// implementing a new rendering format.
//
// Only an HTML implementation is provided in this repository, see the README
// for external implementations.
type Renderer interface {
// RenderNode is the main rendering method. It will be called once for
// every leaf node and twice for every non-leaf node (first with
// entering=true, then with entering=false). The method should write its
// rendition of the node to the supplied writer w.
RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
// RenderHeader is a method that allows the renderer to produce some
// content preceding the main body of the output document. The header is
// understood in the broad sense here. For example, the default HTML
// renderer will write not only the HTML document preamble, but also the
// table of contents if it was requested.
//
// The method will be passed an entire document tree, in case a particular
// implementation needs to inspect it to produce output.
//
// The output should be written to the supplied writer w. If your
// implementation has no header to write, supply an empty implementation.
RenderHeader(w io.Writer, ast *Node)
// RenderFooter is a symmetric counterpart of RenderHeader.
RenderFooter(w io.Writer, ast *Node)
}
// Callback functions for inline parsing. One such function is defined
// for each character that triggers a response when parsing inline data.
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
// Markdown is a type that holds extensions and the runtime state used by
// Parse, and the renderer. You can not use it directly, construct it with New.
type Markdown struct {
renderer Renderer
referenceOverride ReferenceOverrideFunc
refs map[string]*reference
inlineCallback [256]inlineParser
extensions Extensions
nesting int
maxNesting int
insideLink bool
// Footnotes need to be ordered as well as available to quickly check for
// presence. If a ref is also a footnote, it's stored both in refs and here
// in notes. Slice is nil if footnotes not enabled.
notes []*reference
doc *Node
tip *Node // = doc
oldTip *Node
lastMatchedContainer *Node // = doc
allClosed bool
}
func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
if p.referenceOverride != nil {
r, overridden := p.referenceOverride(refid)
if overridden {
if r == nil {
return nil, false
}
return &reference{
link: []byte(r.Link),
title: []byte(r.Title),
noteID: 0,
hasBlock: false,
text: []byte(r.Text)}, true
}
}
// refs are case insensitive
ref, found = p.refs[strings.ToLower(refid)]
return ref, found
}
func (p *Markdown) finalize(block *Node) {
above := block.Parent
block.open = false
p.tip = above
}
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
return p.addExistingChild(NewNode(node), offset)
}
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
for !p.tip.canContain(node.Type) {
p.finalize(p.tip)
}
p.tip.AppendChild(node)
p.tip = node
return node
}
func (p *Markdown) closeUnmatchedBlocks() {
if !p.allClosed {
for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.Parent
p.finalize(p.oldTip)
p.oldTip = parent
}
p.allClosed = true
}
}
//
//
// Public interface
//
//
// Reference represents the details of a link.
// See the documentation in Options for more details on use-case.
type Reference struct {
// Link is usually the URL the reference points to.
Link string
// Title is the alternate text describing the link in more detail.
Title string
// Text is the optional text to override the ref with if the syntax used was
// [refid][]
Text string
}
// ReferenceOverrideFunc is expected to be called with a reference string and
// return either a valid Reference type that the reference string maps to or
// nil. If overridden is false, the default reference logic will be executed.
// See the documentation in Options for more details on use-case.
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
// New constructs a Markdown processor. You can use the same With* functions as
// for Run() to customize parser's behavior and the renderer.
func New(opts ...Option) *Markdown {
var p Markdown
for _, opt := range opts {
opt(&p)
}
p.refs = make(map[string]*reference)
p.maxNesting = 16
p.insideLink = false
docNode := NewNode(Document)
p.doc = docNode
p.tip = docNode
p.oldTip = docNode
p.lastMatchedContainer = docNode
p.allClosed = true
// register inline parsers
p.inlineCallback[' '] = maybeLineBreak
p.inlineCallback['*'] = emphasis
p.inlineCallback['_'] = emphasis
if p.extensions&Strikethrough != 0 {
p.inlineCallback['~'] = emphasis
}
p.inlineCallback['`'] = codeSpan
p.inlineCallback['\n'] = lineBreak
p.inlineCallback['['] = link
p.inlineCallback['<'] = leftAngle
p.inlineCallback['\\'] = escape
p.inlineCallback['&'] = entity
p.inlineCallback['!'] = maybeImage
p.inlineCallback['^'] = maybeInlineFootnote
if p.extensions&Autolink != 0 {
p.inlineCallback['h'] = maybeAutoLink
p.inlineCallback['m'] = maybeAutoLink
p.inlineCallback['f'] = maybeAutoLink
p.inlineCallback['H'] = maybeAutoLink
p.inlineCallback['M'] = maybeAutoLink
p.inlineCallback['F'] = maybeAutoLink
}
if p.extensions&Footnotes != 0 {
p.notes = make([]*reference, 0)
}
return &p
}
// Option customizes the Markdown processor's default behavior.
type Option func(*Markdown)
// WithRenderer allows you to override the default renderer.
func WithRenderer(r Renderer) Option {
return func(p *Markdown) {
p.renderer = r
}
}
// WithExtensions allows you to pick some of the many extensions provided by
// Blackfriday. You can bitwise OR them.
func WithExtensions(e Extensions) Option {
return func(p *Markdown) {
p.extensions = e
}
}
// WithNoExtensions turns off all extensions and custom behavior.
func WithNoExtensions() Option {
return func(p *Markdown) {
p.extensions = NoExtensions
p.renderer = NewHTMLRenderer(HTMLRendererParameters{
Flags: HTMLFlagsNone,
})
}
}
// WithRefOverride sets an optional function callback that is called every
// time a reference is resolved.
//
// In Markdown, the link reference syntax can be made to resolve a link to
// a reference instead of an inline URL, in one of the following ways:
//
// * [link text][refid]
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
func WithRefOverride(o ReferenceOverrideFunc) Option {
return func(p *Markdown) {
p.referenceOverride = o
}
}
// Run is the main entry point to Blackfriday. It parses and renders a
// block of markdown-encoded text.
//
// The simplest invocation of Run takes one argument, input:
// output := Run(input)
// This will parse the input with CommonExtensions enabled and render it with
// the default HTMLRenderer (with CommonHTMLFlags).
//
// Variadic arguments opts can customize the default behavior. Since Markdown
// type does not contain exported fields, you can not use it directly. Instead,
// use the With* functions. For example, this will call the most basic
// functionality, with no extensions:
// output := Run(input, WithNoExtensions())
//
// You can use any number of With* arguments, even contradicting ones. They
// will be applied in order of appearance and the latter will override the
// former:
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
// WithRenderer(yourRenderer))
func Run(input []byte, opts ...Option) []byte {
r := NewHTMLRenderer(HTMLRendererParameters{
Flags: CommonHTMLFlags,
})
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
optList = append(optList, opts...)
parser := New(optList...)
ast := parser.Parse(input)
var buf bytes.Buffer
parser.renderer.RenderHeader(&buf, ast)
ast.Walk(func(node *Node, entering bool) WalkStatus {
return parser.renderer.RenderNode(&buf, node, entering)
})
parser.renderer.RenderFooter(&buf, ast)
return buf.Bytes()
}
// Parse is an entry point to the parsing part of Blackfriday. It takes an
// input markdown document and produces a syntax tree for its contents. This
// tree can then be rendered with a default or custom renderer, or
// analyzed/transformed by the caller to whatever non-standard needs they have.
// The return value is the root node of the syntax tree.
func (p *Markdown) Parse(input []byte) *Node {
p.block(input)
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
}
// Walk the tree again and process inline markdown in each block
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
p.inline(node, node.content)
node.content = nil
}
return GoToNext
})
p.parseRefsToAST()
return p.doc
}
func (p *Markdown) parseRefsToAST() {
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
return
}
p.tip = p.doc
block := p.addBlock(List, nil)
block.IsFootnotesList = true
block.ListFlags = ListTypeOrdered
flags := ListItemBeginningOfList
// Note: this loop is intentionally explicit, not range-form. This is
// because the body of the loop will append nested footnotes to p.notes and
// we need to process those late additions. Range form would only walk over
// the fixed initial set.
for i := 0; i < len(p.notes); i++ {
ref := p.notes[i]
p.addExistingChild(ref.footnote, 0)
block := ref.footnote
block.ListFlags = flags | ListTypeOrdered
block.RefLink = ref.link
if ref.hasBlock {
flags |= ListItemContainsBlock
p.block(ref.title)
} else {
p.inline(block, ref.title)
}
flags &^= ListItemBeginningOfList | ListItemContainsBlock
}
above := block.Parent
finalizeList(block)
p.tip = above
block.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading {
p.inline(node, node.content)
node.content = nil
}
return GoToNext
})
}
//
// Link references
//
// This section implements support for references that (usually) appear
// as footnotes in a document, and can be referenced anywhere in the document.
// The basic format is:
//
// [1]: http://www.google.com/ "Google"
// [2]: http://www.github.com/ "Github"
//
// Anywhere in the document, the reference can be linked by referring to its
// label, i.e., 1 and 2 in this example, as in:
//
// This library is hosted on [Github][2], a git hosting site.
//
// Actual footnotes as specified in Pandoc and supported by some other Markdown
// libraries such as php-markdown are also taken care of. They look like this:
//
// This sentence needs a bit of further explanation.[^note]
//
// [^note]: This is the explanation.
//
// Footnotes should be placed at the end of the document in an ordered list.
// Finally, there are inline footnotes such as:
//
// Inline footnotes^[Also supported.] provide a quick inline explanation,
// but are rendered at the bottom of the document.
//
// reference holds all information necessary for a reference-style links or
// footnotes.
//
// Consider this markdown with reference-style links:
//
// [link][ref]
//
// [ref]: /url/ "tooltip title"
//
// It will be ultimately converted to this HTML:
//
// <p><a href=\"/url/\" title=\"title\">link</a></p>
//
// And a reference structure will be populated as follows:
//
// p.refs["ref"] = &reference{
// link: "/url/",
// title: "tooltip title",
// }
//
// Alternatively, reference can contain information about a footnote. Consider
// this markdown:
//
// Text needing a footnote.[^a]
//
// [^a]: This is the note
//
// A reference structure will be populated as follows:
//
// p.refs["a"] = &reference{
// link: "a",
// title: "This is the note",
// noteID: <some positive int>,
// }
//
// TODO: As you can see, it begs for splitting into two dedicated structures
// for refs and for footnotes.
type reference struct {
link []byte
title []byte
noteID int // 0 if not a footnote ref
hasBlock bool
footnote *Node // a link to the Item node within a list of footnotes
text []byte // only gets populated by refOverride feature with Reference.Text
}
func (r *reference) String() string {
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
r.link, r.title, r.text, r.noteID, r.hasBlock)
}
// Check whether or not data starts with a reference link.
// If so, it is parsed and stored in the list of references
// (in the render struct).
// Returns the number of bytes to skip to move past it,
// or zero if the first line is not a reference.
func isReference(p *Markdown, data []byte, tabSize int) int {
// up to 3 optional leading spaces
if len(data) < 4 {
return 0
}
i := 0
for i < 3 && data[i] == ' ' {
i++
}
noteID := 0
// id part: anything but a newline between brackets
if data[i] != '[' {
return 0
}
i++
if p.extensions&Footnotes != 0 {
if i < len(data) && data[i] == '^' {
// we can set it to anything here because the proper noteIds will
// be assigned later during the second pass. It just has to be != 0
noteID = 1
i++
}
}
idOffset := i
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
i++
}
if i >= len(data) || data[i] != ']' {
return 0
}
idEnd := i
// footnotes can have empty ID, like this: [^], but a reference can not be
// empty like this: []. Break early if it's not a footnote and there's no ID
if noteID == 0 && idOffset == idEnd {
return 0
}
// spacer: colon (space | tab)* newline? (space | tab)*
i++
if i >= len(data) || data[i] != ':' {
return 0
}
i++
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
i++
}
if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
i++
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
i++
}
}
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
i++
}
if i >= len(data) {
return 0
}
var (
linkOffset, linkEnd int
titleOffset, titleEnd int
lineEnd int
raw []byte
hasBlock bool
)
if p.extensions&Footnotes != 0 && noteID != 0 {
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
lineEnd = linkEnd
} else {
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
}
if lineEnd == 0 {
return 0
}
// a valid ref has been found
ref := &reference{
noteID: noteID,
hasBlock: hasBlock,
}
if noteID > 0 {
// reusing the link field for the id since footnotes don't have links
ref.link = data[idOffset:idEnd]
// if footnote, it's not really a title, it's the contained text
ref.title = raw
} else {
ref.link = data[linkOffset:linkEnd]
ref.title = data[titleOffset:titleEnd]
}
// id matches are case-insensitive
id := string(bytes.ToLower(data[idOffset:idEnd]))
p.refs[id] = ref
return lineEnd
}
func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
// link: whitespace-free sequence, optionally between angle brackets
if data[i] == '<' {
i++
}
linkOffset = i
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
i++
}
linkEnd = i
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
linkOffset++
linkEnd--
}
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
i++
}
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
return
}
// compute end-of-line
if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
lineEnd = i
}
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
lineEnd++
}
// optional (space|tab)* spacer after a newline
if lineEnd > 0 {
i = lineEnd + 1
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
i++
}
}
// optional title: any non-newline sequence enclosed in '"() alone on its line
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
i++
titleOffset = i
// look for EOL
for i < len(data) && data[i] != '\n' && data[i] != '\r' {
i++
}
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
titleEnd = i + 1
} else {
titleEnd = i
}
// step back
i--
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
i--
}
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
lineEnd = titleEnd
titleEnd = i
}
}
return
}
// The first bit of this logic is the same as Parser.listItem, but the rest
// is much simpler. This function simply finds the entire block and shifts it
// over by one tab if it is indeed a block (just returns the line if it's not).
// blockEnd is the end of the section in the input buffer, and contents is the
// extracted text that was shifted over one tab. It will need to be rendered at
// the end of the document.
func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
if i == 0 || len(data) == 0 {
return
}
// skip leading whitespace on first line
for i < len(data) && data[i] == ' ' {
i++
}
blockStart = i
// find the end of the line
blockEnd = i
for i < len(data) && data[i-1] != '\n' {
i++
}
// get working buffer
var raw bytes.Buffer
// put the first line into the working buffer
raw.Write(data[blockEnd:i])
blockEnd = i
// process the following lines
containsBlankLine := false
gatherLines:
for blockEnd < len(data) {
i++
// find the end of this line
for i < len(data) && data[i-1] != '\n' {
i++
}
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[blockEnd:i]) > 0 {
containsBlankLine = true
blockEnd = i
continue
}
n := 0
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
// this is the end of the block.
// we don't want to include this last line in the index.
break gatherLines
}
// if there were blank lines before this one, insert a new one now
if containsBlankLine {
raw.WriteByte('\n')
containsBlankLine = false
}
// get rid of that first tab, write to buffer
raw.Write(data[blockEnd+n : i])
hasBlock = true
blockEnd = i
}
if data[blockEnd-1] != '\n' {
raw.WriteByte('\n')
}
contents = raw.Bytes()
return
}
//
//
// Miscellaneous helper functions
//
//
// Test if a character is a punctuation symbol.
// Taken from a private function in regexp in the stdlib.
func ispunct(c byte) bool {
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
if c == r {
return true
}
}
return false
}
// Test if a character is a whitespace character.
func isspace(c byte) bool {
return ishorizontalspace(c) || isverticalspace(c)
}
// Test if a character is a horizontal whitespace character.
func ishorizontalspace(c byte) bool {
return c == ' ' || c == '\t'
}
// Test if a character is a vertical character.
func isverticalspace(c byte) bool {
return c == '\n' || c == '\r' || c == '\f' || c == '\v'
}
// Test if a character is letter.
func isletter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// Test if a character is a letter or a digit.
// TODO: check when this is looking for ASCII alnum and when it should use unicode
func isalnum(c byte) bool {
return (c >= '0' && c <= '9') || isletter(c)
}
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
// always ends output with a newline
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
// first, check for common cases: no tabs, or only tabs at beginning of line
i, prefix := 0, 0
slowcase := false
for i = 0; i < len(line); i++ {
if line[i] == '\t' {
if prefix == i {
prefix++
} else {
slowcase = true
break
}
}
}
// no need to decode runes if all tabs are at the beginning of the line
if !slowcase {
for i = 0; i < prefix*tabSize; i++ {
out.WriteByte(' ')
}
out.Write(line[prefix:])
return
}
// the slow case: we need to count runes to figure out how
// many spaces to insert for each tab
column := 0
i = 0
for i < len(line) {
start := i
for i < len(line) && line[i] != '\t' {
_, size := utf8.DecodeRune(line[i:])
i += size
column++
}
if i > start {
out.Write(line[start:i])
}
if i >= len(line) {
break
}
for {
out.WriteByte(' ')
column++
if column%tabSize == 0 {
break
}
}
i++
}
}
// Find if a line counts as indented or not.
// Returns number of characters the indent is (0 = not indented).
func isIndented(data []byte, indentSize int) int {
if len(data) == 0 {
return 0
}
if data[0] == '\t' {
return 1
}
if len(data) < indentSize {
return 0
}
for i := 0; i < indentSize; i++ {
if data[i] != ' ' {
return 0
}
}
return indentSize
}
// Create a url-safe slug for fragments
func slugify(in []byte) []byte {
if len(in) == 0 {
return in
}
out := make([]byte, 0, len(in))
sym := false
for _, ch := range in {
if isalnum(ch) {
sym = false
out = append(out, ch)
} else if sym {
continue
} else {
out = append(out, '-')
sym = true
}
}
var a, b int
var ch byte
for a, ch = range out {
if ch != '-' {
break
}
}
for b = len(out) - 1; b > 0; b-- {
if out[b] != '-' {
break
}
}
return out[a : b+1]
}
package blackfriday
import (
"bytes"
"fmt"
)
// NodeType specifies a type of a single node of a syntax tree. Usually one
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
// or code block.
type NodeType int
// Constants for identifying different types of nodes. See NodeType.
const (
Document NodeType = iota
BlockQuote
List
Item
Paragraph
Heading
HorizontalRule
Emph
Strong
Del
Link
Image
Text
HTMLBlock
CodeBlock
Softbreak
Hardbreak
Code
HTMLSpan
Table
TableCell
TableHead
TableBody
TableRow
)
var nodeTypeNames = []string{
Document: "Document",
BlockQuote: "BlockQuote",
List: "List",
Item: "Item",
Paragraph: "Paragraph",
Heading: "Heading",
HorizontalRule: "HorizontalRule",
Emph: "Emph",
Strong: "Strong",
Del: "Del",
Link: "Link",
Image: "Image",
Text: "Text",
HTMLBlock: "HTMLBlock",
CodeBlock: "CodeBlock",
Softbreak: "Softbreak",
Hardbreak: "Hardbreak",
Code: "Code",
HTMLSpan: "HTMLSpan",
Table: "Table",
TableCell: "TableCell",
TableHead: "TableHead",
TableBody: "TableBody",
TableRow: "TableRow",
}
func (t NodeType) String() string {
return nodeTypeNames[t]
}
// ListData contains fields relevant to a List and Item node type.
type ListData struct {
ListFlags ListType
Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists
Delimiter byte // '.' or ')' after the number in ordered lists
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
IsFootnotesList bool // This is a list of footnotes
}
// LinkData contains fields relevant to a Link node type.
type LinkData struct {
Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
}
// CodeBlockData contains fields relevant to a CodeBlock node type.
type CodeBlockData struct {
IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string
FenceChar byte
FenceLength int
FenceOffset int
}
// TableCellData contains fields relevant to a TableCell node type.
type TableCellData struct {
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
}
// HeadingData contains fields relevant to a Heading node type.
type HeadingData struct {
Level int // This holds the heading level number
HeadingID string // This might hold heading ID, if present
IsTitleblock bool // Specifies whether it's a title block
}
// Node is a single element in the abstract syntax tree of the parsed document.
// It holds connections to the structurally neighboring nodes and, for certain
// types of nodes, additional information that might be needed when rendering.
type Node struct {
Type NodeType // Determines the type of the node
Parent *Node // Points to the parent
FirstChild *Node // Points to the first child, if any
LastChild *Node // Points to the last child, if any
Prev *Node // Previous sibling; nil if it's the first child
Next *Node // Next sibling; nil if it's the last child
Literal []byte // Text contents of the leaf nodes
HeadingData // Populated if Type is Heading
ListData // Populated if Type is List
CodeBlockData // Populated if Type is CodeBlock
LinkData // Populated if Type is Link
TableCellData // Populated if Type is TableCell
content []byte // Markdown content of the block nodes
open bool // Specifies an open block node that has not been finished to process yet
}
// NewNode allocates a node of a specified type.
func NewNode(typ NodeType) *Node {
return &Node{
Type: typ,
open: true,
}
}
func (n *Node) String() string {
ellipsis := ""
snippet := n.Literal
if len(snippet) > 16 {
snippet = snippet[:16]
ellipsis = "..."
}
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
}
// Unlink removes node 'n' from the tree.
// It panics if the node is nil.
func (n *Node) Unlink() {
if n.Prev != nil {
n.Prev.Next = n.Next
} else if n.Parent != nil {
n.Parent.FirstChild = n.Next
}
if n.Next != nil {
n.Next.Prev = n.Prev
} else if n.Parent != nil {
n.Parent.LastChild = n.Prev
}
n.Parent = nil
n.Next = nil
n.Prev = nil
}
// AppendChild adds a node 'child' as a child of 'n'.
// It panics if either node is nil.
func (n *Node) AppendChild(child *Node) {
child.Unlink()
child.Parent = n
if n.LastChild != nil {
n.LastChild.Next = child
child.Prev = n.LastChild
n.LastChild = child
} else {
n.FirstChild = child
n.LastChild = child
}
}
// InsertBefore inserts 'sibling' immediately before 'n'.
// It panics if either node is nil.
func (n *Node) InsertBefore(sibling *Node) {
sibling.Unlink()
sibling.Prev = n.Prev
if sibling.Prev != nil {
sibling.Prev.Next = sibling
}
sibling.Next = n
n.Prev = sibling
sibling.Parent = n.Parent
if sibling.Prev == nil {
sibling.Parent.FirstChild = sibling
}
}
func (n *Node) isContainer() bool {
switch n.Type {
case Document:
fallthrough
case BlockQuote:
fallthrough
case List:
fallthrough
case Item:
fallthrough
case Paragraph:
fallthrough
case Heading:
fallthrough
case Emph:
fallthrough
case Strong:
fallthrough
case Del:
fallthrough
case Link:
fallthrough
case Image:
fallthrough
case Table:
fallthrough
case TableHead:
fallthrough
case TableBody:
fallthrough
case TableRow:
fallthrough
case TableCell:
return true
default:
return false
}
}
func (n *Node) canContain(t NodeType) bool {
if n.Type == List {
return t == Item
}
if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
return t != Item
}
if n.Type == Table {
return t == TableHead || t == TableBody
}
if n.Type == TableHead || n.Type == TableBody {
return t == TableRow
}
if n.Type == TableRow {
return t == TableCell
}
return false
}
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
// It is returned from NodeVisitor and different values allow Node.Walk to
// decide which node to go to next.
type WalkStatus int
const (
// GoToNext is the default traversal of every node.
GoToNext WalkStatus = iota
// SkipChildren tells walker to skip all children of current node.
SkipChildren
// Terminate tells walker to terminate the traversal.
Terminate
)
// NodeVisitor is a callback to be called when traversing the syntax tree.
// Called twice for every node: once with entering=true when the branch is
// first visited, then with entering=false after all the children are done.
type NodeVisitor func(node *Node, entering bool) WalkStatus
// Walk is a convenience method that instantiates a walker and starts a
// traversal of subtree rooted at n.
func (n *Node) Walk(visitor NodeVisitor) {
w := newNodeWalker(n)
for w.current != nil {
status := visitor(w.current, w.entering)
switch status {
case GoToNext:
w.next()
case SkipChildren:
w.entering = false
w.next()
case Terminate:
return
}
}
}
type nodeWalker struct {
current *Node
root *Node
entering bool
}
func newNodeWalker(root *Node) *nodeWalker {
return &nodeWalker{
current: root,
root: root,
entering: true,
}
}
func (nw *nodeWalker) next() {
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
nw.current = nil
return
}
if nw.entering && nw.current.isContainer() {
if nw.current.FirstChild != nil {
nw.current = nw.current.FirstChild
nw.entering = true
} else {
nw.entering = false
}
} else if nw.current.Next == nil {
nw.current = nw.current.Parent
nw.entering = false
} else {
nw.current = nw.current.Next
nw.entering = true
}
}
func dump(ast *Node) {
fmt.Println(dumpString(ast))
}
func dumpR(ast *Node, depth int) string {
if ast == nil {
return ""
}
indent := bytes.Repeat([]byte("\t"), depth)
content := ast.Literal
if content == nil {
content = ast.content
}
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
for n := ast.FirstChild; n != nil; n = n.Next {
result += dumpR(n, depth+1)
}
return result
}
func dumpString(ast *Node) string {
return dumpR(ast, 0)
}
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
//
// SmartyPants rendering
//
//
package blackfriday
import (
"bytes"
"io"
)
// SPRenderer is a struct containing state of a Smartypants renderer.
type SPRenderer struct {
inSingleQuote bool
inDoubleQuote bool
callbacks [256]smartCallback
}
func wordBoundary(c byte) bool {
return c == 0 || isspace(c) || ispunct(c)
}
func tolower(c byte) byte {
if c >= 'A' && c <= 'Z' {
return c - 'A' + 'a'
}
return c
}
func isdigit(c byte) bool {
return c >= '0' && c <= '9'
}
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
// edge of the buffer is likely to be a tag that we don't get to see,
// so we treat it like text sometimes
// enumerate all sixteen possibilities for (previousChar, nextChar)
// each can be one of {0, space, punct, other}
switch {
case previousChar == 0 && nextChar == 0:
// context is not any help here, so toggle
*isOpen = !*isOpen
case isspace(previousChar) && nextChar == 0:
// [ "] might be [ "<code>foo...]
*isOpen = true
case ispunct(previousChar) && nextChar == 0:
// [!"] hmm... could be [Run!"] or [("<code>...]
*isOpen = false
case /* isnormal(previousChar) && */ nextChar == 0:
// [a"] is probably a close
*isOpen = false
case previousChar == 0 && isspace(nextChar):
// [" ] might be [...foo</code>" ]
*isOpen = false
case isspace(previousChar) && isspace(nextChar):
// [ " ] context is not any help here, so toggle
*isOpen = !*isOpen
case ispunct(previousChar) && isspace(nextChar):
// [!" ] is probably a close
*isOpen = false
case /* isnormal(previousChar) && */ isspace(nextChar):
// [a" ] this is one of the easy cases
*isOpen = false
case previousChar == 0 && ispunct(nextChar):
// ["!] hmm... could be ["$1.95] or [</code>"!...]
*isOpen = false
case isspace(previousChar) && ispunct(nextChar):
// [ "!] looks more like [ "$1.95]
*isOpen = true
case ispunct(previousChar) && ispunct(nextChar):
// [!"!] context is not any help here, so toggle
*isOpen = !*isOpen
case /* isnormal(previousChar) && */ ispunct(nextChar):
// [a"!] is probably a close
*isOpen = false
case previousChar == 0 /* && isnormal(nextChar) */ :
// ["a] is probably an open
*isOpen = true
case isspace(previousChar) /* && isnormal(nextChar) */ :
// [ "a] this is one of the easy cases
*isOpen = true
case ispunct(previousChar) /* && isnormal(nextChar) */ :
// [!"a] is probably an open
*isOpen = true
default:
// [a'b] maybe a contraction?
*isOpen = false
}
// Note that with the limited lookahead, this non-breaking
// space will also be appended to single double quotes.
if addNBSP && !*isOpen {
out.WriteString("&nbsp;")
}
out.WriteByte('&')
if *isOpen {
out.WriteByte('l')
} else {
out.WriteByte('r')
}
out.WriteByte(quote)
out.WriteString("quo;")
if addNBSP && *isOpen {
out.WriteString("&nbsp;")
}
return true
}
func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 {
t1 := tolower(text[1])
if t1 == '\'' {
nextChar := byte(0)
if len(text) >= 3 {
nextChar = text[2]
}
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1
}
}
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
out.WriteString("&rsquo;")
return 0
}
if len(text) >= 3 {
t2 := tolower(text[2])
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
(len(text) < 4 || wordBoundary(text[3])) {
out.WriteString("&rsquo;")
return 0
}
}
}
nextChar := byte(0)
if len(text) > 1 {
nextChar = text[1]
}
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
return 0
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 {
t1 := tolower(text[1])
t2 := tolower(text[2])
if t1 == 'c' && t2 == ')' {
out.WriteString("&copy;")
return 2
}
if t1 == 'r' && t2 == ')' {
out.WriteString("&reg;")
return 2
}
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
out.WriteString("&trade;")
return 3
}
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 {
if text[1] == '-' {
out.WriteString("&mdash;")
return 1
}
if wordBoundary(previousChar) && wordBoundary(text[1]) {
out.WriteString("&ndash;")
return 0
}
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
out.WriteString("&mdash;")
return 2
}
if len(text) >= 2 && text[1] == '-' {
out.WriteString("&ndash;")
return 1
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
if bytes.HasPrefix(text, []byte("&quot;")) {
nextChar := byte(0)
if len(text) >= 7 {
nextChar = text[6]
}
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
return 5
}
}
if bytes.HasPrefix(text, []byte("&#0;")) {
return 3
}
out.WriteByte('&')
return 0
}
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
var quote byte = 'd'
if angledQuotes {
quote = 'a'
}
return func(out *bytes.Buffer, previousChar byte, text []byte) int {
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
}
}
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
out.WriteString("&hellip;")
return 2
}
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
out.WriteString("&hellip;")
return 4
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 && text[1] == '`' {
nextChar := byte(0)
if len(text) >= 3 {
nextChar = text[2]
}
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1
}
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
// and avoid changing dates like 1/23/2005 into fractions.
numEnd := 0
for len(text) > numEnd && isdigit(text[numEnd]) {
numEnd++
}
if numEnd == 0 {
out.WriteByte(text[0])
return 0
}
denStart := numEnd + 1
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
denStart = numEnd + 3
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
out.WriteByte(text[0])
return 0
}
denEnd := denStart
for len(text) > denEnd && isdigit(text[denEnd]) {
denEnd++
}
if denEnd == denStart {
out.WriteByte(text[0])
return 0
}
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
out.WriteString("<sup>")
out.Write(text[:numEnd])
out.WriteString("</sup>&frasl;<sub>")
out.Write(text[denStart:denEnd])
out.WriteString("</sub>")
return denEnd - 1
}
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
out.WriteString("&frac12;")
return 2
}
}
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
out.WriteString("&frac14;")
return 2
}
}
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
out.WriteString("&frac34;")
return 2
}
}
}
out.WriteByte(text[0])
return 0
}
func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
nextChar := byte(0)
if len(text) > 1 {
nextChar = text[1]
}
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
out.WriteString("&quot;")
}
return 0
}
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
}
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
}
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
i := 0
for i < len(text) && text[i] != '>' {
i++
}
out.Write(text[:i+1])
return i
}
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
// NewSmartypantsRenderer constructs a Smartypants renderer object.
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
var (
r SPRenderer
smartAmpAngled = r.smartAmp(true, false)
smartAmpAngledNBSP = r.smartAmp(true, true)
smartAmpRegular = r.smartAmp(false, false)
smartAmpRegularNBSP = r.smartAmp(false, true)
addNBSP = flags&SmartypantsQuotesNBSP != 0
)
if flags&SmartypantsAngledQuotes == 0 {
r.callbacks['"'] = r.smartDoubleQuote
if !addNBSP {
r.callbacks['&'] = smartAmpRegular
} else {
r.callbacks['&'] = smartAmpRegularNBSP
}
} else {
r.callbacks['"'] = r.smartAngledDoubleQuote
if !addNBSP {
r.callbacks['&'] = smartAmpAngled
} else {
r.callbacks['&'] = smartAmpAngledNBSP
}
}
r.callbacks['\''] = r.smartSingleQuote
r.callbacks['('] = r.smartParens
if flags&SmartypantsDashes != 0 {
if flags&SmartypantsLatexDashes == 0 {
r.callbacks['-'] = r.smartDash
} else {
r.callbacks['-'] = r.smartDashLatex
}
}
r.callbacks['.'] = r.smartPeriod
if flags&SmartypantsFractions == 0 {
r.callbacks['1'] = r.smartNumber
r.callbacks['3'] = r.smartNumber
} else {
for ch := '1'; ch <= '9'; ch++ {
r.callbacks[ch] = r.smartNumberGeneric
}
}
r.callbacks['<'] = r.smartLeftAngle
r.callbacks['`'] = r.smartBacktick
return &r
}
// Process is the entry point of the Smartypants renderer.
func (r *SPRenderer) Process(w io.Writer, text []byte) {
mark := 0
for i := 0; i < len(text); i++ {
if action := r.callbacks[text[i]]; action != nil {
if i > mark {
w.Write(text[mark:i])
}
previousChar := byte(0)
if i > 0 {
previousChar = text[i-1]
}
var tmp bytes.Buffer
i += action(&tmp, previousChar, text[i:])
w.Write(tmp.Bytes())
mark = i + 1
}
}
if mark < len(text) {
w.Write(text[mark:])
}
}
sudo: false
language: go
go:
- 1.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...
MIT License
Copyright (c) 2015 Dmitri Shuralyov
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.
sanitized_anchor_name
=====================
[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name)
Package sanitized_anchor_name provides a func to create sanitized anchor names.
Its logic can be reused by multiple packages to create interoperable anchor names
and links to those anchors.
At this time, it does not try to ensure that generated anchor names
are unique, that responsibility falls on the caller.
Installation
------------
```bash
go get -u github.com/shurcooL/sanitized_anchor_name
```
Example
-------
```Go
anchorName := sanitized_anchor_name.Create("This is a header")
fmt.Println(anchorName)
// Output:
// this-is-a-header
```
License
-------
- [MIT License](LICENSE)
module github.com/shurcooL/sanitized_anchor_name
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
//
// Its logic can be reused by multiple packages to create interoperable anchor names
// and links to those anchors.
//
// At this time, it does not try to ensure that generated anchor names
// are unique, that responsibility falls on the caller.
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
import "unicode"
// Create returns a sanitized anchor name for the given text.
func Create(text string) string {
var anchorName []rune
var futureDash = false
for _, r := range text {
switch {
case unicode.IsLetter(r) || unicode.IsNumber(r):
if futureDash && len(anchorName) > 0 {
anchorName = append(anchorName, '-')
}
futureDash = false
anchorName = append(anchorName, unicode.ToLower(r))
default:
futureDash = true
}
}
return string(anchorName)
}
...@@ -60,13 +60,74 @@ type Capabilities interface { ...@@ -60,13 +60,74 @@ type Capabilities interface {
Apply(kind CapType) error Apply(kind CapType) error
} }
// NewPid create new initialized Capabilities object for given pid when it // NewPid initializes a new Capabilities object for given pid when
// is nonzero, or for the current pid if pid is 0 // it is nonzero, or for the current process if pid is 0.
//
// Deprecated: Replace with NewPid2. For example, replace:
//
// c, err := NewPid(0)
// if err != nil {
// return err
// }
//
// with:
//
// c, err := NewPid2(0)
// if err != nil {
// return err
// }
// err = c.Load()
// if err != nil {
// return err
// }
func NewPid(pid int) (Capabilities, error) { func NewPid(pid int) (Capabilities, error) {
c, err := newPid(pid)
if err != nil {
return c, err
}
err = c.Load()
return c, err
}
// NewPid2 initializes a new Capabilities object for given pid when
// it is nonzero, or for the current process if pid is 0. This
// does not load the process's current capabilities; to do that you
// must call Load explicitly.
func NewPid2(pid int) (Capabilities, error) {
return newPid(pid) return newPid(pid)
} }
// NewFile create new initialized Capabilities object for given named file. // NewFile initializes a new Capabilities object for given file path.
func NewFile(name string) (Capabilities, error) { //
return newFile(name) // Deprecated: Replace with NewFile2. For example, replace:
//
// c, err := NewFile(path)
// if err != nil {
// return err
// }
//
// with:
//
// c, err := NewFile2(path)
// if err != nil {
// return err
// }
// err = c.Load()
// if err != nil {
// return err
// }
func NewFile(path string) (Capabilities, error) {
c, err := newFile(path)
if err != nil {
return c, err
}
err = c.Load()
return c, err
}
// NewFile2 creates a new initialized Capabilities object for given
// file path. This does not load the process's current capabilities;
// to do that you must call Load explicitly.
func NewFile2(path string) (Capabilities, error) {
return newFile(path)
} }
...@@ -103,21 +103,17 @@ func newPid(pid int) (c Capabilities, err error) { ...@@ -103,21 +103,17 @@ func newPid(pid int) (c Capabilities, err error) {
case linuxCapVer1: case linuxCapVer1:
p := new(capsV1) p := new(capsV1)
p.hdr.version = capVers p.hdr.version = capVers
p.hdr.pid = pid p.hdr.pid = int32(pid)
c = p c = p
case linuxCapVer2, linuxCapVer3: case linuxCapVer2, linuxCapVer3:
p := new(capsV3) p := new(capsV3)
p.hdr.version = capVers p.hdr.version = capVers
p.hdr.pid = pid p.hdr.pid = int32(pid)
c = p c = p
default: default:
err = errUnknownVers err = errUnknownVers
return return
} }
err = c.Load()
if err != nil {
c = nil
}
return return
} }
...@@ -492,10 +488,6 @@ func (c *capsV3) Apply(kind CapType) (err error) { ...@@ -492,10 +488,6 @@ func (c *capsV3) Apply(kind CapType) (err error) {
func newFile(path string) (c Capabilities, err error) { func newFile(path string) (c Capabilities, err error) {
c = &capsFile{path: path} c = &capsFile{path: path}
err = c.Load()
if err != nil {
c = nil
}
return return
} }
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
type capHeader struct { type capHeader struct {
version uint32 version uint32
pid int pid int32
} }
type capData struct { type capData struct {
......
*.coverprofile *.coverprofile
node_modules/ node_modules/
vendor
\ No newline at end of file
language: go language: go
sudo: false sudo: false
dist: trusty dist: bionic
osx_image: xcode8.3 osx_image: xcode10
go: 1.8.x go:
- 1.11.x
- 1.12.x
- 1.13.x
os: os:
- linux - linux
- osx - osx
env:
GO111MODULE=on
GOPROXY=https://proxy.golang.org
cache: cache:
directories: directories:
- node_modules - node_modules
before_script: before_script:
- go get github.com/urfave/gfmrun/... || true - go get github.com/urfave/gfmrun/cmd/gfmrun
- go get golang.org/x/tools/cmd/goimports - go get golang.org/x/tools/cmd/goimports
- if [ ! -f node_modules/.bin/markdown-toc ] ; then - npm install markdown-toc
npm install markdown-toc ; - go mod tidy
fi
script: script:
- ./runtests gen - go run build.go vet
- ./runtests vet - go run build.go test
- ./runtests test - go run build.go gfmrun
- ./runtests gfmrun - go run build.go toc
- ./runtests toc
after_success:
- bash <(curl -s https://codecov.io/bash)
...@@ -4,7 +4,70 @@ ...@@ -4,7 +4,70 @@
## [Unreleased] ## [Unreleased]
## 1.20.0 - 2017-08-10 ## [1.22.1] - 2019-09-11
### Fixed
* Hide output of hidden commands on man pages in [urfave/cli/pull/889](https://github.com/urfave/cli/pull/889) via [@crosbymichael](https://github.com/crosbymichael)
* Don't generate fish completion for hidden commands [urfave/cli/pull/891](https://github.com/urfave/891) via [@saschagrunert](https://github.com/saschagrunert)
* Using short flag names for required flags throws an error in [urfave/cli/pull/890](https://github.com/urfave/cli/pull/890) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
### Changed
* Remove flag code generation logic, legacy python test runner in [urfave/cli/pull/883](https://github.com/urfave/cli/pull/883) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
* Enable Go Modules support, drop support for `Go 1.10` add support for `Go 1.13` in [urfave/cli/pull/885](https://github.com/urfave/cli/pull/885) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
## [1.22.0] - 2019-09-07
### Fixed
* Fix Subcommands not falling back to `app.ExitEventHandler` in [urfave/cli/pull/856](https://github.com/urfave/cli/pull/856) via [@FaranIdo](https://github.com/FaranIdo)
### Changed
* Clarify that altsrc supports both TOML and JSON in [urfave/cli/pull/774](https://github.com/urfave/cli/pull/774) via [@whereswaldon](https://github.com/whereswaldon)
* Made the exit code example more clear in [urfave/cli/pull/823](https://github.com/urfave/cli/pull/823) via [@xordspar0](https://github.com/xordspar0)
* Removed the use of python for internal flag generation in [urfave/cli/pull/836](https://github.com/urfave/cli/pull/836) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
* Changed the supported go versions to `1.10`, `1.11`, `1.12` in [urfave/cli/pull/843](https://github.com/urfave/cli/pull/843) via [@lafriks](https://github.com/lafriks)
* Changed the v1 releases section in the readme in [urfave/cli/pull/862](https://github.com/urfave/cli/pull/862) via [@russoj88](https://github.com/russoj88)
* Cleaned up go modules in [urfave/cli/pull/874](https://github.com/urfave/cli/pull/874) via [@saschagrunert](https://github.com/saschagrunert)
### Added
* Added `UseShortOptionHandling` for combining short flags in [urfave/cli/pull/735](https://github.com/urfave/cli/pull/735) via [@rliebz](https://github.com/rliebz)
* Added support for flags bash completion in [urfave/cli/pull/808](https://github.com/urfave/cli/pull/808) via [@yogeshlonkar](https://github.com/yogeshlonkar)
* Added the `TakesFile` indicator to flag in [urfave/cli/pull/851](https://github.com/urfave/cli/pull/851) via [@saschagrunert](https://github.com/saschagrunert)
* Added fish shell completion support in [urfave/cli/pull/848](https://github.com/urfave/cli/pull/848) via [@saschagrunert](https://github.com/saschagrunert)
## [1.21.0] - 2019-08-02
### Fixed
* Fix using "slice" flag types with `EnvVar` in [urfave/cli/pull/687](https://github.com/urfave/cli/pull/687) via [@joshuarubin](https://github.com/joshuarubin)
* Fix regression of `SkipFlagParsing` behavior in [urfave/cli/pull/697](https://github.com/urfave/cli/pull/697) via [@jszwedko](https://github.com/jszwedko)
* Fix handling `ShortOptions` and `SkipArgReorder` in [urfave/cli/pull/686](https://github.com/urfave/cli/pull/686) via [@baude](https://github.com/baude)
* Fix args reordering when bool flags are present in [urfave/cli/pull/712](https://github.com/urfave/cli/pull/712) via [@windler](https://github.com/windler)
* Fix parsing of short options in [urfave/cli/pull/758](https://github.com/urfave/cli/pull/758) via [@vrothberg](https://github.com/vrothberg)
* Fix unaligned indents for the command help messages in [urfave/cli/pull/806](https://github.com/urfave/cli/pull/806) via [@mingrammer](https://github.com/mingrammer)
### Changed
* Cleaned up help output in [urfave/cli/pull/664](https://github.com/urfave/cli/pull/664) via [@maguro](https://github.com/maguro)
* Remove redundant nil checks in [urfave/cli/pull/773](https://github.com/urfave/cli/pull/773) via [@teresy](https://github.com/teresy)
* Case is now considered when sorting strings in [urfave/cli/pull/676](https://github.com/urfave/cli/pull/676) via [@rliebz](https://github.com/rliebz)
### Added
* Added _"required flags"_ support in [urfave/cli/pull/819](https://github.com/urfave/cli/pull/819) via [@lynncyrin](https://github.com/lynncyrin/)
* Backport JSON `InputSource` to v1 in [urfave/cli/pull/598](https://github.com/urfave/cli/pull/598) via [@jszwedko](https://github.com/jszwedko)
* Allow more customization of flag help strings in [urfave/cli/pull/661](https://github.com/urfave/cli/pull/661) via [@rliebz](https://github.com/rliebz)
* Allow custom `ExitError` handler function in [urfave/cli/pull/628](https://github.com/urfave/cli/pull/628) via [@phinnaeus](https://github.com/phinnaeus)
* Allow loading a variable from a file in [urfave/cli/pull/675](https://github.com/urfave/cli/pull/675) via [@jmccann](https://github.com/jmccann)
* Allow combining short bool names in [urfave/cli/pull/684](https://github.com/urfave/cli/pull/684) via [@baude](https://github.com/baude)
* Added test coverage to context in [urfave/cli/pull/788](https://github.com/urfave/cli/pull/788) via [@benzvan](https://github.com/benzvan)
* Added go module support in [urfave/cli/pull/831](https://github.com/urfave/cli/pull/831) via [@saschagrunert](https://github.com/saschagrunert)
## [1.20.0] - 2017-08-10
### Fixed ### Fixed
...@@ -407,7 +470,13 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ...@@ -407,7 +470,13 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Added ### Added
- Initial implementation. - Initial implementation.
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD [Unreleased]: https://github.com/urfave/cli/compare/v1.22.1...HEAD
[1.22.1]: https://github.com/urfave/cli/compare/v1.22.0...v1.22.1
[1.22.0]: https://github.com/urfave/cli/compare/v1.21.0...v1.22.0
[1.21.0]: https://github.com/urfave/cli/compare/v1.20.0...v1.21.0
[1.20.0]: https://github.com/urfave/cli/compare/v1.19.1...v1.20.0
[1.19.1]: https://github.com/urfave/cli/compare/v1.19.0...v1.19.1
[1.19.0]: https://github.com/urfave/cli/compare/v1.18.0...v1.19.0
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 [1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 [1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 [1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
......
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
reviewed and investigated and will result in a response that is deemed necessary
and appropriate to the circumstances. The project team is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details of
specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
## Contributing
Use @urfave/cli to ping the maintainers.
Feel free to put up a pull request to fix a bug or maybe add a feature. We will
give it a code review and make sure that it does not break backwards
compatibility. If collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
If you feel like you have contributed to the project but have not yet been added
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
issue!
...@@ -3,15 +3,11 @@ cli ...@@ -3,15 +3,11 @@ cli
[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli)
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc)
**Notice:** This is the library formerly known as
`github.com/codegangsta/cli` -- Github will automatically redirect requests
to this repository, but we recommend updating your references for clarity.
cli is a simple, fast, and fun package for building command line apps in Go. The cli is a simple, fast, and fun package for building command line apps in Go. The
goal is to enable developers to write fast and distributable command line goal is to enable developers to write fast and distributable command line
...@@ -23,7 +19,7 @@ applications in an expressive way. ...@@ -23,7 +19,7 @@ applications in an expressive way.
- [Installation](#installation) - [Installation](#installation)
* [Supported platforms](#supported-platforms) * [Supported platforms](#supported-platforms)
* [Using the `v2` branch](#using-the-v2-branch) * [Using the `v2` branch](#using-the-v2-branch)
* [Pinning to the `v1` releases](#pinning-to-the-v1-releases) * [Using `v1` releases](#using-v1-releases)
- [Getting Started](#getting-started) - [Getting Started](#getting-started)
- [Examples](#examples) - [Examples](#examples)
* [Arguments](#arguments) * [Arguments](#arguments)
...@@ -32,11 +28,13 @@ applications in an expressive way. ...@@ -32,11 +28,13 @@ applications in an expressive way.
+ [Alternate Names](#alternate-names) + [Alternate Names](#alternate-names)
+ [Ordering](#ordering) + [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment) + [Values from the Environment](#values-from-the-environment)
+ [Values from files](#values-from-files)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
+ [Precedence](#precedence) + [Precedence](#precedence)
* [Subcommands](#subcommands) * [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories) * [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code) * [Exit code](#exit-code)
* [Combining short options](#combining-short-options)
* [Bash Completion](#bash-completion) * [Bash Completion](#bash-completion)
+ [Enabling](#enabling) + [Enabling](#enabling)
+ [Distribution](#distribution) + [Distribution](#distribution)
...@@ -62,7 +60,7 @@ organized, and expressive! ...@@ -62,7 +60,7 @@ organized, and expressive!
## Installation ## Installation
Make sure you have a working Go environment. Go version 1.2+ is supported. [See Make sure you have a working Go environment. Go version 1.10+ is supported. [See
the install instructions for Go](http://golang.org/doc/install.html). the install instructions for Go](http://golang.org/doc/install.html).
To install cli, simply run: To install cli, simply run:
...@@ -106,25 +104,20 @@ import ( ...@@ -106,25 +104,20 @@ import (
... ...
``` ```
### Pinning to the `v1` releases ### Using `v1` releases
Similarly to the section above describing use of the `v2` branch, if one wants
to avoid any unexpected compatibility pains once `v2` becomes `master`, then
pinning to `v1` is an acceptable option, e.g.:
``` ```
$ go get gopkg.in/urfave/cli.v1 $ go get github.com/urfave/cli
``` ```
``` go ```go
... ...
import ( import (
"gopkg.in/urfave/cli.v1" // imports as package "cli" "github.com/urfave/cli"
) )
... ...
``` ```
This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing).
## Getting Started ## Getting Started
...@@ -139,13 +132,17 @@ discovery. So a cli app can be as little as one line of code in `main()`. ...@@ -139,13 +132,17 @@ discovery. So a cli app can be as little as one line of code in `main()`.
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func main() { func main() {
cli.NewApp().Run(os.Args) err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -160,6 +157,7 @@ package main ...@@ -160,6 +157,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -174,7 +172,10 @@ func main() { ...@@ -174,7 +172,10 @@ func main() {
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -198,6 +199,7 @@ package main ...@@ -198,6 +199,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -212,7 +214,10 @@ func main() { ...@@ -212,7 +214,10 @@ func main() {
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -261,6 +266,7 @@ package main ...@@ -261,6 +266,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -274,7 +280,10 @@ func main() { ...@@ -274,7 +280,10 @@ func main() {
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -290,6 +299,7 @@ package main ...@@ -290,6 +299,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -319,7 +329,10 @@ func main() { ...@@ -319,7 +329,10 @@ func main() {
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -333,6 +346,7 @@ scanned. ...@@ -333,6 +346,7 @@ scanned.
package main package main
import ( import (
"log"
"os" "os"
"fmt" "fmt"
...@@ -366,7 +380,10 @@ func main() { ...@@ -366,7 +380,10 @@ func main() {
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -387,6 +404,7 @@ For example this: ...@@ -387,6 +404,7 @@ For example this:
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -402,7 +420,10 @@ func main() { ...@@ -402,7 +420,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -428,6 +449,7 @@ list for the `Name`. e.g. ...@@ -428,6 +449,7 @@ list for the `Name`. e.g.
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -444,7 +466,10 @@ func main() { ...@@ -444,7 +466,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -468,6 +493,7 @@ For example this: ...@@ -468,6 +493,7 @@ For example this:
package main package main
import ( import (
"log"
"os" "os"
"sort" "sort"
...@@ -511,7 +537,10 @@ func main() { ...@@ -511,7 +537,10 @@ func main() {
sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands)) sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -534,6 +563,7 @@ You can also have the default value set from the environment via `EnvVar`. e.g. ...@@ -534,6 +563,7 @@ You can also have the default value set from the environment via `EnvVar`. e.g.
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -551,7 +581,10 @@ func main() { ...@@ -551,7 +581,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -566,6 +599,7 @@ environment variable that resolves is used as the default. ...@@ -566,6 +599,7 @@ environment variable that resolves is used as the default.
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -583,10 +617,52 @@ func main() { ...@@ -583,10 +617,52 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
#### Values from files
You can also have the default value set from file via `FilePath`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "password for the mysql database"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "password, p",
Usage: "password for the mysql database",
FilePath: "/etc/mysql/password",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Note that default values set from file (e.g. `FilePath`) take precedence over
default values set from the environment (e.g. `EnvVar`).
#### Values from alternate input sources (YAML, TOML, and others) #### Values from alternate input sources (YAML, TOML, and others)
There is a separate package altsrc that adds support for getting flag values There is a separate package altsrc that adds support for getting flag values
...@@ -594,6 +670,7 @@ from other file input sources. ...@@ -594,6 +670,7 @@ from other file input sources.
Currently supported input source formats: Currently supported input source formats:
* YAML * YAML
* JSON
* TOML * TOML
In order to get values for a flag from an alternate input source the following In order to get values for a flag from an alternate input source the following
...@@ -616,9 +693,9 @@ the yaml input source for any flags that are defined on that command. As a note ...@@ -616,9 +693,9 @@ the yaml input source for any flags that are defined on that command. As a note
the "load" flag used would also have to be defined on the command flags in order the "load" flag used would also have to be defined on the command flags in order
for this code snipped to work. for this code snipped to work.
Currently only the aboved specified formats are supported but developers can Currently only YAML, JSON, and TOML files are supported but developers can add support
add support for other input sources by implementing the for other input sources by implementing the altsrc.InputSourceContext for their
altsrc.InputSourceContext for their given sources. given sources.
Here is a more complete sample of a command using YAML support: Here is a more complete sample of a command using YAML support:
...@@ -631,6 +708,7 @@ package notmain ...@@ -631,6 +708,7 @@ package notmain
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -653,7 +731,10 @@ func main() { ...@@ -653,7 +731,10 @@ func main() {
app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load"))
app.Flags = flags app.Flags = flags
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -679,6 +760,7 @@ package main ...@@ -679,6 +760,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -731,7 +813,10 @@ func main() { ...@@ -731,7 +813,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -747,6 +832,7 @@ E.g. ...@@ -747,6 +832,7 @@ E.g.
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -769,7 +855,10 @@ func main() { ...@@ -769,7 +855,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -777,7 +866,7 @@ Will include: ...@@ -777,7 +866,7 @@ Will include:
``` ```
COMMANDS: COMMANDS:
noop noop
Template actions: Template actions:
add add
...@@ -790,11 +879,14 @@ Calling `App.Run` will not automatically call `os.Exit`, which means that by ...@@ -790,11 +879,14 @@ Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. An explicit exit code default the exit code will "fall through" to being `0`. An explicit exit code
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: `cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
<!-- {
"error": "Ginger croutons are not in the soup"
} -->
``` go ``` go
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -803,22 +895,95 @@ import ( ...@@ -803,22 +895,95 @@ import (
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.BoolTFlag{ cli.BoolFlag{
Name: "ginger-crouton", Name: "ginger-crouton",
Usage: "is it in the soup?", Usage: "Add ginger croutons to the soup",
}, },
} }
app.Action = func(ctx *cli.Context) error { app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") { if !ctx.Bool("ginger-crouton") {
return cli.NewExitError("it is not in the soup", 86) return cli.NewExitError("Ginger croutons are not in the soup", 86)
} }
return nil return nil
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
### Combining short options
Traditional use of options using their shortnames look like this:
```
$ cmd -s -o -m "Some message"
```
Suppose you want users to be able to combine options with their shortnames. This
can be done using the `UseShortOptionHandling` bool in your app configuration,
or for individual commands by attaching it to the command configuration. For
example:
<!-- {
"args": ["short", "&#45;som", "Some message"],
"output": "serve: true\noption: true\nmessage: Some message\n"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.UseShortOptionHandling = true
app.Commands = []cli.Command{
{
Name: "short",
Usage: "complete a task on the list",
Flags: []cli.Flag{
cli.BoolFlag{Name: "serve, s"},
cli.BoolFlag{Name: "option, o"},
cli.StringFlag{Name: "message, m"},
},
Action: func(c *cli.Context) error {
fmt.Println("serve:", c.Bool("serve"))
fmt.Println("option:", c.Bool("option"))
fmt.Println("message:", c.String("message"))
return nil
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
If your program has any number of bool flags such as `serve` and `option`, and
optionally one non-bool flag `message`, with the short options of `-s`, `-o`,
and `-m` respectively, setting `UseShortOptionHandling` will also support the
following syntax:
```
$ cmd -som "Some message"
```
If you enable `UseShortOptionHandling`, then you must not use any flags that
have a single leading `-` or this will result in failures. For example,
`-option` can no longer be used. Flags with two leading dashes (such as
`--options`) are still valid.
### Bash Completion ### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion` You can enable completion commands by setting the `EnableBashCompletion`
...@@ -835,6 +1000,7 @@ package main ...@@ -835,6 +1000,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -866,7 +1032,10 @@ func main() { ...@@ -866,7 +1032,10 @@ func main() {
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -906,6 +1075,7 @@ The default bash completion flag (`--generate-bash-completion`) is defined as ...@@ -906,6 +1075,7 @@ The default bash completion flag (`--generate-bash-completion`) is defined as
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -924,7 +1094,10 @@ func main() { ...@@ -924,7 +1094,10 @@ func main() {
Name: "wat", Name: "wat",
}, },
} }
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -950,6 +1123,7 @@ package main ...@@ -950,6 +1123,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"io" "io"
"os" "os"
...@@ -993,7 +1167,10 @@ VERSION: ...@@ -993,7 +1167,10 @@ VERSION:
fmt.Println("Ha HA. I pwnd the help!!1") fmt.Println("Ha HA. I pwnd the help!!1")
} }
cli.NewApp().Run(os.Args) err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -1008,6 +1185,7 @@ setting `cli.HelpFlag`, e.g.: ...@@ -1008,6 +1185,7 @@ setting `cli.HelpFlag`, e.g.:
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -1020,7 +1198,10 @@ func main() { ...@@ -1020,7 +1198,10 @@ func main() {
EnvVar: "SHOW_HALP,HALPPLZ", EnvVar: "SHOW_HALP,HALPPLZ",
} }
cli.NewApp().Run(os.Args) err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -1043,6 +1224,7 @@ setting `cli.VersionFlag`, e.g.: ...@@ -1043,6 +1224,7 @@ setting `cli.VersionFlag`, e.g.:
package main package main
import ( import (
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -1057,7 +1239,10 @@ func main() { ...@@ -1057,7 +1239,10 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "partay" app.Name = "partay"
app.Version = "19.99.0" app.Version = "19.99.0"
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -1072,6 +1257,7 @@ package main ...@@ -1072,6 +1257,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -1089,7 +1275,10 @@ func main() { ...@@ -1089,7 +1275,10 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "partay" app.Name = "partay"
app.Version = "19.99.0" app.Version = "19.99.0"
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }
``` ```
...@@ -1246,6 +1435,7 @@ func main() { ...@@ -1246,6 +1435,7 @@ func main() {
cli.Uint64Flag{Name: "bigage"}, cli.Uint64Flag{Name: "bigage"},
} }
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.UseShortOptionHandling = true
app.HideHelp = false app.HideHelp = false
app.HideVersion = false app.HideVersion = false
app.BashComplete = func(c *cli.Context) { app.BashComplete = func(c *cli.Context) {
...@@ -1351,7 +1541,7 @@ func main() { ...@@ -1351,7 +1541,7 @@ func main() {
ec := cli.NewExitError("ohwell", 86) ec := cli.NewExitError("ohwell", 86)
fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode())
fmt.Printf("made it!\n") fmt.Printf("made it!\n")
return ec return nil
} }
if os.Getenv("HEXY") != "" { if os.Getenv("HEXY") != "" {
...@@ -1365,7 +1555,9 @@ func main() { ...@@ -1365,7 +1555,9 @@ func main() {
"whatever-values": 19.99, "whatever-values": 19.99,
} }
app.Run(os.Args)
// ignore error so we don't exit non-zero and break gfmrun README example tests
_ = app.Run(os.Args)
} }
func wopAction(c *cli.Context) error { func wopAction(c *cli.Context) error {
...@@ -1376,16 +1568,4 @@ func wopAction(c *cli.Context) error { ...@@ -1376,16 +1568,4 @@ func wopAction(c *cli.Context) error {
## Contribution Guidelines ## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will See [./CONTRIBUTING.md](./CONTRIBUTING.md)
give it a code review and make sure that it does not break backwards
compatibility. If I or any other collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
If you feel like you have contributed to the project but have not yet been
added as a collaborator, we probably forgot to add you, please open an issue.
package cli package cli
import ( import (
"flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
...@@ -11,9 +11,10 @@ import ( ...@@ -11,9 +11,10 @@ import (
) )
var ( var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) // unused variable. commented for now. will remove in future if agreed upon by everyone
//runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
...@@ -94,6 +95,10 @@ type App struct { ...@@ -94,6 +95,10 @@ type App struct {
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
CustomAppHelpTemplate string CustomAppHelpTemplate string
// Boolean to enable short-option handling so user can combine several
// single-character bool arguements into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
didSetup bool didSetup bool
} }
...@@ -138,7 +143,7 @@ func (a *App) Setup() { ...@@ -138,7 +143,7 @@ func (a *App) Setup() {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
} }
newCmds := []Command{} var newCmds []Command
for _, c := range a.Commands { for _, c := range a.Commands {
if c.HelpName == "" { if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
...@@ -173,6 +178,14 @@ func (a *App) Setup() { ...@@ -173,6 +178,14 @@ func (a *App) Setup() {
} }
} }
func (a *App) newFlagSet() (*flag.FlagSet, error) {
return flagSet(a.Name, a.Flags)
}
func (a *App) useShortOptionHandling() bool {
return a.UseShortOptionHandling
}
// Run is the entry point to the cli app. Parses the arguments slice and routes // Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination // to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) { func (a *App) Run(arguments []string) (err error) {
...@@ -186,19 +199,17 @@ func (a *App) Run(arguments []string) (err error) { ...@@ -186,19 +199,17 @@ func (a *App) Run(arguments []string) (err error) {
// always appends the completion flag at the end of the command // always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments) shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags _, err = a.newFlagSet()
set, err := flagSet(a.Name, a.Flags)
if err != nil { if err != nil {
return err return err
} }
set.SetOutput(ioutil.Discard) set, err := parseIter(a, arguments[1:])
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil) context := NewContext(a, set, nil)
if nerr != nil { if nerr != nil {
fmt.Fprintln(a.Writer, nerr) _, _ = fmt.Fprintln(a.Writer, nerr)
ShowAppHelp(context) _ = ShowAppHelp(context)
return nerr return nerr
} }
context.shellComplete = shellComplete context.shellComplete = shellComplete
...@@ -213,13 +224,13 @@ func (a *App) Run(arguments []string) (err error) { ...@@ -213,13 +224,13 @@ func (a *App) Run(arguments []string) (err error) {
a.handleExitCoder(context, err) a.handleExitCoder(context, err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context) _ = ShowAppHelp(context)
return err return err
} }
if !a.HideHelp && checkHelp(context) { if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context) _ = ShowAppHelp(context)
return nil return nil
} }
...@@ -228,6 +239,12 @@ func (a *App) Run(arguments []string) (err error) { ...@@ -228,6 +239,12 @@ func (a *App) Run(arguments []string) (err error) {
return nil return nil
} }
cerr := checkRequiredFlags(a.Flags, context)
if cerr != nil {
_ = ShowAppHelp(context)
return cerr
}
if a.After != nil { if a.After != nil {
defer func() { defer func() {
if afterErr := a.After(context); afterErr != nil { if afterErr := a.After(context); afterErr != nil {
...@@ -243,8 +260,8 @@ func (a *App) Run(arguments []string) (err error) { ...@@ -243,8 +260,8 @@ func (a *App) Run(arguments []string) (err error) {
if a.Before != nil { if a.Before != nil {
beforeErr := a.Before(context) beforeErr := a.Before(context)
if beforeErr != nil { if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context) _ = ShowAppHelp(context)
a.handleExitCoder(context, beforeErr) a.handleExitCoder(context, beforeErr)
err = beforeErr err = beforeErr
return err return err
...@@ -278,7 +295,7 @@ func (a *App) Run(arguments []string) (err error) { ...@@ -278,7 +295,7 @@ func (a *App) Run(arguments []string) (err error) {
// code in the cli.ExitCoder // code in the cli.ExitCoder
func (a *App) RunAndExitOnError() { func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil { if err := a.Run(os.Args); err != nil {
fmt.Fprintln(a.errWriter(), err) _, _ = fmt.Fprintln(a.errWriter(), err)
OsExiter(1) OsExiter(1)
} }
} }
...@@ -305,24 +322,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { ...@@ -305,24 +322,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
} }
a.Commands = newCmds a.Commands = newCmds
// parse flags _, err = a.newFlagSet()
set, err := flagSet(a.Name, a.Flags)
if err != nil { if err != nil {
return err return err
} }
set.SetOutput(ioutil.Discard) set, err := parseIter(a, ctx.Args().Tail())
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx) context := NewContext(a, set, ctx)
if nerr != nil { if nerr != nil {
fmt.Fprintln(a.Writer, nerr) _, _ = fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer) _, _ = fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 { if len(a.Commands) > 0 {
ShowSubcommandHelp(context) _ = ShowSubcommandHelp(context)
} else { } else {
ShowCommandHelp(ctx, context.Args().First()) _ = ShowCommandHelp(ctx, context.Args().First())
} }
return nerr return nerr
} }
...@@ -337,8 +352,8 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { ...@@ -337,8 +352,8 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
a.handleExitCoder(context, err) a.handleExitCoder(context, err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context) _ = ShowSubcommandHelp(context)
return err return err
} }
...@@ -352,6 +367,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { ...@@ -352,6 +367,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
} }
} }
cerr := checkRequiredFlags(a.Flags, context)
if cerr != nil {
_ = ShowSubcommandHelp(context)
return cerr
}
if a.After != nil { if a.After != nil {
defer func() { defer func() {
afterErr := a.After(context) afterErr := a.After(context)
...@@ -428,7 +449,7 @@ func (a *App) VisibleCategories() []*CommandCategory { ...@@ -428,7 +449,7 @@ func (a *App) VisibleCategories() []*CommandCategory {
// VisibleCommands returns a slice of the Commands with Hidden=false // VisibleCommands returns a slice of the Commands with Hidden=false
func (a *App) VisibleCommands() []Command { func (a *App) VisibleCommands() []Command {
ret := []Command{} var ret []Command
for _, command := range a.Commands { for _, command := range a.Commands {
if !command.Hidden { if !command.Hidden {
ret = append(ret, command) ret = append(ret, command)
...@@ -453,7 +474,6 @@ func (a *App) hasFlag(flag Flag) bool { ...@@ -453,7 +474,6 @@ func (a *App) hasFlag(flag Flag) bool {
} }
func (a *App) errWriter() io.Writer { func (a *App) errWriter() io.Writer {
// When the app ErrWriter is nil use the package level one. // When the app ErrWriter is nil use the package level one.
if a.ErrWriter == nil { if a.ErrWriter == nil {
return ErrWriter return ErrWriter
...@@ -496,11 +516,12 @@ func (a Author) String() string { ...@@ -496,11 +516,12 @@ func (a Author) String() string {
// it's an ActionFunc or a func with the legacy signature for Action, the func // it's an ActionFunc or a func with the legacy signature for Action, the func
// is run! // is run!
func HandleAction(action interface{}, context *Context) (err error) { func HandleAction(action interface{}, context *Context) (err error) {
if a, ok := action.(ActionFunc); ok { switch a := action.(type) {
case ActionFunc:
return a(context) return a(context)
} else if a, ok := action.(func(*Context) error); ok { case func(*Context) error:
return a(context) return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature case func(*Context): // deprecated function signature
a(context) a(context)
return nil return nil
} }
......
...@@ -8,19 +8,16 @@ clone_folder: c:\gopath\src\github.com\urfave\cli ...@@ -8,19 +8,16 @@ clone_folder: c:\gopath\src\github.com\urfave\cli
environment: environment:
GOPATH: C:\gopath GOPATH: C:\gopath
GOVERSION: 1.8.x GOVERSION: 1.11.x
PYTHON: C:\Python36-x64
PYTHON_VERSION: 3.6.x
PYTHON_ARCH: 64
install: install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version - go version
- go env - go env
- go get github.com/urfave/gfmrun/... - go get github.com/urfave/gfmrun/...
- go get -v -t ./... - go get -v -t ./...
build_script: build_script:
- python runtests vet - go run build.go vet
- python runtests test - go run build.go test
- python runtests gfmrun - go run build.go gfmrun
...@@ -10,7 +10,7 @@ type CommandCategory struct { ...@@ -10,7 +10,7 @@ type CommandCategory struct {
} }
func (c CommandCategories) Less(i, j int) bool { func (c CommandCategories) Less(i, j int) bool {
return c[i].Name < c[j].Name return lexicographicLess(c[i].Name, c[j].Name)
} }
func (c CommandCategories) Len() int { func (c CommandCategories) Len() int {
......
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
// } // }
package cli package cli
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go //go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go
package cli package cli
import ( import (
"flag"
"fmt" "fmt"
"io/ioutil"
"sort" "sort"
"strings" "strings"
) )
...@@ -55,6 +55,10 @@ type Command struct { ...@@ -55,6 +55,10 @@ type Command struct {
HideHelp bool HideHelp bool
// Boolean to hide this command from help or completion // Boolean to hide this command from help or completion
Hidden bool Hidden bool
// Boolean to enable short-option handling so user can combine several
// single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
// Full name of command for help, defaults to full command name, including parent commands. // Full name of command for help, defaults to full command name, including parent commands.
HelpName string HelpName string
...@@ -73,7 +77,7 @@ func (c CommandsByName) Len() int { ...@@ -73,7 +77,7 @@ func (c CommandsByName) Len() int {
} }
func (c CommandsByName) Less(i, j int) bool { func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name return lexicographicLess(c[i].Name, c[j].Name)
} }
func (c CommandsByName) Swap(i, j int) { func (c CommandsByName) Swap(i, j int) {
...@@ -106,57 +110,11 @@ func (c Command) Run(ctx *Context) (err error) { ...@@ -106,57 +110,11 @@ func (c Command) Run(ctx *Context) (err error) {
) )
} }
set, err := flagSet(c.Name, c.Flags) if ctx.App.UseShortOptionHandling {
if err != nil { c.UseShortOptionHandling = true
return err
}
set.SetOutput(ioutil.Discard)
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if arg == "-" {
// Do nothing. A dash alone is not really a flag.
continue
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
if firstFlagIndex > -1 {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
} else {
err = set.Parse(ctx.Args().Tail())
} }
nerr := normalizeFlags(c.Flags, set) set, err := c.parseFlags(ctx.Args().Tail())
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx) context := NewContext(ctx.App, set, ctx)
context.Command = c context.Command = c
...@@ -170,9 +128,9 @@ func (c Command) Run(ctx *Context) (err error) { ...@@ -170,9 +128,9 @@ func (c Command) Run(ctx *Context) (err error) {
context.App.handleExitCoder(context, err) context.App.handleExitCoder(context, err)
return err return err
} }
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(context.App.Writer) _, _ = fmt.Fprintln(context.App.Writer)
ShowCommandHelp(context, c.Name) _ = ShowCommandHelp(context, c.Name)
return err return err
} }
...@@ -180,6 +138,12 @@ func (c Command) Run(ctx *Context) (err error) { ...@@ -180,6 +138,12 @@ func (c Command) Run(ctx *Context) (err error) {
return nil return nil
} }
cerr := checkRequiredFlags(c.Flags, context)
if cerr != nil {
_ = ShowCommandHelp(context, c.Name)
return cerr
}
if c.After != nil { if c.After != nil {
defer func() { defer func() {
afterErr := c.After(context) afterErr := c.After(context)
...@@ -197,7 +161,7 @@ func (c Command) Run(ctx *Context) (err error) { ...@@ -197,7 +161,7 @@ func (c Command) Run(ctx *Context) (err error) {
if c.Before != nil { if c.Before != nil {
err = c.Before(context) err = c.Before(context)
if err != nil { if err != nil {
ShowCommandHelp(context, c.Name) _ = ShowCommandHelp(context, c.Name)
context.App.handleExitCoder(context, err) context.App.handleExitCoder(context, err)
return err return err
} }
...@@ -215,6 +179,71 @@ func (c Command) Run(ctx *Context) (err error) { ...@@ -215,6 +179,71 @@ func (c Command) Run(ctx *Context) (err error) {
return err return err
} }
func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
if c.SkipFlagParsing {
set, err := c.newFlagSet()
if err != nil {
return nil, err
}
return set, set.Parse(append([]string{"--"}, args...))
}
if !c.SkipArgReorder {
args = reorderArgs(args)
}
set, err := parseIter(c, args)
if err != nil {
return nil, err
}
err = normalizeFlags(c.Flags, set)
if err != nil {
return nil, err
}
return set, nil
}
func (c *Command) newFlagSet() (*flag.FlagSet, error) {
return flagSet(c.Name, c.Flags)
}
func (c *Command) useShortOptionHandling() bool {
return c.UseShortOptionHandling
}
// reorderArgs moves all flags before arguments as this is what flag expects
func reorderArgs(args []string) []string {
var nonflags, flags []string
readFlagValue := false
for i, arg := range args {
if arg == "--" {
nonflags = append(nonflags, args[i:]...)
break
}
if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
readFlagValue = false
flags = append(flags, arg)
continue
}
readFlagValue = false
if arg != "-" && strings.HasPrefix(arg, "-") {
flags = append(flags, arg)
readFlagValue = !strings.Contains(arg, "=")
} else {
nonflags = append(nonflags, arg)
}
}
return append(flags, nonflags...)
}
// Names returns the names including short names and aliases. // Names returns the names including short names and aliases.
func (c Command) Names() []string { func (c Command) Names() []string {
names := []string{c.Name} names := []string{c.Name}
...@@ -239,6 +268,7 @@ func (c Command) HasName(name string) bool { ...@@ -239,6 +268,7 @@ func (c Command) HasName(name string) bool {
func (c Command) startApp(ctx *Context) error { func (c Command) startApp(ctx *Context) error {
app := NewApp() app := NewApp()
app.Metadata = ctx.App.Metadata app.Metadata = ctx.App.Metadata
app.ExitErrHandler = ctx.App.ExitErrHandler
// set the name and usage // set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" { if c.HelpName == "" {
...@@ -267,6 +297,7 @@ func (c Command) startApp(ctx *Context) error { ...@@ -267,6 +297,7 @@ func (c Command) startApp(ctx *Context) error {
app.Email = ctx.App.Email app.Email = ctx.App.Email
app.Writer = ctx.App.Writer app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter app.ErrWriter = ctx.App.ErrWriter
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
app.categories = CommandCategories{} app.categories = CommandCategories{}
for _, command := range c.Subcommands { for _, command := range c.Subcommands {
......
...@@ -3,6 +3,8 @@ package cli ...@@ -3,6 +3,8 @@ package cli
import ( import (
"errors" "errors"
"flag" "flag"
"fmt"
"os"
"reflect" "reflect"
"strings" "strings"
"syscall" "syscall"
...@@ -93,18 +95,26 @@ func (c *Context) IsSet(name string) bool { ...@@ -93,18 +95,26 @@ func (c *Context) IsSet(name string) bool {
val = val.Elem() val = val.Elem()
} }
envVarValue := val.FieldByName("EnvVar") filePathValue := val.FieldByName("FilePath")
if !envVarValue.IsValid() { if filePathValue.IsValid() {
return eachName(filePathValue.String(), func(filePath string) {
if _, err := os.Stat(filePath); err == nil {
c.setFlags[name] = true
return
}
})
} }
eachName(envVarValue.String(), func(envVar string) { envVarValue := val.FieldByName("EnvVar")
envVar = strings.TrimSpace(envVar) if envVarValue.IsValid() {
if _, ok := syscall.Getenv(envVar); ok { eachName(envVarValue.String(), func(envVar string) {
c.setFlags[name] = true envVar = strings.TrimSpace(envVar)
return if _, ok := syscall.Getenv(envVar); ok {
} c.setFlags[name] = true
}) return
}
})
}
}) })
} }
} }
...@@ -129,8 +139,8 @@ func (c *Context) GlobalIsSet(name string) bool { ...@@ -129,8 +139,8 @@ func (c *Context) GlobalIsSet(name string) bool {
// FlagNames returns a slice of flag names used in this context. // FlagNames returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) { func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags { for _, f := range c.Command.Flags {
name := strings.Split(flag.GetName(), ",")[0] name := strings.Split(f.GetName(), ",")[0]
if name == "help" { if name == "help" {
continue continue
} }
...@@ -141,8 +151,8 @@ func (c *Context) FlagNames() (names []string) { ...@@ -141,8 +151,8 @@ func (c *Context) FlagNames() (names []string) {
// GlobalFlagNames returns a slice of global flag names used by the app. // GlobalFlagNames returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) { func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags { for _, f := range c.App.Flags {
name := strings.Split(flag.GetName(), ",")[0] name := strings.Split(f.GetName(), ",")[0]
if name == "help" || name == "version" { if name == "help" || name == "version" {
continue continue
} }
...@@ -240,7 +250,7 @@ func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { ...@@ -240,7 +250,7 @@ func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) { switch ff.Value.(type) {
case *StringSlice: case *StringSlice:
default: default:
set.Set(name, ff.Value.String()) _ = set.Set(name, ff.Value.String())
} }
} }
...@@ -276,3 +286,54 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { ...@@ -276,3 +286,54 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
} }
return nil return nil
} }
type requiredFlagsErr interface {
error
getMissingFlags() []string
}
type errRequiredFlags struct {
missingFlags []string
}
func (e *errRequiredFlags) Error() string {
numberOfMissingFlags := len(e.missingFlags)
if numberOfMissingFlags == 1 {
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
}
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}
func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}
func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
var missingFlags []string
for _, f := range flags {
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
var flagPresent bool
var flagName string
for _, key := range strings.Split(f.GetName(), ",") {
if len(key) > 1 {
flagName = key
}
if context.IsSet(strings.TrimSpace(key)) {
flagPresent = true
}
}
if !flagPresent && flagName != "" {
missingFlags = append(missingFlags, flagName)
}
}
}
if len(missingFlags) != 0 {
return &errRequiredFlags{missingFlags: missingFlags}
}
return nil
}
package cli
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"text/template"
"github.com/cpuguy83/go-md2man/v2/md2man"
)
// ToMarkdown creates a markdown string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMarkdown() (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
return "", err
}
return w.String(), nil
}
// ToMan creates a man page string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMan() (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
return "", err
}
man := md2man.Render(w.Bytes())
return string(man), nil
}
type cliTemplate struct {
App *App
Commands []string
GlobalArgs []string
SynopsisArgs []string
}
func (a *App) writeDocTemplate(w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(MarkdownDocTemplate)
if err != nil {
return err
}
return t.ExecuteTemplate(w, name, &cliTemplate{
App: a,
Commands: prepareCommands(a.Commands, 0),
GlobalArgs: prepareArgsWithValues(a.Flags),
SynopsisArgs: prepareArgsSynopsis(a.Flags),
})
}
func prepareCommands(commands []Command, level int) []string {
coms := []string{}
for i := range commands {
command := &commands[i]
if command.Hidden {
continue
}
usage := ""
if command.Usage != "" {
usage = command.Usage
}
prepared := fmt.Sprintf("%s %s\n\n%s\n",
strings.Repeat("#", level+2),
strings.Join(command.Names(), ", "),
usage,
)
flags := prepareArgsWithValues(command.Flags)
if len(flags) > 0 {
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
}
coms = append(coms, prepared)
// recursevly iterate subcommands
if len(command.Subcommands) > 0 {
coms = append(
coms,
prepareCommands(command.Subcommands, level+1)...,
)
}
}
return coms
}
func prepareArgsWithValues(flags []Flag) []string {
return prepareFlags(flags, ", ", "**", "**", `""`, true)
}
func prepareArgsSynopsis(flags []Flag) []string {
return prepareFlags(flags, "|", "[", "]", "[value]", false)
}
func prepareFlags(
flags []Flag,
sep, opener, closer, value string,
addDetails bool,
) []string {
args := []string{}
for _, f := range flags {
flag, ok := f.(DocGenerationFlag)
if !ok {
continue
}
modifiedArg := opener
for _, s := range strings.Split(flag.GetName(), ",") {
trimmed := strings.TrimSpace(s)
if len(modifiedArg) > len(opener) {
modifiedArg += sep
}
if len(trimmed) > 1 {
modifiedArg += fmt.Sprintf("--%s", trimmed)
} else {
modifiedArg += fmt.Sprintf("-%s", trimmed)
}
}
modifiedArg += closer
if flag.TakesValue() {
modifiedArg += fmt.Sprintf("=%s", value)
}
if addDetails {
modifiedArg += flagDetails(flag)
}
args = append(args, modifiedArg+"\n")
}
sort.Strings(args)
return args
}
// flagDetails returns a string containing the flags metadata
func flagDetails(flag DocGenerationFlag) string {
description := flag.GetUsage()
value := flag.GetValue()
if value != "" {
description += " (default: " + value + ")"
}
return ": " + description
}
package cli
import (
"bytes"
"fmt"
"io"
"strings"
"text/template"
)
// ToFishCompletion creates a fish completion string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToFishCompletion() (string, error) {
var w bytes.Buffer
if err := a.writeFishCompletionTemplate(&w); err != nil {
return "", err
}
return w.String(), nil
}
type fishCompletionTemplate struct {
App *App
Completions []string
AllCommands []string
}
func (a *App) writeFishCompletionTemplate(w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(FishCompletionTemplate)
if err != nil {
return err
}
allCommands := []string{}
// Add global flags
completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
// Add help flag
if !a.HideHelp {
completions = append(
completions,
a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
)
}
// Add version flag
if !a.HideVersion {
completions = append(
completions,
a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
)
}
// Add commands and their flags
completions = append(
completions,
a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})...,
)
return t.ExecuteTemplate(w, name, &fishCompletionTemplate{
App: a,
Completions: completions,
AllCommands: allCommands,
})
}
func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string {
completions := []string{}
for i := range commands {
command := &commands[i]
if command.Hidden {
continue
}
var completion strings.Builder
completion.WriteString(fmt.Sprintf(
"complete -r -c %s -n '%s' -a '%s'",
a.Name,
a.fishSubcommandHelper(previousCommands),
strings.Join(command.Names(), " "),
))
if command.Usage != "" {
completion.WriteString(fmt.Sprintf(" -d '%s'",
escapeSingleQuotes(command.Usage)))
}
if !command.HideHelp {
completions = append(
completions,
a.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
)
}
*allCommands = append(*allCommands, command.Names()...)
completions = append(completions, completion.String())
completions = append(
completions,
a.prepareFishFlags(command.Flags, command.Names())...,
)
// recursevly iterate subcommands
if len(command.Subcommands) > 0 {
completions = append(
completions,
a.prepareFishCommands(
command.Subcommands, allCommands, command.Names(),
)...,
)
}
}
return completions
}
func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string {
completions := []string{}
for _, f := range flags {
flag, ok := f.(DocGenerationFlag)
if !ok {
continue
}
completion := &strings.Builder{}
completion.WriteString(fmt.Sprintf(
"complete -c %s -n '%s'",
a.Name,
a.fishSubcommandHelper(previousCommands),
))
fishAddFileFlag(f, completion)
for idx, opt := range strings.Split(flag.GetName(), ",") {
if idx == 0 {
completion.WriteString(fmt.Sprintf(
" -l %s", strings.TrimSpace(opt),
))
} else {
completion.WriteString(fmt.Sprintf(
" -s %s", strings.TrimSpace(opt),
))
}
}
if flag.TakesValue() {
completion.WriteString(" -r")
}
if flag.GetUsage() != "" {
completion.WriteString(fmt.Sprintf(" -d '%s'",
escapeSingleQuotes(flag.GetUsage())))
}
completions = append(completions, completion.String())
}
return completions
}
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
switch f := flag.(type) {
case GenericFlag:
if f.TakesFile {
return
}
case StringFlag:
if f.TakesFile {
return
}
case StringSliceFlag:
if f.TakesFile {
return
}
}
completion.WriteString(" -f")
}
func (a *App) fishSubcommandHelper(allCommands []string) string {
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name)
if len(allCommands) > 0 {
fishHelper = fmt.Sprintf(
"__fish_seen_subcommand_from %s",
strings.Join(allCommands, " "),
)
}
return fishHelper
}
func escapeSingleQuotes(input string) string {
return strings.Replace(input, `'`, `\'`, -1)
}
[
{
"name": "Bool",
"type": "bool",
"value": false,
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "BoolT",
"type": "bool",
"value": false,
"doctail": " that is true by default",
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "Duration",
"type": "time.Duration",
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
"context_default": "0",
"parser": "time.ParseDuration(f.Value.String())"
},
{
"name": "Float64",
"type": "float64",
"context_default": "0",
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
},
{
"name": "Generic",
"type": "Generic",
"dest": false,
"context_default": "nil",
"context_type": "interface{}"
},
{
"name": "Int64",
"type": "int64",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
},
{
"name": "Int",
"type": "int",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
"parser_cast": "int(parsed)"
},
{
"name": "IntSlice",
"type": "*IntSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]int",
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
},
{
"name": "Int64Slice",
"type": "*Int64Slice",
"dest": false,
"context_default": "nil",
"context_type": "[]int64",
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
},
{
"name": "String",
"type": "string",
"context_default": "\"\"",
"parser": "f.Value.String(), error(nil)"
},
{
"name": "StringSlice",
"type": "*StringSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]string",
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
},
{
"name": "Uint64",
"type": "uint64",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
},
{
"name": "Uint",
"type": "uint",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
"parser_cast": "uint(parsed)"
}
]
...@@ -3,12 +3,12 @@ package cli ...@@ -3,12 +3,12 @@ package cli
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"reflect" "reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
) )
const defaultPlaceholder = "value" const defaultPlaceholder = "value"
...@@ -45,6 +45,10 @@ var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames ...@@ -45,6 +45,10 @@ var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
// details. This is used by the default FlagStringer. // details. This is used by the default FlagStringer.
var FlagEnvHinter FlagEnvHintFunc = withEnvHint var FlagEnvHinter FlagEnvHintFunc = withEnvHint
// FlagFileHinter annotates flag help message with the environment variable
// details. This is used by the default FlagStringer.
var FlagFileHinter FlagFileHintFunc = withFileHint
// FlagsByName is a slice of Flag. // FlagsByName is a slice of Flag.
type FlagsByName []Flag type FlagsByName []Flag
...@@ -53,7 +57,7 @@ func (f FlagsByName) Len() int { ...@@ -53,7 +57,7 @@ func (f FlagsByName) Len() int {
} }
func (f FlagsByName) Less(i, j int) bool { func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName() return lexicographicLess(f[i].GetName(), f[j].GetName())
} }
func (f FlagsByName) Swap(i, j int) { func (f FlagsByName) Swap(i, j int) {
...@@ -70,6 +74,29 @@ type Flag interface { ...@@ -70,6 +74,29 @@ type Flag interface {
GetName() string GetName() string
} }
// RequiredFlag is an interface that allows us to mark flags as required
// it allows flags required flags to be backwards compatible with the Flag interface
type RequiredFlag interface {
Flag
IsRequired() bool
}
// DocGenerationFlag is an interface that allows documentation generation for the flag
type DocGenerationFlag interface {
Flag
// TakesValue returns true of the flag takes a value, otherwise false
TakesValue() bool
// GetUsage returns the usage string for the flag
GetUsage() string
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
}
// errorableFlag is an interface that allows us to return errors during apply // errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible // it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors // TODO remove in v2 and modify the existing Flag interface to return errors
...@@ -92,6 +119,7 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { ...@@ -92,6 +119,7 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
f.Apply(set) f.Apply(set)
} }
} }
set.SetOutput(ioutil.Discard)
return set, nil return set, nil
} }
...@@ -103,544 +131,12 @@ func eachName(longName string, fn func(string)) { ...@@ -103,544 +131,12 @@ func eachName(longName string, fn func(string)) {
} }
} }
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if err := val.Set(envVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
return nil
}
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
type Int64Slice []int64
// Set parses the value into an integer and appends it to the list of values
func (f *Int64Slice) Set(value string) error {
tmp, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Slice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Int(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Duration(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = float64(envValFloat)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Float64(name, f.Value, f.Usage)
})
return nil
}
func visibleFlags(fl []Flag) []Flag { func visibleFlags(fl []Flag) []Flag {
visible := []Flag{} var visible []Flag
for _, flag := range fl { for _, f := range fl {
field := flagValue(flag).FieldByName("Hidden") field := flagValue(f).FieldByName("Hidden")
if !field.IsValid() || !field.Bool() { if !field.IsValid() || !field.Bool() {
visible = append(visible, flag) visible = append(visible, f)
} }
} }
return visible return visible
...@@ -700,11 +196,19 @@ func withEnvHint(envVar, str string) string { ...@@ -700,11 +196,19 @@ func withEnvHint(envVar, str string) string {
suffix = "%" suffix = "%"
sep = "%, %" sep = "%, %"
} }
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]"
} }
return str + envText return str + envText
} }
func withFileHint(filePath, str string) string {
fileText := ""
if filePath != "" {
fileText = fmt.Sprintf(" [%s]", filePath)
}
return str + fileText
}
func flagValue(f Flag) reflect.Value { func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f) fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr { for fv.Kind() == reflect.Ptr {
...@@ -718,14 +222,29 @@ func stringifyFlag(f Flag) string { ...@@ -718,14 +222,29 @@ func stringifyFlag(f Flag) string {
switch f.(type) { switch f.(type) {
case IntSliceFlag: case IntSliceFlag:
return FlagEnvHinter(fv.FieldByName("EnvVar").String(), return FlagFileHinter(
stringifyIntSliceFlag(f.(IntSliceFlag))) fv.FieldByName("FilePath").String(),
FlagEnvHinter(
fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag)),
),
)
case Int64SliceFlag: case Int64SliceFlag:
return FlagEnvHinter(fv.FieldByName("EnvVar").String(), return FlagFileHinter(
stringifyInt64SliceFlag(f.(Int64SliceFlag))) fv.FieldByName("FilePath").String(),
FlagEnvHinter(
fv.FieldByName("EnvVar").String(),
stringifyInt64SliceFlag(f.(Int64SliceFlag)),
),
)
case StringSliceFlag: case StringSliceFlag:
return FlagEnvHinter(fv.FieldByName("EnvVar").String(), return FlagFileHinter(
stringifyStringSliceFlag(f.(StringSliceFlag))) fv.FieldByName("FilePath").String(),
FlagEnvHinter(
fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag)),
),
)
} }
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
...@@ -750,17 +269,22 @@ func stringifyFlag(f Flag) string { ...@@ -750,17 +269,22 @@ func stringifyFlag(f Flag) string {
placeholder = defaultPlaceholder placeholder = defaultPlaceholder
} }
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) usageWithDefault := strings.TrimSpace(usage + defaultValueString)
return FlagEnvHinter(fv.FieldByName("EnvVar").String(), return FlagFileHinter(
fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) fv.FieldByName("FilePath").String(),
FlagEnvHinter(
fv.FieldByName("EnvVar").String(),
FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault,
),
)
} }
func stringifyIntSliceFlag(f IntSliceFlag) string { func stringifyIntSliceFlag(f IntSliceFlag) string {
defaultVals := []string{} var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() { for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) defaultVals = append(defaultVals, strconv.Itoa(i))
} }
} }
...@@ -768,10 +292,10 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { ...@@ -768,10 +292,10 @@ func stringifyIntSliceFlag(f IntSliceFlag) string {
} }
func stringifyInt64SliceFlag(f Int64SliceFlag) string { func stringifyInt64SliceFlag(f Int64SliceFlag) string {
defaultVals := []string{} var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() { for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
} }
} }
...@@ -779,11 +303,11 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string { ...@@ -779,11 +303,11 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string {
} }
func stringifyStringSliceFlag(f StringSliceFlag) string { func stringifyStringSliceFlag(f StringSliceFlag) string {
defaultVals := []string{} var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() { for _, s := range f.Value.Value() {
if len(s) > 0 { if len(s) > 0 {
defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) defaultVals = append(defaultVals, strconv.Quote(s))
} }
} }
} }
...@@ -802,6 +326,21 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { ...@@ -802,6 +326,21 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string {
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
} }
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) usageWithDefault := strings.TrimSpace(usage + defaultVal)
return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault
}
func flagFromFileEnv(filePath, envName string) (val string, ok bool) {
for _, envVar := range strings.Split(envName, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
return envVal, true
}
}
for _, fileVar := range strings.Split(filePath, ",") {
if data, err := ioutil.ReadFile(fileVar); err == nil {
return string(data), true
}
}
return "", false
} }
package cli
import (
"flag"
"fmt"
"strconv"
)
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f BoolFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f BoolFlag) TakesValue() bool {
return false
}
// GetUsage returns the usage string for the flag
func (f BoolFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f BoolFlag) GetValue() string {
return ""
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
if envVal == "" {
val = false
} else {
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// BoolTFlag is a flag with type bool that is true by default
type BoolTFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolTFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f BoolTFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f BoolTFlag) TakesValue() bool {
return false
}
// GetUsage returns the usage string for the flag
func (f BoolTFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f BoolTFlag) GetValue() string {
return ""
}
// BoolT looks up the value of a local BoolTFlag, returns
// false if not found
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// GlobalBoolT looks up the value of a global BoolTFlag, returns
// false if not found
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
if envVal == "" {
val = false
} else {
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
package cli
import (
"flag"
"fmt"
"time"
)
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value time.Duration
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f DurationFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f DurationFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f DurationFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f DurationFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f DurationFlag) GetValue() string {
return f.Value.String()
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Duration(name, f.Value, f.Usage)
})
return nil
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value float64
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Float64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Float64Flag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f Float64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f Float64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f Float64Flag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f Float64Flag) GetValue() string {
return fmt.Sprintf("%f", f.Value)
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValFloat
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Float64(name, f.Value, f.Usage)
})
return nil
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
package cli
import (
"flag"
"strconv"
"time"
)
// WARNING: This file is generated!
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolFlag) GetName() string {
return f.Name
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// BoolTFlag is a flag with type bool that is true by default
type BoolTFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolTFlag) GetName() string {
return f.Name
}
// BoolT looks up the value of a local BoolTFlag, returns
// false if not found
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// GlobalBoolT looks up the value of a global BoolTFlag, returns
// false if not found
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value time.Duration
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f DurationFlag) GetName() string {
return f.Name
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value float64
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Float64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Float64Flag) GetName() string {
return f.Name
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value Generic
}
// String returns a readable representation of this value
// (for usage defaults)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f GenericFlag) GetName() string {
return f.Name
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int64
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64Flag) GetName() string {
return f.Name
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
func (c *Context) GlobalInt64(name string) int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntFlag) GetName() string {
return f.Name
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *IntSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntSliceFlag) GetName() string {
return f.Name
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *Int64Slice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64SliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64SliceFlag) GetName() string {
return f.Name
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
func (c *Context) GlobalInt64Slice(name string) []int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringFlag) GetName() string {
return f.Name
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *StringSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringSliceFlag) GetName() string {
return f.Name
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint64
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Uint64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Uint64Flag) GetName() string {
return f.Name
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
func (c *Context) GlobalUint64(name string) uint64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f UintFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f UintFlag) GetName() string {
return f.Name
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
func (c *Context) GlobalUint(name string) uint {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}
package cli
import (
"flag"
"fmt"
)
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value Generic
}
// String returns a readable representation of this value
// (for usage defaults)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f GenericFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f GenericFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f GenericFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f GenericFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f GenericFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
if err := val.Set(fileEnvVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err)
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value int
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f IntFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f IntFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f IntFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f IntFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Int(name, f.Value, f.Usage)
})
return nil
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value int64
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64Flag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f Int64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f Int64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f Int64Flag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f Int64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
func (c *Context) GlobalInt64(name string) int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
package cli
import (
"flag"
"fmt"
"strconv"
"strings"
)
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
type Int64Slice []int64
// Set parses the value into an integer and appends it to the list of values
func (f *Int64Slice) Set(value string) error {
tmp, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Slice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value *Int64Slice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64SliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64SliceFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f Int64SliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f Int64SliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f Int64SliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f Int64SliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
if f.Value == nil {
f.Value = newVal
} else {
*f.Value = *newVal
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
func (c *Context) GlobalInt64Slice(name string) []int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
package cli
import (
"flag"
"fmt"
"strconv"
"strings"
)
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value *IntSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntSliceFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f IntSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f IntSliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f IntSliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f IntSliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
if f.Value == nil {
f.Value = newVal
} else {
*f.Value = *newVal
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
package cli
import "flag"
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f StringFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f StringFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f StringFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f StringFlag) GetValue() string {
return f.Value
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
f.Value = envVal
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage)
})
return nil
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
package cli
import (
"flag"
"fmt"
"strings"
)
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value *StringSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringSliceFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f StringSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f StringSliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f StringSliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f StringSliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
}
if f.Value == nil {
f.Value = newVal
} else {
*f.Value = *newVal
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value uint
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f UintFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f UintFlag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f UintFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f UintFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f UintFlag) GetUsage() string {
return f.Usage
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f UintFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
func (c *Context) GlobalUint(name string) uint {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}
package cli
import (
"flag"
"fmt"
"strconv"
)
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Usage string
EnvVar string
FilePath string
Required bool
Hidden bool
Value uint64
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Uint64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Uint64Flag) GetName() string {
return f.Name
}
// IsRequired returns whether or not the flag is required
func (f Uint64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f Uint64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f Uint64Flag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f Uint64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
_ = f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
func (c *Context) GlobalUint64(name string) uint64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
...@@ -39,3 +39,6 @@ type FlagNamePrefixFunc func(fullName, placeholder string) string ...@@ -39,3 +39,6 @@ type FlagNamePrefixFunc func(fullName, placeholder string) string
// with the environment variable details. // with the environment variable details.
type FlagEnvHintFunc func(envVar, str string) string type FlagEnvHintFunc func(envVar, str string) string
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
// with the file path details.
type FlagFileHintFunc func(filePath, str string) string
#!/usr/bin/env python
"""
The flag types that ship with the cli library have many things in common, and
so we can take advantage of the `go generate` command to create much of the
source code from a list of definitions. These definitions attempt to cover
the parts that vary between flag types, and should evolve as needed.
An example of the minimum definition needed is:
{
"name": "SomeType",
"type": "sometype",
"context_default": "nil"
}
In this example, the code generated for the `cli` package will include a type
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
Fetching values by name via `*cli.Context` will default to a value of `nil`.
A more complete, albeit somewhat redundant, example showing all available
definition keys is:
{
"name": "VeryMuchType",
"type": "*VeryMuchType",
"value": true,
"dest": false,
"doctail": " which really only wraps a []float64, oh well!",
"context_type": "[]float64",
"context_default": "nil",
"parser": "parseVeryMuchType(f.Value.String())",
"parser_cast": "[]float64(parsed)"
}
The meaning of each field is as follows:
name (string) - The type "name", which will be suffixed with
`Flag` when generating the type definition
for `cli` and the wrapper type for `altsrc`
type (string) - The type that the generated `Flag` type for `cli`
is expected to "contain" as its `.Value` member
value (bool) - Should the generated `cli` type have a `Value`
member?
dest (bool) - Should the generated `cli` type support a
destination pointer?
doctail (string) - Additional docs for the `cli` flag type comment
context_type (string) - The literal type used in the `*cli.Context`
reader func signature
context_default (string) - The literal value used as the default by the
`*cli.Context` reader funcs when no value is
present
parser (string) - Literal code used to parse the flag `f`,
expected to have a return signature of
(value, error)
parser_cast (string) - Literal code used to cast the `parsed` value
returned from the `parser` code
"""
from __future__ import print_function, unicode_literals
import argparse
import json
import os
import subprocess
import sys
import tempfile
import textwrap
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
def main(sysargs=sys.argv[:]):
parser = argparse.ArgumentParser(
description='Generate flag type code!',
formatter_class=_FancyFormatter)
parser.add_argument(
'package',
type=str, default='cli', choices=_WRITEFUNCS.keys(),
help='Package for which flag types will be generated'
)
parser.add_argument(
'-i', '--in-json',
type=argparse.FileType('r'),
default=sys.stdin,
help='Input JSON file which defines each type to be generated'
)
parser.add_argument(
'-o', '--out-go',
type=argparse.FileType('w'),
default=sys.stdout,
help='Output file/stream to which generated source will be written'
)
parser.epilog = __doc__
args = parser.parse_args(sysargs[1:])
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
return 0
def _generate_flag_types(writefunc, output_go, input_json):
types = json.load(input_json)
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
writefunc(tmp, types)
tmp.close()
new_content = subprocess.check_output(
['goimports', tmp.name]
).decode('utf-8')
print(new_content, file=output_go, end='')
output_go.flush()
os.remove(tmp.name)
def _set_typedef_defaults(typedef):
typedef.setdefault('doctail', '')
typedef.setdefault('context_type', typedef['type'])
typedef.setdefault('dest', True)
typedef.setdefault('value', True)
typedef.setdefault('parser', 'f.Value, error(nil)')
typedef.setdefault('parser_cast', 'parsed')
def _write_cli_flag_types(outfile, types):
_fwrite(outfile, """\
package cli
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is a flag with type {type}{doctail}
type {name}Flag struct {{
Name string
Usage string
EnvVar string
Hidden bool
""".format(**typedef))
if typedef['value']:
_fwrite(outfile, """\
Value {type}
""".format(**typedef))
if typedef['dest']:
_fwrite(outfile, """\
Destination *{type}
""".format(**typedef))
_fwrite(outfile, "\n}\n\n")
_fwrite(outfile, """\
// String returns a readable representation of this value
// (for usage defaults)
func (f {name}Flag) String() string {{
return FlagStringer(f)
}}
// GetName returns the name of the flag
func (f {name}Flag) GetName() string {{
return f.Name
}}
// {name} looks up the value of a local {name}Flag, returns
// {context_default} if not found
func (c *Context) {name}(name string) {context_type} {{
return lookup{name}(name, c.flagSet)
}}
// Global{name} looks up the value of a global {name}Flag, returns
// {context_default} if not found
func (c *Context) Global{name}(name string) {context_type} {{
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
return lookup{name}(name, fs)
}}
return {context_default}
}}
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
f := set.Lookup(name)
if f != nil {{
parsed, err := {parser}
if err != nil {{
return {context_default}
}}
return {parser_cast}
}}
return {context_default}
}}
""".format(**typedef))
def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\
package altsrc
import (
"gopkg.in/urfave/cli.v1"
)
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
// for other values to be specified
type {name}Flag struct {{
cli.{name}Flag
set *flag.FlagSet
}}
// New{name}Flag creates a new {name}Flag
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
return &{name}Flag{{{name}Flag: fl, set: nil}}
}}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.Apply
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
f.set = set
f.{name}Flag.Apply(set)
}}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef))
def _fwrite(outfile, text):
print(textwrap.dedent(text), end='', file=outfile)
_WRITEFUNCS = {
'cli': _write_cli_flag_types,
'altsrc': _write_altsrc_flag_types
}
if __name__ == '__main__':
sys.exit(main())
module github.com/urfave/cli
go 1.11
require (
github.com/BurntSushi/toml v0.3.1
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d
gopkg.in/yaml.v2 v2.2.2
)
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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=
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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
...@@ -7,78 +7,9 @@ import ( ...@@ -7,78 +7,9 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"unicode/utf8"
) )
// AppHelpTemplate is the text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{ var helpCommand = Command{
Name: "help", Name: "help",
Aliases: []string{"h"}, Aliases: []string{"h"},
...@@ -90,7 +21,7 @@ var helpCommand = Command{ ...@@ -90,7 +21,7 @@ var helpCommand = Command{
return ShowCommandHelp(c, args.First()) return ShowCommandHelp(c, args.First())
} }
ShowAppHelp(c) _ = ShowAppHelp(c)
return nil return nil
}, },
} }
...@@ -130,7 +61,7 @@ var VersionPrinter = printVersion ...@@ -130,7 +61,7 @@ var VersionPrinter = printVersion
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
func ShowAppHelpAndExit(c *Context, exitCode int) { func ShowAppHelpAndExit(c *Context, exitCode int) {
ShowAppHelp(c) _ = ShowAppHelp(c)
os.Exit(exitCode) os.Exit(exitCode)
} }
...@@ -154,19 +85,94 @@ func ShowAppHelp(c *Context) (err error) { ...@@ -154,19 +85,94 @@ func ShowAppHelp(c *Context) (err error) {
// DefaultAppComplete prints the list of subcommands as the default app completion method // DefaultAppComplete prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) { func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands { DefaultCompleteWithFlags(nil)(c)
}
func printCommandSuggestions(commands []Command, writer io.Writer) {
for _, command := range commands {
if command.Hidden { if command.Hidden {
continue continue
} }
for _, name := range command.Names() { if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
fmt.Fprintln(c.App.Writer, name) for _, name := range command.Names() {
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
}
} else {
for _, name := range command.Names() {
_, _ = fmt.Fprintf(writer, "%s\n", name)
}
}
}
}
func cliArgContains(flagName string) bool {
for _, name := range strings.Split(flagName, ",") {
name = strings.TrimSpace(name)
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2
}
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
for _, a := range os.Args {
if a == flag {
return true
}
}
}
return false
}
func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
cur := strings.TrimPrefix(lastArg, "-")
cur = strings.TrimPrefix(cur, "-")
for _, flag := range flags {
if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
continue
}
for _, name := range strings.Split(flag.GetName(), ",") {
name = strings.TrimSpace(name)
// this will get total count utf8 letters in flag name
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2 // resuse this count to generate single - or -- in flag completion
}
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
// skip flag completion for short flags example -v or -x
if strings.HasPrefix(lastArg, "--") && count == 1 {
continue
}
// match if last argument matches this flag and it is not repeated
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
_, _ = fmt.Fprintln(writer, flagCompletion)
}
}
}
}
func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
return func(c *Context) {
if len(os.Args) > 2 {
lastArg := os.Args[len(os.Args)-2]
if strings.HasPrefix(lastArg, "-") {
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
if cmd != nil {
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
}
return
}
}
if cmd != nil {
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
} else {
printCommandSuggestions(c.App.Commands, c.App.Writer)
} }
} }
} }
// ShowCommandHelpAndExit - exits with code after showing help // ShowCommandHelpAndExit - exits with code after showing help
func ShowCommandHelpAndExit(c *Context, command string, code int) { func ShowCommandHelpAndExit(c *Context, command string, code int) {
ShowCommandHelp(c, command) _ = ShowCommandHelp(c, command)
os.Exit(code) os.Exit(code)
} }
...@@ -208,7 +214,7 @@ func ShowVersion(c *Context) { ...@@ -208,7 +214,7 @@ func ShowVersion(c *Context) {
} }
func printVersion(c *Context) { func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
} }
// ShowCompletions prints the lists of commands within a given context // ShowCompletions prints the lists of commands within a given context
...@@ -222,19 +228,22 @@ func ShowCompletions(c *Context) { ...@@ -222,19 +228,22 @@ func ShowCompletions(c *Context) {
// ShowCommandCompletions prints the custom completions for a given command // ShowCommandCompletions prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) { func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command) c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil { if c != nil {
c.BashComplete(ctx) if c.BashComplete != nil {
c.BashComplete(ctx)
} else {
DefaultCompleteWithFlags(c)(ctx)
}
} }
} }
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"join": strings.Join, "join": strings.Join,
} }
if customFunc != nil { for key, value := range customFunc {
for key, value := range customFunc { funcMap[key] = value
funcMap[key] = value
}
} }
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
...@@ -244,11 +253,11 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m ...@@ -244,11 +253,11 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m
// If the writer is closed, t.Execute will fail, and there's nothing // If the writer is closed, t.Execute will fail, and there's nothing
// we can do to recover. // we can do to recover.
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
} }
return return
} }
w.Flush() _ = w.Flush()
} }
func printHelp(out io.Writer, templ string, data interface{}) { func printHelp(out io.Writer, templ string, data interface{}) {
...@@ -281,7 +290,7 @@ func checkHelp(c *Context) bool { ...@@ -281,7 +290,7 @@ func checkHelp(c *Context) bool {
func checkCommandHelp(c *Context, name string) bool { func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") { if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name) _ = ShowCommandHelp(c, name)
return true return true
} }
...@@ -290,7 +299,7 @@ func checkCommandHelp(c *Context, name string) bool { ...@@ -290,7 +299,7 @@ func checkCommandHelp(c *Context, name string) bool {
func checkSubcommandHelp(c *Context) bool { func checkSubcommandHelp(c *Context) bool {
if c.Bool("h") || c.Bool("help") { if c.Bool("h") || c.Bool("help") {
ShowSubcommandHelp(c) _ = ShowSubcommandHelp(c)
return true return true
} }
......
package cli
import (
"flag"
"strings"
)
type iterativeParser interface {
newFlagSet() (*flag.FlagSet, error)
useShortOptionHandling() bool
}
// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) {
for {
set, err := ip.newFlagSet()
if err != nil {
return nil, err
}
err = set.Parse(args)
if !ip.useShortOptionHandling() || err == nil {
return set, err
}
errStr := err.Error()
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ")
if errStr == trimmed {
return nil, err
}
// regenerate the initial args with the split short opts
newArgs := []string{}
for i, arg := range args {
if arg != trimmed {
newArgs = append(newArgs, arg)
continue
}
shortOpts := splitShortOptions(set, trimmed)
if len(shortOpts) == 1 {
return nil, err
}
// add each short option and all remaining arguments
newArgs = append(newArgs, shortOpts...)
newArgs = append(newArgs, args[i+1:]...)
args = newArgs
}
}
}
func splitShortOptions(set *flag.FlagSet, arg string) []string {
shortFlagsExist := func(s string) bool {
for _, c := range s[1:] {
if f := set.Lookup(string(c)); f == nil {
return false
}
}
return true
}
if !isSplittable(arg) || !shortFlagsExist(arg) {
return []string{arg}
}
separated := make([]string, 0, len(arg)-1)
for _, flagChar := range arg[1:] {
separated = append(separated, "-"+string(flagChar))
}
return separated
}
func isSplittable(flagArg string) bool {
return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
}
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import sys
import tempfile
from subprocess import check_call, check_output
PACKAGE_NAME = os.environ.get(
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
)
def main(sysargs=sys.argv[:]):
targets = {
'vet': _vet,
'test': _test,
'gfmrun': _gfmrun,
'toc': _toc,
'gen': _gen,
}
parser = argparse.ArgumentParser()
parser.add_argument(
'target', nargs='?', choices=tuple(targets.keys()), default='test'
)
args = parser.parse_args(sysargs[1:])
targets[args.target]()
return 0
def _test():
if check_output('go version'.split()).split()[2] < 'go1.2':
_run('go test -v .')
return
coverprofiles = []
for subpackage in ['', 'altsrc']:
coverprofile = 'cli.coverprofile'
if subpackage != '':
coverprofile = '{}.coverprofile'.format(subpackage)
coverprofiles.append(coverprofile)
_run('go test -v'.split() + [
'-coverprofile={}'.format(coverprofile),
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
])
combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined_name))
os.remove(combined_name)
def _gfmrun():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
def _vet():
_run('go vet ./...')
def _toc():
_run('node_modules/.bin/markdown-toc -i README.md')
_run('git diff --exit-code')
def _gen():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.5':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run('go generate ./...')
_run('git diff --exit-code')
def _run(command):
if hasattr(command, 'split'):
command = command.split()
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
check_call(command)
def _gfmrun_count():
with open('README.md') as infile:
lines = infile.read().splitlines()
return len(filter(_is_go_runnable, lines))
def _is_go_runnable(line):
return line.startswith('package main')
def _combine_coverprofiles(coverprofiles):
combined = tempfile.NamedTemporaryFile(
suffix='.coverprofile', delete=False
)
combined.write('mode: set\n')
for coverprofile in coverprofiles:
with open(coverprofile, 'r') as infile:
for line in infile.readlines():
if not line.startswith('mode: '):
combined.write(line)
combined.flush()
name = combined.name
combined.close()
return name
if __name__ == '__main__':
sys.exit(main())
package cli
import "unicode"
// lexicographicLess compares strings alphabetically considering case.
func lexicographicLess(i, j string) bool {
iRunes := []rune(i)
jRunes := []rune(j)
lenShared := len(iRunes)
if lenShared > len(jRunes) {
lenShared = len(jRunes)
}
for index := 0; index < lenShared; index++ {
ir := iRunes[index]
jr := jRunes[index]
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
return lir < ljr
}
if ir != jr {
return ir < jr
}
}
return i < j
}
package cli
// AppHelpTemplate is the text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
% {{ .App.Author }}
# NAME
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
# SYNOPSIS
{{ .App.Name }}
{{ if .SynopsisArgs }}
` + "```" + `
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
{{ end }}{{ if .App.UsageText }}
# DESCRIPTION
{{ .App.UsageText }}
{{ end }}
**Usage**:
` + "```" + `
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
` + "```" + `
{{ if .GlobalArgs }}
# GLOBAL OPTIONS
{{ range $v := .GlobalArgs }}
{{ $v }}{{ end }}
{{ end }}{{ if .Commands }}
# COMMANDS
{{ range $v := .Commands }}
{{ $v }}{{ end }}{{ end }}`
var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion
function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
for i in (commandline -opc)
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
return 1
end
end
return 0
end
{{ range $v := .Completions }}{{ $v }}
{{ end }}`
...@@ -130,6 +130,10 @@ possible reasons, including: ...@@ -130,6 +130,10 @@ possible reasons, including:
1. mis-configured transport credentials, connection failed on handshaking 1. mis-configured transport credentials, connection failed on handshaking
1. bytes disrupted, possibly by a proxy in between 1. bytes disrupted, possibly by a proxy in between
1. server shutdown 1. server shutdown
1. Keepalive parameters caused connection shutdown, for example if you have configured
your server to terminate connections regularly to [trigger DNS lookups](https://github.com/grpc/grpc-go/issues/3170#issuecomment-552517779).
If this is the case, you may want to increase your [MaxConnectionAgeGrace](https://pkg.go.dev/google.golang.org/grpc/keepalive?tab=doc#ServerParameters),
to allow longer RPC calls to finish.
It can be tricky to debug this because the error happens on the client side but It can be tricky to debug this because the error happens on the client side but
the root cause of the connection being closed is on the server side. Turn on the root cause of the connection being closed is on the server side. Turn on
......
...@@ -113,10 +113,6 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error { ...@@ -113,10 +113,6 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
if grpclog.V(2) { if grpclog.V(2) {
grpclog.Infoln("base.baseBalancer: got new ClientConn state: ", s) grpclog.Infoln("base.baseBalancer: got new ClientConn state: ", s)
} }
if len(s.ResolverState.Addresses) == 0 {
b.ResolverError(errors.New("produced zero addresses"))
return balancer.ErrBadResolverState
}
// Successful resolution; clear resolver error and ensure we return nil. // Successful resolution; clear resolver error and ensure we return nil.
b.resolverErr = nil b.resolverErr = nil
// addrsSet is the set converted from addrs, it's used for quick lookup of an address. // addrsSet is the set converted from addrs, it's used for quick lookup of an address.
...@@ -144,6 +140,14 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error { ...@@ -144,6 +140,14 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
// The entry will be deleted in HandleSubConnStateChange. // The entry will be deleted in HandleSubConnStateChange.
} }
} }
// If resolver state contains no addresses, return an error so ClientConn
// will trigger re-resolve. Also records this as an resolver error, so when
// the overall state turns transient failure, the error message will have
// the zero address information.
if len(s.ResolverState.Addresses) == 0 {
b.ResolverError(errors.New("produced zero addresses"))
return balancer.ErrBadResolverState
}
return nil return nil
} }
......
...@@ -194,12 +194,13 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * ...@@ -194,12 +194,13 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
cc.mkp = cc.dopts.copts.KeepaliveParams cc.mkp = cc.dopts.copts.KeepaliveParams
if cc.dopts.copts.Dialer == nil { if cc.dopts.copts.Dialer == nil {
cc.dopts.copts.Dialer = newProxyDialer( cc.dopts.copts.Dialer = func(ctx context.Context, addr string) (net.Conn, error) {
func(ctx context.Context, addr string) (net.Conn, error) { network, addr := parseDialTarget(addr)
network, addr := parseDialTarget(addr) return (&net.Dialer{}).DialContext(ctx, network, addr)
return (&net.Dialer{}).DialContext(ctx, network, addr) }
}, if cc.dopts.withProxy {
) cc.dopts.copts.Dialer = newProxyDialer(cc.dopts.copts.Dialer)
}
} }
if cc.dopts.copts.UserAgent != "" { if cc.dopts.copts.UserAgent != "" {
...@@ -1525,9 +1526,9 @@ var ErrClientConnTimeout = errors.New("grpc: timed out when dialing") ...@@ -1525,9 +1526,9 @@ var ErrClientConnTimeout = errors.New("grpc: timed out when dialing")
func (cc *ClientConn) getResolver(scheme string) resolver.Builder { func (cc *ClientConn) getResolver(scheme string) resolver.Builder {
for _, rb := range cc.dopts.resolvers { for _, rb := range cc.dopts.resolvers {
if cc.parsedTarget.Scheme == rb.Scheme() { if scheme == rb.Scheme() {
return rb return rb
} }
} }
return resolver.Get(cc.parsedTarget.Scheme) return resolver.Get(scheme)
} }
...@@ -135,16 +135,26 @@ func NewTLS(c *tls.Config) TransportCredentials { ...@@ -135,16 +135,26 @@ func NewTLS(c *tls.Config) TransportCredentials {
return tc return tc
} }
// NewClientTLSFromCert constructs TLS credentials from the input certificate for client. // NewClientTLSFromCert constructs TLS credentials from the provided root
// certificate authority certificate(s) to validate server connections. If
// certificates to establish the identity of the client need to be included in
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
// tls.Config can be specified.
// serverNameOverride is for testing only. If set to a non empty string, // serverNameOverride is for testing only. If set to a non empty string,
// it will override the virtual host name of authority (e.g. :authority header field) in requests. // it will override the virtual host name of authority (e.g. :authority header
// field) in requests.
func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials { func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials {
return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}) return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp})
} }
// NewClientTLSFromFile constructs TLS credentials from the input certificate file for client. // NewClientTLSFromFile constructs TLS credentials from the provided root
// certificate authority certificate file(s) to validate server connections. If
// certificates to establish the identity of the client need to be included in
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
// tls.Config can be specified.
// serverNameOverride is for testing only. If set to a non empty string, // serverNameOverride is for testing only. If set to a non empty string,
// it will override the virtual host name of authority (e.g. :authority header field) in requests. // it will override the virtual host name of authority (e.g. :authority header
// field) in requests.
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) { func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
b, err := ioutil.ReadFile(certFile) b, err := ioutil.ReadFile(certFile)
if err != nil { if err != nil {
......
...@@ -72,6 +72,7 @@ type dialOptions struct { ...@@ -72,6 +72,7 @@ type dialOptions struct {
// we need to be able to configure this in tests. // we need to be able to configure this in tests.
resolveNowBackoff func(int) time.Duration resolveNowBackoff func(int) time.Duration
resolvers []resolver.Builder resolvers []resolver.Builder
withProxy bool
} }
// DialOption configures how we set up the connection. // DialOption configures how we set up the connection.
...@@ -307,6 +308,16 @@ func WithInsecure() DialOption { ...@@ -307,6 +308,16 @@ func WithInsecure() DialOption {
}) })
} }
// WithNoProxy returns a DialOption which disables the use of proxies for this
// ClientConn. This is ignored if WithDialer or WithContextDialer are used.
//
// This API is EXPERIMENTAL.
func WithNoProxy() DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.withProxy = false
})
}
// WithTransportCredentials returns a DialOption which configures a connection // WithTransportCredentials returns a DialOption which configures a connection
// level security credentials (e.g., TLS/SSL). This should not be used together // level security credentials (e.g., TLS/SSL). This should not be used together
// with WithCredentialsBundle. // with WithCredentialsBundle.
...@@ -557,6 +568,7 @@ func defaultDialOptions() dialOptions { ...@@ -557,6 +568,7 @@ func defaultDialOptions() dialOptions {
ReadBufferSize: defaultReadBufSize, ReadBufferSize: defaultReadBufSize,
}, },
resolveNowBackoff: internalbackoff.DefaultExponential.Backoff, resolveNowBackoff: internalbackoff.DefaultExponential.Backoff,
withProxy: true,
} }
} }
......
...@@ -3,8 +3,8 @@ module google.golang.org/grpc ...@@ -3,8 +3,8 @@ module google.golang.org/grpc
go 1.11 go 1.11
require ( require (
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f
github.com/envoyproxy/go-control-plane v0.9.4 github.com/envoyproxy/go-control-plane v0.9.4
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.1.1 github.com/golang/mock v1.1.1
github.com/golang/protobuf v1.3.3 github.com/golang/protobuf v1.3.3
......
...@@ -37,11 +37,6 @@ var ( ...@@ -37,11 +37,6 @@ var (
// KeepaliveMinPingTime is the minimum ping interval. This must be 10s by // KeepaliveMinPingTime is the minimum ping interval. This must be 10s by
// default, but tests may wish to set it lower for convenience. // default, but tests may wish to set it lower for convenience.
KeepaliveMinPingTime = 10 * time.Second KeepaliveMinPingTime = 10 * time.Second
// StatusRawProto is exported by status/status.go. This func returns a
// pointer to the wrapped Status proto for a given status.Status without a
// call to proto.Clone(). The returned Status proto should not be mutated by
// the caller.
StatusRawProto interface{} // func (*status.Status) *spb.Status
// NewRequestInfoContext creates a new context based on the argument context attaching // NewRequestInfoContext creates a new context based on the argument context attaching
// the passed in RequestInfo to the new context. // the passed in RequestInfo to the new context.
NewRequestInfoContext interface{} // func(context.Context, credentials.RequestInfo) context.Context NewRequestInfoContext interface{} // func(context.Context, credentials.RequestInfo) context.Context
......
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package status implements errors returned by gRPC. These errors are
// serialized and transmitted on the wire between server and client, and allow
// for additional data to be transmitted via the Details field in the status
// proto. gRPC service handlers should return an error created by this
// package, and gRPC clients should expect a corresponding error to be
// returned from the RPC call.
//
// This package upholds the invariants that a non-nil error may not
// contain an OK code, and an OK code must result in a nil error.
package status
import (
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
)
// Status represents an RPC status code, message, and details. It is immutable
// and should be created with New, Newf, or FromProto.
type Status struct {
s *spb.Status
}
// New returns a Status representing c and msg.
func New(c codes.Code, msg string) *Status {
return &Status{s: &spb.Status{Code: int32(c), Message: msg}}
}
// Newf returns New(c, fmt.Sprintf(format, a...)).
func Newf(c codes.Code, format string, a ...interface{}) *Status {
return New(c, fmt.Sprintf(format, a...))
}
// FromProto returns a Status representing s.
func FromProto(s *spb.Status) *Status {
return &Status{s: proto.Clone(s).(*spb.Status)}
}
// Err returns an error representing c and msg. If c is OK, returns nil.
func Err(c codes.Code, msg string) error {
return New(c, msg).Err()
}
// Errorf returns Error(c, fmt.Sprintf(format, a...)).
func Errorf(c codes.Code, format string, a ...interface{}) error {
return Err(c, fmt.Sprintf(format, a...))
}
// Code returns the status code contained in s.
func (s *Status) Code() codes.Code {
if s == nil || s.s == nil {
return codes.OK
}
return codes.Code(s.s.Code)
}
// Message returns the message contained in s.
func (s *Status) Message() string {
if s == nil || s.s == nil {
return ""
}
return s.s.Message
}
// Proto returns s's status as an spb.Status proto message.
func (s *Status) Proto() *spb.Status {
if s == nil {
return nil
}
return proto.Clone(s.s).(*spb.Status)
}
// Err returns an immutable error representing s; returns nil if s.Code() is OK.
func (s *Status) Err() error {
if s.Code() == codes.OK {
return nil
}
return (*Error)(s.Proto())
}
// WithDetails returns a new status with the provided details messages appended to the status.
// If any errors are encountered, it returns nil and the first error encountered.
func (s *Status) WithDetails(details ...proto.Message) (*Status, error) {
if s.Code() == codes.OK {
return nil, errors.New("no error details for status with code OK")
}
// s.Code() != OK implies that s.Proto() != nil.
p := s.Proto()
for _, detail := range details {
any, err := ptypes.MarshalAny(detail)
if err != nil {
return nil, err
}
p.Details = append(p.Details, any)
}
return &Status{s: p}, nil
}
// Details returns a slice of details messages attached to the status.
// If a detail cannot be decoded, the error is returned in place of the detail.
func (s *Status) Details() []interface{} {
if s == nil || s.s == nil {
return nil
}
details := make([]interface{}, 0, len(s.s.Details))
for _, any := range s.s.Details {
detail := &ptypes.DynamicAny{}
if err := ptypes.UnmarshalAny(any, detail); err != nil {
details = append(details, err)
continue
}
details = append(details, detail.Message)
}
return details
}
// Error is an alias of a status proto. It implements error and Status,
// and a nil Error should never be returned by this package.
type Error spb.Status
func (se *Error) Error() string {
p := (*spb.Status)(se)
return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(p.GetCode()), p.GetMessage())
}
// GRPCStatus returns the Status represented by se.
func (se *Error) GRPCStatus() *Status {
return FromProto((*spb.Status)(se))
}
// Is implements future error.Is functionality.
// A Error is equivalent if the code and message are identical.
func (se *Error) Is(target error) bool {
tse, ok := target.(*Error)
if !ok {
return false
}
return proto.Equal((*spb.Status)(se), (*spb.Status)(tse))
}
...@@ -112,11 +112,10 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats sta ...@@ -112,11 +112,10 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats sta
// at this point to be speaking over HTTP/2, so it's able to speak valid // at this point to be speaking over HTTP/2, so it's able to speak valid
// gRPC. // gRPC.
type serverHandlerTransport struct { type serverHandlerTransport struct {
rw http.ResponseWriter rw http.ResponseWriter
req *http.Request req *http.Request
timeoutSet bool timeoutSet bool
timeout time.Duration timeout time.Duration
didCommonHeaders bool
headerMD metadata.MD headerMD metadata.MD
...@@ -186,8 +185,11 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro ...@@ -186,8 +185,11 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
ht.writeStatusMu.Lock() ht.writeStatusMu.Lock()
defer ht.writeStatusMu.Unlock() defer ht.writeStatusMu.Unlock()
headersWritten := s.updateHeaderSent()
err := ht.do(func() { err := ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
ht.writePendingHeaders(s)
}
// And flush, in case no header or body has been sent yet. // And flush, in case no header or body has been sent yet.
// This forces a separation of headers and trailers if this is the // This forces a separation of headers and trailers if this is the
...@@ -227,6 +229,8 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro ...@@ -227,6 +229,8 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
if err == nil { // transport has not been closed if err == nil { // transport has not been closed
if ht.stats != nil { if ht.stats != nil {
// Note: The trailer fields are compressed with hpack after this call returns.
// No WireLength field is set here.
ht.stats.HandleRPC(s.Context(), &stats.OutTrailer{ ht.stats.HandleRPC(s.Context(), &stats.OutTrailer{
Trailer: s.trailer.Copy(), Trailer: s.trailer.Copy(),
}) })
...@@ -236,14 +240,16 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro ...@@ -236,14 +240,16 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
return err return err
} }
// writePendingHeaders sets common and custom headers on the first
// write call (Write, WriteHeader, or WriteStatus)
func (ht *serverHandlerTransport) writePendingHeaders(s *Stream) {
ht.writeCommonHeaders(s)
ht.writeCustomHeaders(s)
}
// writeCommonHeaders sets common headers on the first write // writeCommonHeaders sets common headers on the first write
// call (Write, WriteHeader, or WriteStatus). // call (Write, WriteHeader, or WriteStatus).
func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) { func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
if ht.didCommonHeaders {
return
}
ht.didCommonHeaders = true
h := ht.rw.Header() h := ht.rw.Header()
h["Date"] = nil // suppress Date to make tests happy; TODO: restore h["Date"] = nil // suppress Date to make tests happy; TODO: restore
h.Set("Content-Type", ht.contentType) h.Set("Content-Type", ht.contentType)
...@@ -262,9 +268,30 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) { ...@@ -262,9 +268,30 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
} }
} }
// writeCustomHeaders sets custom headers set on the stream via SetHeader
// on the first write call (Write, WriteHeader, or WriteStatus).
func (ht *serverHandlerTransport) writeCustomHeaders(s *Stream) {
h := ht.rw.Header()
s.hdrMu.Lock()
for k, vv := range s.header {
if isReservedHeader(k) {
continue
}
for _, v := range vv {
h.Add(k, encodeMetadataHeader(k, v))
}
}
s.hdrMu.Unlock()
}
func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts *Options) error {
headersWritten := s.updateHeaderSent()
return ht.do(func() { return ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
ht.writePendingHeaders(s)
}
ht.rw.Write(hdr) ht.rw.Write(hdr)
ht.rw.Write(data) ht.rw.Write(data)
ht.rw.(http.Flusher).Flush() ht.rw.(http.Flusher).Flush()
...@@ -272,27 +299,27 @@ func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts ...@@ -272,27 +299,27 @@ func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts
} }
func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error {
if err := s.SetHeader(md); err != nil {
return err
}
headersWritten := s.updateHeaderSent()
err := ht.do(func() { err := ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
h := ht.rw.Header() ht.writePendingHeaders(s)
for k, vv := range md {
// Clients don't tolerate reading restricted headers after some non restricted ones were sent.
if isReservedHeader(k) {
continue
}
for _, v := range vv {
v = encodeMetadataHeader(k, v)
h.Add(k, v)
}
} }
ht.rw.WriteHeader(200) ht.rw.WriteHeader(200)
ht.rw.(http.Flusher).Flush() ht.rw.(http.Flusher).Flush()
}) })
if err == nil { if err == nil {
if ht.stats != nil { if ht.stats != nil {
// Note: The header fields are compressed with hpack after this call returns.
// No WireLength field is set here.
ht.stats.HandleRPC(s.Context(), &stats.OutHeader{ ht.stats.HandleRPC(s.Context(), &stats.OutHeader{
Header: md.Copy(), Header: md.Copy(),
Compression: s.sendCompress,
}) })
} }
} }
......
...@@ -686,6 +686,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea ...@@ -686,6 +686,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
} else { } else {
header = metadata.Pairs("user-agent", t.userAgent) header = metadata.Pairs("user-agent", t.userAgent)
} }
// Note: The header fields are compressed with hpack after this call returns.
// No WireLength field is set here.
outHeader := &stats.OutHeader{ outHeader := &stats.OutHeader{
Client: true, Client: true,
FullMethod: callHdr.Method, FullMethod: callHdr.Method,
...@@ -1193,9 +1195,10 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { ...@@ -1193,9 +1195,10 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
if t.statsHandler != nil { if t.statsHandler != nil {
if isHeader { if isHeader {
inHeader := &stats.InHeader{ inHeader := &stats.InHeader{
Client: true, Client: true,
WireLength: int(frame.Header().Length), WireLength: int(frame.Header().Length),
Header: s.header.Copy(), Header: s.header.Copy(),
Compression: s.recvCompress,
} }
t.statsHandler.HandleRPC(s.ctx, inHeader) t.statsHandler.HandleRPC(s.ctx, inHeader)
} else { } else {
......
...@@ -35,11 +35,9 @@ import ( ...@@ -35,11 +35,9 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
...@@ -57,9 +55,6 @@ var ( ...@@ -57,9 +55,6 @@ var (
// ErrHeaderListSizeLimitViolation indicates that the header list size is larger // ErrHeaderListSizeLimitViolation indicates that the header list size is larger
// than the limit set by peer. // than the limit set by peer.
ErrHeaderListSizeLimitViolation = errors.New("transport: trying to send header list size larger than the limit set by peer") ErrHeaderListSizeLimitViolation = errors.New("transport: trying to send header list size larger than the limit set by peer")
// statusRawProto is a function to get to the raw status proto wrapped in a
// status.Status without a proto.Clone().
statusRawProto = internal.StatusRawProto.(func(*status.Status) *spb.Status)
) )
// serverConnectionCounter counts the number of connections a server has seen // serverConnectionCounter counts the number of connections a server has seen
...@@ -813,10 +808,11 @@ func (t *http2Server) writeHeaderLocked(s *Stream) error { ...@@ -813,10 +808,11 @@ func (t *http2Server) writeHeaderLocked(s *Stream) error {
return ErrHeaderListSizeLimitViolation return ErrHeaderListSizeLimitViolation
} }
if t.stats != nil { if t.stats != nil {
// Note: WireLength is not set in outHeader. // Note: Headers are compressed with hpack after this call returns.
// TODO(mmukhi): Revisit this later, if needed. // No WireLength field is set here.
outHeader := &stats.OutHeader{ outHeader := &stats.OutHeader{
Header: s.header.Copy(), Header: s.header.Copy(),
Compression: s.sendCompress,
} }
t.stats.HandleRPC(s.Context(), outHeader) t.stats.HandleRPC(s.Context(), outHeader)
} }
...@@ -849,7 +845,7 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { ...@@ -849,7 +845,7 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))})
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())})
if p := statusRawProto(st); p != nil && len(p.Details) > 0 { if p := st.Proto(); p != nil && len(p.Details) > 0 {
stBytes, err := proto.Marshal(p) stBytes, err := proto.Marshal(p)
if err != nil { if err != nil {
// TODO: return error instead, when callers are able to handle it. // TODO: return error instead, when callers are able to handle it.
...@@ -880,6 +876,8 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { ...@@ -880,6 +876,8 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
rst := s.getState() == streamActive rst := s.getState() == streamActive
t.finishStream(s, rst, http2.ErrCodeNo, trailingHeader, true) t.finishStream(s, rst, http2.ErrCodeNo, trailingHeader, true)
if t.stats != nil { if t.stats != nil {
// Note: The trailer fields are compressed with hpack after this call returns.
// No WireLength field is set here.
t.stats.HandleRPC(s.Context(), &stats.OutTrailer{ t.stats.HandleRPC(s.Context(), &stats.OutTrailer{
Trailer: s.trailer.Copy(), Trailer: s.trailer.Copy(),
}) })
......
...@@ -81,6 +81,10 @@ type InHeader struct { ...@@ -81,6 +81,10 @@ type InHeader struct {
Client bool Client bool
// WireLength is the wire length of header. // WireLength is the wire length of header.
WireLength int WireLength int
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata received.
Header metadata.MD
// The following fields are valid only if Client is false. // The following fields are valid only if Client is false.
// FullMethod is the full RPC method string, i.e., /package.service/method. // FullMethod is the full RPC method string, i.e., /package.service/method.
...@@ -89,10 +93,6 @@ type InHeader struct { ...@@ -89,10 +93,6 @@ type InHeader struct {
RemoteAddr net.Addr RemoteAddr net.Addr
// LocalAddr is the local address of the corresponding connection. // LocalAddr is the local address of the corresponding connection.
LocalAddr net.Addr LocalAddr net.Addr
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata received.
Header metadata.MD
} }
// IsClient indicates if the stats information is from client side. // IsClient indicates if the stats information is from client side.
...@@ -141,6 +141,10 @@ func (s *OutPayload) isRPCStats() {} ...@@ -141,6 +141,10 @@ func (s *OutPayload) isRPCStats() {}
type OutHeader struct { type OutHeader struct {
// Client is true if this OutHeader is from client side. // Client is true if this OutHeader is from client side.
Client bool Client bool
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata sent.
Header metadata.MD
// The following fields are valid only if Client is true. // The following fields are valid only if Client is true.
// FullMethod is the full RPC method string, i.e., /package.service/method. // FullMethod is the full RPC method string, i.e., /package.service/method.
...@@ -149,10 +153,6 @@ type OutHeader struct { ...@@ -149,10 +153,6 @@ type OutHeader struct {
RemoteAddr net.Addr RemoteAddr net.Addr
// LocalAddr is the local address of the corresponding connection. // LocalAddr is the local address of the corresponding connection.
LocalAddr net.Addr LocalAddr net.Addr
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata sent.
Header metadata.MD
} }
// IsClient indicates if this stats information is from client side. // IsClient indicates if this stats information is from client side.
...@@ -165,6 +165,9 @@ type OutTrailer struct { ...@@ -165,6 +165,9 @@ type OutTrailer struct {
// Client is true if this OutTrailer is from client side. // Client is true if this OutTrailer is from client side.
Client bool Client bool
// WireLength is the wire length of trailer. // WireLength is the wire length of trailer.
//
// Deprecated: This field is never set. The length is not known when this message is
// emitted because the trailer fields are compressed with hpack after that.
WireLength int WireLength int
// Trailer contains the trailer metadata sent to the client. This // Trailer contains the trailer metadata sent to the client. This
// field is only valid if this OutTrailer is from the server side. // field is only valid if this OutTrailer is from the server side.
......
...@@ -29,88 +29,23 @@ package status ...@@ -29,88 +29,23 @@ package status
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
spb "google.golang.org/genproto/googleapis/rpc/status" spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/internal" "google.golang.org/grpc/internal/status"
) )
func init() { // Status references google.golang.org/grpc/internal/status. It represents an
internal.StatusRawProto = statusRawProto // RPC status code, message, and details. It is immutable and should be
} // created with New, Newf, or FromProto.
// https://godoc.org/google.golang.org/grpc/internal/status
func statusRawProto(s *Status) *spb.Status { return s.s } type Status = status.Status
// statusError is an alias of a status proto. It implements error and Status,
// and a nil statusError should never be returned by this package.
type statusError spb.Status
func (se *statusError) Error() string {
p := (*spb.Status)(se)
return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(p.GetCode()), p.GetMessage())
}
func (se *statusError) GRPCStatus() *Status {
return &Status{s: (*spb.Status)(se)}
}
// Is implements future error.Is functionality.
// A statusError is equivalent if the code and message are identical.
func (se *statusError) Is(target error) bool {
tse, ok := target.(*statusError)
if !ok {
return false
}
return proto.Equal((*spb.Status)(se), (*spb.Status)(tse))
}
// Status represents an RPC status code, message, and details. It is immutable
// and should be created with New, Newf, or FromProto.
type Status struct {
s *spb.Status
}
// Code returns the status code contained in s.
func (s *Status) Code() codes.Code {
if s == nil || s.s == nil {
return codes.OK
}
return codes.Code(s.s.Code)
}
// Message returns the message contained in s.
func (s *Status) Message() string {
if s == nil || s.s == nil {
return ""
}
return s.s.Message
}
// Proto returns s's status as an spb.Status proto message.
func (s *Status) Proto() *spb.Status {
if s == nil {
return nil
}
return proto.Clone(s.s).(*spb.Status)
}
// Err returns an immutable error representing s; returns nil if s.Code() is
// OK.
func (s *Status) Err() error {
if s.Code() == codes.OK {
return nil
}
return (*statusError)(s.s)
}
// New returns a Status representing c and msg. // New returns a Status representing c and msg.
func New(c codes.Code, msg string) *Status { func New(c codes.Code, msg string) *Status {
return &Status{s: &spb.Status{Code: int32(c), Message: msg}} return status.New(c, msg)
} }
// Newf returns New(c, fmt.Sprintf(format, a...)). // Newf returns New(c, fmt.Sprintf(format, a...)).
...@@ -135,7 +70,7 @@ func ErrorProto(s *spb.Status) error { ...@@ -135,7 +70,7 @@ func ErrorProto(s *spb.Status) error {
// FromProto returns a Status representing s. // FromProto returns a Status representing s.
func FromProto(s *spb.Status) *Status { func FromProto(s *spb.Status) *Status {
return &Status{s: proto.Clone(s).(*spb.Status)} return status.FromProto(s)
} }
// FromError returns a Status representing err if it was produced from this // FromError returns a Status representing err if it was produced from this
...@@ -160,42 +95,6 @@ func Convert(err error) *Status { ...@@ -160,42 +95,6 @@ func Convert(err error) *Status {
return s return s
} }
// WithDetails returns a new status with the provided details messages appended to the status.
// If any errors are encountered, it returns nil and the first error encountered.
func (s *Status) WithDetails(details ...proto.Message) (*Status, error) {
if s.Code() == codes.OK {
return nil, errors.New("no error details for status with code OK")
}
// s.Code() != OK implies that s.Proto() != nil.
p := s.Proto()
for _, detail := range details {
any, err := ptypes.MarshalAny(detail)
if err != nil {
return nil, err
}
p.Details = append(p.Details, any)
}
return &Status{s: p}, nil
}
// Details returns a slice of details messages attached to the status.
// If a detail cannot be decoded, the error is returned in place of the detail.
func (s *Status) Details() []interface{} {
if s == nil || s.s == nil {
return nil
}
details := make([]interface{}, 0, len(s.s.Details))
for _, any := range s.s.Details {
detail := &ptypes.DynamicAny{}
if err := ptypes.UnmarshalAny(any, detail); err != nil {
details = append(details, err)
continue
}
details = append(details, detail.Message)
}
return details
}
// Code returns the Code of the error if it is a Status error, codes.OK if err // Code returns the Code of the error if it is a Status error, codes.OK if err
// is nil, or codes.Unknown otherwise. // is nil, or codes.Unknown otherwise.
func Code(err error) codes.Code { func Code(err error) codes.Code {
......
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
package grpc package grpc
// Version is the current grpc version. // Version is the current grpc version.
const Version = "1.28.0" const Version = "1.29.1"
#!/bin/bash #!/bin/bash
if [[ `uname -a` = *"Darwin"* ]]; then
echo "It seems you are running on Mac. This script does not work on Mac. See https://github.com/grpc/grpc-go/issues/2047"
exit 1
fi
set -ex # Exit on error; debugging enabled. set -ex # Exit on error; debugging enabled.
set -o pipefail # Fail a pipe if any sub-command fails. set -o pipefail # Fail a pipe if any sub-command fails.
# not makes sure the command passed to it does not exit with a return code of 0.
not() {
# This is required instead of the earlier (! $COMMAND) because subshells and
# pipefail don't work the same on Darwin as in Linux.
! "$@"
}
die() { die() {
echo "$@" >&2 echo "$@" >&2
exit 1 exit 1
} }
fail_on_output() { fail_on_output() {
tee /dev/stderr | (! read) tee /dev/stderr | not read
} }
# Check to make sure it's safe to modify the user's git repo. # Check to make sure it's safe to modify the user's git repo.
...@@ -60,7 +62,7 @@ if [[ "$1" = "-install" ]]; then ...@@ -60,7 +62,7 @@ if [[ "$1" = "-install" ]]; then
unzip ${PROTOC_FILENAME} unzip ${PROTOC_FILENAME}
bin/protoc --version bin/protoc --version
popd popd
elif ! which protoc > /dev/null; then elif not which protoc > /dev/null; then
die "Please install protoc into your path" die "Please install protoc into your path"
fi fi
fi fi
...@@ -70,21 +72,21 @@ elif [[ "$#" -ne 0 ]]; then ...@@ -70,21 +72,21 @@ elif [[ "$#" -ne 0 ]]; then
fi fi
# - Ensure all source files contain a copyright message. # - Ensure all source files contain a copyright message.
(! git grep -L "\(Copyright [0-9]\{4,\} gRPC authors\)\|DO NOT EDIT" -- '*.go') not git grep -L "\(Copyright [0-9]\{4,\} gRPC authors\)\|DO NOT EDIT" -- '*.go'
# - Make sure all tests in grpc and grpc/test use leakcheck via Teardown. # - Make sure all tests in grpc and grpc/test use leakcheck via Teardown.
(! grep 'func Test[^(]' *_test.go) not grep 'func Test[^(]' *_test.go
(! grep 'func Test[^(]' test/*.go) not grep 'func Test[^(]' test/*.go
# - Do not import x/net/context. # - Do not import x/net/context.
(! git grep -l 'x/net/context' -- "*.go") not git grep -l 'x/net/context' -- "*.go"
# - Do not import math/rand for real library code. Use internal/grpcrand for # - Do not import math/rand for real library code. Use internal/grpcrand for
# thread safety. # thread safety.
git grep -l '"math/rand"' -- "*.go" 2>&1 | (! grep -v '^examples\|^stress\|grpcrand\|^benchmark\|wrr_test') git grep -l '"math/rand"' -- "*.go" 2>&1 | not grep -v '^examples\|^stress\|grpcrand\|^benchmark\|wrr_test'
# - Ensure all ptypes proto packages are renamed when importing. # - Ensure all ptypes proto packages are renamed when importing.
(! git grep "\(import \|^\s*\)\"github.com/golang/protobuf/ptypes/" -- "*.go") not git grep "\(import \|^\s*\)\"github.com/golang/protobuf/ptypes/" -- "*.go"
# - Check imports that are illegal in appengine (until Go 1.11). # - Check imports that are illegal in appengine (until Go 1.11).
# TODO: Remove when we drop Go 1.10 support # TODO: Remove when we drop Go 1.10 support
...@@ -92,8 +94,8 @@ go list -f {{.Dir}} ./... | xargs go run test/go_vet/vet.go ...@@ -92,8 +94,8 @@ go list -f {{.Dir}} ./... | xargs go run test/go_vet/vet.go
# - gofmt, goimports, golint (with exceptions for generated code), go vet. # - gofmt, goimports, golint (with exceptions for generated code), go vet.
gofmt -s -d -l . 2>&1 | fail_on_output gofmt -s -d -l . 2>&1 | fail_on_output
goimports -l . 2>&1 | (! grep -vE "(_mock|\.pb)\.go") goimports -l . 2>&1 | not grep -vE "(_mock|\.pb)\.go"
golint ./... 2>&1 | (! grep -vE "(_mock|\.pb)\.go:") golint ./... 2>&1 | not grep -vE "(_mock|\.pb)\.go:"
go vet -all ./... go vet -all ./...
misspell -error . misspell -error .
...@@ -119,9 +121,9 @@ fi ...@@ -119,9 +121,9 @@ fi
SC_OUT="$(mktemp)" SC_OUT="$(mktemp)"
staticcheck -go 1.9 -checks 'inherit,-ST1015' ./... > "${SC_OUT}" || true staticcheck -go 1.9 -checks 'inherit,-ST1015' ./... > "${SC_OUT}" || true
# Error if anything other than deprecation warnings are printed. # Error if anything other than deprecation warnings are printed.
(! grep -v "is deprecated:.*SA1019" "${SC_OUT}") not grep -v "is deprecated:.*SA1019" "${SC_OUT}"
# Only ignore the following deprecated types/fields/functions. # Only ignore the following deprecated types/fields/functions.
(! grep -Fv '.HandleResolvedAddrs not grep -Fv '.HandleResolvedAddrs
.HandleSubConnStateChange .HandleSubConnStateChange
.HeaderMap .HeaderMap
.NewAddress .NewAddress
...@@ -157,4 +159,5 @@ naming.Update ...@@ -157,4 +159,5 @@ naming.Update
naming.Watcher naming.Watcher
resolver.Backend resolver.Backend
resolver.GRPCLB' "${SC_OUT}" resolver.GRPCLB' "${SC_OUT}"
)
echo SUCCESS
...@@ -25,7 +25,7 @@ github.com/Microsoft/hcsshim/osversion ...@@ -25,7 +25,7 @@ github.com/Microsoft/hcsshim/osversion
# github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f # github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f
github.com/containerd/cgroups github.com/containerd/cgroups
github.com/containerd/cgroups/stats/v1 github.com/containerd/cgroups/stats/v1
# github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e # github.com/containerd/console v1.0.0
github.com/containerd/console github.com/containerd/console
# github.com/containerd/containerd v1.3.3 # github.com/containerd/containerd v1.3.3
github.com/containerd/containerd github.com/containerd/containerd
...@@ -104,6 +104,8 @@ github.com/containerd/ttrpc ...@@ -104,6 +104,8 @@ github.com/containerd/ttrpc
github.com/containerd/typeurl github.com/containerd/typeurl
# github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e # github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/coreos/go-systemd/dbus github.com/coreos/go-systemd/dbus
# github.com/cpuguy83/go-md2man/v2 v2.0.0
github.com/cpuguy83/go-md2man/v2/md2man
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew github.com/davecgh/go-spew/spew
# github.com/docker/distribution v0.0.0-00010101000000-000000000000 => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible # github.com/docker/distribution v0.0.0-00010101000000-000000000000 => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
...@@ -141,7 +143,7 @@ github.com/gogo/protobuf/sortkeys ...@@ -141,7 +143,7 @@ github.com/gogo/protobuf/sortkeys
github.com/gogo/protobuf/types github.com/gogo/protobuf/types
# github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b # github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/glog github.com/golang/glog
# github.com/golang/protobuf v1.3.3 # github.com/golang/protobuf v1.3.5
github.com/golang/protobuf/proto github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/any
...@@ -169,15 +171,21 @@ github.com/opencontainers/go-digest ...@@ -169,15 +171,21 @@ github.com/opencontainers/go-digest
github.com/opencontainers/image-spec/identity github.com/opencontainers/image-spec/identity
github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go
github.com/opencontainers/image-spec/specs-go/v1 github.com/opencontainers/image-spec/specs-go/v1
# github.com/opencontainers/runc v0.1.1 # github.com/opencontainers/runc v0.1.1 => github.com/alibaba/inclavare-containers/rune v0.0.0-20200903043353-e35cb5b583ad
github.com/opencontainers/runc/libcontainer/system github.com/opencontainers/runc/libcontainer/system
github.com/opencontainers/runc/libcontainer/user github.com/opencontainers/runc/libcontainer/user
github.com/opencontainers/runc/libenclave/attestation/sgx
github.com/opencontainers/runc/libenclave/proto
# github.com/opencontainers/runtime-spec v1.0.2 # github.com/opencontainers/runtime-spec v1.0.2
github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go
# github.com/pkg/errors v0.9.1 # github.com/pkg/errors v0.9.1
github.com/pkg/errors github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0 # github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib github.com/pmezard/go-difflib/difflib
# github.com/russross/blackfriday/v2 v2.0.1
github.com/russross/blackfriday/v2
# github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/shurcooL/sanitized_anchor_name
# github.com/sirupsen/logrus v1.5.0 # github.com/sirupsen/logrus v1.5.0
github.com/sirupsen/logrus github.com/sirupsen/logrus
# github.com/spf13/cobra v1.0.0 # github.com/spf13/cobra v1.0.0
...@@ -186,11 +194,11 @@ github.com/spf13/cobra ...@@ -186,11 +194,11 @@ github.com/spf13/cobra
github.com/spf13/pflag github.com/spf13/pflag
# github.com/stretchr/testify v1.4.0 # github.com/stretchr/testify v1.4.0
github.com/stretchr/testify/assert github.com/stretchr/testify/assert
# github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 # github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/syndtr/gocapability/capability github.com/syndtr/gocapability/capability
# github.com/ugorji/go/codec v1.1.7 # github.com/ugorji/go/codec v1.1.7
github.com/ugorji/go/codec github.com/ugorji/go/codec
# github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5 # github.com/urfave/cli v1.22.1
github.com/urfave/cli github.com/urfave/cli
# go.opencensus.io v0.22.0 # go.opencensus.io v0.22.0
go.opencensus.io go.opencensus.io
...@@ -219,7 +227,7 @@ golang.org/x/text/unicode/bidi ...@@ -219,7 +227,7 @@ golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm golang.org/x/text/unicode/norm
# google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 # google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
google.golang.org/genproto/googleapis/rpc/status google.golang.org/genproto/googleapis/rpc/status
# google.golang.org/grpc v1.28.0 # google.golang.org/grpc v1.29.1
google.golang.org/grpc google.golang.org/grpc
google.golang.org/grpc/attributes google.golang.org/grpc/attributes
google.golang.org/grpc/backoff google.golang.org/grpc/backoff
...@@ -248,6 +256,7 @@ google.golang.org/grpc/internal/grpcsync ...@@ -248,6 +256,7 @@ google.golang.org/grpc/internal/grpcsync
google.golang.org/grpc/internal/grpcutil google.golang.org/grpc/internal/grpcutil
google.golang.org/grpc/internal/resolver/dns google.golang.org/grpc/internal/resolver/dns
google.golang.org/grpc/internal/resolver/passthrough google.golang.org/grpc/internal/resolver/passthrough
google.golang.org/grpc/internal/status
google.golang.org/grpc/internal/syscall google.golang.org/grpc/internal/syscall
google.golang.org/grpc/internal/transport google.golang.org/grpc/internal/transport
google.golang.org/grpc/keepalive google.golang.org/grpc/keepalive
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册