| @@ -94,11 +94,12 @@ require ( | |||
| github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect | |||
| github.com/tinylib/msgp v1.1.2 // indirect | |||
| 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/com v1.0.1 | |||
| github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | |||
| 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/yohcop/openid-go v1.0.0 | |||
| 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/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/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/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/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= | |||
| 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/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/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/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= | |||
| 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/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/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/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= | |||
| 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/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/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/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU= | |||
| 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/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/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/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/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/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | |||
| 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-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-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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
| 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | |||
| 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/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= | |||
| gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
| @@ -1,7 +1,7 @@ | |||
| home = Home | |||
| dashboard = Dashboard | |||
| explore = Explore | |||
| data_sets = Data Sets | |||
| datasets = Datasets | |||
| help = Help | |||
| sign_in = Sign In | |||
| sign_in_with = Sign In With | |||
| @@ -49,6 +49,7 @@ organization = Organization | |||
| mirror = Mirror | |||
| new_repo = New Repository | |||
| new_migrate = New Migration | |||
| new_dataset = New Dataset | |||
| new_mirror = New Mirror | |||
| new_fork = New Repository Fork | |||
| new_org = New Organization | |||
| @@ -618,6 +619,13 @@ email_notifications.onmention = Only Email on Mention | |||
| email_notifications.disable = Disable Email Notifications | |||
| email_notifications.submit = Set Email Preference | |||
| [dataset] | |||
| title = Title | |||
| description = Description | |||
| create_dataset = Create Dataset | |||
| category = Category | |||
| file = Dataset File | |||
| [repo] | |||
| owner = Owner | |||
| repo_name = Repository Name | |||
| @@ -667,7 +675,7 @@ pick_reaction = Pick your reaction | |||
| reactions_more = and %d more | |||
| unit_disabled = The site administrator has disabled this repository section. | |||
| language_other = Other | |||
| data_sets = Data Sets | |||
| datasets = Datasets | |||
| template.items = Template Items | |||
| template.git_content = Git Content (Default Branch) | |||
| @@ -47,6 +47,7 @@ repository=仓库 | |||
| organization=组织 | |||
| mirror=镜像 | |||
| new_repo=创建仓库 | |||
| new_dataset=创建数据集 | |||
| new_migrate=迁移外部仓库 | |||
| new_mirror=创建新的镜像 | |||
| new_fork=新的仓库Fork | |||
| @@ -514,8 +515,8 @@ last_used=上次使用在 | |||
| no_activity=没有最近活动 | |||
| can_read_info=读取 | |||
| can_write_info=写入 | |||
| key_state_desc=7 天内使用过该密钥 | |||
| token_state_desc=7 天内使用过该密钥 | |||
| key_state_desc=7 天内使用过该密钥 | |||
| token_state_desc=7 天内使用过该密钥 | |||
| show_openid=在个人信息上显示 | |||
| hide_openid=在个人信息上隐藏 | |||
| ssh_disabled=SSH 被禁用 | |||
| @@ -616,6 +617,13 @@ email_notifications.onmention=只在被提到时邮件通知 | |||
| email_notifications.disable=停用邮件通知 | |||
| email_notifications.submit=邮件通知设置 | |||
| [dataset] | |||
| title = 主题 | |||
| description = 描述 | |||
| create_dataset = 创建数据集 | |||
| category = 分类 | |||
| file = 数据集文件 | |||
| [repo] | |||
| owner=拥有者 | |||
| repo_name=仓库名称 | |||
| @@ -662,9 +670,10 @@ watchers=关注者 | |||
| stargazers=称赞者 | |||
| forks=派生仓库 | |||
| pick_reaction=选择你的表情 | |||
| reactions_more=再加载 %d | |||
| reactions_more=再加载 %d | |||
| unit_disabled=站点管理员已禁用此仓库单元。 | |||
| language_other=其它 | |||
| datasets=数据集 | |||
| template.items=模板选项 | |||
| template.git_content=Git数据(默认分支) | |||
| @@ -7,16 +7,18 @@ import ( | |||
| ) | |||
| const ( | |||
| tplDataSet base.TplName = "dataset/index" | |||
| tplDataSet base.TplName = "datasets/index" | |||
| tplCreate base.TplName = "datasets/create" | |||
| ) | |||
| func MyList(ctx *context.Context) { | |||
| log.Debug("[dataset] mylist...\n") | |||
| ctx.HTML(200, tplDataSet) | |||
| } | |||
| func Create(ctx *context.Context) { | |||
| log.Debug("[dataset] Create...\n") | |||
| ctx.HTML(200, tplCreate) | |||
| } | |||
| func Delete(ctx *context.Context) { | |||
| log.Debug("[dataset] Delete...\n") | |||
| @@ -1001,9 +1001,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| // ***** END: Repository ***** | |||
| // DataSet | |||
| m.Group("/dataset", func() { | |||
| m.Group("/datasets", func() { | |||
| m.Get("", dataset.MyList) | |||
| m.Post("", dataset.Create) | |||
| m.Get("/create", dataset.Create) | |||
| m.Post("/delete", dataset.Delete) | |||
| }, ignSignIn) | |||
| // ***** END: DataSet***** | |||
| @@ -10,6 +10,7 @@ | |||
| {{if .IsSigned}} | |||
| <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}} | |||
| <a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | |||
| {{end}} | |||
| @@ -20,17 +21,16 @@ | |||
| {{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}} | |||
| {{end}} | |||
| <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}} | |||
| <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 .PageIsDataSets}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "data_sets"}}</a> | |||
| {{else if .IsLandingPageExplore}} | |||
| <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}} | |||
| <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}} | |||
| {{template "custom/extra_links" .}} | |||
| @@ -68,6 +68,9 @@ | |||
| <a class="item" href="{{AppSubUrl}}/repo/create"> | |||
| <span class="fitted">{{svg "octicon-plus" 16}}</span> {{.i18n.Tr "new_repo"}} | |||
| </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"> | |||
| <span class="fitted">{{svg "octicon-repo-clone" 16}}</span> {{.i18n.Tr "new_migrate"}} | |||
| </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"> | |||
| {{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 class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | |||
| {{svg "octicon-file-submodule" 16}} {{.i18n.Tr "repo.data_sets"}} | |||
| </a> | |||
| {{end}} | |||
| {{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 | |||
| node_modules/ | |||
| vendor | |||
| @@ -1,27 +1,35 @@ | |||
| language: go | |||
| 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: | |||
| - linux | |||
| - osx | |||
| - linux | |||
| - osx | |||
| env: | |||
| GO111MODULE=on | |||
| GOPROXY=https://proxy.golang.org | |||
| cache: | |||
| directories: | |||
| - node_modules | |||
| - node_modules | |||
| 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: | |||
| - ./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] | |||
| ## 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 | |||
| @@ -407,7 +470,13 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. | |||
| ### Added | |||
| - 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.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 | |||
| @@ -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://ci.appveyor.com/project/urfave/cli) | |||
| [](https://godoc.org/github.com/urfave/cli) | |||
| [](https://codebeat.co/projects/github-com-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 | |||
| goal is to enable developers to write fast and distributable command line | |||
| @@ -23,7 +19,7 @@ applications in an expressive way. | |||
| - [Installation](#installation) | |||
| * [Supported platforms](#supported-platforms) | |||
| * [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) | |||
| - [Examples](#examples) | |||
| * [Arguments](#arguments) | |||
| @@ -32,10 +28,13 @@ applications in an expressive way. | |||
| + [Alternate Names](#alternate-names) | |||
| + [Ordering](#ordering) | |||
| + [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) | |||
| + [Precedence](#precedence) | |||
| * [Subcommands](#subcommands) | |||
| * [Subcommands categories](#subcommands-categories) | |||
| * [Exit code](#exit-code) | |||
| * [Combining short options](#combining-short-options) | |||
| * [Bash Completion](#bash-completion) | |||
| + [Enabling](#enabling) | |||
| + [Distribution](#distribution) | |||
| @@ -61,7 +60,7 @@ organized, and expressive! | |||
| ## 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). | |||
| 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 ( | |||
| "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 | |||
| @@ -138,13 +132,17 @@ discovery. So a cli app can be as little as one line of code in `main()`. | |||
| package main | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| ) | |||
| 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 ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -173,7 +172,10 @@ func main() { | |||
| return nil | |||
| } | |||
| app.Run(os.Args) | |||
| err := app.Run(os.Args) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| ``` | |||
| @@ -197,6 +199,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -211,7 +214,10 @@ func main() { | |||
| return nil | |||
| } | |||
| app.Run(os.Args) | |||
| err := app.Run(os.Args) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| ``` | |||
| @@ -260,6 +266,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -273,7 +280,10 @@ func main() { | |||
| return nil | |||
| } | |||
| app.Run(os.Args) | |||
| err := app.Run(os.Args) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| ``` | |||
| @@ -289,6 +299,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -318,7 +329,10 @@ func main() { | |||
| return nil | |||
| } | |||
| app.Run(os.Args) | |||
| err := app.Run(os.Args) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| ``` | |||
| @@ -332,6 +346,7 @@ scanned. | |||
| package main | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "fmt" | |||
| @@ -365,7 +380,10 @@ func main() { | |||
| 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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "sort" | |||
| @@ -510,7 +537,10 @@ func main() { | |||
| sort.Sort(cli.FlagsByName(app.Flags)) | |||
| 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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "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) | |||
| 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: | |||
| * YAML | |||
| * JSON | |||
| * TOML | |||
| 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 | |||
| 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: | |||
| @@ -630,6 +708,7 @@ package notmain | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -652,10 +731,22 @@ func main() { | |||
| app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) | |||
| 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 can be defined for a more git-like command line app. | |||
| @@ -669,6 +760,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -751,15 +847,18 @@ func main() { | |||
| }, | |||
| { | |||
| Name: "add", | |||
| Category: "template", | |||
| Category: "Template actions", | |||
| }, | |||
| { | |||
| 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: | |||
| noop | |||
| noop | |||
| Template actions: | |||
| 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 | |||
| 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.: | |||
| <!-- { | |||
| "error": "Ginger croutons are not in the soup" | |||
| } --> | |||
| ``` go | |||
| package main | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -793,22 +895,95 @@ import ( | |||
| func main() { | |||
| app := cli.NewApp() | |||
| app.Flags = []cli.Flag{ | |||
| cli.BoolTFlag{ | |||
| cli.BoolFlag{ | |||
| Name: "ginger-crouton", | |||
| Usage: "is it in the soup?", | |||
| Usage: "Add ginger croutons to the soup", | |||
| }, | |||
| } | |||
| app.Action = func(ctx *cli.Context) error { | |||
| 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 | |||
| } | |||
| 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 | |||
| You can enable completion commands by setting the `EnableBashCompletion` | |||
| @@ -825,6 +1000,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -914,7 +1094,10 @@ func main() { | |||
| Name: "wat", | |||
| }, | |||
| } | |||
| app.Run(os.Args) | |||
| err := app.Run(os.Args) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| ``` | |||
| @@ -940,6 +1123,7 @@ package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "io" | |||
| "os" | |||
| @@ -983,7 +1167,10 @@ VERSION: | |||
| 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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -1010,7 +1198,10 @@ func main() { | |||
| 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 | |||
| import ( | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -1047,7 +1239,10 @@ func main() { | |||
| app := cli.NewApp() | |||
| app.Name = "partay" | |||
| 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 ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "github.com/urfave/cli" | |||
| @@ -1079,7 +1275,10 @@ func main() { | |||
| app := cli.NewApp() | |||
| app.Name = "partay" | |||
| 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"}, | |||
| } | |||
| app.EnableBashCompletion = true | |||
| app.UseShortOptionHandling = true | |||
| app.HideHelp = false | |||
| app.HideVersion = false | |||
| app.BashComplete = func(c *cli.Context) { | |||
| @@ -1341,7 +1541,7 @@ func main() { | |||
| ec := cli.NewExitError("ohwell", 86) | |||
| fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) | |||
| fmt.Printf("made it!\n") | |||
| return ec | |||
| return nil | |||
| } | |||
| if os.Getenv("HEXY") != "" { | |||
| @@ -1355,7 +1555,9 @@ func main() { | |||
| "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 { | |||
| @@ -1366,16 +1568,4 @@ func wopAction(c *cli.Context) error { | |||
| ## 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 | |||
| import ( | |||
| "flag" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "os" | |||
| "path/filepath" | |||
| "sort" | |||
| @@ -11,9 +11,10 @@ import ( | |||
| ) | |||
| 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." | |||
| @@ -83,6 +84,9 @@ type App struct { | |||
| Writer io.Writer | |||
| // ErrWriter writes error output | |||
| 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 | |||
| Metadata map[string]interface{} | |||
| // 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 | |||
| // render custom help text by setting this variable. | |||
| 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 | |||
| } | |||
| @@ -135,7 +143,7 @@ func (a *App) Setup() { | |||
| a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) | |||
| } | |||
| newCmds := []Command{} | |||
| var newCmds []Command | |||
| for _, c := range a.Commands { | |||
| if c.HelpName == "" { | |||
| 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 | |||
| // to the proper flag/args combination | |||
| 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 | |||
| shellComplete, arguments := checkShellCompleteFlag(a, arguments) | |||
| // parse flags | |||
| set, err := flagSet(a.Name, a.Flags) | |||
| _, err = a.newFlagSet() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| set.SetOutput(ioutil.Discard) | |||
| err = set.Parse(arguments[1:]) | |||
| set, err := parseIter(a, arguments[1:]) | |||
| nerr := normalizeFlags(a.Flags, set) | |||
| context := NewContext(a, set, nil) | |||
| if nerr != nil { | |||
| fmt.Fprintln(a.Writer, nerr) | |||
| ShowAppHelp(context) | |||
| _, _ = fmt.Fprintln(a.Writer, nerr) | |||
| _ = ShowAppHelp(context) | |||
| return nerr | |||
| } | |||
| context.shellComplete = shellComplete | |||
| @@ -207,16 +221,16 @@ func (a *App) Run(arguments []string) (err error) { | |||
| if err != nil { | |||
| if a.OnUsageError != nil { | |||
| err := a.OnUsageError(context, err, false) | |||
| HandleExitCoder(err) | |||
| a.handleExitCoder(context, 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 | |||
| } | |||
| if !a.HideHelp && checkHelp(context) { | |||
| ShowAppHelp(context) | |||
| _ = ShowAppHelp(context) | |||
| return nil | |||
| } | |||
| @@ -225,6 +239,12 @@ func (a *App) Run(arguments []string) (err error) { | |||
| return nil | |||
| } | |||
| cerr := checkRequiredFlags(a.Flags, context) | |||
| if cerr != nil { | |||
| _ = ShowAppHelp(context) | |||
| return cerr | |||
| } | |||
| if a.After != nil { | |||
| defer func() { | |||
| if afterErr := a.After(context); afterErr != nil { | |||
| @@ -240,8 +260,9 @@ func (a *App) Run(arguments []string) (err error) { | |||
| if a.Before != nil { | |||
| beforeErr := a.Before(context) | |||
| if beforeErr != nil { | |||
| ShowAppHelp(context) | |||
| HandleExitCoder(beforeErr) | |||
| _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) | |||
| _ = ShowAppHelp(context) | |||
| a.handleExitCoder(context, beforeErr) | |||
| err = beforeErr | |||
| return err | |||
| } | |||
| @@ -263,7 +284,7 @@ func (a *App) Run(arguments []string) (err error) { | |||
| // Run default Action | |||
| err = HandleAction(a.Action, context) | |||
| HandleExitCoder(err) | |||
| a.handleExitCoder(context, err) | |||
| return err | |||
| } | |||
| @@ -274,7 +295,7 @@ func (a *App) Run(arguments []string) (err error) { | |||
| // code in the cli.ExitCoder | |||
| func (a *App) RunAndExitOnError() { | |||
| if err := a.Run(os.Args); err != nil { | |||
| fmt.Fprintln(a.errWriter(), err) | |||
| _, _ = fmt.Fprintln(a.errWriter(), err) | |||
| OsExiter(1) | |||
| } | |||
| } | |||
| @@ -301,24 +322,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||
| } | |||
| a.Commands = newCmds | |||
| // parse flags | |||
| set, err := flagSet(a.Name, a.Flags) | |||
| _, err = a.newFlagSet() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| set.SetOutput(ioutil.Discard) | |||
| err = set.Parse(ctx.Args().Tail()) | |||
| set, err := parseIter(a, ctx.Args().Tail()) | |||
| nerr := normalizeFlags(a.Flags, set) | |||
| context := NewContext(a, set, ctx) | |||
| 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 { | |||
| ShowSubcommandHelp(context) | |||
| _ = ShowSubcommandHelp(context) | |||
| } else { | |||
| ShowCommandHelp(ctx, context.Args().First()) | |||
| _ = ShowCommandHelp(ctx, context.Args().First()) | |||
| } | |||
| return nerr | |||
| } | |||
| @@ -330,11 +349,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||
| if err != nil { | |||
| if a.OnUsageError != nil { | |||
| err = a.OnUsageError(context, err, true) | |||
| HandleExitCoder(err) | |||
| a.handleExitCoder(context, 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 | |||
| } | |||
| @@ -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 { | |||
| defer func() { | |||
| afterErr := a.After(context) | |||
| if afterErr != nil { | |||
| HandleExitCoder(err) | |||
| a.handleExitCoder(context, err) | |||
| if err != nil { | |||
| err = NewMultiError(err, afterErr) | |||
| } else { | |||
| @@ -365,7 +390,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||
| if a.Before != nil { | |||
| beforeErr := a.Before(context) | |||
| if beforeErr != nil { | |||
| HandleExitCoder(beforeErr) | |||
| a.handleExitCoder(context, beforeErr) | |||
| err = beforeErr | |||
| return err | |||
| } | |||
| @@ -383,7 +408,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { | |||
| // Run default Action | |||
| err = HandleAction(a.Action, context) | |||
| HandleExitCoder(err) | |||
| a.handleExitCoder(context, err) | |||
| return err | |||
| } | |||
| @@ -424,7 +449,7 @@ func (a *App) VisibleCategories() []*CommandCategory { | |||
| // VisibleCommands returns a slice of the Commands with Hidden=false | |||
| func (a *App) VisibleCommands() []Command { | |||
| ret := []Command{} | |||
| var ret []Command | |||
| for _, command := range a.Commands { | |||
| if !command.Hidden { | |||
| ret = append(ret, command) | |||
| @@ -449,7 +474,6 @@ func (a *App) hasFlag(flag Flag) bool { | |||
| } | |||
| func (a *App) errWriter() io.Writer { | |||
| // When the app ErrWriter is nil use the package level one. | |||
| if a.ErrWriter == nil { | |||
| 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. | |||
| type Author struct { | |||
| 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 | |||
| // is run! | |||
| func HandleAction(action interface{}, context *Context) (err error) { | |||
| if a, ok := action.(ActionFunc); ok { | |||
| switch a := action.(type) { | |||
| case ActionFunc: | |||
| return a(context) | |||
| } else if a, ok := action.(func(*Context) error); ok { | |||
| case func(*Context) error: | |||
| return a(context) | |||
| } else if a, ok := action.(func(*Context)); ok { // deprecated function signature | |||
| case func(*Context): // deprecated function signature | |||
| a(context) | |||
| return nil | |||
| } else { | |||
| return errInvalidActionType | |||
| } | |||
| return errInvalidActionType | |||
| } | |||
| @@ -8,19 +8,16 @@ clone_folder: c:\gopath\src\github.com\urfave\cli | |||
| environment: | |||
| GOPATH: C:\gopath | |||
| GOVERSION: 1.8.x | |||
| PYTHON: C:\Python36-x64 | |||
| PYTHON_VERSION: 3.6.x | |||
| PYTHON_ARCH: 64 | |||
| GOVERSION: 1.11.x | |||
| 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: | |||
| - 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 { | |||
| return c[i].Name < c[j].Name | |||
| return lexicographicLess(c[i].Name, c[j].Name) | |||
| } | |||
| func (c CommandCategories) Len() int { | |||
| @@ -19,4 +19,4 @@ | |||
| // } | |||
| 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 | |||
| import ( | |||
| "flag" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "sort" | |||
| "strings" | |||
| ) | |||
| @@ -55,6 +55,10 @@ type Command struct { | |||
| HideHelp bool | |||
| // Boolean to hide this command from help or completion | |||
| 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. | |||
| HelpName string | |||
| @@ -73,7 +77,7 @@ func (c CommandsByName) Len() int { | |||
| } | |||
| 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) { | |||
| @@ -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.Command = c | |||
| @@ -167,12 +125,12 @@ func (c Command) Run(ctx *Context) (err error) { | |||
| if err != nil { | |||
| if c.OnUsageError != nil { | |||
| err := c.OnUsageError(context, err, false) | |||
| HandleExitCoder(err) | |||
| context.App.handleExitCoder(context, 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 | |||
| } | |||
| @@ -180,11 +138,17 @@ func (c Command) Run(ctx *Context) (err error) { | |||
| return nil | |||
| } | |||
| cerr := checkRequiredFlags(c.Flags, context) | |||
| if cerr != nil { | |||
| _ = ShowCommandHelp(context, c.Name) | |||
| return cerr | |||
| } | |||
| if c.After != nil { | |||
| defer func() { | |||
| afterErr := c.After(context) | |||
| if afterErr != nil { | |||
| HandleExitCoder(err) | |||
| context.App.handleExitCoder(context, err) | |||
| if err != nil { | |||
| err = NewMultiError(err, afterErr) | |||
| } else { | |||
| @@ -197,8 +161,8 @@ func (c Command) Run(ctx *Context) (err error) { | |||
| if c.Before != nil { | |||
| err = c.Before(context) | |||
| if err != nil { | |||
| ShowCommandHelp(context, c.Name) | |||
| HandleExitCoder(err) | |||
| _ = ShowCommandHelp(context, c.Name) | |||
| context.App.handleExitCoder(context, err) | |||
| return err | |||
| } | |||
| } | |||
| @@ -210,11 +174,76 @@ func (c Command) Run(ctx *Context) (err error) { | |||
| err = HandleAction(c.Action, context) | |||
| if err != nil { | |||
| HandleExitCoder(err) | |||
| context.App.handleExitCoder(context, 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. | |||
| func (c Command) Names() []string { | |||
| names := []string{c.Name} | |||
| @@ -239,6 +268,7 @@ func (c Command) HasName(name string) bool { | |||
| func (c Command) startApp(ctx *Context) error { | |||
| app := NewApp() | |||
| app.Metadata = ctx.App.Metadata | |||
| app.ExitErrHandler = ctx.App.ExitErrHandler | |||
| // set the name and usage | |||
| app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) | |||
| if c.HelpName == "" { | |||
| @@ -267,6 +297,7 @@ func (c Command) startApp(ctx *Context) error { | |||
| app.Email = ctx.App.Email | |||
| app.Writer = ctx.App.Writer | |||
| app.ErrWriter = ctx.App.ErrWriter | |||
| app.UseShortOptionHandling = ctx.App.UseShortOptionHandling | |||
| app.categories = CommandCategories{} | |||
| for _, command := range c.Subcommands { | |||
| @@ -3,6 +3,8 @@ package cli | |||
| import ( | |||
| "errors" | |||
| "flag" | |||
| "fmt" | |||
| "os" | |||
| "reflect" | |||
| "strings" | |||
| "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 | |||
| // responsibility closer to where the information required to determine | |||
| // 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 | |||
| flags := c.Command.Flags | |||
| @@ -93,18 +95,26 @@ func (c *Context) IsSet(name string) bool { | |||
| 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. | |||
| 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" { | |||
| continue | |||
| } | |||
| @@ -141,8 +151,8 @@ func (c *Context) FlagNames() (names []string) { | |||
| // GlobalFlagNames returns a slice of global flag names used by the app. | |||
| 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" { | |||
| continue | |||
| } | |||
| @@ -240,7 +250,7 @@ func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { | |||
| switch ff.Value.(type) { | |||
| case *StringSlice: | |||
| 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 | |||
| } | |||
| 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 ( | |||
| "flag" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "reflect" | |||
| "runtime" | |||
| "strconv" | |||
| "strings" | |||
| "syscall" | |||
| "time" | |||
| ) | |||
| const defaultPlaceholder = "value" | |||
| @@ -37,6 +37,18 @@ var HelpFlag Flag = BoolFlag{ | |||
| // to display a flag. | |||
| 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. | |||
| type FlagsByName []Flag | |||
| @@ -45,7 +57,7 @@ func (f FlagsByName) Len() int { | |||
| } | |||
| 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) { | |||
| @@ -62,6 +74,29 @@ type Flag interface { | |||
| 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 | |||
| // 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 | |||
| @@ -84,6 +119,7 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { | |||
| f.Apply(set) | |||
| } | |||
| } | |||
| set.SetOutput(ioutil.Discard) | |||
| 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 { | |||
| 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() { | |||
| visible = append(visible, flag) | |||
| visible = append(visible, f) | |||
| } | |||
| } | |||
| return visible | |||
| @@ -692,11 +196,19 @@ func withEnvHint(envVar, str string) string { | |||
| suffix = "%" | |||
| 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 | |||
| } | |||
| func withFileHint(filePath, str string) string { | |||
| fileText := "" | |||
| if filePath != "" { | |||
| fileText = fmt.Sprintf(" [%s]", filePath) | |||
| } | |||
| return str + fileText | |||
| } | |||
| func flagValue(f Flag) reflect.Value { | |||
| fv := reflect.ValueOf(f) | |||
| for fv.Kind() == reflect.Ptr { | |||
| @@ -710,14 +222,29 @@ func stringifyFlag(f Flag) string { | |||
| switch f.(type) { | |||
| 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: | |||
| 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: | |||
| 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()) | |||
| @@ -742,17 +269,22 @@ func stringifyFlag(f Flag) string { | |||
| 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 { | |||
| defaultVals := []string{} | |||
| var defaultVals []string | |||
| if f.Value != nil && len(f.Value.Value()) > 0 { | |||
| 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 { | |||
| defaultVals := []string{} | |||
| var defaultVals []string | |||
| if f.Value != nil && len(f.Value.Value()) > 0 { | |||
| 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 { | |||
| defaultVals := []string{} | |||
| var defaultVals []string | |||
| if f.Value != nil && len(f.Value.Value()) > 0 { | |||
| for _, s := range f.Value.Value() { | |||
| 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, ", ")) | |||
| } | |||
| 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. | |||
| 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 | |||
| // expected to be a single line. | |||
| 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" | |||
| "text/tabwriter" | |||
| "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{ | |||
| Name: "help", | |||
| Aliases: []string{"h"}, | |||
| @@ -89,7 +21,7 @@ var helpCommand = Command{ | |||
| return ShowCommandHelp(c, args.First()) | |||
| } | |||
| ShowAppHelp(c) | |||
| _ = ShowAppHelp(c) | |||
| return nil | |||
| }, | |||
| } | |||
| @@ -129,7 +61,7 @@ var VersionPrinter = printVersion | |||
| // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. | |||
| func ShowAppHelpAndExit(c *Context, exitCode int) { | |||
| ShowAppHelp(c) | |||
| _ = ShowAppHelp(c) | |||
| 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 | |||
| 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 { | |||
| 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 | |||
| func ShowCommandHelpAndExit(c *Context, command string, code int) { | |||
| ShowCommandHelp(c, command) | |||
| _ = ShowCommandHelp(c, command) | |||
| os.Exit(code) | |||
| } | |||
| @@ -207,7 +214,7 @@ func ShowVersion(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 | |||
| @@ -221,19 +228,22 @@ func ShowCompletions(c *Context) { | |||
| // ShowCommandCompletions prints the custom completions for a given command | |||
| func ShowCommandCompletions(ctx *Context, command string) { | |||
| 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{}) { | |||
| funcMap := template.FuncMap{ | |||
| "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) | |||
| @@ -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 | |||
| // we can do to recover. | |||
| 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 | |||
| } | |||
| w.Flush() | |||
| _ = w.Flush() | |||
| } | |||
| 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 { | |||
| if c.Bool("h") || c.Bool("help") { | |||
| ShowCommandHelp(c, name) | |||
| _ = ShowCommandHelp(c, name) | |||
| return true | |||
| } | |||
| @@ -289,7 +299,7 @@ func checkCommandHelp(c *Context, name string) bool { | |||
| func checkSubcommandHelp(c *Context) bool { | |||
| if c.Bool("h") || c.Bool("help") { | |||
| ShowSubcommandHelp(c) | |||
| _ = ShowSubcommandHelp(c) | |||
| 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 | |||
| last := pc.LastOpenedBlock().Node | |||
| if ast.IsParagraph(last) { | |||
| c, ok := matchesSetextHeadingBar(line) | |||
| c, ok := matchesSetextHeadingBar(line[match[3]-1:]) | |||
| if ok && c == '-' { | |||
| isHeading = true | |||
| } | |||
| @@ -268,7 +268,7 @@ func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) i | |||
| if codeSpanCloser == codeSpanOpener { | |||
| 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 | |||
| continue | |||
| } else if codeSpan && codeSpanOpener == 0 && c == '`' { | |||
| @@ -1,4 +1,4 @@ | |||
| // +build appengine,js | |||
| // +build appengine js | |||
| package util | |||
| @@ -7,6 +7,8 @@ import ( | |||
| "go/token" | |||
| "go/types" | |||
| "reflect" | |||
| "golang.org/x/tools/internal/analysisinternal" | |||
| ) | |||
| // 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 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 | |||
| // 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. | |||
| AllObjectFacts func() []ObjectFact | |||
| // typeErrors contains types.Errors that are associated with the pkg. | |||
| typeErrors []types.Error | |||
| /* Further fields may be added in future. */ | |||
| // 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() { | |||
| data, err := json.MarshalIndent(tree, "", "\t") | |||
| 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) | |||
| } | |||
| @@ -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)) | |||
| } | |||
| // 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 | |||
| } | |||
| @@ -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. | |||
| 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: | |||
| err = fmt.Errorf("unknown export data header: %q", hdr) | |||
| @@ -11,6 +11,7 @@ package gcimporter | |||
| import ( | |||
| "bytes" | |||
| "encoding/binary" | |||
| "fmt" | |||
| "go/ast" | |||
| "go/constant" | |||
| "go/token" | |||
| @@ -25,6 +26,15 @@ import ( | |||
| // 0: Go1.11 encoding | |||
| 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. | |||
| // | |||
| // If no file set is provided, position info will be missing. | |||
| @@ -528,6 +538,16 @@ func constantToFloat(x constant.Value) *big.Float { | |||
| 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. | |||
| // | |||
| // For unsigned types, small values are written out as a single | |||
| @@ -18,6 +18,9 @@ import ( | |||
| "go/types" | |||
| "io" | |||
| "sort" | |||
| "sync" | |||
| "unicode" | |||
| "unicode/utf8" | |||
| ) | |||
| type intReader struct { | |||
| @@ -25,6 +28,10 @@ type intReader struct { | |||
| path string | |||
| } | |||
| func errorf(format string, args ...interface{}) { | |||
| panic(fmt.Sprintf(format, args...)) | |||
| } | |||
| func (r *intReader) int64() int64 { | |||
| i, err := binary.ReadVarint(r.Reader) | |||
| if err != nil { | |||
| @@ -628,3 +635,166 @@ func (r *importReader) byte() byte { | |||
| } | |||
| 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 | |||
| // 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. | |||
| const toolPrefix = "GOPACKAGESDRIVER=" | |||
| tool := "" | |||
| @@ -40,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||
| } | |||
| if tool == "off" { | |||
| return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) | |||
| return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) | |||
| } | |||
| req, err := json.Marshal(struct { | |||
| @@ -76,7 +75,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||
| 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{ | |||
| Verb: "list", | |||
| 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, | |||
| WorkingDir: dir, | |||
| } | |||
| stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx) | |||
| stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) | |||
| var goarch, compiler string | |||
| if rawErr != nil { | |||
| 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, | |||
| WorkingDir: dir, | |||
| } | |||
| envout, enverr := inv.Run(ctx) | |||
| envout, enverr := gocmdRunner.Run(ctx, inv) | |||
| if enverr != nil { | |||
| return nil, enverr | |||
| } | |||
| @@ -25,6 +25,7 @@ import ( | |||
| "golang.org/x/tools/go/internal/packagesdriver" | |||
| "golang.org/x/tools/internal/gocommand" | |||
| "golang.org/x/tools/internal/packagesinternal" | |||
| "golang.org/x/xerrors" | |||
| ) | |||
| // debug controls verbose logging. | |||
| @@ -142,7 +143,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { | |||
| sizeswg.Add(1) | |||
| go func() { | |||
| 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. | |||
| response.dr.Sizes, _ = sizes.(*types.StdSizes) | |||
| sizeswg.Done() | |||
| @@ -502,10 +503,19 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
| errkind = "use of internal package not allowed" | |||
| } | |||
| 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{ | |||
| Pos: old.Error.Pos, | |||
| Msg: old.Error.Err, | |||
| @@ -534,6 +544,25 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
| 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: | |||
| // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. | |||
| // 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) { | |||
| cfg := state.cfg | |||
| inv := &gocommand.Invocation{ | |||
| inv := gocommand.Invocation{ | |||
| Verb: verb, | |||
| Args: args, | |||
| BuildFlags: cfg.BuildFlags, | |||
| @@ -715,8 +744,11 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, | |||
| Logf: cfg.Logf, | |||
| 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 { | |||
| // Check for 'go' executable not being found. | |||
| 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 { | |||
| // Catastrophic error: | |||
| // - 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? | |||
| @@ -5,6 +5,7 @@ import ( | |||
| "fmt" | |||
| "go/parser" | |||
| "go/token" | |||
| "log" | |||
| "os" | |||
| "path/filepath" | |||
| "sort" | |||
| @@ -22,10 +23,15 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
| needPkgsSet := make(map[string]bool) | |||
| modifiedPkgsSet := make(map[string]bool) | |||
| pkgOfDir := make(map[string][]*Package) | |||
| for _, pkg := range response.dr.Packages { | |||
| // This is an approximation of import path to id. This can be | |||
| // wrong for tests, vendored packages, and a number of other cases. | |||
| 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. | |||
| @@ -64,6 +70,9 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
| // to the overlay. | |||
| 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: | |||
| for _, p := range response.dr.Packages { | |||
| 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) { | |||
| 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 { | |||
| return nil, err | |||
| } | |||
| @@ -374,3 +393,46 @@ func extractPackageName(filename string, contents []byte) (string, bool) { | |||
| } | |||
| 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 { | |||
| m := mod | |||
| if m == 0 { | |||
| return fmt.Sprintf("LoadMode(0)") | |||
| return "LoadMode(0)" | |||
| } | |||
| var out []string | |||
| for i, x := range allModes { | |||
| @@ -19,10 +19,12 @@ import ( | |||
| "log" | |||
| "os" | |||
| "path/filepath" | |||
| "reflect" | |||
| "strings" | |||
| "sync" | |||
| "golang.org/x/tools/go/gcexportdata" | |||
| "golang.org/x/tools/internal/gocommand" | |||
| "golang.org/x/tools/internal/packagesinternal" | |||
| ) | |||
| @@ -69,6 +71,10 @@ const ( | |||
| // NeedTypesSizes adds TypesSizes. | |||
| 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 ( | |||
| @@ -127,6 +133,9 @@ type Config struct { | |||
| // | |||
| 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 | |||
| // the build system's query tool. | |||
| BuildFlags []string | |||
| @@ -253,7 +262,7 @@ type Package struct { | |||
| GoFiles []string | |||
| // 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. | |||
| CompiledGoFiles []string | |||
| @@ -311,6 +320,12 @@ func init() { | |||
| packagesinternal.GetModule = func(p interface{}) *packagesinternal.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. | |||
| @@ -473,6 +488,9 @@ func newLoader(cfg *Config) *loader { | |||
| if ld.Config.Env == nil { | |||
| ld.Config.Env = os.Environ() | |||
| } | |||
| if ld.Config.gocmdRunner == nil { | |||
| ld.Config.gocmdRunner = &gocommand.Runner{} | |||
| } | |||
| if ld.Context == nil { | |||
| ld.Context = context.Background() | |||
| } | |||
| @@ -865,6 +883,19 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { | |||
| Error: appendError, | |||
| 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) | |||
| lpkg.importErrors = nil // no longer needed | |||
| @@ -226,7 +226,8 @@ func For(obj types.Object) (Path, error) { | |||
| // the best paths because non-types may | |||
| // refer to types, but not the reverse. | |||
| empty := make([]byte, 0, 48) // initial space | |||
| for _, name := range scope.Names() { | |||
| names := scope.Names() | |||
| for _, name := range names { | |||
| o := scope.Lookup(name) | |||
| tname, ok := o.(*types.TypeName) | |||
| if !ok { | |||
| @@ -253,7 +254,7 @@ func For(obj types.Object) (Path, error) { | |||
| // Then inspect everything else: | |||
| // non-types, and declared methods of defined types. | |||
| for _, name := range scope.Names() { | |||
| for _, name := range names { | |||
| o := scope.Lookup(name) | |||
| path := append(empty, name...) | |||
| 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 | |||
| @@ -8,10 +12,70 @@ import ( | |||
| "io" | |||
| "os" | |||
| "os/exec" | |||
| "regexp" | |||
| "strings" | |||
| "sync" | |||
| "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. | |||
| type Invocation struct { | |||
| Verb string | |||
| @@ -22,20 +86,14 @@ type Invocation struct { | |||
| 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 | |||
| // 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{} | |||
| stderr = &bytes.Buffer{} | |||
| rawError = i.RunPiped(ctx, stdout, stderr) | |||
| if rawError != nil { | |||
| friendlyError = rawError | |||
| // Check for 'go' executable not being found. | |||
| if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | |||
| 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 { | |||
| friendlyError = ctx.Err() | |||
| } | |||
| friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr) | |||
| friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) | |||
| } | |||
| 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 | |||
| // 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. | |||
| 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()) | |||
| @@ -50,7 +50,8 @@ var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool | |||
| return | |||
| }, | |||
| 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 | |||
| @@ -747,6 +748,8 @@ func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchP | |||
| type ProcessEnv struct { | |||
| LocalPrefix string | |||
| GocmdRunner *gocommand.Runner | |||
| BuildFlags []string | |||
| // 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, | |||
| WorkingDir: e.WorkingDir, | |||
| } | |||
| return inv.Run(ctx) | |||
| return e.GocmdRunner.Run(ctx, inv) | |||
| } | |||
| func addStdlibCandidates(pass *pass, refs references) { | |||
| @@ -27,6 +27,7 @@ import ( | |||
| "strings" | |||
| "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. | |||
| @@ -154,6 +155,10 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er | |||
| 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 { | |||
| b, err := ioutil.ReadFile(filename) | |||
| if err != nil { | |||