| @@ -94,11 +94,12 @@ require ( | |||||
| github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect | github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect | ||||
| github.com/tinylib/msgp v1.1.2 // indirect | github.com/tinylib/msgp v1.1.2 // indirect | ||||
| github.com/tstranex/u2f v1.0.0 | github.com/tstranex/u2f v1.0.0 | ||||
| github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect | |||||
| github.com/unknwon/cae v1.0.0 | github.com/unknwon/cae v1.0.0 | ||||
| github.com/unknwon/com v1.0.1 | github.com/unknwon/com v1.0.1 | ||||
| github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | ||||
| github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | ||||
| github.com/urfave/cli v1.20.0 | |||||
| github.com/urfave/cli v1.22.1 | |||||
| github.com/xanzy/go-gitlab v0.31.0 | github.com/xanzy/go-gitlab v0.31.0 | ||||
| github.com/yohcop/openid-go v1.0.0 | github.com/yohcop/openid-go v1.0.0 | ||||
| github.com/yuin/goldmark v1.1.27 | github.com/yuin/goldmark v1.1.27 | ||||
| @@ -132,7 +132,10 @@ github.com/couchbase/vellum v1.0.1 h1:qrj9ohvZedvc51S5KzPfJ6P6z0Vqzv7Lx7k3mVc2WO | |||||
| github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= | github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= | ||||
| github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8= | github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8= | ||||
| github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc= | github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc= | ||||
| github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= | |||||
| github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | ||||
| 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= | ||||
| github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= | github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= | ||||
| @@ -556,12 +559,16 @@ github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qq | |||||
| 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 v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= | github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= | ||||
| github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | ||||
| 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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= | ||||
| github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= | ||||
| github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= | ||||
| github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||
| github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= | github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= | ||||
| github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= | github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= | ||||
| 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/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= | ||||
| github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= | ||||
| github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= | ||||
| @@ -626,6 +633,8 @@ github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= | |||||
| github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= | github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= | ||||
| 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/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||||
| github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= | |||||
| github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= | |||||
| github.com/unknwon/cae v1.0.0 h1:i39lOFaBXZxhGjQOy/RNbi8uzettCs6OQxpR0xXohGU= | github.com/unknwon/cae v1.0.0 h1:i39lOFaBXZxhGjQOy/RNbi8uzettCs6OQxpR0xXohGU= | ||||
| github.com/unknwon/cae v1.0.0/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU= | github.com/unknwon/cae v1.0.0/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU= | ||||
| github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM= | github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM= | ||||
| @@ -634,10 +643,14 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= | |||||
| github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | ||||
| github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE= | github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE= | ||||
| github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= | github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= | ||||
| github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= | |||||
| github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= | |||||
| github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:Z79lyIznnziKADUf0J7EP8Z4ZL7YJDiPuaazlfUBSy4= | github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:Z79lyIznnziKADUf0J7EP8Z4ZL7YJDiPuaazlfUBSy4= | ||||
| github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0= | github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0= | ||||
| github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= | ||||
| github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | github.com/urfave/cli v1.20.0/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/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= | github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= | ||||
| github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | ||||
| github.com/xanzy/go-gitlab v0.31.0 h1:+nHztQuCXGSMluKe5Q9IRaPdz6tO8O0gMkQ0vqGpiBk= | github.com/xanzy/go-gitlab v0.31.0 h1:+nHztQuCXGSMluKe5Q9IRaPdz6tO8O0gMkQ0vqGpiBk= | ||||
| @@ -771,6 +784,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w | |||||
| golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= | ||||
| golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= | ||||
| @@ -868,6 +882,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X | |||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | ||||
| gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||
| gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= | |||||
| gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= | |||||
| gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= | ||||
| gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= | ||||
| gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| @@ -1,7 +1,7 @@ | |||||
| home = Home | home = Home | ||||
| dashboard = Dashboard | dashboard = Dashboard | ||||
| explore = Explore | explore = Explore | ||||
| data_sets = Data Sets | |||||
| datasets = Datasets | |||||
| help = Help | help = Help | ||||
| sign_in = Sign In | sign_in = Sign In | ||||
| sign_in_with = Sign In With | sign_in_with = Sign In With | ||||
| @@ -49,6 +49,7 @@ organization = Organization | |||||
| mirror = Mirror | mirror = Mirror | ||||
| new_repo = New Repository | new_repo = New Repository | ||||
| new_migrate = New Migration | new_migrate = New Migration | ||||
| new_dataset = New Dataset | |||||
| new_mirror = New Mirror | new_mirror = New Mirror | ||||
| new_fork = New Repository Fork | new_fork = New Repository Fork | ||||
| new_org = New Organization | new_org = New Organization | ||||
| @@ -618,6 +619,13 @@ email_notifications.onmention = Only Email on Mention | |||||
| email_notifications.disable = Disable Email Notifications | email_notifications.disable = Disable Email Notifications | ||||
| email_notifications.submit = Set Email Preference | email_notifications.submit = Set Email Preference | ||||
| [dataset] | |||||
| title = Title | |||||
| description = Description | |||||
| create_dataset = Create Dataset | |||||
| category = Category | |||||
| file = Dataset File | |||||
| [repo] | [repo] | ||||
| owner = Owner | owner = Owner | ||||
| repo_name = Repository Name | repo_name = Repository Name | ||||
| @@ -667,7 +675,7 @@ pick_reaction = Pick your reaction | |||||
| reactions_more = and %d more | reactions_more = and %d more | ||||
| unit_disabled = The site administrator has disabled this repository section. | unit_disabled = The site administrator has disabled this repository section. | ||||
| language_other = Other | language_other = Other | ||||
| data_sets = Data Sets | |||||
| datasets = Datasets | |||||
| template.items = Template Items | template.items = Template Items | ||||
| template.git_content = Git Content (Default Branch) | template.git_content = Git Content (Default Branch) | ||||
| @@ -47,6 +47,7 @@ repository=仓库 | |||||
| organization=组织 | organization=组织 | ||||
| mirror=镜像 | mirror=镜像 | ||||
| new_repo=创建仓库 | new_repo=创建仓库 | ||||
| new_dataset=创建数据集 | |||||
| new_migrate=迁移外部仓库 | new_migrate=迁移外部仓库 | ||||
| new_mirror=创建新的镜像 | new_mirror=创建新的镜像 | ||||
| new_fork=新的仓库Fork | new_fork=新的仓库Fork | ||||
| @@ -514,8 +515,8 @@ last_used=上次使用在 | |||||
| no_activity=没有最近活动 | no_activity=没有最近活动 | ||||
| can_read_info=读取 | can_read_info=读取 | ||||
| can_write_info=写入 | can_write_info=写入 | ||||
| key_state_desc=7 天内使用过该密钥 | |||||
| token_state_desc=7 天内使用过该密钥 | |||||
| key_state_desc=7 天内使用过该密钥 | |||||
| token_state_desc=7 天内使用过该密钥 | |||||
| show_openid=在个人信息上显示 | show_openid=在个人信息上显示 | ||||
| hide_openid=在个人信息上隐藏 | hide_openid=在个人信息上隐藏 | ||||
| ssh_disabled=SSH 被禁用 | ssh_disabled=SSH 被禁用 | ||||
| @@ -616,6 +617,13 @@ email_notifications.onmention=只在被提到时邮件通知 | |||||
| email_notifications.disable=停用邮件通知 | email_notifications.disable=停用邮件通知 | ||||
| email_notifications.submit=邮件通知设置 | email_notifications.submit=邮件通知设置 | ||||
| [dataset] | |||||
| title = 主题 | |||||
| description = 描述 | |||||
| create_dataset = 创建数据集 | |||||
| category = 分类 | |||||
| file = 数据集文件 | |||||
| [repo] | [repo] | ||||
| owner=拥有者 | owner=拥有者 | ||||
| repo_name=仓库名称 | repo_name=仓库名称 | ||||
| @@ -662,9 +670,10 @@ watchers=关注者 | |||||
| stargazers=称赞者 | stargazers=称赞者 | ||||
| forks=派生仓库 | forks=派生仓库 | ||||
| pick_reaction=选择你的表情 | pick_reaction=选择你的表情 | ||||
| reactions_more=再加载 %d | |||||
| reactions_more=再加载 %d | |||||
| unit_disabled=站点管理员已禁用此仓库单元。 | unit_disabled=站点管理员已禁用此仓库单元。 | ||||
| language_other=其它 | language_other=其它 | ||||
| datasets=数据集 | |||||
| template.items=模板选项 | template.items=模板选项 | ||||
| template.git_content=Git数据(默认分支) | template.git_content=Git数据(默认分支) | ||||
| @@ -7,16 +7,18 @@ import ( | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| tplDataSet base.TplName = "dataset/index" | |||||
| tplDataSet base.TplName = "datasets/index" | |||||
| tplCreate base.TplName = "datasets/create" | |||||
| ) | ) | ||||
| func MyList(ctx *context.Context) { | func MyList(ctx *context.Context) { | ||||
| log.Debug("[dataset] mylist...\n") | log.Debug("[dataset] mylist...\n") | ||||
| ctx.HTML(200, tplDataSet) | ctx.HTML(200, tplDataSet) | ||||
| } | } | ||||
| func Create(ctx *context.Context) { | func Create(ctx *context.Context) { | ||||
| log.Debug("[dataset] Create...\n") | log.Debug("[dataset] Create...\n") | ||||
| ctx.HTML(200, tplCreate) | |||||
| } | } | ||||
| func Delete(ctx *context.Context) { | func Delete(ctx *context.Context) { | ||||
| log.Debug("[dataset] Delete...\n") | log.Debug("[dataset] Delete...\n") | ||||
| @@ -1001,9 +1001,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| // ***** END: Repository ***** | // ***** END: Repository ***** | ||||
| // DataSet | // DataSet | ||||
| m.Group("/dataset", func() { | |||||
| m.Group("/datasets", func() { | |||||
| m.Get("", dataset.MyList) | m.Get("", dataset.MyList) | ||||
| m.Post("", dataset.Create) | |||||
| m.Get("/create", dataset.Create) | |||||
| m.Post("/delete", dataset.Delete) | m.Post("/delete", dataset.Delete) | ||||
| }, ignSignIn) | }, ignSignIn) | ||||
| // ***** END: DataSet***** | // ***** END: DataSet***** | ||||
| @@ -10,6 +10,7 @@ | |||||
| {{if .IsSigned}} | {{if .IsSigned}} | ||||
| <a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a> | <a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/datasets">{{.i18n.Tr "datasets"}}</a> | |||||
| {{if not .UnitIssuesGlobalDisabled}} | {{if not .UnitIssuesGlobalDisabled}} | ||||
| <a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | <a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | ||||
| {{end}} | {{end}} | ||||
| @@ -20,17 +21,16 @@ | |||||
| {{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}} | {{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}} | ||||
| {{end}} | {{end}} | ||||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a> | <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "data_sets"}}</a> | |||||
| {{else if .IsLandingPageHome}} | {{else if .IsLandingPageHome}} | ||||
| <a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a> | <a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/datasets">{{.i18n.Tr "datasets"}}</a> | |||||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a> | <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "data_sets"}}</a> | |||||
| {{else if .IsLandingPageExplore}} | {{else if .IsLandingPageExplore}} | ||||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "data_sets"}}</a> | |||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/datasets">{{.i18n.Tr "datasets"}}</a> | |||||
| {{else if .IsLandingPageOrganizations}} | {{else if .IsLandingPageOrganizations}} | ||||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | ||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "data_sets"}}</a> | |||||
| <a class="item {{if .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/datasets">{{.i18n.Tr "datasets"}}</a> | |||||
| {{end}} | {{end}} | ||||
| {{template "custom/extra_links" .}} | {{template "custom/extra_links" .}} | ||||
| @@ -68,6 +68,9 @@ | |||||
| <a class="item" href="{{AppSubUrl}}/repo/create"> | <a class="item" href="{{AppSubUrl}}/repo/create"> | ||||
| <span class="fitted">{{svg "octicon-plus" 16}}</span> {{.i18n.Tr "new_repo"}} | <span class="fitted">{{svg "octicon-plus" 16}}</span> {{.i18n.Tr "new_repo"}} | ||||
| </a> | </a> | ||||
| <a class="item" href="{{AppSubUrl}}/datasets/create"> | |||||
| <span class="fitted">{{svg "octicon-plus" 16}}</span> {{.i18n.Tr "new_dataset"}} | |||||
| </a> | |||||
| <a class="item" href="{{AppSubUrl}}/repo/migrate"> | <a class="item" href="{{AppSubUrl}}/repo/migrate"> | ||||
| <span class="fitted">{{svg "octicon-repo-clone" 16}}</span> {{.i18n.Tr "new_migrate"}} | <span class="fitted">{{svg "octicon-repo-clone" 16}}</span> {{.i18n.Tr "new_migrate"}} | ||||
| </a> | </a> | ||||
| @@ -1 +0,0 @@ | |||||
| index.tmpl | |||||
| @@ -0,0 +1,45 @@ | |||||
| {{template "base/head" .}} | |||||
| <div class="dataset new dataset"> | |||||
| <div class="ui middle very relaxed page grid"> | |||||
| <div class="column"> | |||||
| <form class="ui form" action="{{.Link}}" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <h3 class="ui top attached header"> | |||||
| {{.i18n.Tr "new_dataset"}} | |||||
| </h3> | |||||
| <div class="ui attached segment"> | |||||
| {{template "base/alert" .}} | |||||
| <div class="field"> | |||||
| <label>{{.i18n.Tr "dataset.title"}}</label> | |||||
| <input name="title" placeholder="{{.i18n.Tr "dataset.title"}}" value="{{.title}}" autofocus required maxlength="255"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label>{{.i18n.Tr "dataset.description"}}</label> | |||||
| <textarea name="description">{{.description}}</textarea> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label>{{.i18n.Tr "dataset.category"}}</label> | |||||
| <input name="category" placeholder="{{.i18n.Tr "dataset.category"}}" value="{{.category}}" autofocus required maxlength="255"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label>{{.i18n.Tr "dataset.file"}}</label> | |||||
| <div class="files"></div> | |||||
| <div class="ui dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div> | |||||
| </div> | |||||
| <br/> | |||||
| <div class="inline field"> | |||||
| <label></label> | |||||
| <button class="ui green button"> | |||||
| {{.i18n.Tr "repo.create_dataset"}} | |||||
| </button> | |||||
| <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | |||||
| </div> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,3 @@ | |||||
| {{template "base/head" .}} | |||||
| index.tmpl | |||||
| {{template "base/footer" .}} | |||||
| @@ -101,10 +101,6 @@ | |||||
| <a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | <a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | ||||
| {{svg "octicon-issue-opened" 16}} {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span> | {{svg "octicon-issue-opened" 16}} {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span> | ||||
| </a> | </a> | ||||
| <a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | |||||
| {{svg "octicon-file-submodule" 16}} {{.i18n.Tr "repo.data_sets"}} | |||||
| </a> | |||||
| {{end}} | {{end}} | ||||
| {{if .Permission.CanRead $.UnitTypeExternalTracker}} | {{if .Permission.CanRead $.UnitTypeExternalTracker}} | ||||
| @@ -0,0 +1,21 @@ | |||||
| 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. | |||||
| @@ -0,0 +1,14 @@ | |||||
| 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())}...) | |||||
| } | |||||
| @@ -0,0 +1,345 @@ | |||||
| 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 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| *.out | |||||
| *.swp | |||||
| *.8 | |||||
| *.6 | |||||
| _obj | |||||
| _test* | |||||
| markdown | |||||
| tags | |||||
| @@ -0,0 +1,17 @@ | |||||
| 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 ./... | |||||
| @@ -0,0 +1,29 @@ | |||||
| 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. | |||||
| @@ -0,0 +1,291 @@ | |||||
| Blackfriday [](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 `–`, and `---` is translated into | |||||
| `—`. 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>⁄<sub>5</sub>`, which renders as | |||||
| <sup>4</sup>⁄<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" | |||||
| @@ -0,0 +1,18 @@ | |||||
| // 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 | |||||
| @@ -0,0 +1,34 @@ | |||||
| package blackfriday | |||||
| import ( | |||||
| "html" | |||||
| "io" | |||||
| ) | |||||
| var htmlEscaper = [256][]byte{ | |||||
| '&': []byte("&"), | |||||
| '<': []byte("<"), | |||||
| '>': []byte(">"), | |||||
| '"': []byte("""), | |||||
| } | |||||
| 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)) | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| module github.com/russross/blackfriday/v2 | |||||
| @@ -0,0 +1,949 @@ | |||||
| // | |||||
| // 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() | |||||
| } | |||||
| @@ -0,0 +1,950 @@ | |||||
| // 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] | |||||
| } | |||||
| @@ -0,0 +1,354 @@ | |||||
| 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) | |||||
| } | |||||
| @@ -0,0 +1,457 @@ | |||||
| // | |||||
| // 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(" ") | |||||
| } | |||||
| out.WriteByte('&') | |||||
| if *isOpen { | |||||
| out.WriteByte('l') | |||||
| } else { | |||||
| out.WriteByte('r') | |||||
| } | |||||
| out.WriteByte(quote) | |||||
| out.WriteString("quo;") | |||||
| if addNBSP && *isOpen { | |||||
| out.WriteString(" ") | |||||
| } | |||||
| 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("’") | |||||
| 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("’") | |||||
| 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("©") | |||||
| return 2 | |||||
| } | |||||
| if t1 == 'r' && t2 == ')' { | |||||
| out.WriteString("®") | |||||
| return 2 | |||||
| } | |||||
| if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { | |||||
| out.WriteString("™") | |||||
| 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("—") | |||||
| return 1 | |||||
| } | |||||
| if wordBoundary(previousChar) && wordBoundary(text[1]) { | |||||
| out.WriteString("–") | |||||
| 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("—") | |||||
| return 2 | |||||
| } | |||||
| if len(text) >= 2 && text[1] == '-' { | |||||
| out.WriteString("–") | |||||
| 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(""")) { | |||||
| 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("�")) { | |||||
| 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("…") | |||||
| return 2 | |||||
| } | |||||
| if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { | |||||
| out.WriteString("…") | |||||
| 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>⁄<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("½") | |||||
| 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("¼") | |||||
| 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("¾") | |||||
| 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(""") | |||||
| } | |||||
| 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:]) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| 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 ./... | |||||
| @@ -0,0 +1,21 @@ | |||||
| 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. | |||||
| @@ -0,0 +1,36 @@ | |||||
| sanitized_anchor_name | |||||
| ===================== | |||||
| [](https://travis-ci.org/shurcooL/sanitized_anchor_name) [](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) | |||||
| @@ -0,0 +1 @@ | |||||
| module github.com/shurcooL/sanitized_anchor_name | |||||
| @@ -0,0 +1,29 @@ | |||||
| // 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) | |||||
| } | |||||
| @@ -1,2 +1,3 @@ | |||||
| *.coverprofile | *.coverprofile | ||||
| node_modules/ | node_modules/ | ||||
| vendor | |||||
| @@ -1,27 +1,35 @@ | |||||
| language: go | language: go | ||||
| sudo: false | sudo: false | ||||
| dist: trusty | |||||
| osx_image: xcode8.3 | |||||
| go: 1.8.x | |||||
| dist: bionic | |||||
| osx_image: xcode10 | |||||
| go: | |||||
| - 1.11.x | |||||
| - 1.12.x | |||||
| - 1.13.x | |||||
| os: | os: | ||||
| - linux | |||||
| - osx | |||||
| - linux | |||||
| - 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 golang.org/x/tools/cmd/goimports | |||||
| - if [ ! -f node_modules/.bin/markdown-toc ] ; then | |||||
| npm install markdown-toc ; | |||||
| fi | |||||
| - go get github.com/urfave/gfmrun/cmd/gfmrun | |||||
| - go get golang.org/x/tools/cmd/goimports | |||||
| - npm install markdown-toc | |||||
| - go mod tidy | |||||
| script: | script: | ||||
| - ./runtests gen | |||||
| - ./runtests vet | |||||
| - ./runtests test | |||||
| - ./runtests gfmrun | |||||
| - ./runtests toc | |||||
| - go run build.go vet | |||||
| - go run build.go test | |||||
| - go run build.go gfmrun | |||||
| - go run build.go toc | |||||
| after_success: | |||||
| - bash <(curl -s https://codecov.io/bash) | |||||
| @@ -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`. | |||||
| ### 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 | ||||
| @@ -0,0 +1,74 @@ | |||||
| # 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 | |||||
| @@ -0,0 +1,18 @@ | |||||
| ## 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 | |||||
| [](https://travis-ci.org/urfave/cli) | [](https://travis-ci.org/urfave/cli) | ||||
| [](https://ci.appveyor.com/project/urfave/cli) | [](https://ci.appveyor.com/project/urfave/cli) | ||||
| [](https://godoc.org/github.com/urfave/cli) | [](https://godoc.org/github.com/urfave/cli) | ||||
| [](https://codebeat.co/projects/github-com-urfave-cli) | [](https://codebeat.co/projects/github-com-urfave-cli) | ||||
| [](https://goreportcard.com/report/urfave/cli) | [](https://goreportcard.com/report/urfave/cli) | ||||
| [](http://gocover.io/github.com/urfave/cli) / | |||||
| [](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. | |||||
| [](https://codecov.io/gh/urfave/cli) | |||||
| 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. | |||||
| - [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,10 +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) | |||||
| * [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) | ||||
| @@ -61,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: | ||||
| @@ -105,25 +104,20 @@ import ( | |||||
| ... | ... | ||||
| ``` | ``` | ||||
| ### Pinning to the `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.: | |||||
| ### Using `v1` releases | |||||
| ``` | ``` | ||||
| $ 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 | ||||
| @@ -138,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -159,6 +157,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -173,7 +172,10 @@ func main() { | |||||
| return nil | return nil | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -197,6 +199,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -211,7 +214,10 @@ func main() { | |||||
| return nil | return nil | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -260,6 +266,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -273,7 +280,10 @@ func main() { | |||||
| return nil | return nil | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -289,6 +299,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -318,7 +329,10 @@ func main() { | |||||
| return nil | return nil | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -332,6 +346,7 @@ scanned. | |||||
| package main | package main | ||||
| import ( | import ( | ||||
| "log" | |||||
| "os" | "os" | ||||
| "fmt" | "fmt" | ||||
| @@ -365,7 +380,10 @@ func main() { | |||||
| return nil | return nil | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -386,6 +404,7 @@ For example this: | |||||
| package main | package main | ||||
| import ( | import ( | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -401,7 +420,10 @@ func main() { | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -427,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" | ||||
| @@ -443,7 +466,10 @@ func main() { | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -467,6 +493,7 @@ For example this: | |||||
| package main | package main | ||||
| import ( | import ( | ||||
| "log" | |||||
| "os" | "os" | ||||
| "sort" | "sort" | ||||
| @@ -510,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -533,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" | ||||
| @@ -550,7 +581,10 @@ func main() { | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -565,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" | ||||
| @@ -582,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": ["--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 | ||||
| @@ -593,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 | ||||
| @@ -615,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 | |||||
| add support for other input sources by implementing the | |||||
| altsrc.InputSourceContext for their given sources. | |||||
| Currently only YAML, JSON, and TOML files are supported but developers can add support | |||||
| for other input sources by implementing the altsrc.InputSourceContext for their | |||||
| 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: | ||||
| @@ -630,6 +708,7 @@ package notmain | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -652,10 +731,22 @@ 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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| #### Precedence | |||||
| The precedence for flag value sources is as follows (highest to lowest): | |||||
| 0. Command line flag value from user | |||||
| 0. Environment variable (if specified) | |||||
| 0. Configuration file (if specified) | |||||
| 0. Default defined on the flag | |||||
| ### Subcommands | ### Subcommands | ||||
| Subcommands can be defined for a more git-like command line app. | Subcommands can be defined for a more git-like command line app. | ||||
| @@ -669,6 +760,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -721,7 +813,10 @@ func main() { | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -737,6 +832,7 @@ E.g. | |||||
| package main | package main | ||||
| import ( | import ( | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -751,15 +847,18 @@ func main() { | |||||
| }, | }, | ||||
| { | { | ||||
| Name: "add", | Name: "add", | ||||
| Category: "template", | |||||
| Category: "Template actions", | |||||
| }, | }, | ||||
| { | { | ||||
| Name: "remove", | Name: "remove", | ||||
| Category: "template", | |||||
| Category: "Template actions", | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -767,7 +866,7 @@ Will include: | |||||
| ``` | ``` | ||||
| COMMANDS: | COMMANDS: | ||||
| noop | |||||
| noop | |||||
| Template actions: | Template actions: | ||||
| add | add | ||||
| @@ -780,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" | ||||
| @@ -793,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", "-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` | ||||
| @@ -825,6 +1000,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -856,7 +1032,10 @@ func main() { | |||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -896,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" | ||||
| @@ -914,7 +1094,10 @@ func main() { | |||||
| Name: "wat", | Name: "wat", | ||||
| }, | }, | ||||
| } | } | ||||
| app.Run(os.Args) | |||||
| err := app.Run(os.Args) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -940,6 +1123,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "io" | "io" | ||||
| "os" | "os" | ||||
| @@ -983,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -998,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" | ||||
| @@ -1010,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -1033,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" | ||||
| @@ -1047,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -1062,6 +1257,7 @@ package main | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "github.com/urfave/cli" | "github.com/urfave/cli" | ||||
| @@ -1079,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) | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -1236,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) { | ||||
| @@ -1341,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") != "" { | ||||
| @@ -1355,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 { | ||||
| @@ -1366,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 | |||||
| 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. | |||||
| See [./CONTRIBUTING.md](./CONTRIBUTING.md) | |||||
| @@ -1,9 +1,9 @@ | |||||
| 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 ( | |||||
| ) | ) | ||||
| var ( | var ( | ||||
| changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" | |||||
| appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) | |||||
| runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) | |||||
| changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" | |||||
| appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", 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." | ||||
| @@ -83,6 +84,9 @@ type App struct { | |||||
| Writer io.Writer | Writer io.Writer | ||||
| // ErrWriter writes error output | // ErrWriter writes error output | ||||
| ErrWriter io.Writer | ErrWriter io.Writer | ||||
| // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to | |||||
| // function as a default, so this is optional. | |||||
| ExitErrHandler ExitErrHandlerFunc | |||||
| // Other custom info | // Other custom info | ||||
| Metadata map[string]interface{} | Metadata map[string]interface{} | ||||
| // Carries a function which returns app specific info. | // Carries a function which returns app specific info. | ||||
| @@ -91,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 | ||||
| } | } | ||||
| @@ -135,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) | ||||
| @@ -170,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) { | ||||
| @@ -183,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 | |||||
| set, err := flagSet(a.Name, a.Flags) | |||||
| _, err = a.newFlagSet() | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| set.SetOutput(ioutil.Discard) | |||||
| err = set.Parse(arguments[1:]) | |||||
| set, err := parseIter(a, 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) | |||||
| ShowAppHelp(context) | |||||
| _, _ = fmt.Fprintln(a.Writer, nerr) | |||||
| _ = ShowAppHelp(context) | |||||
| return nerr | return nerr | ||||
| } | } | ||||
| context.shellComplete = shellComplete | context.shellComplete = shellComplete | ||||
| @@ -207,16 +221,16 @@ func (a *App) Run(arguments []string) (err error) { | |||||
| if err != nil { | if err != nil { | ||||
| if a.OnUsageError != nil { | if a.OnUsageError != nil { | ||||
| err := a.OnUsageError(context, err, false) | err := a.OnUsageError(context, err, false) | ||||
| HandleExitCoder(err) | |||||
| a.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) | |||||
| ShowAppHelp(context) | |||||
| _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) | |||||
| _ = ShowAppHelp(context) | |||||
| return err | return err | ||||
| } | } | ||||
| if !a.HideHelp && checkHelp(context) { | if !a.HideHelp && checkHelp(context) { | ||||
| ShowAppHelp(context) | |||||
| _ = ShowAppHelp(context) | |||||
| return nil | return nil | ||||
| } | } | ||||
| @@ -225,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 { | ||||
| @@ -240,8 +260,9 @@ 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 { | ||||
| ShowAppHelp(context) | |||||
| HandleExitCoder(beforeErr) | |||||
| _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) | |||||
| _ = ShowAppHelp(context) | |||||
| a.handleExitCoder(context, beforeErr) | |||||
| err = beforeErr | err = beforeErr | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -263,7 +284,7 @@ func (a *App) Run(arguments []string) (err error) { | |||||
| // Run default Action | // Run default Action | ||||
| err = HandleAction(a.Action, context) | err = HandleAction(a.Action, context) | ||||
| HandleExitCoder(err) | |||||
| a.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -274,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) | ||||
| } | } | ||||
| } | } | ||||
| @@ -301,24 +322,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||||
| } | } | ||||
| a.Commands = newCmds | a.Commands = newCmds | ||||
| // parse flags | |||||
| set, err := flagSet(a.Name, a.Flags) | |||||
| _, err = a.newFlagSet() | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| set.SetOutput(ioutil.Discard) | |||||
| err = set.Parse(ctx.Args().Tail()) | |||||
| set, err := parseIter(a, 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) | |||||
| _, _ = fmt.Fprintln(a.Writer, nerr) | |||||
| _, _ = 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 | ||||
| } | } | ||||
| @@ -330,11 +349,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||||
| if err != nil { | if err != nil { | ||||
| if a.OnUsageError != nil { | if a.OnUsageError != nil { | ||||
| err = a.OnUsageError(context, err, true) | err = a.OnUsageError(context, err, true) | ||||
| HandleExitCoder(err) | |||||
| a.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) | |||||
| ShowSubcommandHelp(context) | |||||
| _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) | |||||
| _ = ShowSubcommandHelp(context) | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -348,11 +367,17 @@ 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) | ||||
| if afterErr != nil { | if afterErr != nil { | ||||
| HandleExitCoder(err) | |||||
| a.handleExitCoder(context, err) | |||||
| if err != nil { | if err != nil { | ||||
| err = NewMultiError(err, afterErr) | err = NewMultiError(err, afterErr) | ||||
| } else { | } else { | ||||
| @@ -365,7 +390,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||||
| if a.Before != nil { | if a.Before != nil { | ||||
| beforeErr := a.Before(context) | beforeErr := a.Before(context) | ||||
| if beforeErr != nil { | if beforeErr != nil { | ||||
| HandleExitCoder(beforeErr) | |||||
| a.handleExitCoder(context, beforeErr) | |||||
| err = beforeErr | err = beforeErr | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -383,7 +408,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||||
| // Run default Action | // Run default Action | ||||
| err = HandleAction(a.Action, context) | err = HandleAction(a.Action, context) | ||||
| HandleExitCoder(err) | |||||
| a.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -424,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) | ||||
| @@ -449,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 | ||||
| @@ -464,6 +488,14 @@ func (a *App) appendFlag(flag Flag) { | |||||
| } | } | ||||
| } | } | ||||
| func (a *App) handleExitCoder(context *Context, err error) { | |||||
| if a.ExitErrHandler != nil { | |||||
| a.ExitErrHandler(context, err) | |||||
| } else { | |||||
| HandleExitCoder(err) | |||||
| } | |||||
| } | |||||
| // Author represents someone who has contributed to a cli project. | // Author represents someone who has contributed to a cli project. | ||||
| type Author struct { | type Author struct { | ||||
| Name string // The Authors name | Name string // The Authors name | ||||
| @@ -484,14 +516,15 @@ 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 | ||||
| } else { | |||||
| return errInvalidActionType | |||||
| } | } | ||||
| return errInvalidActionType | |||||
| } | } | ||||
| @@ -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 | |||||
| PYTHON: C:\Python36-x64 | |||||
| PYTHON_VERSION: 3.6.x | |||||
| PYTHON_ARCH: 64 | |||||
| GOVERSION: 1.11.x | |||||
| install: | install: | ||||
| - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% | |||||
| - go version | |||||
| - go env | |||||
| - go get github.com/urfave/gfmrun/... | |||||
| - go get -v -t ./... | |||||
| - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% | |||||
| - go version | |||||
| - go env | |||||
| - go get github.com/urfave/gfmrun/... | |||||
| - go get -v -t ./... | |||||
| build_script: | build_script: | ||||
| - python runtests vet | |||||
| - python runtests test | |||||
| - python runtests gfmrun | |||||
| - go run build.go vet | |||||
| - go run build.go test | |||||
| - go run build.go gfmrun | |||||
| @@ -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 @@ | |||||
| // } | // } | ||||
| 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 | |||||
| @@ -1,8 +1,8 @@ | |||||
| package cli | package cli | ||||
| import ( | import ( | ||||
| "flag" | |||||
| "fmt" | "fmt" | ||||
| "io/ioutil" | |||||
| "sort" | "sort" | ||||
| "strings" | "strings" | ||||
| ) | ) | ||||
| @@ -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 { | |||||
| } | } | ||||
| 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) { | |||||
| ) | ) | ||||
| } | } | ||||
| set, err := flagSet(c.Name, c.Flags) | |||||
| if err != nil { | |||||
| 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()) | |||||
| if ctx.App.UseShortOptionHandling { | |||||
| c.UseShortOptionHandling = true | |||||
| } | } | ||||
| nerr := normalizeFlags(c.Flags, set) | |||||
| if nerr != nil { | |||||
| fmt.Fprintln(ctx.App.Writer, nerr) | |||||
| fmt.Fprintln(ctx.App.Writer) | |||||
| ShowCommandHelp(ctx, c.Name) | |||||
| return nerr | |||||
| } | |||||
| set, err := c.parseFlags(ctx.Args().Tail()) | |||||
| context := NewContext(ctx.App, set, ctx) | context := NewContext(ctx.App, set, ctx) | ||||
| context.Command = c | context.Command = c | ||||
| @@ -167,12 +125,12 @@ func (c Command) Run(ctx *Context) (err error) { | |||||
| if err != nil { | if err != nil { | ||||
| if c.OnUsageError != nil { | if c.OnUsageError != nil { | ||||
| err := c.OnUsageError(context, err, false) | err := c.OnUsageError(context, err, false) | ||||
| HandleExitCoder(err) | |||||
| context.App.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) | |||||
| fmt.Fprintln(context.App.Writer) | |||||
| ShowCommandHelp(context, c.Name) | |||||
| _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) | |||||
| _, _ = fmt.Fprintln(context.App.Writer) | |||||
| _ = ShowCommandHelp(context, c.Name) | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -180,11 +138,17 @@ 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) | ||||
| if afterErr != nil { | if afterErr != nil { | ||||
| HandleExitCoder(err) | |||||
| context.App.handleExitCoder(context, err) | |||||
| if err != nil { | if err != nil { | ||||
| err = NewMultiError(err, afterErr) | err = NewMultiError(err, afterErr) | ||||
| } else { | } else { | ||||
| @@ -197,8 +161,8 @@ 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) | |||||
| HandleExitCoder(err) | |||||
| _ = ShowCommandHelp(context, c.Name) | |||||
| context.App.handleExitCoder(context, err) | |||||
| return err | return err | ||||
| } | } | ||||
| } | } | ||||
| @@ -210,11 +174,76 @@ func (c Command) Run(ctx *Context) (err error) { | |||||
| err = HandleAction(c.Action, context) | err = HandleAction(c.Action, context) | ||||
| if err != nil { | if err != nil { | ||||
| HandleExitCoder(err) | |||||
| context.App.handleExitCoder(context, err) | |||||
| } | } | ||||
| 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 { | |||||
| 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 { | |||||
| 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 | |||||
| import ( | import ( | ||||
| "errors" | "errors" | ||||
| "flag" | "flag" | ||||
| "fmt" | |||||
| "os" | |||||
| "reflect" | "reflect" | ||||
| "strings" | "strings" | ||||
| "syscall" | "syscall" | ||||
| @@ -73,7 +75,7 @@ func (c *Context) IsSet(name string) bool { | |||||
| // change in version 2 to add `IsSet` to the Flag interface to push the | // change in version 2 to add `IsSet` to the Flag interface to push the | ||||
| // responsibility closer to where the information required to determine | // responsibility closer to where the information required to determine | ||||
| // whether a flag is set by non-standard means such as environment | // whether a flag is set by non-standard means such as environment | ||||
| // variables is avaliable. | |||||
| // variables is available. | |||||
| // | // | ||||
| // See https://github.com/urfave/cli/issues/294 for additional discussion | // See https://github.com/urfave/cli/issues/294 for additional discussion | ||||
| flags := c.Command.Flags | flags := c.Command.Flags | ||||
| @@ -93,18 +95,26 @@ func (c *Context) IsSet(name string) bool { | |||||
| val = val.Elem() | val = val.Elem() | ||||
| } | } | ||||
| envVarValue := val.FieldByName("EnvVar") | |||||
| if !envVarValue.IsValid() { | |||||
| return | |||||
| filePathValue := val.FieldByName("FilePath") | |||||
| if filePathValue.IsValid() { | |||||
| eachName(filePathValue.String(), func(filePath string) { | |||||
| if _, err := os.Stat(filePath); err == nil { | |||||
| c.setFlags[name] = true | |||||
| return | |||||
| } | |||||
| }) | |||||
| } | } | ||||
| eachName(envVarValue.String(), func(envVar string) { | |||||
| envVar = strings.TrimSpace(envVar) | |||||
| if _, ok := syscall.Getenv(envVar); ok { | |||||
| c.setFlags[name] = true | |||||
| return | |||||
| } | |||||
| }) | |||||
| envVarValue := val.FieldByName("EnvVar") | |||||
| if envVarValue.IsValid() { | |||||
| eachName(envVarValue.String(), func(envVar string) { | |||||
| envVar = strings.TrimSpace(envVar) | |||||
| if _, ok := syscall.Getenv(envVar); ok { | |||||
| c.setFlags[name] = true | |||||
| return | |||||
| } | |||||
| }) | |||||
| } | |||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| @@ -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 { | |||||
| name := strings.Split(flag.GetName(), ",")[0] | |||||
| for _, f := range c.Command.Flags { | |||||
| name := strings.Split(f.GetName(), ",")[0] | |||||
| if name == "help" { | if name == "help" { | ||||
| continue | continue | ||||
| } | } | ||||
| @@ -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 { | |||||
| name := strings.Split(flag.GetName(), ",")[0] | |||||
| for _, f := range c.App.Flags { | |||||
| 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) { | |||||
| 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 { | |||||
| } | } | ||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,148 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,194 @@ | |||||
| 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) | |||||
| } | |||||
| @@ -1,93 +0,0 @@ | |||||
| [ | |||||
| { | |||||
| "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 | |||||
| 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" | ||||
| @@ -37,6 +37,18 @@ var HelpFlag Flag = BoolFlag{ | |||||
| // to display a flag. | // to display a flag. | ||||
| var FlagStringer FlagStringFunc = stringifyFlag | var FlagStringer FlagStringFunc = stringifyFlag | ||||
| // FlagNamePrefixer converts a full flag name and its placeholder into the help | |||||
| // message flag prefix. This is used by the default FlagStringer. | |||||
| var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames | |||||
| // FlagEnvHinter annotates flag help message with the environment variable | |||||
| // details. This is used by the default FlagStringer. | |||||
| 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 | ||||
| @@ -45,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) { | ||||
| @@ -62,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 | ||||
| @@ -84,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 | ||||
| } | } | ||||
| @@ -95,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{} | |||||
| for _, flag := range fl { | |||||
| field := flagValue(flag).FieldByName("Hidden") | |||||
| var visible []Flag | |||||
| for _, f := range fl { | |||||
| 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 | ||||
| @@ -692,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 { | ||||
| @@ -710,14 +222,29 @@ func stringifyFlag(f Flag) string { | |||||
| switch f.(type) { | switch f.(type) { | ||||
| case IntSliceFlag: | case IntSliceFlag: | ||||
| return withEnvHint(fv.FieldByName("EnvVar").String(), | |||||
| stringifyIntSliceFlag(f.(IntSliceFlag))) | |||||
| return FlagFileHinter( | |||||
| fv.FieldByName("FilePath").String(), | |||||
| FlagEnvHinter( | |||||
| fv.FieldByName("EnvVar").String(), | |||||
| stringifyIntSliceFlag(f.(IntSliceFlag)), | |||||
| ), | |||||
| ) | |||||
| case Int64SliceFlag: | case Int64SliceFlag: | ||||
| return withEnvHint(fv.FieldByName("EnvVar").String(), | |||||
| stringifyInt64SliceFlag(f.(Int64SliceFlag))) | |||||
| return FlagFileHinter( | |||||
| fv.FieldByName("FilePath").String(), | |||||
| FlagEnvHinter( | |||||
| fv.FieldByName("EnvVar").String(), | |||||
| stringifyInt64SliceFlag(f.(Int64SliceFlag)), | |||||
| ), | |||||
| ) | |||||
| case StringSliceFlag: | case StringSliceFlag: | ||||
| return withEnvHint(fv.FieldByName("EnvVar").String(), | |||||
| stringifyStringSliceFlag(f.(StringSliceFlag))) | |||||
| return FlagFileHinter( | |||||
| 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()) | ||||
| @@ -742,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 withEnvHint(fv.FieldByName("EnvVar").String(), | |||||
| fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) | |||||
| return FlagFileHinter( | |||||
| 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)) | |||||
| } | } | ||||
| } | } | ||||
| @@ -760,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)) | |||||
| } | } | ||||
| } | } | ||||
| @@ -771,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)) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -794,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)) | |||||
| return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) | |||||
| usageWithDefault := strings.TrimSpace(usage + defaultVal) | |||||
| 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 | |||||
| } | } | ||||
| @@ -0,0 +1,109 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,110 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -1,627 +0,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 | |||||
| } | |||||
| @@ -0,0 +1,110 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,141 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,142 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| 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 "" | |||||
| } | |||||
| @@ -0,0 +1,138 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string) | |||||
| // is displayed and the execution is interrupted. | // is displayed and the execution is interrupted. | ||||
| type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error | type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error | ||||
| // ExitErrHandlerFunc is executed if provided in order to handle ExitError values | |||||
| // returned by Actions and Before/After functions. | |||||
| type ExitErrHandlerFunc func(context *Context, err error) | |||||
| // FlagStringFunc is used by the help generation to display a flag, which is | // FlagStringFunc is used by the help generation to display a flag, which is | ||||
| // expected to be a single line. | // expected to be a single line. | ||||
| type FlagStringFunc func(Flag) string | type FlagStringFunc func(Flag) string | ||||
| // FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix | |||||
| // text for a flag's full name. | |||||
| type FlagNamePrefixFunc func(fullName, placeholder string) string | |||||
| // FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help | |||||
| // with the environment variable details. | |||||
| 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 | |||||
| @@ -1,255 +0,0 @@ | |||||
| #!/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()) | |||||
| @@ -0,0 +1,9 @@ | |||||
| 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 | |||||
| ) | |||||
| @@ -0,0 +1,14 @@ | |||||
| 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,77 +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"}, | ||||
| @@ -89,7 +21,7 @@ var helpCommand = Command{ | |||||
| return ShowCommandHelp(c, args.First()) | return ShowCommandHelp(c, args.First()) | ||||
| } | } | ||||
| ShowAppHelp(c) | |||||
| _ = ShowAppHelp(c) | |||||
| return nil | return nil | ||||
| }, | }, | ||||
| } | } | ||||
| @@ -129,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) | ||||
| } | } | ||||
| @@ -153,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() { | |||||
| fmt.Fprintln(c.App.Writer, name) | |||||
| if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { | |||||
| 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) | ||||
| } | } | ||||
| @@ -207,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 | ||||
| @@ -221,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 { | |||||
| c.BashComplete(ctx) | |||||
| if c != nil { | |||||
| 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 { | |||||
| funcMap[key] = value | |||||
| } | |||||
| for key, value := range customFunc { | |||||
| funcMap[key] = value | |||||
| } | } | ||||
| w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) | w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) | ||||
| @@ -243,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{}) { | ||||
| @@ -280,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 | ||||
| } | } | ||||
| @@ -289,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 | ||||
| } | } | ||||
| @@ -0,0 +1,80 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -1,122 +0,0 @@ | |||||
| #!/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()) | |||||
| @@ -0,0 +1,29 @@ | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,121 @@ | |||||
| 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 }}` | |||||
| @@ -191,7 +191,7 @@ func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) Sta | |||||
| isHeading := false | isHeading := false | ||||
| last := pc.LastOpenedBlock().Node | last := pc.LastOpenedBlock().Node | ||||
| if ast.IsParagraph(last) { | if ast.IsParagraph(last) { | ||||
| c, ok := matchesSetextHeadingBar(line) | |||||
| c, ok := matchesSetextHeadingBar(line[match[3]-1:]) | |||||
| if ok && c == '-' { | if ok && c == '-' { | ||||
| isHeading = true | isHeading = true | ||||
| } | } | ||||
| @@ -268,7 +268,7 @@ func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) i | |||||
| if codeSpanCloser == codeSpanOpener { | if codeSpanCloser == codeSpanOpener { | ||||
| codeSpanOpener = 0 | codeSpanOpener = 0 | ||||
| } | } | ||||
| } else if c == '\\' && i < len(bs)-1 && IsPunct(bs[i+1]) { | |||||
| } else if codeSpanOpener == 0 && c == '\\' && i < len(bs)-1 && IsPunct(bs[i+1]) { | |||||
| i += 2 | i += 2 | ||||
| continue | continue | ||||
| } else if codeSpan && codeSpanOpener == 0 && c == '`' { | } else if codeSpan && codeSpanOpener == 0 && c == '`' { | ||||
| @@ -1,4 +1,4 @@ | |||||
| // +build appengine,js | |||||
| // +build appengine js | |||||
| package util | package util | ||||
| @@ -7,6 +7,8 @@ import ( | |||||
| "go/token" | "go/token" | ||||
| "go/types" | "go/types" | ||||
| "reflect" | "reflect" | ||||
| "golang.org/x/tools/internal/analysisinternal" | |||||
| ) | ) | ||||
| // An Analyzer describes an analysis function and its options. | // An Analyzer describes an analysis function and its options. | ||||
| @@ -69,6 +71,17 @@ type Analyzer struct { | |||||
| func (a *Analyzer) String() string { return a.Name } | func (a *Analyzer) String() string { return a.Name } | ||||
| func init() { | |||||
| // Set the analysisinternal functions to be able to pass type errors | |||||
| // to the Pass type without modifying the go/analysis API. | |||||
| analysisinternal.SetTypeErrors = func(p interface{}, errors []types.Error) { | |||||
| p.(*Pass).typeErrors = errors | |||||
| } | |||||
| analysisinternal.GetTypeErrors = func(p interface{}) []types.Error { | |||||
| return p.(*Pass).typeErrors | |||||
| } | |||||
| } | |||||
| // A Pass provides information to the Run function that | // A Pass provides information to the Run function that | ||||
| // applies a specific analyzer to a single Go package. | // applies a specific analyzer to a single Go package. | ||||
| // | // | ||||
| @@ -138,6 +151,9 @@ type Pass struct { | |||||
| // WARNING: This is an experimental API and may change in the future. | // WARNING: This is an experimental API and may change in the future. | ||||
| AllObjectFacts func() []ObjectFact | AllObjectFacts func() []ObjectFact | ||||
| // typeErrors contains types.Errors that are associated with the pkg. | |||||
| typeErrors []types.Error | |||||
| /* Further fields may be added in future. */ | /* Further fields may be added in future. */ | ||||
| // For example, suggested or applied refactorings. | // For example, suggested or applied refactorings. | ||||
| } | } | ||||
| @@ -382,7 +382,7 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis. | |||||
| func (tree JSONTree) Print() { | func (tree JSONTree) Print() { | ||||
| data, err := json.MarshalIndent(tree, "", "\t") | data, err := json.MarshalIndent(tree, "", "\t") | ||||
| if err != nil { | if err != nil { | ||||
| log.Panicf("internal error: JSON marshalling failed: %v", err) | |||||
| log.Panicf("internal error: JSON marshaling failed: %v", err) | |||||
| } | } | ||||
| fmt.Printf("%s\n", data) | fmt.Printf("%s\n", data) | ||||
| } | } | ||||
| @@ -85,15 +85,11 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, | |||||
| return gcimporter.ImportData(imports, path, path, bytes.NewReader(data)) | return gcimporter.ImportData(imports, path, path, bytes.NewReader(data)) | ||||
| } | } | ||||
| // The indexed export format starts with an 'i'; the older | |||||
| // binary export format starts with a 'c', 'd', or 'v' | |||||
| // (from "version"). Select appropriate importer. | |||||
| if len(data) > 0 && data[0] == 'i' { | |||||
| _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) | |||||
| return pkg, err | |||||
| // The indexed export format starts with an 'i'. | |||||
| if len(data) == 0 || data[0] != 'i' { | |||||
| return nil, fmt.Errorf("unknown export data format") | |||||
| } | } | ||||
| _, pkg, err := gcimporter.BImportData(fset, imports, data, path) | |||||
| _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) | |||||
| return pkg, err | return pkg, err | ||||
| } | } | ||||
| @@ -1,852 +0,0 @@ | |||||
| // Copyright 2016 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| // Binary package export. | |||||
| // This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go; | |||||
| // see that file for specification of the format. | |||||
| package gcimporter | |||||
| import ( | |||||
| "bytes" | |||||
| "encoding/binary" | |||||
| "fmt" | |||||
| "go/ast" | |||||
| "go/constant" | |||||
| "go/token" | |||||
| "go/types" | |||||
| "math" | |||||
| "math/big" | |||||
| "sort" | |||||
| "strings" | |||||
| ) | |||||
| // If debugFormat is set, each integer and string value is preceded by a marker | |||||
| // and position information in the encoding. This mechanism permits an importer | |||||
| // to recognize immediately when it is out of sync. The importer recognizes this | |||||
| // mode automatically (i.e., it can import export data produced with debugging | |||||
| // support even if debugFormat is not set at the time of import). This mode will | |||||
| // lead to massively larger export data (by a factor of 2 to 3) and should only | |||||
| // be enabled during development and debugging. | |||||
| // | |||||
| // NOTE: This flag is the first flag to enable if importing dies because of | |||||
| // (suspected) format errors, and whenever a change is made to the format. | |||||
| const debugFormat = false // default: false | |||||
| // If trace is set, debugging output is printed to std out. | |||||
| const trace = false // default: false | |||||
| // Current export format version. Increase with each format change. | |||||
| // Note: The latest binary (non-indexed) export format is at version 6. | |||||
| // This exporter is still at level 4, but it doesn't matter since | |||||
| // the binary importer can handle older versions just fine. | |||||
| // 6: package height (CL 105038) -- NOT IMPLEMENTED HERE | |||||
| // 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE | |||||
| // 4: type name objects support type aliases, uses aliasTag | |||||
| // 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) | |||||
| // 2: removed unused bool in ODCL export (compiler only) | |||||
| // 1: header format change (more regular), export package for _ struct fields | |||||
| // 0: Go1.7 encoding | |||||
| const exportVersion = 4 | |||||
| // trackAllTypes enables cycle tracking for all types, not just named | |||||
| // types. The existing compiler invariants assume that unnamed types | |||||
| // that are not completely set up are not used, or else there are spurious | |||||
| // errors. | |||||
| // If disabled, only named types are tracked, possibly leading to slightly | |||||
| // less efficient encoding in rare cases. It also prevents the export of | |||||
| // some corner-case type declarations (but those are not handled correctly | |||||
| // with with the textual export format either). | |||||
| // TODO(gri) enable and remove once issues caused by it are fixed | |||||
| const trackAllTypes = false | |||||
| type exporter struct { | |||||
| fset *token.FileSet | |||||
| out bytes.Buffer | |||||
| // object -> index maps, indexed in order of serialization | |||||
| strIndex map[string]int | |||||
| pkgIndex map[*types.Package]int | |||||
| typIndex map[types.Type]int | |||||
| // position encoding | |||||
| posInfoFormat bool | |||||
| prevFile string | |||||
| prevLine int | |||||
| // debugging support | |||||
| written int // bytes written | |||||
| indent int // for trace | |||||
| } | |||||
| // internalError represents an error generated inside this package. | |||||
| type internalError string | |||||
| func (e internalError) Error() string { return "gcimporter: " + string(e) } | |||||
| func internalErrorf(format string, args ...interface{}) error { | |||||
| return internalError(fmt.Sprintf(format, args...)) | |||||
| } | |||||
| // BExportData returns binary export data for pkg. | |||||
| // If no file set is provided, position info will be missing. | |||||
| func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { | |||||
| defer func() { | |||||
| if e := recover(); e != nil { | |||||
| if ierr, ok := e.(internalError); ok { | |||||
| err = ierr | |||||
| return | |||||
| } | |||||
| // Not an internal error; panic again. | |||||
| panic(e) | |||||
| } | |||||
| }() | |||||
| p := exporter{ | |||||
| fset: fset, | |||||
| strIndex: map[string]int{"": 0}, // empty string is mapped to 0 | |||||
| pkgIndex: make(map[*types.Package]int), | |||||
| typIndex: make(map[types.Type]int), | |||||
| posInfoFormat: true, // TODO(gri) might become a flag, eventually | |||||
| } | |||||
| // write version info | |||||
| // The version string must start with "version %d" where %d is the version | |||||
| // number. Additional debugging information may follow after a blank; that | |||||
| // text is ignored by the importer. | |||||
| p.rawStringln(fmt.Sprintf("version %d", exportVersion)) | |||||
| var debug string | |||||
| if debugFormat { | |||||
| debug = "debug" | |||||
| } | |||||
| p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly | |||||
| p.bool(trackAllTypes) | |||||
| p.bool(p.posInfoFormat) | |||||
| // --- generic export data --- | |||||
| // populate type map with predeclared "known" types | |||||
| for index, typ := range predeclared() { | |||||
| p.typIndex[typ] = index | |||||
| } | |||||
| if len(p.typIndex) != len(predeclared()) { | |||||
| return nil, internalError("duplicate entries in type map?") | |||||
| } | |||||
| // write package data | |||||
| p.pkg(pkg, true) | |||||
| if trace { | |||||
| p.tracef("\n") | |||||
| } | |||||
| // write objects | |||||
| objcount := 0 | |||||
| scope := pkg.Scope() | |||||
| for _, name := range scope.Names() { | |||||
| if !ast.IsExported(name) { | |||||
| continue | |||||
| } | |||||
| if trace { | |||||
| p.tracef("\n") | |||||
| } | |||||
| p.obj(scope.Lookup(name)) | |||||
| objcount++ | |||||
| } | |||||
| // indicate end of list | |||||
| if trace { | |||||
| p.tracef("\n") | |||||
| } | |||||
| p.tag(endTag) | |||||
| // for self-verification only (redundant) | |||||
| p.int(objcount) | |||||
| if trace { | |||||
| p.tracef("\n") | |||||
| } | |||||
| // --- end of export data --- | |||||
| return p.out.Bytes(), nil | |||||
| } | |||||
| func (p *exporter) pkg(pkg *types.Package, emptypath bool) { | |||||
| if pkg == nil { | |||||
| panic(internalError("unexpected nil pkg")) | |||||
| } | |||||
| // if we saw the package before, write its index (>= 0) | |||||
| if i, ok := p.pkgIndex[pkg]; ok { | |||||
| p.index('P', i) | |||||
| return | |||||
| } | |||||
| // otherwise, remember the package, write the package tag (< 0) and package data | |||||
| if trace { | |||||
| p.tracef("P%d = { ", len(p.pkgIndex)) | |||||
| defer p.tracef("} ") | |||||
| } | |||||
| p.pkgIndex[pkg] = len(p.pkgIndex) | |||||
| p.tag(packageTag) | |||||
| p.string(pkg.Name()) | |||||
| if emptypath { | |||||
| p.string("") | |||||
| } else { | |||||
| p.string(pkg.Path()) | |||||
| } | |||||
| } | |||||
| func (p *exporter) obj(obj types.Object) { | |||||
| switch obj := obj.(type) { | |||||
| case *types.Const: | |||||
| p.tag(constTag) | |||||
| p.pos(obj) | |||||
| p.qualifiedName(obj) | |||||
| p.typ(obj.Type()) | |||||
| p.value(obj.Val()) | |||||
| case *types.TypeName: | |||||
| if obj.IsAlias() { | |||||
| p.tag(aliasTag) | |||||
| p.pos(obj) | |||||
| p.qualifiedName(obj) | |||||
| } else { | |||||
| p.tag(typeTag) | |||||
| } | |||||
| p.typ(obj.Type()) | |||||
| case *types.Var: | |||||
| p.tag(varTag) | |||||
| p.pos(obj) | |||||
| p.qualifiedName(obj) | |||||
| p.typ(obj.Type()) | |||||
| case *types.Func: | |||||
| p.tag(funcTag) | |||||
| p.pos(obj) | |||||
| p.qualifiedName(obj) | |||||
| sig := obj.Type().(*types.Signature) | |||||
| p.paramList(sig.Params(), sig.Variadic()) | |||||
| p.paramList(sig.Results(), false) | |||||
| default: | |||||
| panic(internalErrorf("unexpected object %v (%T)", obj, obj)) | |||||
| } | |||||
| } | |||||
| func (p *exporter) pos(obj types.Object) { | |||||
| if !p.posInfoFormat { | |||||
| return | |||||
| } | |||||
| file, line := p.fileLine(obj) | |||||
| if file == p.prevFile { | |||||
| // common case: write line delta | |||||
| // delta == 0 means different file or no line change | |||||
| delta := line - p.prevLine | |||||
| p.int(delta) | |||||
| if delta == 0 { | |||||
| p.int(-1) // -1 means no file change | |||||
| } | |||||
| } else { | |||||
| // different file | |||||
| p.int(0) | |||||
| // Encode filename as length of common prefix with previous | |||||
| // filename, followed by (possibly empty) suffix. Filenames | |||||
| // frequently share path prefixes, so this can save a lot | |||||
| // of space and make export data size less dependent on file | |||||
| // path length. The suffix is unlikely to be empty because | |||||
| // file names tend to end in ".go". | |||||
| n := commonPrefixLen(p.prevFile, file) | |||||
| p.int(n) // n >= 0 | |||||
| p.string(file[n:]) // write suffix only | |||||
| p.prevFile = file | |||||
| p.int(line) | |||||
| } | |||||
| p.prevLine = line | |||||
| } | |||||
| func (p *exporter) fileLine(obj types.Object) (file string, line int) { | |||||
| if p.fset != nil { | |||||
| pos := p.fset.Position(obj.Pos()) | |||||
| file = pos.Filename | |||||
| line = pos.Line | |||||
| } | |||||
| return | |||||
| } | |||||
| func commonPrefixLen(a, b string) int { | |||||
| if len(a) > len(b) { | |||||
| a, b = b, a | |||||
| } | |||||
| // len(a) <= len(b) | |||||
| i := 0 | |||||
| for i < len(a) && a[i] == b[i] { | |||||
| i++ | |||||
| } | |||||
| return i | |||||
| } | |||||
| func (p *exporter) qualifiedName(obj types.Object) { | |||||
| p.string(obj.Name()) | |||||
| p.pkg(obj.Pkg(), false) | |||||
| } | |||||
| func (p *exporter) typ(t types.Type) { | |||||
| if t == nil { | |||||
| panic(internalError("nil type")) | |||||
| } | |||||
| // Possible optimization: Anonymous pointer types *T where | |||||
| // T is a named type are common. We could canonicalize all | |||||
| // such types *T to a single type PT = *T. This would lead | |||||
| // to at most one *T entry in typIndex, and all future *T's | |||||
| // would be encoded as the respective index directly. Would | |||||
| // save 1 byte (pointerTag) per *T and reduce the typIndex | |||||
| // size (at the cost of a canonicalization map). We can do | |||||
| // this later, without encoding format change. | |||||
| // if we saw the type before, write its index (>= 0) | |||||
| if i, ok := p.typIndex[t]; ok { | |||||
| p.index('T', i) | |||||
| return | |||||
| } | |||||
| // otherwise, remember the type, write the type tag (< 0) and type data | |||||
| if trackAllTypes { | |||||
| if trace { | |||||
| p.tracef("T%d = {>\n", len(p.typIndex)) | |||||
| defer p.tracef("<\n} ") | |||||
| } | |||||
| p.typIndex[t] = len(p.typIndex) | |||||
| } | |||||
| switch t := t.(type) { | |||||
| case *types.Named: | |||||
| if !trackAllTypes { | |||||
| // if we don't track all types, track named types now | |||||
| p.typIndex[t] = len(p.typIndex) | |||||
| } | |||||
| p.tag(namedTag) | |||||
| p.pos(t.Obj()) | |||||
| p.qualifiedName(t.Obj()) | |||||
| p.typ(t.Underlying()) | |||||
| if !types.IsInterface(t) { | |||||
| p.assocMethods(t) | |||||
| } | |||||
| case *types.Array: | |||||
| p.tag(arrayTag) | |||||
| p.int64(t.Len()) | |||||
| p.typ(t.Elem()) | |||||
| case *types.Slice: | |||||
| p.tag(sliceTag) | |||||
| p.typ(t.Elem()) | |||||
| case *dddSlice: | |||||
| p.tag(dddTag) | |||||
| p.typ(t.elem) | |||||
| case *types.Struct: | |||||
| p.tag(structTag) | |||||
| p.fieldList(t) | |||||
| case *types.Pointer: | |||||
| p.tag(pointerTag) | |||||
| p.typ(t.Elem()) | |||||
| case *types.Signature: | |||||
| p.tag(signatureTag) | |||||
| p.paramList(t.Params(), t.Variadic()) | |||||
| p.paramList(t.Results(), false) | |||||
| case *types.Interface: | |||||
| p.tag(interfaceTag) | |||||
| p.iface(t) | |||||
| case *types.Map: | |||||
| p.tag(mapTag) | |||||
| p.typ(t.Key()) | |||||
| p.typ(t.Elem()) | |||||
| case *types.Chan: | |||||
| p.tag(chanTag) | |||||
| p.int(int(3 - t.Dir())) // hack | |||||
| p.typ(t.Elem()) | |||||
| default: | |||||
| panic(internalErrorf("unexpected type %T: %s", t, t)) | |||||
| } | |||||
| } | |||||
| func (p *exporter) assocMethods(named *types.Named) { | |||||
| // Sort methods (for determinism). | |||||
| var methods []*types.Func | |||||
| for i := 0; i < named.NumMethods(); i++ { | |||||
| methods = append(methods, named.Method(i)) | |||||
| } | |||||
| sort.Sort(methodsByName(methods)) | |||||
| p.int(len(methods)) | |||||
| if trace && methods != nil { | |||||
| p.tracef("associated methods {>\n") | |||||
| } | |||||
| for i, m := range methods { | |||||
| if trace && i > 0 { | |||||
| p.tracef("\n") | |||||
| } | |||||
| p.pos(m) | |||||
| name := m.Name() | |||||
| p.string(name) | |||||
| if !exported(name) { | |||||
| p.pkg(m.Pkg(), false) | |||||
| } | |||||
| sig := m.Type().(*types.Signature) | |||||
| p.paramList(types.NewTuple(sig.Recv()), false) | |||||
| p.paramList(sig.Params(), sig.Variadic()) | |||||
| p.paramList(sig.Results(), false) | |||||
| p.int(0) // dummy value for go:nointerface pragma - ignored by importer | |||||
| } | |||||
| if trace && methods != nil { | |||||
| p.tracef("<\n} ") | |||||
| } | |||||
| } | |||||
| type methodsByName []*types.Func | |||||
| func (x methodsByName) Len() int { return len(x) } | |||||
| func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } | |||||
| func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } | |||||
| func (p *exporter) fieldList(t *types.Struct) { | |||||
| if trace && t.NumFields() > 0 { | |||||
| p.tracef("fields {>\n") | |||||
| defer p.tracef("<\n} ") | |||||
| } | |||||
| p.int(t.NumFields()) | |||||
| for i := 0; i < t.NumFields(); i++ { | |||||
| if trace && i > 0 { | |||||
| p.tracef("\n") | |||||
| } | |||||
| p.field(t.Field(i)) | |||||
| p.string(t.Tag(i)) | |||||
| } | |||||
| } | |||||
| func (p *exporter) field(f *types.Var) { | |||||
| if !f.IsField() { | |||||
| panic(internalError("field expected")) | |||||
| } | |||||
| p.pos(f) | |||||
| p.fieldName(f) | |||||
| p.typ(f.Type()) | |||||
| } | |||||
| func (p *exporter) iface(t *types.Interface) { | |||||
| // TODO(gri): enable importer to load embedded interfaces, | |||||
| // then emit Embeddeds and ExplicitMethods separately here. | |||||
| p.int(0) | |||||
| n := t.NumMethods() | |||||
| if trace && n > 0 { | |||||
| p.tracef("methods {>\n") | |||||
| defer p.tracef("<\n} ") | |||||
| } | |||||
| p.int(n) | |||||
| for i := 0; i < n; i++ { | |||||
| if trace && i > 0 { | |||||
| p.tracef("\n") | |||||
| } | |||||
| p.method(t.Method(i)) | |||||
| } | |||||
| } | |||||
| func (p *exporter) method(m *types.Func) { | |||||
| sig := m.Type().(*types.Signature) | |||||
| if sig.Recv() == nil { | |||||
| panic(internalError("method expected")) | |||||
| } | |||||
| p.pos(m) | |||||
| p.string(m.Name()) | |||||
| if m.Name() != "_" && !ast.IsExported(m.Name()) { | |||||
| p.pkg(m.Pkg(), false) | |||||
| } | |||||
| // interface method; no need to encode receiver. | |||||
| p.paramList(sig.Params(), sig.Variadic()) | |||||
| p.paramList(sig.Results(), false) | |||||
| } | |||||
| func (p *exporter) fieldName(f *types.Var) { | |||||
| name := f.Name() | |||||
| if f.Anonymous() { | |||||
| // anonymous field - we distinguish between 3 cases: | |||||
| // 1) field name matches base type name and is exported | |||||
| // 2) field name matches base type name and is not exported | |||||
| // 3) field name doesn't match base type name (alias name) | |||||
| bname := basetypeName(f.Type()) | |||||
| if name == bname { | |||||
| if ast.IsExported(name) { | |||||
| name = "" // 1) we don't need to know the field name or package | |||||
| } else { | |||||
| name = "?" // 2) use unexported name "?" to force package export | |||||
| } | |||||
| } else { | |||||
| // 3) indicate alias and export name as is | |||||
| // (this requires an extra "@" but this is a rare case) | |||||
| p.string("@") | |||||
| } | |||||
| } | |||||
| p.string(name) | |||||
| if name != "" && !ast.IsExported(name) { | |||||
| p.pkg(f.Pkg(), false) | |||||
| } | |||||
| } | |||||
| func basetypeName(typ types.Type) string { | |||||
| switch typ := deref(typ).(type) { | |||||
| case *types.Basic: | |||||
| return typ.Name() | |||||
| case *types.Named: | |||||
| return typ.Obj().Name() | |||||
| default: | |||||
| return "" // unnamed type | |||||
| } | |||||
| } | |||||
| func (p *exporter) paramList(params *types.Tuple, variadic bool) { | |||||
| // use negative length to indicate unnamed parameters | |||||
| // (look at the first parameter only since either all | |||||
| // names are present or all are absent) | |||||
| n := params.Len() | |||||
| if n > 0 && params.At(0).Name() == "" { | |||||
| n = -n | |||||
| } | |||||
| p.int(n) | |||||
| for i := 0; i < params.Len(); i++ { | |||||
| q := params.At(i) | |||||
| t := q.Type() | |||||
| if variadic && i == params.Len()-1 { | |||||
| t = &dddSlice{t.(*types.Slice).Elem()} | |||||
| } | |||||
| p.typ(t) | |||||
| if n > 0 { | |||||
| name := q.Name() | |||||
| p.string(name) | |||||
| if name != "_" { | |||||
| p.pkg(q.Pkg(), false) | |||||
| } | |||||
| } | |||||
| p.string("") // no compiler-specific info | |||||
| } | |||||
| } | |||||
| func (p *exporter) value(x constant.Value) { | |||||
| if trace { | |||||
| p.tracef("= ") | |||||
| } | |||||
| switch x.Kind() { | |||||
| case constant.Bool: | |||||
| tag := falseTag | |||||
| if constant.BoolVal(x) { | |||||
| tag = trueTag | |||||
| } | |||||
| p.tag(tag) | |||||
| case constant.Int: | |||||
| if v, exact := constant.Int64Val(x); exact { | |||||
| // common case: x fits into an int64 - use compact encoding | |||||
| p.tag(int64Tag) | |||||
| p.int64(v) | |||||
| return | |||||
| } | |||||
| // uncommon case: large x - use float encoding | |||||
| // (powers of 2 will be encoded efficiently with exponent) | |||||
| p.tag(floatTag) | |||||
| p.float(constant.ToFloat(x)) | |||||
| case constant.Float: | |||||
| p.tag(floatTag) | |||||
| p.float(x) | |||||
| case constant.Complex: | |||||
| p.tag(complexTag) | |||||
| p.float(constant.Real(x)) | |||||
| p.float(constant.Imag(x)) | |||||
| case constant.String: | |||||
| p.tag(stringTag) | |||||
| p.string(constant.StringVal(x)) | |||||
| case constant.Unknown: | |||||
| // package contains type errors | |||||
| p.tag(unknownTag) | |||||
| default: | |||||
| panic(internalErrorf("unexpected value %v (%T)", x, x)) | |||||
| } | |||||
| } | |||||
| func (p *exporter) float(x constant.Value) { | |||||
| if x.Kind() != constant.Float { | |||||
| panic(internalErrorf("unexpected constant %v, want float", x)) | |||||
| } | |||||
| // extract sign (there is no -0) | |||||
| sign := constant.Sign(x) | |||||
| if sign == 0 { | |||||
| // x == 0 | |||||
| p.int(0) | |||||
| return | |||||
| } | |||||
| // x != 0 | |||||
| var f big.Float | |||||
| if v, exact := constant.Float64Val(x); exact { | |||||
| // float64 | |||||
| f.SetFloat64(v) | |||||
| } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { | |||||
| // TODO(gri): add big.Rat accessor to constant.Value. | |||||
| r := valueToRat(num) | |||||
| f.SetRat(r.Quo(r, valueToRat(denom))) | |||||
| } else { | |||||
| // Value too large to represent as a fraction => inaccessible. | |||||
| // TODO(gri): add big.Float accessor to constant.Value. | |||||
| f.SetFloat64(math.MaxFloat64) // FIXME | |||||
| } | |||||
| // extract exponent such that 0.5 <= m < 1.0 | |||||
| var m big.Float | |||||
| exp := f.MantExp(&m) | |||||
| // extract mantissa as *big.Int | |||||
| // - set exponent large enough so mant satisfies mant.IsInt() | |||||
| // - get *big.Int from mant | |||||
| m.SetMantExp(&m, int(m.MinPrec())) | |||||
| mant, acc := m.Int(nil) | |||||
| if acc != big.Exact { | |||||
| panic(internalError("internal error")) | |||||
| } | |||||
| p.int(sign) | |||||
| p.int(exp) | |||||
| p.string(string(mant.Bytes())) | |||||
| } | |||||
| func valueToRat(x constant.Value) *big.Rat { | |||||
| // Convert little-endian to big-endian. | |||||
| // I can't believe this is necessary. | |||||
| bytes := constant.Bytes(x) | |||||
| for i := 0; i < len(bytes)/2; i++ { | |||||
| bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] | |||||
| } | |||||
| return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) | |||||
| } | |||||
| func (p *exporter) bool(b bool) bool { | |||||
| if trace { | |||||
| p.tracef("[") | |||||
| defer p.tracef("= %v] ", b) | |||||
| } | |||||
| x := 0 | |||||
| if b { | |||||
| x = 1 | |||||
| } | |||||
| p.int(x) | |||||
| return b | |||||
| } | |||||
| // ---------------------------------------------------------------------------- | |||||
| // Low-level encoders | |||||
| func (p *exporter) index(marker byte, index int) { | |||||
| if index < 0 { | |||||
| panic(internalError("invalid index < 0")) | |||||
| } | |||||
| if debugFormat { | |||||
| p.marker('t') | |||||
| } | |||||
| if trace { | |||||
| p.tracef("%c%d ", marker, index) | |||||
| } | |||||
| p.rawInt64(int64(index)) | |||||
| } | |||||
| func (p *exporter) tag(tag int) { | |||||
| if tag >= 0 { | |||||
| panic(internalError("invalid tag >= 0")) | |||||
| } | |||||
| if debugFormat { | |||||
| p.marker('t') | |||||
| } | |||||
| if trace { | |||||
| p.tracef("%s ", tagString[-tag]) | |||||
| } | |||||
| p.rawInt64(int64(tag)) | |||||
| } | |||||
| func (p *exporter) int(x int) { | |||||
| p.int64(int64(x)) | |||||
| } | |||||
| func (p *exporter) int64(x int64) { | |||||
| if debugFormat { | |||||
| p.marker('i') | |||||
| } | |||||
| if trace { | |||||
| p.tracef("%d ", x) | |||||
| } | |||||
| p.rawInt64(x) | |||||
| } | |||||
| func (p *exporter) string(s string) { | |||||
| if debugFormat { | |||||
| p.marker('s') | |||||
| } | |||||
| if trace { | |||||
| p.tracef("%q ", s) | |||||
| } | |||||
| // if we saw the string before, write its index (>= 0) | |||||
| // (the empty string is mapped to 0) | |||||
| if i, ok := p.strIndex[s]; ok { | |||||
| p.rawInt64(int64(i)) | |||||
| return | |||||
| } | |||||
| // otherwise, remember string and write its negative length and bytes | |||||
| p.strIndex[s] = len(p.strIndex) | |||||
| p.rawInt64(-int64(len(s))) | |||||
| for i := 0; i < len(s); i++ { | |||||
| p.rawByte(s[i]) | |||||
| } | |||||
| } | |||||
| // marker emits a marker byte and position information which makes | |||||
| // it easy for a reader to detect if it is "out of sync". Used for | |||||
| // debugFormat format only. | |||||
| func (p *exporter) marker(m byte) { | |||||
| p.rawByte(m) | |||||
| // Enable this for help tracking down the location | |||||
| // of an incorrect marker when running in debugFormat. | |||||
| if false && trace { | |||||
| p.tracef("#%d ", p.written) | |||||
| } | |||||
| p.rawInt64(int64(p.written)) | |||||
| } | |||||
| // rawInt64 should only be used by low-level encoders. | |||||
| func (p *exporter) rawInt64(x int64) { | |||||
| var tmp [binary.MaxVarintLen64]byte | |||||
| n := binary.PutVarint(tmp[:], x) | |||||
| for i := 0; i < n; i++ { | |||||
| p.rawByte(tmp[i]) | |||||
| } | |||||
| } | |||||
| // rawStringln should only be used to emit the initial version string. | |||||
| func (p *exporter) rawStringln(s string) { | |||||
| for i := 0; i < len(s); i++ { | |||||
| p.rawByte(s[i]) | |||||
| } | |||||
| p.rawByte('\n') | |||||
| } | |||||
| // rawByte is the bottleneck interface to write to p.out. | |||||
| // rawByte escapes b as follows (any encoding does that | |||||
| // hides '$'): | |||||
| // | |||||
| // '$' => '|' 'S' | |||||
| // '|' => '|' '|' | |||||
| // | |||||
| // Necessary so other tools can find the end of the | |||||
| // export data by searching for "$$". | |||||
| // rawByte should only be used by low-level encoders. | |||||
| func (p *exporter) rawByte(b byte) { | |||||
| switch b { | |||||
| case '$': | |||||
| // write '$' as '|' 'S' | |||||
| b = 'S' | |||||
| fallthrough | |||||
| case '|': | |||||
| // write '|' as '|' '|' | |||||
| p.out.WriteByte('|') | |||||
| p.written++ | |||||
| } | |||||
| p.out.WriteByte(b) | |||||
| p.written++ | |||||
| } | |||||
| // tracef is like fmt.Printf but it rewrites the format string | |||||
| // to take care of indentation. | |||||
| func (p *exporter) tracef(format string, args ...interface{}) { | |||||
| if strings.ContainsAny(format, "<>\n") { | |||||
| var buf bytes.Buffer | |||||
| for i := 0; i < len(format); i++ { | |||||
| // no need to deal with runes | |||||
| ch := format[i] | |||||
| switch ch { | |||||
| case '>': | |||||
| p.indent++ | |||||
| continue | |||||
| case '<': | |||||
| p.indent-- | |||||
| continue | |||||
| } | |||||
| buf.WriteByte(ch) | |||||
| if ch == '\n' { | |||||
| for j := p.indent; j > 0; j-- { | |||||
| buf.WriteString(". ") | |||||
| } | |||||
| } | |||||
| } | |||||
| format = buf.String() | |||||
| } | |||||
| fmt.Printf(format, args...) | |||||
| } | |||||
| // Debugging support. | |||||
| // (tagString is only used when tracing is enabled) | |||||
| var tagString = [...]string{ | |||||
| // Packages | |||||
| -packageTag: "package", | |||||
| // Types | |||||
| -namedTag: "named type", | |||||
| -arrayTag: "array", | |||||
| -sliceTag: "slice", | |||||
| -dddTag: "ddd", | |||||
| -structTag: "struct", | |||||
| -pointerTag: "pointer", | |||||
| -signatureTag: "signature", | |||||
| -interfaceTag: "interface", | |||||
| -mapTag: "map", | |||||
| -chanTag: "chan", | |||||
| // Values | |||||
| -falseTag: "false", | |||||
| -trueTag: "true", | |||||
| -int64Tag: "int64", | |||||
| -floatTag: "float", | |||||
| -fractionTag: "fraction", | |||||
| -complexTag: "complex", | |||||
| -stringTag: "string", | |||||
| -unknownTag: "unknown", | |||||
| // Type aliases | |||||
| -aliasTag: "alias", | |||||
| } | |||||
| @@ -204,14 +204,11 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func | |||||
| // Or, define a new standard go/types/gcexportdata package. | // Or, define a new standard go/types/gcexportdata package. | ||||
| fset := token.NewFileSet() | fset := token.NewFileSet() | ||||
| // The indexed export format starts with an 'i'; the older | |||||
| // binary export format starts with a 'c', 'd', or 'v' | |||||
| // (from "version"). Select appropriate importer. | |||||
| if len(data) > 0 && data[0] == 'i' { | |||||
| _, pkg, err = IImportData(fset, packages, data[1:], id) | |||||
| } else { | |||||
| _, pkg, err = BImportData(fset, packages, data, id) | |||||
| // The indexed export format starts with an 'i'. | |||||
| if len(data) == 0 || data[0] != 'i' { | |||||
| return nil, fmt.Errorf("unknown export data format") | |||||
| } | } | ||||
| _, pkg, err = IImportData(fset, packages, data[1:], id) | |||||
| default: | default: | ||||
| err = fmt.Errorf("unknown export data header: %q", hdr) | err = fmt.Errorf("unknown export data header: %q", hdr) | ||||
| @@ -11,6 +11,7 @@ package gcimporter | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "encoding/binary" | "encoding/binary" | ||||
| "fmt" | |||||
| "go/ast" | "go/ast" | ||||
| "go/constant" | "go/constant" | ||||
| "go/token" | "go/token" | ||||
| @@ -25,6 +26,15 @@ import ( | |||||
| // 0: Go1.11 encoding | // 0: Go1.11 encoding | ||||
| const iexportVersion = 0 | const iexportVersion = 0 | ||||
| // internalError represents an error generated inside this package. | |||||
| type internalError string | |||||
| func (e internalError) Error() string { return "gcimporter: " + string(e) } | |||||
| func internalErrorf(format string, args ...interface{}) error { | |||||
| return internalError(fmt.Sprintf(format, args...)) | |||||
| } | |||||
| // IExportData returns the binary export data for pkg. | // IExportData returns the binary export data for pkg. | ||||
| // | // | ||||
| // If no file set is provided, position info will be missing. | // If no file set is provided, position info will be missing. | ||||
| @@ -528,6 +538,16 @@ func constantToFloat(x constant.Value) *big.Float { | |||||
| return &f | return &f | ||||
| } | } | ||||
| func valueToRat(x constant.Value) *big.Rat { | |||||
| // Convert little-endian to big-endian. | |||||
| // I can't believe this is necessary. | |||||
| bytes := constant.Bytes(x) | |||||
| for i := 0; i < len(bytes)/2; i++ { | |||||
| bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] | |||||
| } | |||||
| return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) | |||||
| } | |||||
| // mpint exports a multi-precision integer. | // mpint exports a multi-precision integer. | ||||
| // | // | ||||
| // For unsigned types, small values are written out as a single | // For unsigned types, small values are written out as a single | ||||
| @@ -18,6 +18,9 @@ import ( | |||||
| "go/types" | "go/types" | ||||
| "io" | "io" | ||||
| "sort" | "sort" | ||||
| "sync" | |||||
| "unicode" | |||||
| "unicode/utf8" | |||||
| ) | ) | ||||
| type intReader struct { | type intReader struct { | ||||
| @@ -25,6 +28,10 @@ type intReader struct { | |||||
| path string | path string | ||||
| } | } | ||||
| func errorf(format string, args ...interface{}) { | |||||
| panic(fmt.Sprintf(format, args...)) | |||||
| } | |||||
| func (r *intReader) int64() int64 { | func (r *intReader) int64() int64 { | ||||
| i, err := binary.ReadVarint(r.Reader) | i, err := binary.ReadVarint(r.Reader) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -628,3 +635,166 @@ func (r *importReader) byte() byte { | |||||
| } | } | ||||
| return x | return x | ||||
| } | } | ||||
| const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go | |||||
| // Synthesize a token.Pos | |||||
| type fakeFileSet struct { | |||||
| fset *token.FileSet | |||||
| files map[string]*token.File | |||||
| } | |||||
| func (s *fakeFileSet) pos(file string, line, column int) token.Pos { | |||||
| // TODO(mdempsky): Make use of column. | |||||
| // Since we don't know the set of needed file positions, we | |||||
| // reserve maxlines positions per file. | |||||
| const maxlines = 64 * 1024 | |||||
| f := s.files[file] | |||||
| if f == nil { | |||||
| f = s.fset.AddFile(file, -1, maxlines) | |||||
| s.files[file] = f | |||||
| // Allocate the fake linebreak indices on first use. | |||||
| // TODO(adonovan): opt: save ~512KB using a more complex scheme? | |||||
| fakeLinesOnce.Do(func() { | |||||
| fakeLines = make([]int, maxlines) | |||||
| for i := range fakeLines { | |||||
| fakeLines[i] = i | |||||
| } | |||||
| }) | |||||
| f.SetLines(fakeLines) | |||||
| } | |||||
| if line > maxlines { | |||||
| line = 1 | |||||
| } | |||||
| // Treat the file as if it contained only newlines | |||||
| // and column=1: use the line number as the offset. | |||||
| return f.Pos(line - 1) | |||||
| } | |||||
| var ( | |||||
| fakeLines []int | |||||
| fakeLinesOnce sync.Once | |||||
| ) | |||||
| func chanDir(d int) types.ChanDir { | |||||
| // tag values must match the constants in cmd/compile/internal/gc/go.go | |||||
| switch d { | |||||
| case 1 /* Crecv */ : | |||||
| return types.RecvOnly | |||||
| case 2 /* Csend */ : | |||||
| return types.SendOnly | |||||
| case 3 /* Cboth */ : | |||||
| return types.SendRecv | |||||
| default: | |||||
| errorf("unexpected channel dir %d", d) | |||||
| return 0 | |||||
| } | |||||
| } | |||||
| func exported(name string) bool { | |||||
| ch, _ := utf8.DecodeRuneInString(name) | |||||
| return unicode.IsUpper(ch) | |||||
| } | |||||
| // ---------------------------------------------------------------------------- | |||||
| // Export format | |||||
| // Tags. Must be < 0. | |||||
| const ( | |||||
| // Objects | |||||
| packageTag = -(iota + 1) | |||||
| constTag | |||||
| typeTag | |||||
| varTag | |||||
| funcTag | |||||
| endTag | |||||
| // Types | |||||
| namedTag | |||||
| arrayTag | |||||
| sliceTag | |||||
| dddTag | |||||
| structTag | |||||
| pointerTag | |||||
| signatureTag | |||||
| interfaceTag | |||||
| mapTag | |||||
| chanTag | |||||
| // Values | |||||
| falseTag | |||||
| trueTag | |||||
| int64Tag | |||||
| floatTag | |||||
| fractionTag // not used by gc | |||||
| complexTag | |||||
| stringTag | |||||
| nilTag // only used by gc (appears in exported inlined function bodies) | |||||
| unknownTag // not used by gc (only appears in packages with errors) | |||||
| // Type aliases | |||||
| aliasTag | |||||
| ) | |||||
| var predeclOnce sync.Once | |||||
| var predecl []types.Type // initialized lazily | |||||
| func predeclared() []types.Type { | |||||
| predeclOnce.Do(func() { | |||||
| // initialize lazily to be sure that all | |||||
| // elements have been initialized before | |||||
| predecl = []types.Type{ // basic types | |||||
| types.Typ[types.Bool], | |||||
| types.Typ[types.Int], | |||||
| types.Typ[types.Int8], | |||||
| types.Typ[types.Int16], | |||||
| types.Typ[types.Int32], | |||||
| types.Typ[types.Int64], | |||||
| types.Typ[types.Uint], | |||||
| types.Typ[types.Uint8], | |||||
| types.Typ[types.Uint16], | |||||
| types.Typ[types.Uint32], | |||||
| types.Typ[types.Uint64], | |||||
| types.Typ[types.Uintptr], | |||||
| types.Typ[types.Float32], | |||||
| types.Typ[types.Float64], | |||||
| types.Typ[types.Complex64], | |||||
| types.Typ[types.Complex128], | |||||
| types.Typ[types.String], | |||||
| // basic type aliases | |||||
| types.Universe.Lookup("byte").Type(), | |||||
| types.Universe.Lookup("rune").Type(), | |||||
| // error | |||||
| types.Universe.Lookup("error").Type(), | |||||
| // untyped types | |||||
| types.Typ[types.UntypedBool], | |||||
| types.Typ[types.UntypedInt], | |||||
| types.Typ[types.UntypedRune], | |||||
| types.Typ[types.UntypedFloat], | |||||
| types.Typ[types.UntypedComplex], | |||||
| types.Typ[types.UntypedString], | |||||
| types.Typ[types.UntypedNil], | |||||
| // package unsafe | |||||
| types.Typ[types.UnsafePointer], | |||||
| // invalid type | |||||
| types.Typ[types.Invalid], // only appears in packages with errors | |||||
| // used internally by gc; never used by this package or in .a files | |||||
| anyType{}, | |||||
| } | |||||
| }) | |||||
| return predecl | |||||
| } | |||||
| type anyType struct{} | |||||
| func (t anyType) Underlying() types.Type { return t } | |||||
| func (t anyType) String() string { return "any" } | |||||
| @@ -19,8 +19,7 @@ import ( | |||||
| var debug = false | var debug = false | ||||
| // GetSizes returns the sizes used by the underlying driver with the given parameters. | |||||
| func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { | |||||
| func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | |||||
| // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. | // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. | ||||
| const toolPrefix = "GOPACKAGESDRIVER=" | const toolPrefix = "GOPACKAGESDRIVER=" | ||||
| tool := "" | tool := "" | ||||
| @@ -40,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||||
| } | } | ||||
| if tool == "off" { | if tool == "off" { | ||||
| return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) | |||||
| return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) | |||||
| } | } | ||||
| req, err := json.Marshal(struct { | req, err := json.Marshal(struct { | ||||
| @@ -76,7 +75,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||||
| return response.Sizes, nil | return response.Sizes, nil | ||||
| } | } | ||||
| func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { | |||||
| func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | |||||
| inv := gocommand.Invocation{ | inv := gocommand.Invocation{ | ||||
| Verb: "list", | Verb: "list", | ||||
| Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, | Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, | ||||
| @@ -84,7 +83,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u | |||||
| BuildFlags: buildFlags, | BuildFlags: buildFlags, | ||||
| WorkingDir: dir, | WorkingDir: dir, | ||||
| } | } | ||||
| stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx) | |||||
| stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) | |||||
| var goarch, compiler string | var goarch, compiler string | ||||
| if rawErr != nil { | if rawErr != nil { | ||||
| if strings.Contains(rawErr.Error(), "cannot find main module") { | if strings.Contains(rawErr.Error(), "cannot find main module") { | ||||
| @@ -96,7 +95,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u | |||||
| Env: env, | Env: env, | ||||
| WorkingDir: dir, | WorkingDir: dir, | ||||
| } | } | ||||
| envout, enverr := inv.Run(ctx) | |||||
| envout, enverr := gocmdRunner.Run(ctx, inv) | |||||
| if enverr != nil { | if enverr != nil { | ||||
| return nil, enverr | return nil, enverr | ||||
| } | } | ||||
| @@ -25,6 +25,7 @@ import ( | |||||
| "golang.org/x/tools/go/internal/packagesdriver" | "golang.org/x/tools/go/internal/packagesdriver" | ||||
| "golang.org/x/tools/internal/gocommand" | "golang.org/x/tools/internal/gocommand" | ||||
| "golang.org/x/tools/internal/packagesinternal" | "golang.org/x/tools/internal/packagesinternal" | ||||
| "golang.org/x/xerrors" | |||||
| ) | ) | ||||
| // debug controls verbose logging. | // debug controls verbose logging. | ||||
| @@ -142,7 +143,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { | |||||
| sizeswg.Add(1) | sizeswg.Add(1) | ||||
| go func() { | go func() { | ||||
| var sizes types.Sizes | var sizes types.Sizes | ||||
| sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) | |||||
| sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir) | |||||
| // types.SizesFor always returns nil or a *types.StdSizes. | // types.SizesFor always returns nil or a *types.StdSizes. | ||||
| response.dr.Sizes, _ = sizes.(*types.StdSizes) | response.dr.Sizes, _ = sizes.(*types.StdSizes) | ||||
| sizeswg.Done() | sizeswg.Done() | ||||
| @@ -502,10 +503,19 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||||
| errkind = "use of internal package not allowed" | errkind = "use of internal package not allowed" | ||||
| } | } | ||||
| if errkind != "" { | if errkind != "" { | ||||
| if len(old.Error.ImportStack) < 2 { | |||||
| return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack with fewer than two elements`, errkind) | |||||
| if len(old.Error.ImportStack) < 1 { | |||||
| return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) | |||||
| } | |||||
| importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] | |||||
| if importingPkg == old.ImportPath { | |||||
| // Using an older version of Go which put this package itself on top of import | |||||
| // stack, instead of the importer. Look for importer in second from top | |||||
| // position. | |||||
| if len(old.Error.ImportStack) < 2 { | |||||
| return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) | |||||
| } | |||||
| importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] | |||||
| } | } | ||||
| importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-2] | |||||
| additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ | additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ | ||||
| Pos: old.Error.Pos, | Pos: old.Error.Pos, | ||||
| Msg: old.Error.Err, | Msg: old.Error.Err, | ||||
| @@ -534,6 +544,25 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||||
| module: p.Module, | module: p.Module, | ||||
| } | } | ||||
| if (state.cfg.Mode&TypecheckCgo) != 0 && len(p.CgoFiles) != 0 { | |||||
| if len(p.CompiledGoFiles) > len(p.GoFiles) { | |||||
| // We need the cgo definitions, which are in the first | |||||
| // CompiledGoFile after the non-cgo ones. This is a hack but there | |||||
| // isn't currently a better way to find it. We also need the pure | |||||
| // Go files and unprocessed cgo files, all of which are already | |||||
| // in pkg.GoFiles. | |||||
| cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] | |||||
| pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) | |||||
| } else { | |||||
| // golang/go#38990: go list silently fails to do cgo processing | |||||
| pkg.CompiledGoFiles = nil | |||||
| pkg.Errors = append(pkg.Errors, Error{ | |||||
| Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", | |||||
| Kind: ListError, | |||||
| }) | |||||
| } | |||||
| } | |||||
| // Work around https://golang.org/issue/28749: | // Work around https://golang.org/issue/28749: | ||||
| // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. | // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. | ||||
| // Filter out any elements of CompiledGoFiles that are also in OtherFiles. | // Filter out any elements of CompiledGoFiles that are also in OtherFiles. | ||||
| @@ -707,7 +736,7 @@ func golistargs(cfg *Config, words []string) []string { | |||||
| func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { | func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { | ||||
| cfg := state.cfg | cfg := state.cfg | ||||
| inv := &gocommand.Invocation{ | |||||
| inv := gocommand.Invocation{ | |||||
| Verb: verb, | Verb: verb, | ||||
| Args: args, | Args: args, | ||||
| BuildFlags: cfg.BuildFlags, | BuildFlags: cfg.BuildFlags, | ||||
| @@ -715,8 +744,11 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, | |||||
| Logf: cfg.Logf, | Logf: cfg.Logf, | ||||
| WorkingDir: cfg.Dir, | WorkingDir: cfg.Dir, | ||||
| } | } | ||||
| stdout, stderr, _, err := inv.RunRaw(cfg.Context) | |||||
| gocmdRunner := cfg.gocmdRunner | |||||
| if gocmdRunner == nil { | |||||
| gocmdRunner = &gocommand.Runner{} | |||||
| } | |||||
| stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv) | |||||
| if err != nil { | if err != nil { | ||||
| // Check for 'go' executable not being found. | // Check for 'go' executable not being found. | ||||
| if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | ||||
| @@ -727,7 +759,7 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, | |||||
| if !ok { | if !ok { | ||||
| // Catastrophic error: | // Catastrophic error: | ||||
| // - context cancellation | // - context cancellation | ||||
| return nil, fmt.Errorf("couldn't run 'go': %v", err) | |||||
| return nil, xerrors.Errorf("couldn't run 'go': %w", err) | |||||
| } | } | ||||
| // Old go version? | // Old go version? | ||||
| @@ -5,6 +5,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "go/parser" | "go/parser" | ||||
| "go/token" | "go/token" | ||||
| "log" | |||||
| "os" | "os" | ||||
| "path/filepath" | "path/filepath" | ||||
| "sort" | "sort" | ||||
| @@ -22,10 +23,15 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||||
| needPkgsSet := make(map[string]bool) | needPkgsSet := make(map[string]bool) | ||||
| modifiedPkgsSet := make(map[string]bool) | modifiedPkgsSet := make(map[string]bool) | ||||
| pkgOfDir := make(map[string][]*Package) | |||||
| for _, pkg := range response.dr.Packages { | for _, pkg := range response.dr.Packages { | ||||
| // This is an approximation of import path to id. This can be | // This is an approximation of import path to id. This can be | ||||
| // wrong for tests, vendored packages, and a number of other cases. | // wrong for tests, vendored packages, and a number of other cases. | ||||
| havePkgs[pkg.PkgPath] = pkg.ID | havePkgs[pkg.PkgPath] = pkg.ID | ||||
| x := commonDir(pkg.GoFiles) | |||||
| if x != "" { | |||||
| pkgOfDir[x] = append(pkgOfDir[x], pkg) | |||||
| } | |||||
| } | } | ||||
| // If no new imports are added, it is safe to avoid loading any needPkgs. | // If no new imports are added, it is safe to avoid loading any needPkgs. | ||||
| @@ -64,6 +70,9 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||||
| // to the overlay. | // to the overlay. | ||||
| continue | continue | ||||
| } | } | ||||
| // if all the overlay files belong to a different package, change the package | |||||
| // name to that package. Otherwise leave it alone; there will be an error message. | |||||
| maybeFixPackageName(pkgName, pkgOfDir, dir) | |||||
| nextPackage: | nextPackage: | ||||
| for _, p := range response.dr.Packages { | for _, p := range response.dr.Packages { | ||||
| if pkgName != p.Name && p.ID != "command-line-arguments" { | if pkgName != p.Name && p.ID != "command-line-arguments" { | ||||
| @@ -282,7 +291,17 @@ func (state *golistState) determineRootDirs() (map[string]string, error) { | |||||
| } | } | ||||
| func (state *golistState) determineRootDirsModules() (map[string]string, error) { | func (state *golistState) determineRootDirsModules() (map[string]string, error) { | ||||
| out, err := state.invokeGo("list", "-m", "-json", "all") | |||||
| // This will only return the root directory for the main module. | |||||
| // For now we only support overlays in main modules. | |||||
| // Editing files in the module cache isn't a great idea, so we don't | |||||
| // plan to ever support that, but editing files in replaced modules | |||||
| // is something we may want to support. To do that, we'll want to | |||||
| // do a go list -m to determine the replaced module's module path and | |||||
| // directory, and then a go list -m {{with .Replace}}{{.Dir}}{{end}} <replaced module's path> | |||||
| // from the main module to determine if that module is actually a replacement. | |||||
| // See bcmills's comment here: https://github.com/golang/go/issues/37629#issuecomment-594179751 | |||||
| // for more information. | |||||
| out, err := state.invokeGo("list", "-m", "-json") | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -374,3 +393,46 @@ func extractPackageName(filename string, contents []byte) (string, bool) { | |||||
| } | } | ||||
| return f.Name.Name, true | return f.Name.Name, true | ||||
| } | } | ||||
| func commonDir(a []string) string { | |||||
| seen := make(map[string]bool) | |||||
| x := append([]string{}, a...) | |||||
| for _, f := range x { | |||||
| seen[filepath.Dir(f)] = true | |||||
| } | |||||
| if len(seen) > 1 { | |||||
| log.Fatalf("commonDir saw %v for %v", seen, x) | |||||
| } | |||||
| for k := range seen { | |||||
| // len(seen) == 1 | |||||
| return k | |||||
| } | |||||
| return "" // no files | |||||
| } | |||||
| // It is possible that the files in the disk directory dir have a different package | |||||
| // name from newName, which is deduced from the overlays. If they all have a different | |||||
| // package name, and they all have the same package name, then that name becomes | |||||
| // the package name. | |||||
| // It returns true if it changes the package name, false otherwise. | |||||
| func maybeFixPackageName(newName string, pkgOfDir map[string][]*Package, dir string) bool { | |||||
| names := make(map[string]int) | |||||
| for _, p := range pkgOfDir[dir] { | |||||
| names[p.Name]++ | |||||
| } | |||||
| if len(names) != 1 { | |||||
| // some files are in different packages | |||||
| return false | |||||
| } | |||||
| oldName := "" | |||||
| for k := range names { | |||||
| oldName = k | |||||
| } | |||||
| if newName == oldName { | |||||
| return false | |||||
| } | |||||
| for _, p := range pkgOfDir[dir] { | |||||
| p.Name = newName | |||||
| } | |||||
| return true | |||||
| } | |||||
| @@ -38,7 +38,7 @@ var modeStrings = []string{ | |||||
| func (mod LoadMode) String() string { | func (mod LoadMode) String() string { | ||||
| m := mod | m := mod | ||||
| if m == 0 { | if m == 0 { | ||||
| return fmt.Sprintf("LoadMode(0)") | |||||
| return "LoadMode(0)" | |||||
| } | } | ||||
| var out []string | var out []string | ||||
| for i, x := range allModes { | for i, x := range allModes { | ||||
| @@ -19,10 +19,12 @@ import ( | |||||
| "log" | "log" | ||||
| "os" | "os" | ||||
| "path/filepath" | "path/filepath" | ||||
| "reflect" | |||||
| "strings" | "strings" | ||||
| "sync" | "sync" | ||||
| "golang.org/x/tools/go/gcexportdata" | "golang.org/x/tools/go/gcexportdata" | ||||
| "golang.org/x/tools/internal/gocommand" | |||||
| "golang.org/x/tools/internal/packagesinternal" | "golang.org/x/tools/internal/packagesinternal" | ||||
| ) | ) | ||||
| @@ -69,6 +71,10 @@ const ( | |||||
| // NeedTypesSizes adds TypesSizes. | // NeedTypesSizes adds TypesSizes. | ||||
| NeedTypesSizes | NeedTypesSizes | ||||
| // TypecheckCgo enables full support for type checking cgo. Requires Go 1.15+. | |||||
| // Modifies CompiledGoFiles and Types, and has no effect on its own. | |||||
| TypecheckCgo | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| @@ -127,6 +133,9 @@ type Config struct { | |||||
| // | // | ||||
| Env []string | Env []string | ||||
| // gocmdRunner guards go command calls from concurrency errors. | |||||
| gocmdRunner *gocommand.Runner | |||||
| // BuildFlags is a list of command-line flags to be passed through to | // BuildFlags is a list of command-line flags to be passed through to | ||||
| // the build system's query tool. | // the build system's query tool. | ||||
| BuildFlags []string | BuildFlags []string | ||||
| @@ -253,7 +262,7 @@ type Package struct { | |||||
| GoFiles []string | GoFiles []string | ||||
| // CompiledGoFiles lists the absolute file paths of the package's source | // CompiledGoFiles lists the absolute file paths of the package's source | ||||
| // files that were presented to the compiler. | |||||
| // files that are suitable for type checking. | |||||
| // This may differ from GoFiles if files are processed before compilation. | // This may differ from GoFiles if files are processed before compilation. | ||||
| CompiledGoFiles []string | CompiledGoFiles []string | ||||
| @@ -311,6 +320,12 @@ func init() { | |||||
| packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module { | packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module { | ||||
| return p.(*Package).module | return p.(*Package).module | ||||
| } | } | ||||
| packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner { | |||||
| return config.(*Config).gocmdRunner | |||||
| } | |||||
| packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) { | |||||
| config.(*Config).gocmdRunner = runner | |||||
| } | |||||
| } | } | ||||
| // An Error describes a problem with a package's metadata, syntax, or types. | // An Error describes a problem with a package's metadata, syntax, or types. | ||||
| @@ -473,6 +488,9 @@ func newLoader(cfg *Config) *loader { | |||||
| if ld.Config.Env == nil { | if ld.Config.Env == nil { | ||||
| ld.Config.Env = os.Environ() | ld.Config.Env = os.Environ() | ||||
| } | } | ||||
| if ld.Config.gocmdRunner == nil { | |||||
| ld.Config.gocmdRunner = &gocommand.Runner{} | |||||
| } | |||||
| if ld.Context == nil { | if ld.Context == nil { | ||||
| ld.Context = context.Background() | ld.Context = context.Background() | ||||
| } | } | ||||
| @@ -865,6 +883,19 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { | |||||
| Error: appendError, | Error: appendError, | ||||
| Sizes: ld.sizes, | Sizes: ld.sizes, | ||||
| } | } | ||||
| if (ld.Mode & TypecheckCgo) != 0 { | |||||
| // TODO: remove this when we stop supporting 1.14. | |||||
| rtc := reflect.ValueOf(tc).Elem() | |||||
| usesCgo := rtc.FieldByName("UsesCgo") | |||||
| if !usesCgo.IsValid() { | |||||
| appendError(Error{ | |||||
| Msg: "TypecheckCgo requires Go 1.15+", | |||||
| Kind: ListError, | |||||
| }) | |||||
| return | |||||
| } | |||||
| usesCgo.SetBool(true) | |||||
| } | |||||
| types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) | types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) | ||||
| lpkg.importErrors = nil // no longer needed | lpkg.importErrors = nil // no longer needed | ||||
| @@ -226,7 +226,8 @@ func For(obj types.Object) (Path, error) { | |||||
| // the best paths because non-types may | // the best paths because non-types may | ||||
| // refer to types, but not the reverse. | // refer to types, but not the reverse. | ||||
| empty := make([]byte, 0, 48) // initial space | empty := make([]byte, 0, 48) // initial space | ||||
| for _, name := range scope.Names() { | |||||
| names := scope.Names() | |||||
| for _, name := range names { | |||||
| o := scope.Lookup(name) | o := scope.Lookup(name) | ||||
| tname, ok := o.(*types.TypeName) | tname, ok := o.(*types.TypeName) | ||||
| if !ok { | if !ok { | ||||
| @@ -253,7 +254,7 @@ func For(obj types.Object) (Path, error) { | |||||
| // Then inspect everything else: | // Then inspect everything else: | ||||
| // non-types, and declared methods of defined types. | // non-types, and declared methods of defined types. | ||||
| for _, name := range scope.Names() { | |||||
| for _, name := range names { | |||||
| o := scope.Lookup(name) | o := scope.Lookup(name) | ||||
| path := append(empty, name...) | path := append(empty, name...) | ||||
| if _, ok := o.(*types.TypeName); !ok { | if _, ok := o.(*types.TypeName); !ok { | ||||
| @@ -0,0 +1,118 @@ | |||||
| // Copyright 2020 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| // Package analysisinternal exposes internal-only fields from go/analysis. | |||||
| package analysisinternal | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "go/ast" | |||||
| "go/token" | |||||
| "go/types" | |||||
| "strings" | |||||
| "golang.org/x/tools/go/ast/astutil" | |||||
| ) | |||||
| func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { | |||||
| // Get the end position for the type error. | |||||
| offset, end := fset.PositionFor(start, false).Offset, start | |||||
| if offset >= len(src) { | |||||
| return end | |||||
| } | |||||
| if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { | |||||
| end = start + token.Pos(width) | |||||
| } | |||||
| return end | |||||
| } | |||||
| func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { | |||||
| under := typ | |||||
| if n, ok := typ.(*types.Named); ok { | |||||
| under = n.Underlying() | |||||
| } | |||||
| switch u := under.(type) { | |||||
| case *types.Basic: | |||||
| switch { | |||||
| case u.Info()&types.IsNumeric != 0: | |||||
| return &ast.BasicLit{Kind: token.INT, Value: "0"} | |||||
| case u.Info()&types.IsBoolean != 0: | |||||
| return &ast.Ident{Name: "false"} | |||||
| case u.Info()&types.IsString != 0: | |||||
| return &ast.BasicLit{Kind: token.STRING, Value: `""`} | |||||
| default: | |||||
| panic("unknown basic type") | |||||
| } | |||||
| case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice: | |||||
| return ast.NewIdent("nil") | |||||
| case *types.Struct: | |||||
| texpr := typeExpr(fset, f, pkg, typ) // typ because we want the name here. | |||||
| if texpr == nil { | |||||
| return nil | |||||
| } | |||||
| return &ast.CompositeLit{ | |||||
| Type: texpr, | |||||
| } | |||||
| case *types.Array: | |||||
| texpr := typeExpr(fset, f, pkg, u.Elem()) | |||||
| if texpr == nil { | |||||
| return nil | |||||
| } | |||||
| return &ast.CompositeLit{ | |||||
| Type: &ast.ArrayType{ | |||||
| Elt: texpr, | |||||
| Len: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%v", u.Len())}, | |||||
| }, | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { | |||||
| switch t := typ.(type) { | |||||
| case *types.Basic: | |||||
| switch t.Kind() { | |||||
| case types.UnsafePointer: | |||||
| return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} | |||||
| default: | |||||
| return ast.NewIdent(t.Name()) | |||||
| } | |||||
| case *types.Named: | |||||
| if t.Obj().Pkg() == pkg { | |||||
| return ast.NewIdent(t.Obj().Name()) | |||||
| } | |||||
| pkgName := t.Obj().Pkg().Name() | |||||
| // If the file already imports the package under another name, use that. | |||||
| for _, group := range astutil.Imports(fset, f) { | |||||
| for _, cand := range group { | |||||
| if strings.Trim(cand.Path.Value, `"`) == t.Obj().Pkg().Path() { | |||||
| if cand.Name != nil && cand.Name.Name != "" { | |||||
| pkgName = cand.Name.Name | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if pkgName == "." { | |||||
| return ast.NewIdent(t.Obj().Name()) | |||||
| } | |||||
| return &ast.SelectorExpr{ | |||||
| X: ast.NewIdent(pkgName), | |||||
| Sel: ast.NewIdent(t.Obj().Name()), | |||||
| } | |||||
| default: | |||||
| return nil // TODO: anonymous structs, but who does that | |||||
| } | |||||
| } | |||||
| var GetTypeErrors = func(p interface{}) []types.Error { return nil } | |||||
| var SetTypeErrors = func(p interface{}, errors []types.Error) {} | |||||
| type TypeErrorPass string | |||||
| const ( | |||||
| NoNewVars TypeErrorPass = "nonewvars" | |||||
| NoResultValues TypeErrorPass = "noresultvalues" | |||||
| UndeclaredName TypeErrorPass = "undeclaredname" | |||||
| ) | |||||
| @@ -0,0 +1,85 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| // Package core provides support for event based telemetry. | |||||
| package core | |||||
| import ( | |||||
| "fmt" | |||||
| "time" | |||||
| "golang.org/x/tools/internal/event/label" | |||||
| ) | |||||
| // Event holds the information about an event of note that ocurred. | |||||
| type Event struct { | |||||
| at time.Time | |||||
| // As events are often on the stack, storing the first few labels directly | |||||
| // in the event can avoid an allocation at all for the very common cases of | |||||
| // simple events. | |||||
| // The length needs to be large enough to cope with the majority of events | |||||
| // but no so large as to cause undue stack pressure. | |||||
| // A log message with two values will use 3 labels (one for each value and | |||||
| // one for the message itself). | |||||
| static [3]label.Label // inline storage for the first few labels | |||||
| dynamic []label.Label // dynamically sized storage for remaining labels | |||||
| } | |||||
| // eventLabelMap implements label.Map for a the labels of an Event. | |||||
| type eventLabelMap struct { | |||||
| event Event | |||||
| } | |||||
| func (ev Event) At() time.Time { return ev.at } | |||||
| func (ev Event) Format(f fmt.State, r rune) { | |||||
| if !ev.at.IsZero() { | |||||
| fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) | |||||
| } | |||||
| for index := 0; ev.Valid(index); index++ { | |||||
| if l := ev.Label(index); l.Valid() { | |||||
| fmt.Fprintf(f, "\n\t%v", l) | |||||
| } | |||||
| } | |||||
| } | |||||
| func (ev Event) Valid(index int) bool { | |||||
| return index >= 0 && index < len(ev.static)+len(ev.dynamic) | |||||
| } | |||||
| func (ev Event) Label(index int) label.Label { | |||||
| if index < len(ev.static) { | |||||
| return ev.static[index] | |||||
| } | |||||
| return ev.dynamic[index-len(ev.static)] | |||||
| } | |||||
| func (ev Event) Find(key label.Key) label.Label { | |||||
| for _, l := range ev.static { | |||||
| if l.Key() == key { | |||||
| return l | |||||
| } | |||||
| } | |||||
| for _, l := range ev.dynamic { | |||||
| if l.Key() == key { | |||||
| return l | |||||
| } | |||||
| } | |||||
| return label.Label{} | |||||
| } | |||||
| func MakeEvent(static [3]label.Label, labels []label.Label) Event { | |||||
| return Event{ | |||||
| static: static, | |||||
| dynamic: labels, | |||||
| } | |||||
| } | |||||
| // CloneEvent event returns a copy of the event with the time adjusted to at. | |||||
| func CloneEvent(ev Event, at time.Time) Event { | |||||
| ev.at = at | |||||
| return ev | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "sync/atomic" | |||||
| "time" | |||||
| "unsafe" | |||||
| "golang.org/x/tools/internal/event/label" | |||||
| ) | |||||
| // Exporter is a function that handles events. | |||||
| // It may return a modified context and event. | |||||
| type Exporter func(context.Context, Event, label.Map) context.Context | |||||
| var ( | |||||
| exporter unsafe.Pointer | |||||
| ) | |||||
| // SetExporter sets the global exporter function that handles all events. | |||||
| // The exporter is called synchronously from the event call site, so it should | |||||
| // return quickly so as not to hold up user code. | |||||
| func SetExporter(e Exporter) { | |||||
| p := unsafe.Pointer(&e) | |||||
| if e == nil { | |||||
| // &e is always valid, and so p is always valid, but for the early abort | |||||
| // of ProcessEvent to be efficient it needs to make the nil check on the | |||||
| // pointer without having to dereference it, so we make the nil function | |||||
| // also a nil pointer | |||||
| p = nil | |||||
| } | |||||
| atomic.StorePointer(&exporter, p) | |||||
| } | |||||
| // deliver is called to deliver an event to the supplied exporter. | |||||
| // it will fill in the time. | |||||
| func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { | |||||
| // add the current time to the event | |||||
| ev.at = time.Now() | |||||
| // hand the event off to the current exporter | |||||
| return exporter(ctx, ev, ev) | |||||
| } | |||||
| // Export is called to deliver an event to the global exporter if set. | |||||
| func Export(ctx context.Context, ev Event) context.Context { | |||||
| // get the global exporter and abort early if there is not one | |||||
| exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | |||||
| if exporterPtr == nil { | |||||
| return ctx | |||||
| } | |||||
| return deliver(ctx, *exporterPtr, ev) | |||||
| } | |||||
| // ExportPair is called to deliver a start event to the supplied exporter. | |||||
| // It also returns a function that will deliver the end event to the same | |||||
| // exporter. | |||||
| // It will fill in the time. | |||||
| func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { | |||||
| // get the global exporter and abort early if there is not one | |||||
| exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | |||||
| if exporterPtr == nil { | |||||
| return ctx, func() {} | |||||
| } | |||||
| ctx = deliver(ctx, *exporterPtr, begin) | |||||
| return ctx, func() { deliver(ctx, *exporterPtr, end) } | |||||
| } | |||||
| @@ -0,0 +1,77 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package core | |||||
| import ( | |||||
| "context" | |||||
| "golang.org/x/tools/internal/event/keys" | |||||
| "golang.org/x/tools/internal/event/label" | |||||
| ) | |||||
| // Log1 takes a message and one label delivers a log event to the exporter. | |||||
| // It is a customized version of Print that is faster and does no allocation. | |||||
| func Log1(ctx context.Context, message string, t1 label.Label) { | |||||
| Export(ctx, MakeEvent([3]label.Label{ | |||||
| keys.Msg.Of(message), | |||||
| t1, | |||||
| }, nil)) | |||||
| } | |||||
| // Log2 takes a message and two labels and delivers a log event to the exporter. | |||||
| // It is a customized version of Print that is faster and does no allocation. | |||||
| func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { | |||||
| Export(ctx, MakeEvent([3]label.Label{ | |||||
| keys.Msg.Of(message), | |||||
| t1, | |||||
| t2, | |||||
| }, nil)) | |||||
| } | |||||
| // Metric1 sends a label event to the exporter with the supplied labels. | |||||
| func Metric1(ctx context.Context, t1 label.Label) context.Context { | |||||
| return Export(ctx, MakeEvent([3]label.Label{ | |||||
| keys.Metric.New(), | |||||
| t1, | |||||
| }, nil)) | |||||
| } | |||||
| // Metric2 sends a label event to the exporter with the supplied labels. | |||||
| func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { | |||||
| return Export(ctx, MakeEvent([3]label.Label{ | |||||
| keys.Metric.New(), | |||||
| t1, | |||||
| t2, | |||||
| }, nil)) | |||||
| } | |||||
| // Start1 sends a span start event with the supplied label list to the exporter. | |||||
| // It also returns a function that will end the span, which should normally be | |||||
| // deferred. | |||||
| func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { | |||||
| return ExportPair(ctx, | |||||
| MakeEvent([3]label.Label{ | |||||
| keys.Start.Of(name), | |||||
| t1, | |||||
| }, nil), | |||||
| MakeEvent([3]label.Label{ | |||||
| keys.End.New(), | |||||
| }, nil)) | |||||
| } | |||||
| // Start2 sends a span start event with the supplied label list to the exporter. | |||||
| // It also returns a function that will end the span, which should normally be | |||||
| // deferred. | |||||
| func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { | |||||
| return ExportPair(ctx, | |||||
| MakeEvent([3]label.Label{ | |||||
| keys.Start.Of(name), | |||||
| t1, | |||||
| t2, | |||||
| }, nil), | |||||
| MakeEvent([3]label.Label{ | |||||
| keys.End.New(), | |||||
| }, nil)) | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| // Package event provides a set of packages that cover the main | |||||
| // concepts of telemetry in an implementation agnostic way. | |||||
| package event | |||||
| @@ -0,0 +1,127 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package event | |||||
| import ( | |||||
| "context" | |||||
| "golang.org/x/tools/internal/event/core" | |||||
| "golang.org/x/tools/internal/event/keys" | |||||
| "golang.org/x/tools/internal/event/label" | |||||
| ) | |||||
| // Exporter is a function that handles events. | |||||
| // It may return a modified context and event. | |||||
| type Exporter func(context.Context, core.Event, label.Map) context.Context | |||||
| // SetExporter sets the global exporter function that handles all events. | |||||
| // The exporter is called synchronously from the event call site, so it should | |||||
| // return quickly so as not to hold up user code. | |||||
| func SetExporter(e Exporter) { | |||||
| core.SetExporter(core.Exporter(e)) | |||||
| } | |||||
| // Log takes a message and a label list and combines them into a single event | |||||
| // before delivering them to the exporter. | |||||
| func Log(ctx context.Context, message string, labels ...label.Label) { | |||||
| core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
| keys.Msg.Of(message), | |||||
| }, labels)) | |||||
| } | |||||
| // IsLog returns true if the event was built by the Log function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsLog(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Msg | |||||
| } | |||||
| // Error takes a message and a label list and combines them into a single event | |||||
| // before delivering them to the exporter. It captures the error in the | |||||
| // delivered event. | |||||
| func Error(ctx context.Context, message string, err error, labels ...label.Label) { | |||||
| core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
| keys.Msg.Of(message), | |||||
| keys.Err.Of(err), | |||||
| }, labels)) | |||||
| } | |||||
| // IsError returns true if the event was built by the Error function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsError(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Msg && | |||||
| ev.Label(1).Key() == keys.Err | |||||
| } | |||||
| // Metric sends a label event to the exporter with the supplied labels. | |||||
| func Metric(ctx context.Context, labels ...label.Label) { | |||||
| core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
| keys.Metric.New(), | |||||
| }, labels)) | |||||
| } | |||||
| // IsMetric returns true if the event was built by the Metric function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsMetric(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Metric | |||||
| } | |||||
| // Label sends a label event to the exporter with the supplied labels. | |||||
| func Label(ctx context.Context, labels ...label.Label) context.Context { | |||||
| return core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
| keys.Label.New(), | |||||
| }, labels)) | |||||
| } | |||||
| // IsLabel returns true if the event was built by the Label function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsLabel(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Label | |||||
| } | |||||
| // Start sends a span start event with the supplied label list to the exporter. | |||||
| // It also returns a function that will end the span, which should normally be | |||||
| // deferred. | |||||
| func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { | |||||
| return core.ExportPair(ctx, | |||||
| core.MakeEvent([3]label.Label{ | |||||
| keys.Start.Of(name), | |||||
| }, labels), | |||||
| core.MakeEvent([3]label.Label{ | |||||
| keys.End.New(), | |||||
| }, nil)) | |||||
| } | |||||
| // IsStart returns true if the event was built by the Start function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsStart(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Start | |||||
| } | |||||
| // IsEnd returns true if the event was built by the End function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsEnd(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.End | |||||
| } | |||||
| // Detach returns a context without an associated span. | |||||
| // This allows the creation of spans that are not children of the current span. | |||||
| func Detach(ctx context.Context) context.Context { | |||||
| return core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
| keys.Detach.New(), | |||||
| }, nil)) | |||||
| } | |||||
| // IsDetach returns true if the event was built by the Detach function. | |||||
| // It is intended to be used in exporters to identify the semantics of the | |||||
| // event when deciding what to do with it. | |||||
| func IsDetach(ev core.Event) bool { | |||||
| return ev.Label(0).Key() == keys.Detach | |||||
| } | |||||
| @@ -0,0 +1,564 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package keys | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "math" | |||||
| "strconv" | |||||
| "golang.org/x/tools/internal/event/label" | |||||
| ) | |||||
| // Value represents a key for untyped values. | |||||
| type Value struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // New creates a new Key for untyped values. | |||||
| func New(name, description string) *Value { | |||||
| return &Value{name: name, description: description} | |||||
| } | |||||
| func (k *Value) Name() string { return k.name } | |||||
| func (k *Value) Description() string { return k.description } | |||||
| func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| fmt.Fprint(w, k.From(l)) | |||||
| } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Value) Get(lm label.Map) interface{} { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } | |||||
| // Tag represents a key for tagging labels that have no value. | |||||
| // These are used when the existence of the label is the entire information it | |||||
| // carries, such as marking events to be of a specific kind, or from a specific | |||||
| // package. | |||||
| type Tag struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewTag creates a new Key for tagging labels. | |||||
| func NewTag(name, description string) *Tag { | |||||
| return &Tag{name: name, description: description} | |||||
| } | |||||
| func (k *Tag) Name() string { return k.name } | |||||
| func (k *Tag) Description() string { return k.description } | |||||
| func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} | |||||
| // New creates a new Label with this key. | |||||
| func (k *Tag) New() label.Label { return label.OfValue(k, nil) } | |||||
| // Int represents a key | |||||
| type Int struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewInt creates a new Key for int values. | |||||
| func NewInt(name, description string) *Int { | |||||
| return &Int{name: name, description: description} | |||||
| } | |||||
| func (k *Int) Name() string { return k.name } | |||||
| func (k *Int) Description() string { return k.description } | |||||
| func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Int) Get(lm label.Map) int { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } | |||||
| // Int8 represents a key | |||||
| type Int8 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewInt8 creates a new Key for int8 values. | |||||
| func NewInt8(name, description string) *Int8 { | |||||
| return &Int8{name: name, description: description} | |||||
| } | |||||
| func (k *Int8) Name() string { return k.name } | |||||
| func (k *Int8) Description() string { return k.description } | |||||
| func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Int8) Get(lm label.Map) int8 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } | |||||
| // Int16 represents a key | |||||
| type Int16 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewInt16 creates a new Key for int16 values. | |||||
| func NewInt16(name, description string) *Int16 { | |||||
| return &Int16{name: name, description: description} | |||||
| } | |||||
| func (k *Int16) Name() string { return k.name } | |||||
| func (k *Int16) Description() string { return k.description } | |||||
| func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Int16) Get(lm label.Map) int16 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } | |||||
| // Int32 represents a key | |||||
| type Int32 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewInt32 creates a new Key for int32 values. | |||||
| func NewInt32(name, description string) *Int32 { | |||||
| return &Int32{name: name, description: description} | |||||
| } | |||||
| func (k *Int32) Name() string { return k.name } | |||||
| func (k *Int32) Description() string { return k.description } | |||||
| func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Int32) Get(lm label.Map) int32 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } | |||||
| // Int64 represents a key | |||||
| type Int64 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewInt64 creates a new Key for int64 values. | |||||
| func NewInt64(name, description string) *Int64 { | |||||
| return &Int64{name: name, description: description} | |||||
| } | |||||
| func (k *Int64) Name() string { return k.name } | |||||
| func (k *Int64) Description() string { return k.description } | |||||
| func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendInt(buf, k.From(l), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Int64) Get(lm label.Map) int64 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } | |||||
| // UInt represents a key | |||||
| type UInt struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewUInt creates a new Key for uint values. | |||||
| func NewUInt(name, description string) *UInt { | |||||
| return &UInt{name: name, description: description} | |||||
| } | |||||
| func (k *UInt) Name() string { return k.name } | |||||
| func (k *UInt) Description() string { return k.description } | |||||
| func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *UInt) Get(lm label.Map) uint { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } | |||||
| // UInt8 represents a key | |||||
| type UInt8 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewUInt8 creates a new Key for uint8 values. | |||||
| func NewUInt8(name, description string) *UInt8 { | |||||
| return &UInt8{name: name, description: description} | |||||
| } | |||||
| func (k *UInt8) Name() string { return k.name } | |||||
| func (k *UInt8) Description() string { return k.description } | |||||
| func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *UInt8) Get(lm label.Map) uint8 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } | |||||
| // UInt16 represents a key | |||||
| type UInt16 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewUInt16 creates a new Key for uint16 values. | |||||
| func NewUInt16(name, description string) *UInt16 { | |||||
| return &UInt16{name: name, description: description} | |||||
| } | |||||
| func (k *UInt16) Name() string { return k.name } | |||||
| func (k *UInt16) Description() string { return k.description } | |||||
| func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *UInt16) Get(lm label.Map) uint16 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } | |||||
| // UInt32 represents a key | |||||
| type UInt32 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewUInt32 creates a new Key for uint32 values. | |||||
| func NewUInt32(name, description string) *UInt32 { | |||||
| return &UInt32{name: name, description: description} | |||||
| } | |||||
| func (k *UInt32) Name() string { return k.name } | |||||
| func (k *UInt32) Description() string { return k.description } | |||||
| func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *UInt32) Get(lm label.Map) uint32 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } | |||||
| // UInt64 represents a key | |||||
| type UInt64 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewUInt64 creates a new Key for uint64 values. | |||||
| func NewUInt64(name, description string) *UInt64 { | |||||
| return &UInt64{name: name, description: description} | |||||
| } | |||||
| func (k *UInt64) Name() string { return k.name } | |||||
| func (k *UInt64) Description() string { return k.description } | |||||
| func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendUint(buf, k.From(l), 10)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *UInt64) Get(lm label.Map) uint64 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } | |||||
| // Float32 represents a key | |||||
| type Float32 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewFloat32 creates a new Key for float32 values. | |||||
| func NewFloat32(name, description string) *Float32 { | |||||
| return &Float32{name: name, description: description} | |||||
| } | |||||
| func (k *Float32) Name() string { return k.name } | |||||
| func (k *Float32) Description() string { return k.description } | |||||
| func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Float32) Of(v float32) label.Label { | |||||
| return label.Of64(k, uint64(math.Float32bits(v))) | |||||
| } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Float32) Get(lm label.Map) float32 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Float32) From(t label.Label) float32 { | |||||
| return math.Float32frombits(uint32(t.Unpack64())) | |||||
| } | |||||
| // Float64 represents a key | |||||
| type Float64 struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewFloat64 creates a new Key for int64 values. | |||||
| func NewFloat64(name, description string) *Float64 { | |||||
| return &Float64{name: name, description: description} | |||||
| } | |||||
| func (k *Float64) Name() string { return k.name } | |||||
| func (k *Float64) Description() string { return k.description } | |||||
| func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Float64) Of(v float64) label.Label { | |||||
| return label.Of64(k, math.Float64bits(v)) | |||||
| } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Float64) Get(lm label.Map) float64 { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return 0 | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Float64) From(t label.Label) float64 { | |||||
| return math.Float64frombits(t.Unpack64()) | |||||
| } | |||||
| // String represents a key | |||||
| type String struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewString creates a new Key for int64 values. | |||||
| func NewString(name, description string) *String { | |||||
| return &String{name: name, description: description} | |||||
| } | |||||
| func (k *String) Name() string { return k.name } | |||||
| func (k *String) Description() string { return k.description } | |||||
| func (k *String) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendQuote(buf, k.From(l))) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *String) Of(v string) label.Label { return label.OfString(k, v) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *String) Get(lm label.Map) string { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return "" | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *String) From(t label.Label) string { return t.UnpackString() } | |||||
| // Boolean represents a key | |||||
| type Boolean struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewBoolean creates a new Key for bool values. | |||||
| func NewBoolean(name, description string) *Boolean { | |||||
| return &Boolean{name: name, description: description} | |||||
| } | |||||
| func (k *Boolean) Name() string { return k.name } | |||||
| func (k *Boolean) Description() string { return k.description } | |||||
| func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| w.Write(strconv.AppendBool(buf, k.From(l))) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Boolean) Of(v bool) label.Label { | |||||
| if v { | |||||
| return label.Of64(k, 1) | |||||
| } | |||||
| return label.Of64(k, 0) | |||||
| } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Boolean) Get(lm label.Map) bool { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return false | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } | |||||
| // Error represents a key | |||||
| type Error struct { | |||||
| name string | |||||
| description string | |||||
| } | |||||
| // NewError creates a new Key for int64 values. | |||||
| func NewError(name, description string) *Error { | |||||
| return &Error{name: name, description: description} | |||||
| } | |||||
| func (k *Error) Name() string { return k.name } | |||||
| func (k *Error) Description() string { return k.description } | |||||
| func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { | |||||
| io.WriteString(w, k.From(l).Error()) | |||||
| } | |||||
| // Of creates a new Label with this key and the supplied value. | |||||
| func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } | |||||
| // Get can be used to get a label for the key from a label.Map. | |||||
| func (k *Error) Get(lm label.Map) error { | |||||
| if t := lm.Find(k); t.Valid() { | |||||
| return k.From(t) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // From can be used to get a value from a Label. | |||||
| func (k *Error) From(t label.Label) error { | |||||
| err, _ := t.UnpackValue().(error) | |||||
| return err | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| // Copyright 2020 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package keys | |||||
| var ( | |||||
| // Msg is a key used to add message strings to label lists. | |||||
| Msg = NewString("message", "a readable message") | |||||
| // Label is a key used to indicate an event adds labels to the context. | |||||
| Label = NewTag("label", "a label context marker") | |||||
| // Start is used for things like traces that have a name. | |||||
| Start = NewString("start", "span start") | |||||
| // Metric is a key used to indicate an event records metrics. | |||||
| End = NewTag("end", "a span end marker") | |||||
| // Metric is a key used to indicate an event records metrics. | |||||
| Detach = NewTag("detach", "a span detach marker") | |||||
| // Err is a key used to add error values to label lists. | |||||
| Err = NewError("error", "an error that occurred") | |||||
| // Metric is a key used to indicate an event records metrics. | |||||
| Metric = NewTag("metric", "a metric event marker") | |||||
| ) | |||||
| @@ -0,0 +1,213 @@ | |||||
| // Copyright 2019 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package label | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "reflect" | |||||
| "unsafe" | |||||
| ) | |||||
| // Key is used as the identity of a Label. | |||||
| // Keys are intended to be compared by pointer only, the name should be unique | |||||
| // for communicating with external systems, but it is not required or enforced. | |||||
| type Key interface { | |||||
| // Name returns the key name. | |||||
| Name() string | |||||
| // Description returns a string that can be used to describe the value. | |||||
| Description() string | |||||
| // Format is used in formatting to append the value of the label to the | |||||
| // supplied buffer. | |||||
| // The formatter may use the supplied buf as a scratch area to avoid | |||||
| // allocations. | |||||
| Format(w io.Writer, buf []byte, l Label) | |||||
| } | |||||
| // Label holds a key and value pair. | |||||
| // It is normally used when passing around lists of labels. | |||||
| type Label struct { | |||||
| key Key | |||||
| packed uint64 | |||||
| untyped interface{} | |||||
| } | |||||
| // Map is the interface to a collection of Labels indexed by key. | |||||
| type Map interface { | |||||
| // Find returns the label that matches the supplied key. | |||||
| Find(key Key) Label | |||||
| } | |||||
| // List is the interface to something that provides an iterable | |||||
| // list of labels. | |||||
| // Iteration should start from 0 and continue until Valid returns false. | |||||
| type List interface { | |||||
| // Valid returns true if the index is within range for the list. | |||||
| // It does not imply the label at that index will itself be valid. | |||||
| Valid(index int) bool | |||||
| // Label returns the label at the given index. | |||||
| Label(index int) Label | |||||
| } | |||||
| // list implements LabelList for a list of Labels. | |||||
| type list struct { | |||||
| labels []Label | |||||
| } | |||||
| // filter wraps a LabelList filtering out specific labels. | |||||
| type filter struct { | |||||
| keys []Key | |||||
| underlying List | |||||
| } | |||||
| // listMap implements LabelMap for a simple list of labels. | |||||
| type listMap struct { | |||||
| labels []Label | |||||
| } | |||||
| // mapChain implements LabelMap for a list of underlying LabelMap. | |||||
| type mapChain struct { | |||||
| maps []Map | |||||
| } | |||||
| // OfValue creates a new label from the key and value. | |||||
| // This method is for implementing new key types, label creation should | |||||
| // normally be done with the Of method of the key. | |||||
| func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } | |||||
| // UnpackValue assumes the label was built using LabelOfValue and returns the value | |||||
| // that was passed to that constructor. | |||||
| // This method is for implementing new key types, for type safety normal | |||||
| // access should be done with the From method of the key. | |||||
| func (t Label) UnpackValue() interface{} { return t.untyped } | |||||
| // Of64 creates a new label from a key and a uint64. This is often | |||||
| // used for non uint64 values that can be packed into a uint64. | |||||
| // This method is for implementing new key types, label creation should | |||||
| // normally be done with the Of method of the key. | |||||
| func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } | |||||
| // Unpack64 assumes the label was built using LabelOf64 and returns the value that | |||||
| // was passed to that constructor. | |||||
| // This method is for implementing new key types, for type safety normal | |||||
| // access should be done with the From method of the key. | |||||
| func (t Label) Unpack64() uint64 { return t.packed } | |||||
| // OfString creates a new label from a key and a string. | |||||
| // This method is for implementing new key types, label creation should | |||||
| // normally be done with the Of method of the key. | |||||
| func OfString(k Key, v string) Label { | |||||
| hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | |||||
| return Label{ | |||||
| key: k, | |||||
| packed: uint64(hdr.Len), | |||||
| untyped: unsafe.Pointer(hdr.Data), | |||||
| } | |||||
| } | |||||
| // UnpackString assumes the label was built using LabelOfString and returns the | |||||
| // value that was passed to that constructor. | |||||
| // This method is for implementing new key types, for type safety normal | |||||
| // access should be done with the From method of the key. | |||||
| func (t Label) UnpackString() string { | |||||
| var v string | |||||
| hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | |||||
| hdr.Data = uintptr(t.untyped.(unsafe.Pointer)) | |||||
| hdr.Len = int(t.packed) | |||||
| return *(*string)(unsafe.Pointer(hdr)) | |||||
| } | |||||
| // Valid returns true if the Label is a valid one (it has a key). | |||||
| func (t Label) Valid() bool { return t.key != nil } | |||||
| // Key returns the key of this Label. | |||||
| func (t Label) Key() Key { return t.key } | |||||
| // Format is used for debug printing of labels. | |||||
| func (t Label) Format(f fmt.State, r rune) { | |||||
| if !t.Valid() { | |||||
| io.WriteString(f, `nil`) | |||||
| return | |||||
| } | |||||
| io.WriteString(f, t.Key().Name()) | |||||
| io.WriteString(f, "=") | |||||
| var buf [128]byte | |||||
| t.Key().Format(f, buf[:0], t) | |||||
| } | |||||
| func (l *list) Valid(index int) bool { | |||||
| return index >= 0 && index < len(l.labels) | |||||
| } | |||||
| func (l *list) Label(index int) Label { | |||||
| return l.labels[index] | |||||
| } | |||||
| func (f *filter) Valid(index int) bool { | |||||
| return f.underlying.Valid(index) | |||||
| } | |||||
| func (f *filter) Label(index int) Label { | |||||
| l := f.underlying.Label(index) | |||||
| for _, f := range f.keys { | |||||
| if l.Key() == f { | |||||
| return Label{} | |||||
| } | |||||
| } | |||||
| return l | |||||
| } | |||||
| func (lm listMap) Find(key Key) Label { | |||||
| for _, l := range lm.labels { | |||||
| if l.Key() == key { | |||||
| return l | |||||
| } | |||||
| } | |||||
| return Label{} | |||||
| } | |||||
| func (c mapChain) Find(key Key) Label { | |||||
| for _, src := range c.maps { | |||||
| l := src.Find(key) | |||||
| if l.Valid() { | |||||
| return l | |||||
| } | |||||
| } | |||||
| return Label{} | |||||
| } | |||||
| var emptyList = &list{} | |||||
| func NewList(labels ...Label) List { | |||||
| if len(labels) == 0 { | |||||
| return emptyList | |||||
| } | |||||
| return &list{labels: labels} | |||||
| } | |||||
| func Filter(l List, keys ...Key) List { | |||||
| if len(keys) == 0 { | |||||
| return l | |||||
| } | |||||
| return &filter{keys: keys, underlying: l} | |||||
| } | |||||
| func NewMap(labels ...Label) Map { | |||||
| return listMap{labels: labels} | |||||
| } | |||||
| func MergeMaps(srcs ...Map) Map { | |||||
| var nonNil []Map | |||||
| for _, src := range srcs { | |||||
| if src != nil { | |||||
| nonNil = append(nonNil, src) | |||||
| } | |||||
| } | |||||
| if len(nonNil) == 1 { | |||||
| return nonNil[0] | |||||
| } | |||||
| return mapChain{maps: nonNil} | |||||
| } | |||||
| @@ -1,3 +1,7 @@ | |||||
| // Copyright 2020 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| // Package gocommand is a helper for calling the go command. | // Package gocommand is a helper for calling the go command. | ||||
| package gocommand | package gocommand | ||||
| @@ -8,10 +12,70 @@ import ( | |||||
| "io" | "io" | ||||
| "os" | "os" | ||||
| "os/exec" | "os/exec" | ||||
| "regexp" | |||||
| "strings" | "strings" | ||||
| "sync" | |||||
| "time" | "time" | ||||
| "golang.org/x/tools/internal/event" | |||||
| ) | ) | ||||
| // An Runner will run go command invocations and serialize | |||||
| // them if it sees a concurrency error. | |||||
| type Runner struct { | |||||
| // LoadMu guards packages.Load calls and associated state. | |||||
| loadMu sync.Mutex | |||||
| serializeLoads int | |||||
| } | |||||
| // 1.13: go: updates to go.mod needed, but contents have changed | |||||
| // 1.14: go: updating go.mod: existing contents have changed since last read | |||||
| var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) | |||||
| // Run calls Runner.RunRaw, serializing requests if they fight over | |||||
| // go.mod changes. | |||||
| func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { | |||||
| stdout, _, friendly, _ := runner.RunRaw(ctx, inv) | |||||
| return stdout, friendly | |||||
| } | |||||
| // RunRaw calls Invocation.runRaw, serializing requests if they fight over | |||||
| // go.mod changes. | |||||
| func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { | |||||
| // We want to run invocations concurrently as much as possible. However, | |||||
| // if go.mod updates are needed, only one can make them and the others will | |||||
| // fail. We need to retry in those cases, but we don't want to thrash so | |||||
| // badly we never recover. To avoid that, once we've seen one concurrency | |||||
| // error, start serializing everything until the backlog has cleared out. | |||||
| runner.loadMu.Lock() | |||||
| var locked bool // If true, we hold the mutex and have incremented. | |||||
| if runner.serializeLoads == 0 { | |||||
| runner.loadMu.Unlock() | |||||
| } else { | |||||
| locked = true | |||||
| runner.serializeLoads++ | |||||
| } | |||||
| defer func() { | |||||
| if locked { | |||||
| runner.serializeLoads-- | |||||
| runner.loadMu.Unlock() | |||||
| } | |||||
| }() | |||||
| for { | |||||
| stdout, stderr, friendlyErr, err := inv.runRaw(ctx) | |||||
| if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { | |||||
| return stdout, stderr, friendlyErr, err | |||||
| } | |||||
| event.Error(ctx, "Load concurrency error, will retry serially", err) | |||||
| if !locked { | |||||
| runner.loadMu.Lock() | |||||
| runner.serializeLoads++ | |||||
| locked = true | |||||
| } | |||||
| } | |||||
| } | |||||
| // An Invocation represents a call to the go command. | // An Invocation represents a call to the go command. | ||||
| type Invocation struct { | type Invocation struct { | ||||
| Verb string | Verb string | ||||
| @@ -22,20 +86,14 @@ type Invocation struct { | |||||
| Logf func(format string, args ...interface{}) | Logf func(format string, args ...interface{}) | ||||
| } | } | ||||
| // Run runs the invocation, returning its stdout and an error suitable for | |||||
| // human consumption, including stderr. | |||||
| func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) { | |||||
| stdout, _, friendly, _ := i.RunRaw(ctx) | |||||
| return stdout, friendly | |||||
| } | |||||
| // RunRaw is like RunPiped, but also returns the raw stderr and error for callers | // RunRaw is like RunPiped, but also returns the raw stderr and error for callers | ||||
| // that want to do low-level error handling/recovery. | // that want to do low-level error handling/recovery. | ||||
| func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) { | |||||
| func (i *Invocation) runRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) { | |||||
| stdout = &bytes.Buffer{} | stdout = &bytes.Buffer{} | ||||
| stderr = &bytes.Buffer{} | stderr = &bytes.Buffer{} | ||||
| rawError = i.RunPiped(ctx, stdout, stderr) | rawError = i.RunPiped(ctx, stdout, stderr) | ||||
| if rawError != nil { | if rawError != nil { | ||||
| friendlyError = rawError | |||||
| // Check for 'go' executable not being found. | // Check for 'go' executable not being found. | ||||
| if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | ||||
| friendlyError = fmt.Errorf("go command required, not found: %v", ee) | friendlyError = fmt.Errorf("go command required, not found: %v", ee) | ||||
| @@ -43,7 +101,7 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr * | |||||
| if ctx.Err() != nil { | if ctx.Err() != nil { | ||||
| friendlyError = ctx.Err() | friendlyError = ctx.Err() | ||||
| } | } | ||||
| friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr) | |||||
| friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) | |||||
| } | } | ||||
| return | return | ||||
| } | } | ||||
| @@ -78,8 +136,11 @@ func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) err | |||||
| // The Go stdlib has a special feature where if the cwd and the PWD are the | // The Go stdlib has a special feature where if the cwd and the PWD are the | ||||
| // same node then it trusts the PWD, so by setting it in the env for the child | // same node then it trusts the PWD, so by setting it in the env for the child | ||||
| // process we fix up all the paths returned by the go command. | // process we fix up all the paths returned by the go command. | ||||
| cmd.Env = append(append([]string{}, i.Env...), "PWD="+i.WorkingDir) | |||||
| cmd.Dir = i.WorkingDir | |||||
| cmd.Env = append(os.Environ(), i.Env...) | |||||
| if i.WorkingDir != "" { | |||||
| cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) | |||||
| cmd.Dir = i.WorkingDir | |||||
| } | |||||
| defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) | defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) | ||||
| @@ -50,7 +50,8 @@ var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool | |||||
| return | return | ||||
| }, | }, | ||||
| func(_ *ProcessEnv, importPath string) (num int, ok bool) { | func(_ *ProcessEnv, importPath string) (num int, ok bool) { | ||||
| if strings.Contains(importPath, ".") { | |||||
| firstComponent := strings.Split(importPath, "/")[0] | |||||
| if strings.Contains(firstComponent, ".") { | |||||
| return 1, true | return 1, true | ||||
| } | } | ||||
| return | return | ||||
| @@ -747,6 +748,8 @@ func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchP | |||||
| type ProcessEnv struct { | type ProcessEnv struct { | ||||
| LocalPrefix string | LocalPrefix string | ||||
| GocmdRunner *gocommand.Runner | |||||
| BuildFlags []string | BuildFlags []string | ||||
| // If non-empty, these will be used instead of the | // If non-empty, these will be used instead of the | ||||
| @@ -830,7 +833,7 @@ func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) | |||||
| Logf: e.Logf, | Logf: e.Logf, | ||||
| WorkingDir: e.WorkingDir, | WorkingDir: e.WorkingDir, | ||||
| } | } | ||||
| return inv.Run(ctx) | |||||
| return e.GocmdRunner.Run(ctx, inv) | |||||
| } | } | ||||
| func addStdlibCandidates(pass *pass, refs references) { | func addStdlibCandidates(pass *pass, refs references) { | ||||
| @@ -27,6 +27,7 @@ import ( | |||||
| "strings" | "strings" | ||||
| "golang.org/x/tools/go/ast/astutil" | "golang.org/x/tools/go/ast/astutil" | ||||
| "golang.org/x/tools/internal/gocommand" | |||||
| ) | ) | ||||
| // Options is golang.org/x/tools/imports.Options with extra internal-only options. | // Options is golang.org/x/tools/imports.Options with extra internal-only options. | ||||
| @@ -154,6 +155,10 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er | |||||
| GOSUMDB: os.Getenv("GOSUMDB"), | GOSUMDB: os.Getenv("GOSUMDB"), | ||||
| } | } | ||||
| } | } | ||||
| // Set the gocmdRunner if the user has not provided it. | |||||
| if opt.Env.GocmdRunner == nil { | |||||
| opt.Env.GocmdRunner = &gocommand.Runner{} | |||||
| } | |||||
| if src == nil { | if src == nil { | ||||
| b, err := ioutil.ReadFile(filename) | b, err := ioutil.ReadFile(filename) | ||||
| if err != nil { | if err != nil { | ||||