go mod vendor

pull/162/head
Miguel Mota 3 years ago
parent 914b2e650f
commit 1a789cb587
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9

@ -1,31 +1,43 @@
module github.com/miguelmota/cointop module github.com/miguelmota/cointop
go 1.17
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.4.1
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/anaskhan96/soup v1.1.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/antonmedv/expr v1.9.0 github.com/antonmedv/expr v1.9.0
github.com/creack/pty v1.1.11 github.com/creack/pty v1.1.15
github.com/fatih/color v1.9.0 github.com/fatih/color v1.12.0
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1
github.com/gliderlabs/ssh v0.3.0 github.com/gliderlabs/ssh v0.3.3
github.com/maruel/panicparse v1.5.0 github.com/maruel/panicparse v1.6.1
github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-runewidth v0.0.13
github.com/mattn/go-runewidth v0.0.9 github.com/miguelmota/go-coinmarketcap v0.1.8
github.com/miguelmota/go-coinmarketcap v0.1.7
github.com/miguelmota/gocui v0.4.2 github.com/miguelmota/gocui v0.4.2
github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7
github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/go-wordwrap v1.0.1
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.5
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5 // indirect
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect golang.org/x/text v0.3.7
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed // indirect
golang.org/x/text v0.3.3
) )
go 1.13 require (
github.com/anaskhan96/soup v1.0.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gopherjs/gopherwasm v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
)

668
go.sum

@ -1,235 +1,657 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/anaskhan96/soup v1.0.1 h1:3p9zOr7o2weHqDakRA1uR0SZNr6VhH5qPkm6p3gvS6o=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anaskhan96/soup v1.0.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0= github.com/anaskhan96/soup v1.0.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
github.com/anaskhan96/soup v1.1.1 h1:Duux/0htS2Va7XLJ9qIakCSey790hg9OFRm2FwlMTy0=
github.com/anaskhan96/soup v1.1.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU= github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 h1:M2Zt3G2w6Q57GZndOYk42p7RvMeO8izO8yKTfIxGqxA= github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1 h1:Xh9mvwEmhbdXlRSsgn+N0zj/NqnKvpeqL08oKDHln2s=
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8= github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.3.0 h1:7GcKy4erEljCE/QeQ2jTVpu+3f3zkpZOxOJjFYkMqYU= github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA=
github.com/gliderlabs/ssh v0.3.0/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE= github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10= github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ= github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/maruel/panicparse v1.5.0 h1:etK4QAf/Spw8eyowKbOHRkOfhblp/kahGUy96RvbMjI= github.com/maruel/panicparse v1.6.1 h1:803MjBzGcUgE1vYgg3UMNq3G1oyYeKkMu3t6hBS97x0=
github.com/maruel/panicparse v1.5.0/go.mod h1:aOutY/MUjdj80R0AEVI9qE2zHqig+67t2ffUDDiLzAM= github.com/maruel/panicparse v1.6.1/go.mod h1:uoxI4w9gJL6XahaYPMq/z9uadrdr1SyHuQwV2q80Mm0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/maruel/panicparse/v2 v2.1.1/go.mod h1:AeTWdCE4lcq8OKsLb6cHSj1RWHVSnV9HBCk7sKLF4Jg=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/miguelmota/go-coinmarketcap v0.1.7 h1:9kTFWMom73IuGXqacD/LYPiUeX1qLpuLH8BhceHXYt0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miguelmota/go-coinmarketcap v0.1.7/go.mod h1:hBjej1IiB5+pfj+0cZhnxRkAc2bgky8qWLhCJTQ3zjw= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miguelmota/go-coinmarketcap v0.1.8 h1:rZhB7xs1j7qxxd1zftjADhAv6ECJQVhBom1dh3zURKY=
github.com/miguelmota/go-coinmarketcap v0.1.8/go.mod h1:hBjej1IiB5+pfj+0cZhnxRkAc2bgky8qWLhCJTQ3zjw=
github.com/miguelmota/gocui v0.4.2 h1:nMYnYn3RjV7FlWFcidQa9eAkX3kT7XMI6yJMxEkAz6s= github.com/miguelmota/gocui v0.4.2 h1:nMYnYn3RjV7FlWFcidQa9eAkX3kT7XMI6yJMxEkAz6s=
github.com/miguelmota/gocui v0.4.2/go.mod h1:wVtmhuLR+VAS9VRBIJZBNJS9IgH+9QOZ/m/MvRarOZ4= github.com/miguelmota/gocui v0.4.2/go.mod h1:wVtmhuLR+VAS9VRBIJZBNJS9IgH+9QOZ/m/MvRarOZ4=
github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:sZmjSV25xMXIGAaATVuOtC9VtGHMydXpd9OejNaTxQE= github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:sZmjSV25xMXIGAaATVuOtC9VtGHMydXpd9OejNaTxQE=
github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7/go.mod h1:DRZE481VrAygaB/4DTvG0To/HsucthXAu0sY1Exb7gw= github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7/go.mod h1:DRZE481VrAygaB/4DTvG0To/HsucthXAu0sY1Exb7gw=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

@ -1,5 +1,2 @@
TAGS
tags
.*.swp
tomlcheck/tomlcheck
toml.test toml.test
/toml-test

@ -1,15 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
install:
- go install ./...
- go get github.com/BurntSushi/toml-test
script:
- export PATH="$PATH:$HOME/gopath/bin"
- make test

@ -1,3 +1 @@
Compatible with TOML version Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)

@ -1,19 +0,0 @@
install:
go install ./...
test: install
go test -v
toml-test toml-test-decoder
toml-test -encoder toml-test-encoder
fmt:
gofmt -w *.go */*.go
colcheck *.go */*.go
tags:
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
push:
git push origin master
git push github master

@ -6,27 +6,22 @@ packages. This package also supports the `encoding.TextUnmarshaler` and
`encoding.TextMarshaler` interfaces so that you can define custom data `encoding.TextMarshaler` interfaces so that you can define custom data
representations. (There is an example of this below.) representations. (There is an example of this below.)
Spec: https://github.com/toml-lang/toml Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
Compatible with TOML version Documentation: https://godocs.io/github.com/BurntSushi/toml
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
Documentation: https://godoc.org/github.com/BurntSushi/toml See the [releases page](https://github.com/BurntSushi/toml/releases) for a
changelog; this information is also in the git tag annotations (e.g. `git show
v0.4.0`).
Installation: This library requires Go 1.13 or newer; install it with:
```bash $ go get github.com/BurntSushi/toml
go get github.com/BurntSushi/toml
```
Try the toml validator:
```bash It also comes with a TOML validator CLI tool:
go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml
```
[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml) $ go get github.com/BurntSushi/toml/cmd/tomlv
$ tomlv some-toml-file.toml
### Testing ### Testing
@ -36,8 +31,8 @@ and the encoder.
### Examples ### Examples
This package works similarly to how the Go standard library handles `XML` This package works similarly to how the Go standard library handles XML and
and `JSON`. Namely, data is loaded into Go values via reflection. JSON. Namely, data is loaded into Go values via reflection.
For the simplest example, consider some TOML file as just a list of keys For the simplest example, consider some TOML file as just a list of keys
and values: and values:
@ -54,11 +49,11 @@ Which could be defined in Go as:
```go ```go
type Config struct { type Config struct {
Age int Age int
Cats []string Cats []string
Pi float64 Pi float64
Perfection []int Perfection []int
DOB time.Time // requires `import time` DOB time.Time // requires `import time`
} }
``` ```
@ -84,6 +79,9 @@ type TOML struct {
} }
``` ```
Beware that like other most other decoders **only exported fields** are
considered when encoding and decoding; private fields are silently ignored.
### Using the `encoding.TextUnmarshaler` interface ### Using the `encoding.TextUnmarshaler` interface
Here's an example that automatically parses duration strings into Here's an example that automatically parses duration strings into
@ -103,19 +101,19 @@ Which can be decoded with:
```go ```go
type song struct { type song struct {
Name string Name string
Duration duration Duration duration
} }
type songs struct { type songs struct {
Song []song Song []song
} }
var favorites songs var favorites songs
if _, err := toml.Decode(blob, &favorites); err != nil { if _, err := toml.Decode(blob, &favorites); err != nil {
log.Fatal(err) log.Fatal(err)
} }
for _, s := range favorites.Song { for _, s := range favorites.Song {
fmt.Printf("%s (%s)\n", s.Name, s.Duration) fmt.Printf("%s (%s)\n", s.Name, s.Duration)
} }
``` ```
@ -134,6 +132,9 @@ func (d *duration) UnmarshalText(text []byte) error {
} }
``` ```
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
a similar way.
### More complex usage ### More complex usage
Here's an example of how to load the example from the official spec page: Here's an example of how to load the example from the official spec page:
@ -180,23 +181,23 @@ And the corresponding Go types are:
```go ```go
type tomlConfig struct { type tomlConfig struct {
Title string Title string
Owner ownerInfo Owner ownerInfo
DB database `toml:"database"` DB database `toml:"database"`
Servers map[string]server Servers map[string]server
Clients clients Clients clients
} }
type ownerInfo struct { type ownerInfo struct {
Name string Name string
Org string `toml:"organization"` Org string `toml:"organization"`
Bio string Bio string
DOB time.Time DOB time.Time
} }
type database struct { type database struct {
Server string Server string
Ports []int Ports []int
ConnMax int `toml:"connection_max"` ConnMax int `toml:"connection_max"`
Enabled bool Enabled bool
} }
@ -207,7 +208,7 @@ type server struct {
} }
type clients struct { type clients struct {
Data [][]interface{} Data [][]interface{}
Hosts []string Hosts []string
} }
``` ```
@ -216,3 +217,4 @@ Note that a case insensitive match will be tried if an exact match can't be
found. found.
A working example of the above can be found in `_examples/example.{go,toml}`. A working example of the above can be found in `_examples/example.{go,toml}`.

@ -1,19 +1,17 @@
package toml package toml
import ( import (
"encoding"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
"os"
"reflect" "reflect"
"strings" "strings"
"time" "time"
) )
func e(format string, args ...interface{}) error {
return fmt.Errorf("toml: "+format, args...)
}
// Unmarshaler is the interface implemented by objects that can unmarshal a // Unmarshaler is the interface implemented by objects that can unmarshal a
// TOML description of themselves. // TOML description of themselves.
type Unmarshaler interface { type Unmarshaler interface {
@ -27,30 +25,21 @@ func Unmarshal(p []byte, v interface{}) error {
} }
// Primitive is a TOML value that hasn't been decoded into a Go value. // Primitive is a TOML value that hasn't been decoded into a Go value.
// When using the various `Decode*` functions, the type `Primitive` may
// be given to any value, and its decoding will be delayed.
// //
// A `Primitive` value can be decoded using the `PrimitiveDecode` function. // This type can be used for any value, which will cause decoding to be delayed.
// You can use the PrimitiveDecode() function to "manually" decode these values.
// //
// The underlying representation of a `Primitive` value is subject to change. // NOTE: The underlying representation of a `Primitive` value is subject to
// Do not rely on it. // change. Do not rely on it.
// //
// N.B. Primitive values are still parsed, so using them will only avoid // NOTE: Primitive values are still parsed, so using them will only avoid the
// the overhead of reflection. They can be useful when you don't know the // overhead of reflection. They can be useful when you don't know the exact type
// exact type of TOML data until run time. // of TOML data until runtime.
type Primitive struct { type Primitive struct {
undecoded interface{} undecoded interface{}
context Key context Key
} }
// DEPRECATED!
//
// Use MetaData.PrimitiveDecode instead.
func PrimitiveDecode(primValue Primitive, v interface{}) error {
md := MetaData{decoded: make(map[string]bool)}
return md.unify(primValue.undecoded, rvalue(v))
}
// PrimitiveDecode is just like the other `Decode*` functions, except it // PrimitiveDecode is just like the other `Decode*` functions, except it
// decodes a TOML value that has already been parsed. Valid primitive values // decodes a TOML value that has already been parsed. Valid primitive values
// can *only* be obtained from values filled by the decoder functions, // can *only* be obtained from values filled by the decoder functions,
@ -68,43 +57,51 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
return md.unify(primValue.undecoded, rvalue(v)) return md.unify(primValue.undecoded, rvalue(v))
} }
// Decode will decode the contents of `data` in TOML format into a pointer // Decoder decodes TOML data.
// `v`.
// //
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be // TOML tables correspond to Go structs or maps (dealer's choice they can be
// used interchangeably.) // used interchangeably).
// //
// TOML arrays of tables correspond to either a slice of structs or a slice // TOML table arrays correspond to either a slice of structs or a slice of maps.
// of maps.
// //
// TOML datetimes correspond to Go `time.Time` values. // TOML datetimes correspond to Go time.Time values. Local datetimes are parsed
// in the local timezone.
// //
// All other TOML types (float, string, int, bool and array) correspond // All other TOML types (float, string, int, bool and array) correspond to the
// to the obvious Go types. // obvious Go types.
// //
// An exception to the above rules is if a type implements the // An exception to the above rules is if a type implements the TextUnmarshaler
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value // interface, in which case any primitive TOML value (floats, strings, integers,
// (floats, strings, integers, booleans and datetimes) will be converted to // booleans, datetimes) will be converted to a []byte and given to the value's
// a byte string and given to the value's UnmarshalText method. See the // UnmarshalText method. See the Unmarshaler example for a demonstration with
// Unmarshaler example for a demonstration with time duration strings. // time duration strings.
// //
// Key mapping // Key mapping
// //
// TOML keys can map to either keys in a Go map or field names in a Go // TOML keys can map to either keys in a Go map or field names in a Go struct.
// struct. The special `toml` struct tag may be used to map TOML keys to // The special `toml` struct tag can be used to map TOML keys to struct fields
// struct fields that don't match the key name exactly. (See the example.) // that don't match the key name exactly (see the example). A case insensitive
// A case insensitive match to struct names will be tried if an exact match // match to struct names will be tried if an exact match can't be found.
// can't be found.
// //
// The mapping between TOML values and Go values is loose. That is, there // The mapping between TOML values and Go values is loose. That is, there may
// may exist TOML values that cannot be placed into your representation, and // exist TOML values that cannot be placed into your representation, and there
// there may be parts of your representation that do not correspond to // may be parts of your representation that do not correspond to TOML values.
// TOML values. This loose mapping can be made stricter by using the IsDefined // This loose mapping can be made stricter by using the IsDefined and/or
// and/or Undecoded methods on the MetaData returned. // Undecoded methods on the MetaData returned.
// //
// This decoder will not handle cyclic types. If a cyclic type is passed, // This decoder does not handle cyclic types. Decode will not terminate if a
// `Decode` will not terminate. // cyclic type is passed.
func Decode(data string, v interface{}) (MetaData, error) { type Decoder struct {
r io.Reader
}
// NewDecoder creates a new Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// Decode TOML data in to the pointer `v`.
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr { if rv.Kind() != reflect.Ptr {
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
@ -112,7 +109,15 @@ func Decode(data string, v interface{}) (MetaData, error) {
if rv.IsNil() { if rv.IsNil() {
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
} }
p, err := parse(data)
// TODO: have parser should read from io.Reader? Or at the very least, make
// it read from []byte rather than string
data, err := ioutil.ReadAll(dec.r)
if err != nil {
return MetaData{}, err
}
p, err := parse(string(data))
if err != nil { if err != nil {
return MetaData{}, err return MetaData{}, err
} }
@ -123,24 +128,22 @@ func Decode(data string, v interface{}) (MetaData, error) {
return md, md.unify(p.mapping, indirect(rv)) return md, md.unify(p.mapping, indirect(rv))
} }
// DecodeFile is just like Decode, except it will automatically read the // Decode the TOML data in to the pointer v.
// contents of the file at `fpath` and decode it for you. //
func DecodeFile(fpath string, v interface{}) (MetaData, error) { // See the documentation on Decoder for a description of the decoding process.
bs, err := ioutil.ReadFile(fpath) func Decode(data string, v interface{}) (MetaData, error) {
if err != nil { return NewDecoder(strings.NewReader(data)).Decode(v)
return MetaData{}, err
}
return Decode(string(bs), v)
} }
// DecodeReader is just like Decode, except it will consume all bytes // DecodeFile is just like Decode, except it will automatically read the
// from the reader and decode it for you. // contents of the file at path and decode it for you.
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { func DecodeFile(path string, v interface{}) (MetaData, error) {
bs, err := ioutil.ReadAll(r) fp, err := os.Open(path)
if err != nil { if err != nil {
return MetaData{}, err return MetaData{}, err
} }
return Decode(string(bs), v) defer fp.Close()
return NewDecoder(fp).Decode(v)
} }
// unify performs a sort of type unification based on the structure of `rv`, // unify performs a sort of type unification based on the structure of `rv`,
@ -149,8 +152,8 @@ func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
// Any type mismatch produces an error. Finding a type that we don't know // Any type mismatch produces an error. Finding a type that we don't know
// how to handle produces an unsupported type error. // how to handle produces an unsupported type error.
func (md *MetaData) unify(data interface{}, rv reflect.Value) error { func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
// Special case. Look for a `Primitive` value. // Special case. Look for a `Primitive` value.
// TODO: #76 would make this superfluous after implemented.
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
// Save the undecoded data and the key context into the primitive // Save the undecoded data and the key context into the primitive
// value. // value.
@ -170,25 +173,17 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
} }
} }
// Special case. Handle time.Time values specifically.
// TODO: Remove this code when we decide to drop support for Go 1.1.
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
// interfaces.
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
return md.unifyDatetime(data, rv)
}
// Special case. Look for a value satisfying the TextUnmarshaler interface. // Special case. Look for a value satisfying the TextUnmarshaler interface.
if v, ok := rv.Interface().(TextUnmarshaler); ok { if v, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
return md.unifyText(data, v) return md.unifyText(data, v)
} }
// BUG(burntsushi) // TODO:
// The behavior here is incorrect whenever a Go type satisfies the // The behavior here is incorrect whenever a Go type satisfies the
// encoding.TextUnmarshaler interface but also corresponds to a TOML // encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
// hash or array. In particular, the unmarshaler should only be applied // array. In particular, the unmarshaler should only be applied to primitive
// to primitive TOML values. But at this point, it will be applied to // TOML values. But at this point, it will be applied to all kinds of values
// all kinds of values and produce an incorrect error whenever those values // and produce an incorrect error whenever those values are hashes or arrays
// are hashes or arrays (including arrays of tables). // (including arrays of tables).
k := rv.Kind() k := rv.Kind()
@ -277,6 +272,12 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
} }
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
if k := rv.Type().Key().Kind(); k != reflect.String {
return fmt.Errorf(
"toml: cannot decode to a map with non-string key type (%s in %q)",
k, rv.Type())
}
tmap, ok := mapping.(map[string]interface{}) tmap, ok := mapping.(map[string]interface{})
if !ok { if !ok {
if tmap == nil { if tmap == nil {
@ -312,10 +313,8 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
} }
return badtype("slice", data) return badtype("slice", data)
} }
sliceLen := datav.Len() if l := datav.Len(); l != rv.Len() {
if sliceLen != rv.Len() { return e("expected array length %d; got TOML array of length %d", rv.Len(), l)
return e("expected array length %d; got TOML array of length %d",
rv.Len(), sliceLen)
} }
return md.unifySliceArray(datav, rv) return md.unifySliceArray(datav, rv)
} }
@ -337,11 +336,10 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
} }
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
sliceLen := data.Len() l := data.Len()
for i := 0; i < sliceLen; i++ { for i := 0; i < l; i++ {
v := data.Index(i).Interface() err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
sliceval := indirect(rv.Index(i)) if err != nil {
if err := md.unify(v, sliceval); err != nil {
return err return err
} }
} }
@ -439,7 +437,7 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
return nil return nil
} }
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
var s string var s string
switch sdata := data.(type) { switch sdata := data.(type) {
case TextMarshaler: case TextMarshaler:
@ -482,7 +480,7 @@ func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr { if v.Kind() != reflect.Ptr {
if v.CanSet() { if v.CanSet() {
pv := v.Addr() pv := v.Addr()
if _, ok := pv.Interface().(TextUnmarshaler); ok { if _, ok := pv.Interface().(encoding.TextUnmarshaler); ok {
return pv return pv
} }
} }
@ -498,12 +496,16 @@ func isUnifiable(rv reflect.Value) bool {
if rv.CanSet() { if rv.CanSet() {
return true return true
} }
if _, ok := rv.Interface().(TextUnmarshaler); ok { if _, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
return true return true
} }
return false return false
} }
func e(format string, args ...interface{}) error {
return fmt.Errorf("toml: "+format, args...)
}
func badtype(expected string, data interface{}) error { func badtype(expected string, data interface{}) error {
return e("cannot load TOML value of type %T into a Go %s", data, expected) return e("cannot load TOML value of type %T into a Go %s", data, expected)
} }

@ -0,0 +1,18 @@
// +build go1.16
package toml
import (
"io/fs"
)
// DecodeFS is just like Decode, except it will automatically read the contents
// of the file at `path` from a fs.FS instance.
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
fp, err := fsys.Open(path)
if err != nil {
return MetaData{}, err
}
defer fp.Close()
return NewDecoder(fp).Decode(v)
}

@ -2,9 +2,9 @@ package toml
import "strings" import "strings"
// MetaData allows access to meta information about TOML data that may not // MetaData allows access to meta information about TOML data that may not be
// be inferrable via reflection. In particular, whether a key has been defined // inferable via reflection. In particular, whether a key has been defined and
// and the TOML type of a key. // the TOML type of a key.
type MetaData struct { type MetaData struct {
mapping map[string]interface{} mapping map[string]interface{}
types map[string]tomlType types map[string]tomlType
@ -13,10 +13,11 @@ type MetaData struct {
context Key // Used only during decoding. context Key // Used only during decoding.
} }
// IsDefined returns true if the key given exists in the TOML data. The key // IsDefined reports if the key exists in the TOML data.
// should be specified hierarchially. e.g., //
// The key should be specified hierarchically, for example to access the TOML
// key "a.b.c" you would use:
// //
// // access the TOML key 'a.b.c'
// IsDefined("a", "b", "c") // IsDefined("a", "b", "c")
// //
// IsDefined will return false if an empty key given. Keys are case sensitive. // IsDefined will return false if an empty key given. Keys are case sensitive.
@ -41,8 +42,8 @@ func (md *MetaData) IsDefined(key ...string) bool {
// Type returns a string representation of the type of the key specified. // Type returns a string representation of the type of the key specified.
// //
// Type will return the empty string if given an empty key or a key that // Type will return the empty string if given an empty key or a key that does
// does not exist. Keys are case sensitive. // not exist. Keys are case sensitive.
func (md *MetaData) Type(key ...string) string { func (md *MetaData) Type(key ...string) string {
fullkey := strings.Join(key, ".") fullkey := strings.Join(key, ".")
if typ, ok := md.types[fullkey]; ok { if typ, ok := md.types[fullkey]; ok {
@ -51,13 +52,11 @@ func (md *MetaData) Type(key ...string) string {
return "" return ""
} }
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys // Key represents any TOML key, including key groups. Use (MetaData).Keys to get
// to get values of this type. // values of this type.
type Key []string type Key []string
func (k Key) String() string { func (k Key) String() string { return strings.Join(k, ".") }
return strings.Join(k, ".")
}
func (k Key) maybeQuotedAll() string { func (k Key) maybeQuotedAll() string {
var ss []string var ss []string
@ -68,6 +67,9 @@ func (k Key) maybeQuotedAll() string {
} }
func (k Key) maybeQuoted(i int) string { func (k Key) maybeQuoted(i int) string {
if k[i] == "" {
return `""`
}
quote := false quote := false
for _, c := range k[i] { for _, c := range k[i] {
if !isBareKeyChar(c) { if !isBareKeyChar(c) {
@ -76,7 +78,7 @@ func (k Key) maybeQuoted(i int) string {
} }
} }
if quote { if quote {
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" return `"` + quotedReplacer.Replace(k[i]) + `"`
} }
return k[i] return k[i]
} }
@ -89,10 +91,10 @@ func (k Key) add(piece string) Key {
} }
// Keys returns a slice of every key in the TOML data, including key groups. // Keys returns a slice of every key in the TOML data, including key groups.
// Each key is itself a slice, where the first element is the top of the
// hierarchy and the last is the most specific.
// //
// The list will have the same order as the keys appeared in the TOML data. // Each key is itself a slice, where the first element is the top of the
// hierarchy and the last is the most specific. The list will have the same
// order as the keys appeared in the TOML data.
// //
// All keys returned are non-empty. // All keys returned are non-empty.
func (md *MetaData) Keys() []Key { func (md *MetaData) Keys() []Key {

@ -0,0 +1,33 @@
package toml
import (
"encoding"
"io"
)
// DEPRECATED!
//
// Use the identical encoding.TextMarshaler instead. It is defined here to
// support Go 1.1 and older.
type TextMarshaler encoding.TextMarshaler
// DEPRECATED!
//
// Use the identical encoding.TextUnmarshaler instead. It is defined here to
// support Go 1.1 and older.
type TextUnmarshaler encoding.TextUnmarshaler
// DEPRECATED!
//
// Use MetaData.PrimitiveDecode instead.
func PrimitiveDecode(primValue Primitive, v interface{}) error {
md := MetaData{decoded: make(map[string]bool)}
return md.unify(primValue.undecoded, rvalue(v))
}
// DEPRECATED!
//
// Use NewDecoder(reader).Decode(&v) instead.
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
return NewDecoder(r).Decode(v)
}

@ -1,27 +1,13 @@
/* /*
Package toml provides facilities for decoding and encoding TOML configuration Package toml implements decoding and encoding of TOML files.
files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the
MetaData type.
The specification implemented: https://github.com/toml-lang/toml This package supports TOML v1.0.0, as listed on https://toml.io
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify There is also support for delaying decoding with the Primitive type, and
whether a file is a valid TOML document. It can also be used to print the querying the set of keys in a TOML document with the MetaData type.
type of each key in a TOML document.
Testing The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
and can be used to verify if TOML document is valid. It can also be used to
There are two important types of tests used for this package. The first is print the type of each key.
contained inside '*_test.go' files and uses the standard Go unit testing
framework. These tests are primarily devoted to holistically testing the
decoder and encoder.
The second type of testing is used to verify the implementation's adherence
to the TOML specification. These tests have been factored into their own
project: https://github.com/BurntSushi/toml-test
The reason the tests are in a separate project is so that they can be used by
any implementation of TOML. Namely, it is language agnostic.
*/ */
package toml package toml

@ -2,48 +2,92 @@ package toml
import ( import (
"bufio" "bufio"
"encoding"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/BurntSushi/toml/internal"
) )
type tomlEncodeError struct{ error } type tomlEncodeError struct{ error }
var ( var (
errArrayMixedElementTypes = errors.New( errArrayNilElement = errors.New("toml: cannot encode array with nil element")
"toml: cannot encode array with mixed element types") errNonString = errors.New("toml: cannot encode a map with non-string key type")
errArrayNilElement = errors.New( errAnonNonStruct = errors.New("toml: cannot encode an anonymous field that is not a struct")
"toml: cannot encode array with nil element") errNoKey = errors.New("toml: top-level values must be Go maps or structs")
errNonString = errors.New( errAnything = errors.New("") // used in testing
"toml: cannot encode a map with non-string key type")
errAnonNonStruct = errors.New(
"toml: cannot encode an anonymous field that is not a struct")
errArrayNoTable = errors.New(
"toml: TOML array element cannot contain a table")
errNoKey = errors.New(
"toml: top-level values must be Go maps or structs")
errAnything = errors.New("") // used in testing
) )
var quotedReplacer = strings.NewReplacer( var quotedReplacer = strings.NewReplacer(
"\t", "\\t",
"\n", "\\n",
"\r", "\\r",
"\"", "\\\"", "\"", "\\\"",
"\\", "\\\\", "\\", "\\\\",
"\x00", `\u0000`,
"\x01", `\u0001`,
"\x02", `\u0002`,
"\x03", `\u0003`,
"\x04", `\u0004`,
"\x05", `\u0005`,
"\x06", `\u0006`,
"\x07", `\u0007`,
"\b", `\b`,
"\t", `\t`,
"\n", `\n`,
"\x0b", `\u000b`,
"\f", `\f`,
"\r", `\r`,
"\x0e", `\u000e`,
"\x0f", `\u000f`,
"\x10", `\u0010`,
"\x11", `\u0011`,
"\x12", `\u0012`,
"\x13", `\u0013`,
"\x14", `\u0014`,
"\x15", `\u0015`,
"\x16", `\u0016`,
"\x17", `\u0017`,
"\x18", `\u0018`,
"\x19", `\u0019`,
"\x1a", `\u001a`,
"\x1b", `\u001b`,
"\x1c", `\u001c`,
"\x1d", `\u001d`,
"\x1e", `\u001e`,
"\x1f", `\u001f`,
"\x7f", `\u007f`,
) )
// Encoder controls the encoding of Go values to a TOML document to some // Encoder encodes a Go to a TOML document.
// io.Writer. //
// The mapping between Go values and TOML values should be precisely the same as
// for the Decode* functions. Similarly, the TextMarshaler interface is
// supported by encoding the resulting bytes as strings. If you want to write
// arbitrary binary data then you will need to use something like base64 since
// TOML does not have any binary types.
//
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
// are encoded first.
//
// Go maps will be sorted alphabetically by key for deterministic output.
// //
// The indentation level can be controlled with the Indent field. // Encoding Go values without a corresponding TOML representation will return an
// error. Examples of this includes maps with non-string keys, slices with nil
// elements, embedded non-struct types, and nested slices containing maps or
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
// is okay, as is []map[string][]string).
//
// NOTE: Only exported keys are encoded due to the use of reflection. Unexported
// keys are silently discarded.
type Encoder struct { type Encoder struct {
// A single indentation level. By default it is two spaces. // The string to use for a single indentation level. The default is two
// spaces.
Indent string Indent string
// hasWritten is whether we have written any output to w yet. // hasWritten is whether we have written any output to w yet.
@ -51,8 +95,7 @@ type Encoder struct {
w *bufio.Writer w *bufio.Writer
} }
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer // NewEncoder create a new Encoder.
// given. By default, a single indentation level is 2 spaces.
func NewEncoder(w io.Writer) *Encoder { func NewEncoder(w io.Writer) *Encoder {
return &Encoder{ return &Encoder{
w: bufio.NewWriter(w), w: bufio.NewWriter(w),
@ -60,29 +103,10 @@ func NewEncoder(w io.Writer) *Encoder {
} }
} }
// Encode writes a TOML representation of the Go value to the underlying // Encode writes a TOML representation of the Go value to the Encoder's writer.
// io.Writer. If the value given cannot be encoded to a valid TOML document,
// then an error is returned.
//
// The mapping between Go values and TOML values should be precisely the same
// as for the Decode* functions. Similarly, the TextMarshaler interface is
// supported by encoding the resulting bytes as strings. (If you want to write
// arbitrary binary data then you will need to use something like base64 since
// TOML does not have any binary types.)
//
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
// sub-hashes are encoded first.
// //
// If a Go map is encoded, then its keys are sorted alphabetically for // An error is returned if the value given cannot be encoded to a valid TOML
// deterministic output. More control over this behavior may be provided if // document.
// there is demand for it.
//
// Encoding Go values without a corresponding TOML representation---like map
// types with non-string keys---will cause an error to be returned. Similarly
// for mixed arrays/slices, arrays/slices with nil elements, embedded
// non-struct types and nested slices containing maps or structs.
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
// and so is []map[string][]string.)
func (enc *Encoder) Encode(v interface{}) error { func (enc *Encoder) Encode(v interface{}) error {
rv := eindirect(reflect.ValueOf(v)) rv := eindirect(reflect.ValueOf(v))
if err := enc.safeEncode(Key([]string{}), rv); err != nil { if err := enc.safeEncode(Key([]string{}), rv); err != nil {
@ -110,9 +134,13 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
// Special case. If we can marshal the type to text, then we used that. // Special case. If we can marshal the type to text, then we used that.
// Basically, this prevents the encoder for handling these types as // Basically, this prevents the encoder for handling these types as
// generic structs (or whatever the underlying type of a TextMarshaler is). // generic structs (or whatever the underlying type of a TextMarshaler is).
switch rv.Interface().(type) { switch t := rv.Interface().(type) {
case time.Time, TextMarshaler: case time.Time, encoding.TextMarshaler:
enc.keyEqElement(key, rv) enc.writeKeyValue(key, rv, false)
return
// TODO: #76 would make this superfluous after implemented.
case Primitive:
enc.encode(key, reflect.ValueOf(t.undecoded))
return return
} }
@ -123,12 +151,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64, reflect.Uint64,
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
enc.keyEqElement(key, rv) enc.writeKeyValue(key, rv, false)
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
enc.eArrayOfTables(key, rv) enc.eArrayOfTables(key, rv)
} else { } else {
enc.keyEqElement(key, rv) enc.writeKeyValue(key, rv, false)
} }
case reflect.Interface: case reflect.Interface:
if rv.IsNil() { if rv.IsNil() {
@ -148,22 +176,32 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
case reflect.Struct: case reflect.Struct:
enc.eTable(key, rv) enc.eTable(key, rv)
default: default:
panic(e("unsupported type for key '%s': %s", key, k)) encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
} }
} }
// eElement encodes any value that can be an array element (primitives and // eElement encodes any value that can be an array element.
// arrays).
func (enc *Encoder) eElement(rv reflect.Value) { func (enc *Encoder) eElement(rv reflect.Value) {
switch v := rv.Interface().(type) { switch v := rv.Interface().(type) {
case time.Time: case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
// Special case time.Time as a primitive. Has to come before format := time.RFC3339Nano
// TextMarshaler below because time.Time implements switch v.Location() {
// encoding.TextMarshaler, but we need to always use UTC. case internal.LocalDatetime:
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) format = "2006-01-02T15:04:05.999999999"
case internal.LocalDate:
format = "2006-01-02"
case internal.LocalTime:
format = "15:04:05.999999999"
}
switch v.Location() {
default:
enc.wf(v.Format(format))
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
enc.wf(v.In(time.UTC).Format(format))
}
return return
case TextMarshaler: case encoding.TextMarshaler:
// Special case. Use text marshaler if it's available for this value. // Use text marshaler if it's available for this value.
if s, err := v.MarshalText(); err != nil { if s, err := v.MarshalText(); err != nil {
encPanic(err) encPanic(err)
} else { } else {
@ -171,32 +209,49 @@ func (enc *Encoder) eElement(rv reflect.Value) {
} }
return return
} }
switch rv.Kind() { switch rv.Kind() {
case reflect.String:
enc.writeQuoted(rv.String())
case reflect.Bool: case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool())) enc.wf(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10)) enc.wf(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10)) enc.wf(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32: case reflect.Float32:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) f := rv.Float()
if math.IsNaN(f) {
enc.wf("nan")
} else if math.IsInf(f, 0) {
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
}
case reflect.Float64: case reflect.Float64:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) f := rv.Float()
if math.IsNaN(f) {
enc.wf("nan")
} else if math.IsInf(f, 0) {
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
}
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv) enc.eArrayOrSliceElement(rv)
case reflect.Struct:
enc.eStruct(nil, rv, true)
case reflect.Map:
enc.eMap(nil, rv, true)
case reflect.Interface: case reflect.Interface:
enc.eElement(rv.Elem()) enc.eElement(rv.Elem())
case reflect.String:
enc.writeQuoted(rv.String())
default: default:
panic(e("unexpected primitive type: %s", rv.Kind())) encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface()))
} }
} }
// By the TOML spec, all floats must have a decimal with at least one // By the TOML spec, all floats must have a decimal with at least one number on
// number on either side. // either side.
func floatAddDecimal(fstr string) string { func floatAddDecimal(fstr string) string {
if !strings.Contains(fstr, ".") { if !strings.Contains(fstr, ".") {
return fstr + ".0" return fstr + ".0"
@ -230,16 +285,14 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
if isNil(trv) { if isNil(trv) {
continue continue
} }
panicIfInvalidKey(key)
enc.newline() enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
enc.newline() enc.newline()
enc.eMapOrStruct(key, trv) enc.eMapOrStruct(key, trv, false)
} }
} }
func (enc *Encoder) eTable(key Key, rv reflect.Value) { func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key)
if len(key) == 1 { if len(key) == 1 {
// Output an extra newline between top-level tables. // Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.) // (The newline isn't written if nothing else has been written though.)
@ -249,21 +302,22 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
enc.newline() enc.newline()
} }
enc.eMapOrStruct(key, rv) enc.eMapOrStruct(key, rv, false)
} }
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
switch rv := eindirect(rv); rv.Kind() { switch rv := eindirect(rv); rv.Kind() {
case reflect.Map: case reflect.Map:
enc.eMap(key, rv) enc.eMap(key, rv, inline)
case reflect.Struct: case reflect.Struct:
enc.eStruct(key, rv) enc.eStruct(key, rv, inline)
default: default:
// Should never happen?
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
} }
} }
func (enc *Encoder) eMap(key Key, rv reflect.Value) { func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
rt := rv.Type() rt := rv.Type()
if rt.Key().Kind() != reflect.String { if rt.Key().Kind() != reflect.String {
encPanic(errNonString) encPanic(errNonString)
@ -281,57 +335,76 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) {
} }
} }
var writeMapKeys = func(mapKeys []string) { var writeMapKeys = func(mapKeys []string, trailC bool) {
sort.Strings(mapKeys) sort.Strings(mapKeys)
for _, mapKey := range mapKeys { for i, mapKey := range mapKeys {
mrv := rv.MapIndex(reflect.ValueOf(mapKey)) val := rv.MapIndex(reflect.ValueOf(mapKey))
if isNil(mrv) { if isNil(val) {
// Don't write anything for nil fields.
continue continue
} }
enc.encode(key.add(mapKey), mrv)
if inline {
enc.writeKeyValue(Key{mapKey}, val, true)
if trailC || i != len(mapKeys)-1 {
enc.wf(", ")
}
} else {
enc.encode(key.add(mapKey), val)
}
} }
} }
writeMapKeys(mapKeysDirect)
writeMapKeys(mapKeysSub) if inline {
enc.wf("{")
}
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
writeMapKeys(mapKeysSub, false)
if inline {
enc.wf("}")
}
} }
func (enc *Encoder) eStruct(key Key, rv reflect.Value) { func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
// Write keys for fields directly under this key first, because if we write // Write keys for fields directly under this key first, because if we write
// a field that creates a new table, then all keys under it will be in that // a field that creates a new table then all keys under it will be in that
// table (not the one we're writing here). // table (not the one we're writing here).
rt := rv.Type() //
var fieldsDirect, fieldsSub [][]int // Fields is a [][]int: for fieldsDirect this always has one entry (the
var addFields func(rt reflect.Type, rv reflect.Value, start []int) // struct index). For fieldsSub it contains two entries: the parent field
// index from tv, and the field indexes for the fields of the sub.
var (
rt = rv.Type()
fieldsDirect, fieldsSub [][]int
addFields func(rt reflect.Type, rv reflect.Value, start []int)
)
addFields = func(rt reflect.Type, rv reflect.Value, start []int) { addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
for i := 0; i < rt.NumField(); i++ { for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i) f := rt.Field(i)
// skip unexported fields if f.PkgPath != "" && !f.Anonymous { /// Skip unexported fields.
if f.PkgPath != "" && !f.Anonymous {
continue continue
} }
frv := rv.Field(i) frv := rv.Field(i)
// Treat anonymous struct fields with tag names as though they are
// not anonymous, like encoding/json does.
//
// Non-struct anonymous fields use the normal encoding logic.
if f.Anonymous { if f.Anonymous {
t := f.Type t := f.Type
switch t.Kind() { switch t.Kind() {
case reflect.Struct: case reflect.Struct:
// Treat anonymous struct fields with
// tag names as though they are not
// anonymous, like encoding/json does.
if getOptions(f.Tag).name == "" { if getOptions(f.Tag).name == "" {
addFields(t, frv, f.Index) addFields(t, frv, append(start, f.Index...))
continue continue
} }
case reflect.Ptr: case reflect.Ptr:
if t.Elem().Kind() == reflect.Struct && if t.Elem().Kind() == reflect.Struct && getOptions(f.Tag).name == "" {
getOptions(f.Tag).name == "" {
if !frv.IsNil() { if !frv.IsNil() {
addFields(t.Elem(), frv.Elem(), f.Index) addFields(t.Elem(), frv.Elem(), append(start, f.Index...))
} }
continue continue
} }
// Fall through to the normal field encoding logic below
// for non-struct anonymous fields.
} }
} }
@ -344,35 +417,49 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
} }
addFields(rt, rv, nil) addFields(rt, rv, nil)
var writeFields = func(fields [][]int) { writeFields := func(fields [][]int) {
for _, fieldIndex := range fields { for _, fieldIndex := range fields {
sft := rt.FieldByIndex(fieldIndex) fieldType := rt.FieldByIndex(fieldIndex)
sf := rv.FieldByIndex(fieldIndex) fieldVal := rv.FieldByIndex(fieldIndex)
if isNil(sf) {
// Don't write anything for nil fields. if isNil(fieldVal) { /// Don't write anything for nil fields.
continue continue
} }
opts := getOptions(sft.Tag) opts := getOptions(fieldType.Tag)
if opts.skip { if opts.skip {
continue continue
} }
keyName := sft.Name keyName := fieldType.Name
if opts.name != "" { if opts.name != "" {
keyName = opts.name keyName = opts.name
} }
if opts.omitempty && isEmpty(sf) { if opts.omitempty && isEmpty(fieldVal) {
continue continue
} }
if opts.omitzero && isZero(sf) { if opts.omitzero && isZero(fieldVal) {
continue continue
} }
enc.encode(key.add(keyName), sf) if inline {
enc.writeKeyValue(Key{keyName}, fieldVal, true)
if fieldIndex[0] != len(fields)-1 {
enc.wf(", ")
}
} else {
enc.encode(key.add(keyName), fieldVal)
}
} }
} }
if inline {
enc.wf("{")
}
writeFields(fieldsDirect) writeFields(fieldsDirect)
writeFields(fieldsSub) writeFields(fieldsSub)
if inline {
enc.wf("}")
}
} }
// tomlTypeName returns the TOML type name of the Go value's type. It is // tomlTypeName returns the TOML type name of the Go value's type. It is
@ -411,13 +498,26 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
switch rv.Interface().(type) { switch rv.Interface().(type) {
case time.Time: case time.Time:
return tomlDatetime return tomlDatetime
case TextMarshaler: case encoding.TextMarshaler:
return tomlString return tomlString
default: default:
// Someone used a pointer receiver: we can make it work for pointer
// values.
if rv.CanAddr() {
_, ok := rv.Addr().Interface().(encoding.TextMarshaler)
if ok {
return tomlString
}
}
return tomlHash return tomlHash
} }
default: default:
panic("unexpected reflect.Kind: " + rv.Kind().String()) _, ok := rv.Interface().(encoding.TextMarshaler)
if ok {
return tomlString
}
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
panic("") // Need *some* return value
} }
} }
@ -430,29 +530,18 @@ func tomlArrayType(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
return nil return nil
} }
firstType := tomlTypeOfGo(rv.Index(0))
if firstType == nil {
encPanic(errArrayNilElement)
}
/// Don't allow nil.
rvlen := rv.Len() rvlen := rv.Len()
for i := 1; i < rvlen; i++ { for i := 1; i < rvlen; i++ {
elem := rv.Index(i) if tomlTypeOfGo(rv.Index(i)) == nil {
switch elemType := tomlTypeOfGo(elem); {
case elemType == nil:
encPanic(errArrayNilElement) encPanic(errArrayNilElement)
case !typeEqual(firstType, elemType):
encPanic(errArrayMixedElementTypes)
} }
} }
// If we have a nested array, then we must make sure that the nested
// array contains ONLY primitives. firstType := tomlTypeOfGo(rv.Index(0))
// This checks arbitrarily nested arrays. if firstType == nil {
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { encPanic(errArrayNilElement)
nest := tomlArrayType(eindirect(rv.Index(0)))
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
encPanic(errArrayNoTable)
}
} }
return firstType return firstType
} }
@ -511,14 +600,20 @@ func (enc *Encoder) newline() {
} }
} }
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { // Write a key/value pair:
//
// key = <any value>
//
// If inline is true it won't add a newline at the end.
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
if len(key) == 0 { if len(key) == 0 {
encPanic(errNoKey) encPanic(errNoKey)
} }
panicIfInvalidKey(key)
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val) enc.eElement(val)
enc.newline() if !inline {
enc.newline()
}
} }
func (enc *Encoder) wf(format string, v ...interface{}) { func (enc *Encoder) wf(format string, v ...interface{}) {
@ -553,16 +648,3 @@ func isNil(rv reflect.Value) bool {
return false return false
} }
} }
func panicIfInvalidKey(key Key) {
for _, k := range key {
if len(k) == 0 {
encPanic(e("Key '%s' is not a valid table name. Key names "+
"cannot be empty.", key.maybeQuotedAll()))
}
}
}
func isValidKeyName(s string) bool {
return len(s) != 0
}

@ -1,19 +0,0 @@
// +build go1.2
package toml
// In order to support Go 1.1, we define our own TextMarshaler and
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
// standard library interfaces.
import (
"encoding"
)
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler encoding.TextMarshaler
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler encoding.TextUnmarshaler

@ -1,18 +0,0 @@
// +build !go1.2
package toml
// These interfaces were introduced in Go 1.2, so we add them manually when
// compiling for Go 1.1.
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}

@ -0,0 +1,36 @@
package internal
import "time"
// Timezones used for local datetime, date, and time TOML types.
//
// The exact way times and dates without a timezone should be interpreted is not
// well-defined in the TOML specification and left to the implementation. These
// defaults to current local timezone offset of the computer, but this can be
// changed by changing these variables before decoding.
//
// TODO:
// Ideally we'd like to offer people the ability to configure the used timezone
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
// tricky: the reason we use three different variables for this is to support
// round-tripping without these specific TZ names we wouldn't know which
// format to use.
//
// There isn't a good way to encode this right now though, and passing this sort
// of information also ties in to various related issues such as string format
// encoding, encoding of comments, etc.
//
// So, for the time being, just put this in internal until we can write a good
// comprehensive API for doing all of this.
//
// The reason they're exported is because they're referred from in e.g.
// internal/tag.
//
// Note that this behaviour is valid according to the TOML spec as the exact
// behaviour is left up to implementations.
var (
localOffset = func() int { _, o := time.Now().Zone(); return o }()
LocalDatetime = time.FixedZone("datetime-local", localOffset)
LocalDate = time.FixedZone("date-local", localOffset)
LocalTime = time.FixedZone("time-local", localOffset)
)

@ -2,6 +2,8 @@ package toml
import ( import (
"fmt" "fmt"
"reflect"
"runtime"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -29,6 +31,7 @@ const (
itemArrayTableStart itemArrayTableStart
itemArrayTableEnd itemArrayTableEnd
itemKeyStart itemKeyStart
itemKeyEnd
itemCommentStart itemCommentStart
itemInlineTableStart itemInlineTableStart
itemInlineTableEnd itemInlineTableEnd
@ -64,9 +67,9 @@ type lexer struct {
state stateFn state stateFn
items chan item items chan item
// Allow for backing up up to three runes. // Allow for backing up up to four runes.
// This is necessary because TOML contains 3-rune tokens (""" and '''). // This is necessary because TOML contains 3-rune tokens (""" and ''').
prevWidths [3]int prevWidths [4]int
nprev int // how many of prevWidths are in use nprev int // how many of prevWidths are in use
// If we emit an eof, we can still back up, but it is not OK to call // If we emit an eof, we can still back up, but it is not OK to call
// next again. // next again.
@ -93,6 +96,7 @@ func (lx *lexer) nextItem() item {
return item return item
default: default:
lx.state = lx.state(lx) lx.state = lx.state(lx)
//fmt.Printf(" STATE %-24s current: %-10q stack: %s\n", lx.state, lx.current(), lx.stack)
} }
} }
} }
@ -137,7 +141,7 @@ func (lx *lexer) emitTrim(typ itemType) {
func (lx *lexer) next() (r rune) { func (lx *lexer) next() (r rune) {
if lx.atEOF { if lx.atEOF {
panic("next called after EOF") panic("BUG in lexer: next called after EOF")
} }
if lx.pos >= len(lx.input) { if lx.pos >= len(lx.input) {
lx.atEOF = true lx.atEOF = true
@ -147,12 +151,19 @@ func (lx *lexer) next() (r rune) {
if lx.input[lx.pos] == '\n' { if lx.input[lx.pos] == '\n' {
lx.line++ lx.line++
} }
lx.prevWidths[3] = lx.prevWidths[2]
lx.prevWidths[2] = lx.prevWidths[1] lx.prevWidths[2] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[0] lx.prevWidths[1] = lx.prevWidths[0]
if lx.nprev < 3 { if lx.nprev < 4 {
lx.nprev++ lx.nprev++
} }
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
if r == utf8.RuneError {
lx.errorf("invalid UTF-8 byte at position %d (line %d): 0x%02x", lx.pos, lx.line, lx.input[lx.pos])
return utf8.RuneError
}
lx.prevWidths[0] = w lx.prevWidths[0] = w
lx.pos += w lx.pos += w
return r return r
@ -163,18 +174,19 @@ func (lx *lexer) ignore() {
lx.start = lx.pos lx.start = lx.pos
} }
// backup steps back one rune. Can be called only twice between calls to next. // backup steps back one rune. Can be called 4 times between calls to next.
func (lx *lexer) backup() { func (lx *lexer) backup() {
if lx.atEOF { if lx.atEOF {
lx.atEOF = false lx.atEOF = false
return return
} }
if lx.nprev < 1 { if lx.nprev < 1 {
panic("backed up too far") panic("BUG in lexer: backed up too far")
} }
w := lx.prevWidths[0] w := lx.prevWidths[0]
lx.prevWidths[0] = lx.prevWidths[1] lx.prevWidths[0] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[2] lx.prevWidths[1] = lx.prevWidths[2]
lx.prevWidths[2] = lx.prevWidths[3]
lx.nprev-- lx.nprev--
lx.pos -= w lx.pos -= w
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
@ -269,8 +281,9 @@ func lexTopEnd(lx *lexer) stateFn {
lx.emit(itemEOF) lx.emit(itemEOF)
return nil return nil
} }
return lx.errorf("expected a top-level item to end with a newline, "+ return lx.errorf(
"comment, or EOF, but got %q instead", r) "expected a top-level item to end with a newline, comment, or EOF, but got %q instead",
r)
} }
// lexTable lexes the beginning of a table. Namely, it makes sure that // lexTable lexes the beginning of a table. Namely, it makes sure that
@ -297,8 +310,9 @@ func lexTableEnd(lx *lexer) stateFn {
func lexArrayTableEnd(lx *lexer) stateFn { func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd { if r := lx.next(); r != arrayTableEnd {
return lx.errorf("expected end of table array name delimiter %q, "+ return lx.errorf(
"but got %q instead", arrayTableEnd, r) "expected end of table array name delimiter %q, but got %q instead",
arrayTableEnd, r)
} }
lx.emit(itemArrayTableEnd) lx.emit(itemArrayTableEnd)
return lexTopEnd return lexTopEnd
@ -308,30 +322,17 @@ func lexTableNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace) lx.skip(isWhitespace)
switch r := lx.peek(); { switch r := lx.peek(); {
case r == tableEnd || r == eof: case r == tableEnd || r == eof:
return lx.errorf("unexpected end of table name " + return lx.errorf("unexpected end of table name (table names cannot be empty)")
"(table names cannot be empty)")
case r == tableSep: case r == tableSep:
return lx.errorf("unexpected table separator " + return lx.errorf("unexpected table separator (table names cannot be empty)")
"(table names cannot be empty)")
case r == stringStart || r == rawStringStart: case r == stringStart || r == rawStringStart:
lx.ignore() lx.ignore()
lx.push(lexTableNameEnd) lx.push(lexTableNameEnd)
return lexValue // reuse string lexing return lexQuotedName
default: default:
return lexBareTableName lx.push(lexTableNameEnd)
} return lexBareName
}
// lexBareTableName lexes the name of a table. It assumes that at least one
// valid character for the table has already been read.
func lexBareTableName(lx *lexer) stateFn {
r := lx.next()
if isBareKeyChar(r) {
return lexBareTableName
} }
lx.backup()
lx.emit(itemText)
return lexTableNameEnd
} }
// lexTableNameEnd reads the end of a piece of a table name, optionally // lexTableNameEnd reads the end of a piece of a table name, optionally
@ -347,63 +348,101 @@ func lexTableNameEnd(lx *lexer) stateFn {
case r == tableEnd: case r == tableEnd:
return lx.pop() return lx.pop()
default: default:
return lx.errorf("expected '.' or ']' to end table name, "+ return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r)
"but got %q instead", r)
} }
} }
// lexKeyStart consumes a key name up until the first non-whitespace character. // lexBareName lexes one part of a key or table.
// lexKeyStart will ignore whitespace. //
func lexKeyStart(lx *lexer) stateFn { // It assumes that at least one valid character for the table has already been
r := lx.peek() // read.
//
// Lexes only one part, e.g. only 'a' inside 'a.b'.
func lexBareName(lx *lexer) stateFn {
r := lx.next()
if isBareKeyChar(r) {
return lexBareName
}
lx.backup()
lx.emit(itemText)
return lx.pop()
}
// lexBareName lexes one part of a key or table.
//
// It assumes that at least one valid character for the table has already been
// read.
//
// Lexes only one part, e.g. only '"a"' inside '"a".b'.
func lexQuotedName(lx *lexer) stateFn {
r := lx.next()
switch { switch {
case r == keySep: case isWhitespace(r):
return lx.errorf("unexpected key separator %q", keySep) return lexSkip(lx, lexValue)
case isWhitespace(r) || isNL(r): case r == stringStart:
lx.next() lx.ignore() // ignore the '"'
return lexSkip(lx, lexKeyStart) return lexString
case r == stringStart || r == rawStringStart: case r == rawStringStart:
lx.ignore() lx.ignore() // ignore the "'"
lx.emit(itemKeyStart) return lexRawString
lx.push(lexKeyEnd) case r == eof:
return lexValue // reuse string lexing return lx.errorf("unexpected EOF; expected value")
default: default:
return lx.errorf("expected value but found %q instead", r)
}
}
// lexKeyStart consumes all key parts until a '='.
func lexKeyStart(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.peek(); {
case r == '=' || r == eof:
return lx.errorf("unexpected '=': key name appears blank")
case r == '.':
return lx.errorf("unexpected '.': keys cannot start with a '.'")
case r == stringStart || r == rawStringStart:
lx.ignore() lx.ignore()
fallthrough
default: // Bare key
lx.emit(itemKeyStart) lx.emit(itemKeyStart)
return lexBareKey return lexKeyNameStart
} }
} }
// lexBareKey consumes the text of a bare key. Assumes that the first character func lexKeyNameStart(lx *lexer) stateFn {
// (which is not whitespace) has not yet been consumed. lx.skip(isWhitespace)
func lexBareKey(lx *lexer) stateFn { switch r := lx.peek(); {
switch r := lx.next(); { case r == '=' || r == eof:
case isBareKeyChar(r): return lx.errorf("unexpected '='")
return lexBareKey case r == '.':
case isWhitespace(r): return lx.errorf("unexpected '.'")
lx.backup() case r == stringStart || r == rawStringStart:
lx.emit(itemText) lx.ignore()
return lexKeyEnd lx.push(lexKeyEnd)
case r == keySep: return lexQuotedName
lx.backup()
lx.emit(itemText)
return lexKeyEnd
default: default:
return lx.errorf("bare keys cannot contain %q", r) lx.push(lexKeyEnd)
return lexBareName
} }
} }
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key // lexKeyEnd consumes the end of a key and trims whitespace (up to the key
// separator). // separator).
func lexKeyEnd(lx *lexer) stateFn { func lexKeyEnd(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.next(); { switch r := lx.next(); {
case r == keySep:
return lexSkip(lx, lexValue)
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexKeyEnd) return lexSkip(lx, lexKeyEnd)
case r == eof:
return lx.errorf("unexpected EOF; expected key separator %q", keySep)
case r == '.':
lx.ignore()
return lexKeyNameStart
case r == '=':
lx.emit(itemKeyEnd)
return lexSkip(lx, lexValue)
default: default:
return lx.errorf("expected key separator %q, but got %q instead", return lx.errorf("expected '.' or '=', but got %q instead", r)
keySep, r)
} }
} }
@ -450,10 +489,15 @@ func lexValue(lx *lexer) stateFn {
} }
lx.ignore() // ignore the "'" lx.ignore() // ignore the "'"
return lexRawString return lexRawString
case '+', '-':
return lexNumberStart
case '.': // special error case, be kind to users case '.': // special error case, be kind to users
return lx.errorf("floats must start with a digit, not '.'") return lx.errorf("floats must start with a digit, not '.'")
case 'i', 'n':
if (lx.accept('n') && lx.accept('f')) || (lx.accept('a') && lx.accept('n')) {
lx.emit(itemFloat)
return lx.pop()
}
case '-', '+':
return lexDecimalNumberStart
} }
if unicode.IsLetter(r) { if unicode.IsLetter(r) {
// Be permissive here; lexBool will give a nice error if the // Be permissive here; lexBool will give a nice error if the
@ -463,6 +507,9 @@ func lexValue(lx *lexer) stateFn {
lx.backup() lx.backup()
return lexBool return lexBool
} }
if r == eof {
return lx.errorf("unexpected EOF; expected value")
}
return lx.errorf("expected value but found %q instead", r) return lx.errorf("expected value but found %q instead", r)
} }
@ -507,9 +554,8 @@ func lexArrayValueEnd(lx *lexer) stateFn {
return lexArrayEnd return lexArrayEnd
} }
return lx.errorf( return lx.errorf(
"expected a comma or array terminator %q, but got %q instead", "expected a comma or array terminator %q, but got %s instead",
arrayEnd, r, arrayEnd, runeOrEOF(r))
)
} }
// lexArrayEnd finishes the lexing of an array. // lexArrayEnd finishes the lexing of an array.
@ -546,8 +592,7 @@ func lexInlineTableValue(lx *lexer) stateFn {
// key/value pair and the next pair (or the end of the table): // key/value pair and the next pair (or the end of the table):
// it ignores whitespace and expects either a ',' or a '}'. // it ignores whitespace and expects either a ',' or a '}'.
func lexInlineTableValueEnd(lx *lexer) stateFn { func lexInlineTableValueEnd(lx *lexer) stateFn {
r := lx.next() switch r := lx.next(); {
switch {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd) return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r): case isNL(r):
@ -557,12 +602,25 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
return lexCommentStart return lexCommentStart
case r == comma: case r == comma:
lx.ignore() lx.ignore()
lx.skip(isWhitespace)
if lx.peek() == '}' {
return lx.errorf("trailing comma not allowed in inline tables")
}
return lexInlineTableValue return lexInlineTableValue
case r == inlineTableEnd: case r == inlineTableEnd:
return lexInlineTableEnd return lexInlineTableEnd
default:
return lx.errorf(
"expected a comma or an inline table terminator %q, but got %s instead",
inlineTableEnd, runeOrEOF(r))
}
}
func runeOrEOF(r rune) string {
if r == eof {
return "end of file"
} }
return lx.errorf("expected a comma or an inline table terminator %q, "+ return "'" + string(r) + "'"
"but got %q instead", inlineTableEnd, r)
} }
// lexInlineTableEnd finishes the lexing of an inline table. // lexInlineTableEnd finishes the lexing of an inline table.
@ -579,7 +637,9 @@ func lexString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof: case r == eof:
return lx.errorf("unexpected EOF") return lx.errorf(`unexpected EOF; expected '"'`)
case isControl(r) || r == '\r':
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
case isNL(r): case isNL(r):
return lx.errorf("strings cannot contain newlines") return lx.errorf("strings cannot contain newlines")
case r == '\\': case r == '\\':
@ -598,19 +658,40 @@ func lexString(lx *lexer) stateFn {
// lexMultilineString consumes the inner contents of a string. It assumes that // lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored. // the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn { func lexMultilineString(lx *lexer) stateFn {
switch lx.next() { r := lx.next()
switch r {
case eof: case eof:
return lx.errorf("unexpected EOF") return lx.errorf(`unexpected EOF; expected '"""'`)
case '\r':
if lx.peek() != '\n' {
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
}
return lexMultilineString
case '\\': case '\\':
return lexMultilineStringEscape return lexMultilineStringEscape
case stringEnd: case stringEnd:
/// Found " → try to read two more "".
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
if lx.accept(stringEnd) { if lx.accept(stringEnd) {
lx.backup() /// Peek ahead: the string can contain " and "", including at the
/// end: """str"""""
/// 6 or more at the end, however, is an error.
if lx.peek() == stringEnd {
/// Check if we already lexed 5 's; if so we have 6 now, and
/// that's just too many man!
if strings.HasSuffix(lx.current(), `"""""`) {
return lx.errorf(`unexpected '""""""'`)
}
lx.backup()
lx.backup()
return lexMultilineString
}
lx.backup() /// backup: don't include the """ in the item.
lx.backup() lx.backup()
lx.backup() lx.backup()
lx.emit(itemMultilineString) lx.emit(itemMultilineString)
lx.next() lx.next() /// Read over ''' again and discard it.
lx.next() lx.next()
lx.next() lx.next()
lx.ignore() lx.ignore()
@ -619,6 +700,10 @@ func lexMultilineString(lx *lexer) stateFn {
lx.backup() lx.backup()
} }
} }
if isControl(r) {
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
}
return lexMultilineString return lexMultilineString
} }
@ -628,7 +713,9 @@ func lexRawString(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case r == eof: case r == eof:
return lx.errorf("unexpected EOF") return lx.errorf(`unexpected EOF; expected "'"`)
case isControl(r) || r == '\r':
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
case isNL(r): case isNL(r):
return lx.errorf("strings cannot contain newlines") return lx.errorf("strings cannot contain newlines")
case r == rawStringEnd: case r == rawStringEnd:
@ -645,17 +732,38 @@ func lexRawString(lx *lexer) stateFn {
// a string. It assumes that the beginning "'''" has already been consumed and // a string. It assumes that the beginning "'''" has already been consumed and
// ignored. // ignored.
func lexMultilineRawString(lx *lexer) stateFn { func lexMultilineRawString(lx *lexer) stateFn {
switch lx.next() { r := lx.next()
switch r {
case eof: case eof:
return lx.errorf("unexpected EOF") return lx.errorf(`unexpected EOF; expected "'''"`)
case '\r':
if lx.peek() != '\n' {
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
}
return lexMultilineRawString
case rawStringEnd: case rawStringEnd:
/// Found ' → try to read two more ''.
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) { if lx.accept(rawStringEnd) {
lx.backup() /// Peek ahead: the string can contain ' and '', including at the
/// end: '''str'''''
/// 6 or more at the end, however, is an error.
if lx.peek() == rawStringEnd {
/// Check if we already lexed 5 's; if so we have 6 now, and
/// that's just too many man!
if strings.HasSuffix(lx.current(), "'''''") {
return lx.errorf(`unexpected "''''''"`)
}
lx.backup()
lx.backup()
return lexMultilineRawString
}
lx.backup() /// backup: don't include the ''' in the item.
lx.backup() lx.backup()
lx.backup() lx.backup()
lx.emit(itemRawMultilineString) lx.emit(itemRawMultilineString)
lx.next() lx.next() /// Read over ''' again and discard it.
lx.next() lx.next()
lx.next() lx.next()
lx.ignore() lx.ignore()
@ -664,6 +772,10 @@ func lexMultilineRawString(lx *lexer) stateFn {
lx.backup() lx.backup()
} }
} }
if isControl(r) {
return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r)
}
return lexMultilineRawString return lexMultilineRawString
} }
@ -694,6 +806,10 @@ func lexStringEscape(lx *lexer) stateFn {
fallthrough fallthrough
case '"': case '"':
fallthrough fallthrough
case ' ', '\t':
// Inside """ .. """ strings you can use \ to escape newlines, and any
// amount of whitespace can be between the \ and \n.
fallthrough
case '\\': case '\\':
return lx.pop() return lx.pop()
case 'u': case 'u':
@ -701,8 +817,7 @@ func lexStringEscape(lx *lexer) stateFn {
case 'U': case 'U':
return lexLongUnicodeEscape return lexLongUnicodeEscape
} }
return lx.errorf("invalid escape character %q; only the following "+ return lx.errorf("invalid escape character %q; only the following escape characters are allowed: "+
"escape characters are allowed: "+
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
} }
@ -711,8 +826,9 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf(`expected four hexadecimal digits after '\u', `+ return lx.errorf(
"but got %q instead", lx.current()) `expected four hexadecimal digits after '\u', but got %q instead`,
lx.current())
} }
} }
return lx.pop() return lx.pop()
@ -723,28 +839,33 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
r = lx.next() r = lx.next()
if !isHexadecimal(r) { if !isHexadecimal(r) {
return lx.errorf(`expected eight hexadecimal digits after '\U', `+ return lx.errorf(
"but got %q instead", lx.current()) `expected eight hexadecimal digits after '\U', but got %q instead`,
lx.current())
} }
} }
return lx.pop() return lx.pop()
} }
// lexNumberOrDateStart consumes either an integer, a float, or datetime. // lexNumberOrDateStart processes the first character of a value which begins
// with a digit. It exists to catch values starting with '0', so that
// lexBaseNumberOrDate can differentiate base prefixed integers from other
// types.
func lexNumberOrDateStart(lx *lexer) stateFn { func lexNumberOrDateStart(lx *lexer) stateFn {
r := lx.next() r := lx.next()
if isDigit(r) {
return lexNumberOrDate
}
switch r { switch r {
case '_': case '0':
return lexNumber return lexBaseNumberOrDate
case 'e', 'E':
return lexFloat
case '.':
return lx.errorf("floats must start with a digit, not '.'")
} }
return lx.errorf("expected a digit but got %q", r)
if !isDigit(r) {
// The only way to reach this state is if the value starts
// with a digit, so specifically treat anything else as an
// error.
return lx.errorf("expected a digit but got %q", r)
}
return lexNumberOrDate
} }
// lexNumberOrDate consumes either an integer, float or datetime. // lexNumberOrDate consumes either an integer, float or datetime.
@ -754,10 +875,10 @@ func lexNumberOrDate(lx *lexer) stateFn {
return lexNumberOrDate return lexNumberOrDate
} }
switch r { switch r {
case '-': case '-', ':':
return lexDatetime return lexDatetime
case '_': case '_':
return lexNumber return lexDecimalNumber
case '.', 'e', 'E': case '.', 'e', 'E':
return lexFloat return lexFloat
} }
@ -775,41 +896,156 @@ func lexDatetime(lx *lexer) stateFn {
return lexDatetime return lexDatetime
} }
switch r { switch r {
case '-', 'T', ':', '.', 'Z', '+': case '-', ':', 'T', 't', ' ', '.', 'Z', 'z', '+':
return lexDatetime return lexDatetime
} }
lx.backup() lx.backup()
lx.emit(itemDatetime) lx.emitTrim(itemDatetime)
return lx.pop() return lx.pop()
} }
// lexNumberStart consumes either an integer or a float. It assumes that a sign // lexHexInteger consumes a hexadecimal integer after seeing the '0x' prefix.
// has already been read, but that *no* digits have been consumed. func lexHexInteger(lx *lexer) stateFn {
// lexNumberStart will move to the appropriate integer or float states.
func lexNumberStart(lx *lexer) stateFn {
// We MUST see a digit. Even floats have to start with a digit.
r := lx.next() r := lx.next()
if !isDigit(r) { if isHexadecimal(r) {
if r == '.' { return lexHexInteger
return lx.errorf("floats must start with a digit, not '.'") }
switch r {
case '_':
return lexHexInteger
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexOctalInteger consumes an octal integer after seeing the '0o' prefix.
func lexOctalInteger(lx *lexer) stateFn {
r := lx.next()
if isOctal(r) {
return lexOctalInteger
}
switch r {
case '_':
return lexOctalInteger
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexBinaryInteger consumes a binary integer after seeing the '0b' prefix.
func lexBinaryInteger(lx *lexer) stateFn {
r := lx.next()
if isBinary(r) {
return lexBinaryInteger
}
switch r {
case '_':
return lexBinaryInteger
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexDecimalNumber consumes a decimal float or integer.
func lexDecimalNumber(lx *lexer) stateFn {
r := lx.next()
if isDigit(r) {
return lexDecimalNumber
}
switch r {
case '.', 'e', 'E':
return lexFloat
case '_':
return lexDecimalNumber
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexDecimalNumber consumes the first digit of a number beginning with a sign.
// It assumes the sign has already been consumed. Values which start with a sign
// are only allowed to be decimal integers or floats.
//
// The special "nan" and "inf" values are also recognized.
func lexDecimalNumberStart(lx *lexer) stateFn {
r := lx.next()
// Special error cases to give users better error messages
switch r {
case 'i':
if !lx.accept('n') || !lx.accept('f') {
return lx.errorf("invalid float: '%s'", lx.current())
} }
return lx.errorf("expected a digit but got %q", r) lx.emit(itemFloat)
return lx.pop()
case 'n':
if !lx.accept('a') || !lx.accept('n') {
return lx.errorf("invalid float: '%s'", lx.current())
}
lx.emit(itemFloat)
return lx.pop()
case '0':
p := lx.peek()
switch p {
case 'b', 'o', 'x':
return lx.errorf("cannot use sign with non-decimal numbers: '%s%c'", lx.current(), p)
}
case '.':
return lx.errorf("floats must start with a digit, not '.'")
}
if isDigit(r) {
return lexDecimalNumber
} }
return lexNumber
return lx.errorf("expected a digit but got %q", r)
} }
// lexNumber consumes an integer or a float after seeing the first digit. // lexBaseNumberOrDate differentiates between the possible values which
func lexNumber(lx *lexer) stateFn { // start with '0'. It assumes that before reaching this state, the initial '0'
// has been consumed.
func lexBaseNumberOrDate(lx *lexer) stateFn {
r := lx.next() r := lx.next()
// Note: All datetimes start with at least two digits, so we don't
// handle date characters (':', '-', etc.) here.
if isDigit(r) { if isDigit(r) {
return lexNumber return lexNumberOrDate
} }
switch r { switch r {
case '_': case '_':
return lexNumber // Can only be decimal, because there can't be an underscore
// between the '0' and the base designator, and dates can't
// contain underscores.
return lexDecimalNumber
case '.', 'e', 'E': case '.', 'e', 'E':
return lexFloat return lexFloat
case 'b':
r = lx.peek()
if !isBinary(r) {
lx.errorf("not a binary number: '%s%c'", lx.current(), r)
}
return lexBinaryInteger
case 'o':
r = lx.peek()
if !isOctal(r) {
lx.errorf("not an octal number: '%s%c'", lx.current(), r)
}
return lexOctalInteger
case 'x':
r = lx.peek()
if !isHexadecimal(r) {
lx.errorf("not a hexidecimal number: '%s%c'", lx.current(), r)
}
return lexHexInteger
} }
lx.backup() lx.backup()
@ -867,21 +1103,22 @@ func lexCommentStart(lx *lexer) stateFn {
// It will consume *up to* the first newline character, and pass control // It will consume *up to* the first newline character, and pass control
// back to the last state on the stack. // back to the last state on the stack.
func lexComment(lx *lexer) stateFn { func lexComment(lx *lexer) stateFn {
r := lx.peek() switch r := lx.next(); {
if isNL(r) || r == eof { case isNL(r) || r == eof:
lx.backup()
lx.emit(itemText) lx.emit(itemText)
return lx.pop() return lx.pop()
case isControl(r):
return lx.errorf("control characters are not allowed inside comments: '0x%02x'", r)
default:
return lexComment
} }
lx.next()
return lexComment
} }
// lexSkip ignores all slurped input and moves on to the next state. // lexSkip ignores all slurped input and moves on to the next state.
func lexSkip(lx *lexer, nextState stateFn) stateFn { func lexSkip(lx *lexer, nextState stateFn) stateFn {
return func(lx *lexer) stateFn { lx.ignore()
lx.ignore() return nextState
return nextState
}
} }
// isWhitespace returns true if `r` is a whitespace character according // isWhitespace returns true if `r` is a whitespace character according
@ -894,6 +1131,16 @@ func isNL(r rune) bool {
return r == '\n' || r == '\r' return r == '\n' || r == '\r'
} }
// Control characters except \n, \t
func isControl(r rune) bool {
switch r {
case '\t', '\r', '\n':
return false
default:
return (r >= 0x00 && r <= 0x1f) || r == 0x7f
}
}
func isDigit(r rune) bool { func isDigit(r rune) bool {
return r >= '0' && r <= '9' return r >= '0' && r <= '9'
} }
@ -904,6 +1151,14 @@ func isHexadecimal(r rune) bool {
(r >= 'A' && r <= 'F') (r >= 'A' && r <= 'F')
} }
func isOctal(r rune) bool {
return r >= '0' && r <= '7'
}
func isBinary(r rune) bool {
return r == '0' || r == '1'
}
func isBareKeyChar(r rune) bool { func isBareKeyChar(r rune) bool {
return (r >= 'A' && r <= 'Z') || return (r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') || (r >= 'a' && r <= 'z') ||
@ -912,6 +1167,17 @@ func isBareKeyChar(r rune) bool {
r == '-' r == '-'
} }
func (s stateFn) String() string {
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
if i := strings.LastIndexByte(name, '.'); i > -1 {
name = name[i+1:]
}
if s == nil {
name = "<nil>"
}
return name + "()"
}
func (itype itemType) String() string { func (itype itemType) String() string {
switch itype { switch itype {
case itemError: case itemError:
@ -938,12 +1204,18 @@ func (itype itemType) String() string {
return "TableEnd" return "TableEnd"
case itemKeyStart: case itemKeyStart:
return "KeyStart" return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemArray: case itemArray:
return "Array" return "Array"
case itemArrayEnd: case itemArrayEnd:
return "ArrayEnd" return "ArrayEnd"
case itemCommentStart: case itemCommentStart:
return "CommentStart" return "CommentStart"
case itemInlineTableStart:
return "InlineTableStart"
case itemInlineTableEnd:
return "InlineTableEnd"
} }
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
} }

@ -1,12 +1,14 @@
package toml package toml
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"unicode/utf8" "unicode/utf8"
"github.com/BurntSushi/toml/internal"
) )
type parser struct { type parser struct {
@ -14,39 +16,54 @@ type parser struct {
types map[string]tomlType types map[string]tomlType
lx *lexer lx *lexer
// A list of keys in the order that they appear in the TOML data. ordered []Key // List of keys in the order that they appear in the TOML data.
ordered []Key context Key // Full key for the current hash in scope.
currentKey string // Base key name for everything except hashes.
// the full key for the current hash in scope approxLine int // Rough approximation of line number
context Key implicits map[string]bool // Record implied keys (e.g. 'key.group.names').
// the base key name for everything except hashes
currentKey string
// rough approximation of line number
approxLine int
// A map of 'key.group.names' to whether they were created implicitly.
implicits map[string]bool
} }
type parseError string // ParseError is used when a file can't be parsed: for example invalid integer
// literals, duplicate keys, etc.
type ParseError struct {
Message string
Line int
LastKey string
}
func (pe parseError) Error() string { func (pe ParseError) Error() string {
return string(pe) return fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
pe.Line, pe.LastKey, pe.Message)
} }
func parse(data string) (p *parser, err error) { func parse(data string) (p *parser, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
var ok bool var ok bool
if err, ok = r.(parseError); ok { if err, ok = r.(ParseError); ok {
return return
} }
panic(r) panic(r)
} }
}() }()
// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
// which mangles stuff.
if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") {
data = data[2:]
}
// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
// file (second byte in surrogate pair being NULL). Again, do this here to
// avoid having to deal with UTF-8/16 stuff in the lexer.
ex := 6
if len(data) < 6 {
ex = len(data)
}
if strings.ContainsRune(data[:ex], 0) {
return nil, errors.New("files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8")
}
p = &parser{ p = &parser{
mapping: make(map[string]interface{}), mapping: make(map[string]interface{}),
types: make(map[string]tomlType), types: make(map[string]tomlType),
@ -66,13 +83,17 @@ func parse(data string) (p *parser, err error) {
} }
func (p *parser) panicf(format string, v ...interface{}) { func (p *parser) panicf(format string, v ...interface{}) {
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", msg := fmt.Sprintf(format, v...)
p.approxLine, p.current(), fmt.Sprintf(format, v...)) panic(ParseError{
panic(parseError(msg)) Message: msg,
Line: p.approxLine,
LastKey: p.current(),
})
} }
func (p *parser) next() item { func (p *parser) next() item {
it := p.lx.nextItem() it := p.lx.nextItem()
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.line, it.val)
if it.typ == itemError { if it.typ == itemError {
p.panicf("%s", it.val) p.panicf("%s", it.val)
} }
@ -97,44 +118,63 @@ func (p *parser) assertEqual(expected, got itemType) {
func (p *parser) topLevel(item item) { func (p *parser) topLevel(item item) {
switch item.typ { switch item.typ {
case itemCommentStart: case itemCommentStart: // # ..
p.approxLine = item.line p.approxLine = item.line
p.expect(itemText) p.expect(itemText)
case itemTableStart: case itemTableStart: // [ .. ]
kg := p.next() name := p.next()
p.approxLine = kg.line p.approxLine = name.line
var key Key var key Key
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
key = append(key, p.keyString(kg)) key = append(key, p.keyString(name))
} }
p.assertEqual(itemTableEnd, kg.typ) p.assertEqual(itemTableEnd, name.typ)
p.establishContext(key, false) p.addContext(key, false)
p.setType("", tomlHash) p.setType("", tomlHash)
p.ordered = append(p.ordered, key) p.ordered = append(p.ordered, key)
case itemArrayTableStart: case itemArrayTableStart: // [[ .. ]]
kg := p.next() name := p.next()
p.approxLine = kg.line p.approxLine = name.line
var key Key var key Key
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
key = append(key, p.keyString(kg)) key = append(key, p.keyString(name))
} }
p.assertEqual(itemArrayTableEnd, kg.typ) p.assertEqual(itemArrayTableEnd, name.typ)
p.establishContext(key, true) p.addContext(key, true)
p.setType("", tomlArrayHash) p.setType("", tomlArrayHash)
p.ordered = append(p.ordered, key) p.ordered = append(p.ordered, key)
case itemKeyStart: case itemKeyStart: // key = ..
kname := p.next() outerContext := p.context
p.approxLine = kname.line /// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
p.currentKey = p.keyString(kname) k := p.next()
p.approxLine = k.line
val, typ := p.value(p.next()) var key Key
p.setValue(p.currentKey, val) for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
p.setType(p.currentKey, typ) key = append(key, p.keyString(k))
}
p.assertEqual(itemKeyEnd, k.typ)
/// The current key is the last part.
p.currentKey = key[len(key)-1]
/// All the other parts (if any) are the context; need to set each part
/// as implicit.
context := key[:len(key)-1]
for i := range context {
p.addImplicitContext(append(p.context, context[i:i+1]...))
}
/// Set value.
val, typ := p.value(p.next(), false)
p.set(p.currentKey, val, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey)) p.ordered = append(p.ordered, p.context.add(p.currentKey))
/// Remove the context we added (preserving any context from [tbl] lines).
p.context = outerContext
p.currentKey = "" p.currentKey = ""
default: default:
p.bug("Unexpected type at top level: %s", item.typ) p.bug("Unexpected type at top level: %s", item.typ)
@ -148,180 +188,253 @@ func (p *parser) keyString(it item) string {
return it.val return it.val
case itemString, itemMultilineString, case itemString, itemMultilineString,
itemRawString, itemRawMultilineString: itemRawString, itemRawMultilineString:
s, _ := p.value(it) s, _ := p.value(it, false)
return s.(string) return s.(string)
default: default:
p.bug("Unexpected key type: %s", it.typ) p.bug("Unexpected key type: %s", it.typ)
panic("unreachable")
} }
panic("unreachable")
} }
var datetimeRepl = strings.NewReplacer(
"z", "Z",
"t", "T",
" ", "T")
// value translates an expected value from the lexer into a Go value wrapped // value translates an expected value from the lexer into a Go value wrapped
// as an empty interface. // as an empty interface.
func (p *parser) value(it item) (interface{}, tomlType) { func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
switch it.typ { switch it.typ {
case itemString: case itemString:
return p.replaceEscapes(it.val), p.typeOfPrimitive(it) return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
case itemMultilineString: case itemMultilineString:
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) return p.replaceEscapes(stripFirstNewline(stripEscapedNewlines(it.val))), p.typeOfPrimitive(it)
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
case itemRawString: case itemRawString:
return it.val, p.typeOfPrimitive(it) return it.val, p.typeOfPrimitive(it)
case itemRawMultilineString: case itemRawMultilineString:
return stripFirstNewline(it.val), p.typeOfPrimitive(it) return stripFirstNewline(it.val), p.typeOfPrimitive(it)
case itemInteger:
return p.valueInteger(it)
case itemFloat:
return p.valueFloat(it)
case itemBool: case itemBool:
switch it.val { switch it.val {
case "true": case "true":
return true, p.typeOfPrimitive(it) return true, p.typeOfPrimitive(it)
case "false": case "false":
return false, p.typeOfPrimitive(it) return false, p.typeOfPrimitive(it)
default:
p.bug("Expected boolean value, but got '%s'.", it.val)
} }
p.bug("Expected boolean value, but got '%s'.", it.val) case itemDatetime:
case itemInteger: return p.valueDatetime(it)
if !numUnderscoresOK(it.val) { case itemArray:
p.panicf("Invalid integer %q: underscores must be surrounded by digits", return p.valueArray(it)
it.val) case itemInlineTableStart:
} return p.valueInlineTable(it, parentIsArray)
val := strings.Replace(it.val, "_", "", -1) default:
num, err := strconv.ParseInt(val, 10, 64) p.bug("Unexpected value type: %s", it.typ)
if err != nil { }
// Distinguish integer values. Normally, it'd be a bug if the lexer panic("unreachable")
// provides an invalid integer, but it's possible that the number is }
// out of range of valid values (which the lexer cannot determine).
// So mark the former as a bug but the latter as a legitimate user func (p *parser) valueInteger(it item) (interface{}, tomlType) {
// error. if !numUnderscoresOK(it.val) {
if e, ok := err.(*strconv.NumError); ok && p.panicf("Invalid integer %q: underscores must be surrounded by digits", it.val)
e.Err == strconv.ErrRange { }
if numHasLeadingZero(it.val) {
p.panicf("Integer '%s' is out of the range of 64-bit "+ p.panicf("Invalid integer %q: cannot have leading zeroes", it.val)
"signed integers.", it.val) }
} else {
p.bug("Expected integer value, but got '%s'.", it.val) num, err := strconv.ParseInt(it.val, 0, 64)
} if err != nil {
// Distinguish integer values. Normally, it'd be a bug if the lexer
// provides an invalid integer, but it's possible that the number is
// out of range of valid values (which the lexer cannot determine).
// So mark the former as a bug but the latter as a legitimate user
// error.
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
p.panicf("Integer '%s' is out of the range of 64-bit signed integers.", it.val)
} else {
p.bug("Expected integer value, but got '%s'.", it.val)
} }
return num, p.typeOfPrimitive(it) }
case itemFloat: return num, p.typeOfPrimitive(it)
parts := strings.FieldsFunc(it.val, func(r rune) bool { }
switch r {
case '.', 'e', 'E': func (p *parser) valueFloat(it item) (interface{}, tomlType) {
return true parts := strings.FieldsFunc(it.val, func(r rune) bool {
} switch r {
return false case '.', 'e', 'E':
}) return true
for _, part := range parts {
if !numUnderscoresOK(part) {
p.panicf("Invalid float %q: underscores must be "+
"surrounded by digits", it.val)
}
} }
if !numPeriodsOK(it.val) { return false
// As a special case, numbers like '123.' or '1.e2', })
// which are valid as far as Go/strconv are concerned, for _, part := range parts {
// must be rejected because TOML says that a fractional if !numUnderscoresOK(part) {
// part consists of '.' followed by 1+ digits. p.panicf("Invalid float %q: underscores must be surrounded by digits", it.val)
p.panicf("Invalid float %q: '.' must be followed "+
"by one or more digits", it.val)
}
val := strings.Replace(it.val, "_", "", -1)
num, err := strconv.ParseFloat(val, 64)
if err != nil {
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
p.panicf("Float '%s' is out of the range of 64-bit "+
"IEEE-754 floating-point numbers.", it.val)
} else {
p.panicf("Invalid float value: %q", it.val)
}
} }
return num, p.typeOfPrimitive(it) }
case itemDatetime: if len(parts) > 0 && numHasLeadingZero(parts[0]) {
var t time.Time p.panicf("Invalid float %q: cannot have leading zeroes", it.val)
var ok bool }
var err error if !numPeriodsOK(it.val) {
for _, format := range []string{ // As a special case, numbers like '123.' or '1.e2',
"2006-01-02T15:04:05Z07:00", // which are valid as far as Go/strconv are concerned,
"2006-01-02T15:04:05", // must be rejected because TOML says that a fractional
"2006-01-02", // part consists of '.' followed by 1+ digits.
} { p.panicf("Invalid float %q: '.' must be followed by one or more digits", it.val)
t, err = time.ParseInLocation(format, it.val, time.Local) }
if err == nil { val := strings.Replace(it.val, "_", "", -1)
ok = true if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
break val = "nan"
} }
num, err := strconv.ParseFloat(val, 64)
if err != nil {
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
p.panicf("Float '%s' is out of the range of 64-bit IEEE-754 floating-point numbers.", it.val)
} else {
p.panicf("Invalid float value: %q", it.val)
} }
if !ok { }
p.panicf("Invalid TOML Datetime: %q.", it.val) return num, p.typeOfPrimitive(it)
}
var dtTypes = []struct {
fmt string
zone *time.Location
}{
{time.RFC3339Nano, time.Local},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
{"2006-01-02", internal.LocalDate},
{"15:04:05.999999999", internal.LocalTime},
}
func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
it.val = datetimeRepl.Replace(it.val)
var (
t time.Time
ok bool
err error
)
for _, dt := range dtTypes {
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
if err == nil {
ok = true
break
} }
return t, p.typeOfPrimitive(it) }
case itemArray: if !ok {
array := make([]interface{}, 0) p.panicf("Invalid TOML Datetime: %q.", it.val)
types := make([]tomlType, 0) }
return t, p.typeOfPrimitive(it)
}
for it = p.next(); it.typ != itemArrayEnd; it = p.next() { func (p *parser) valueArray(it item) (interface{}, tomlType) {
if it.typ == itemCommentStart { p.setType(p.currentKey, tomlArray)
p.expect(itemText)
continue // p.setType(p.currentKey, typ)
} var (
array []interface{}
types []tomlType
)
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}
val, typ := p.value(it, true)
array = append(array, val)
types = append(types, typ)
}
return array, tomlArray
}
func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) {
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)
p.context = append(p.context, p.currentKey)
prevContext := p.context
p.currentKey = ""
p.addImplicit(p.context)
p.addContext(p.context, parentIsArray)
val, typ := p.value(it) /// Loop over all table key/value pairs.
array = append(array, val) for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
types = append(types, typ) if it.typ == itemCommentStart {
p.expect(itemText)
continue
} }
return array, p.typeOfArray(types)
case itemInlineTableStart:
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)
p.context = append(p.context, p.currentKey) /// Read all key parts.
p.currentKey = "" k := p.next()
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { p.approxLine = k.line
if it.typ != itemKeyStart { var key Key
p.bug("Expected key start but instead found %q, around line %d", for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
it.val, p.approxLine) key = append(key, p.keyString(k))
} }
if it.typ == itemCommentStart { p.assertEqual(itemKeyEnd, k.typ)
p.expect(itemText)
continue
}
// retrieve key /// The current key is the last part.
k := p.next() p.currentKey = key[len(key)-1]
p.approxLine = k.line
kname := p.keyString(k)
// retrieve value /// All the other parts (if any) are the context; need to set each part
p.currentKey = kname /// as implicit.
val, typ := p.value(p.next()) context := key[:len(key)-1]
// make sure we keep metadata up to date for i := range context {
p.setType(kname, typ) p.addImplicitContext(append(p.context, context[i:i+1]...))
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[kname] = val
} }
p.context = outerContext
p.currentKey = outerKey /// Set the value.
return hash, tomlHash val, typ := p.value(p.next(), false)
p.set(p.currentKey, val, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[p.currentKey] = val
/// Restore context.
p.context = prevContext
} }
p.bug("Unexpected value type: %s", it.typ) p.context = outerContext
panic("unreachable") p.currentKey = outerKey
return hash, tomlHash
}
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
// +/- signs, and base prefixes.
func numHasLeadingZero(s string) bool {
if len(s) > 1 && s[0] == '0' && isDigit(rune(s[1])) { // >1 to allow "0" and isDigit to allow 0x
return true
}
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
return true
}
return false
} }
// numUnderscoresOK checks whether each underscore in s is surrounded by // numUnderscoresOK checks whether each underscore in s is surrounded by
// characters that are not underscores. // characters that are not underscores.
func numUnderscoresOK(s string) bool { func numUnderscoresOK(s string) bool {
switch s {
case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
return true
}
accept := false accept := false
for _, r := range s { for _, r := range s {
if r == '_' { if r == '_' {
if !accept { if !accept {
return false return false
} }
accept = false
continue
} }
accept = true
// isHexadecimal is a superset of all the permissable characters
// surrounding an underscore.
accept = isHexadecimal(r)
} }
return accept return accept
} }
@ -338,13 +451,12 @@ func numPeriodsOK(s string) bool {
return !period return !period
} }
// establishContext sets the current context of the parser, // Set the current context of the parser, where the context is either a hash or
// where the context is either a hash or an array of hashes. Which one is // an array of hashes, depending on the value of the `array` parameter.
// set depends on the value of the `array` parameter.
// //
// Establishing the context also makes sure that the key isn't a duplicate, and // Establishing the context also makes sure that the key isn't a duplicate, and
// will create implicit hashes automatically. // will create implicit hashes automatically.
func (p *parser) establishContext(key Key, array bool) { func (p *parser) addContext(key Key, array bool) {
var ok bool var ok bool
// Always start at the top level and drill down for our context. // Always start at the top level and drill down for our context.
@ -383,7 +495,7 @@ func (p *parser) establishContext(key Key, array bool) {
// list of tables for it. // list of tables for it.
k := key[len(key)-1] k := key[len(key)-1]
if _, ok := hashContext[k]; !ok { if _, ok := hashContext[k]; !ok {
hashContext[k] = make([]map[string]interface{}, 0, 5) hashContext[k] = make([]map[string]interface{}, 0, 4)
} }
// Add a new table. But make sure the key hasn't already been used // Add a new table. But make sure the key hasn't already been used
@ -391,8 +503,7 @@ func (p *parser) establishContext(key Key, array bool) {
if hash, ok := hashContext[k].([]map[string]interface{}); ok { if hash, ok := hashContext[k].([]map[string]interface{}); ok {
hashContext[k] = append(hash, make(map[string]interface{})) hashContext[k] = append(hash, make(map[string]interface{}))
} else { } else {
p.panicf("Key '%s' was already created and cannot be used as "+ p.panicf("Key '%s' was already created and cannot be used as an array.", keyContext)
"an array.", keyContext)
} }
} else { } else {
p.setValue(key[len(key)-1], make(map[string]interface{})) p.setValue(key[len(key)-1], make(map[string]interface{}))
@ -400,15 +511,22 @@ func (p *parser) establishContext(key Key, array bool) {
p.context = append(p.context, key[len(key)-1]) p.context = append(p.context, key[len(key)-1])
} }
// set calls setValue and setType.
func (p *parser) set(key string, val interface{}, typ tomlType) {
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ)
}
// setValue sets the given key to the given value in the current context. // setValue sets the given key to the given value in the current context.
// It will make sure that the key hasn't already been defined, account for // It will make sure that the key hasn't already been defined, account for
// implicit key groups. // implicit key groups.
func (p *parser) setValue(key string, value interface{}) { func (p *parser) setValue(key string, value interface{}) {
var tmpHash interface{} var (
var ok bool tmpHash interface{}
ok bool
hash := p.mapping hash = p.mapping
keyContext := make(Key, 0) keyContext Key
)
for _, k := range p.context { for _, k := range p.context {
keyContext = append(keyContext, k) keyContext = append(keyContext, k)
if tmpHash, ok = hash[k]; !ok { if tmpHash, ok = hash[k]; !ok {
@ -422,24 +540,26 @@ func (p *parser) setValue(key string, value interface{}) {
case map[string]interface{}: case map[string]interface{}:
hash = t hash = t
default: default:
p.bug("Expected hash to have type 'map[string]interface{}', but "+ p.panicf("Key '%s' has already been defined.", keyContext)
"it has '%T' instead.", tmpHash)
} }
} }
keyContext = append(keyContext, key) keyContext = append(keyContext, key)
if _, ok := hash[key]; ok { if _, ok := hash[key]; ok {
// Typically, if the given key has already been set, then we have // Normally redefining keys isn't allowed, but the key could have been
// to raise an error since duplicate keys are disallowed. However, // defined implicitly and it's allowed to be redefined concretely. (See
// it's possible that a key was previously defined implicitly. In this // the `valid/implicit-and-explicit-after.toml` in toml-test)
// case, it is allowed to be redefined concretely. (See the
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
// //
// But we have to make sure to stop marking it as an implicit. (So that // But we have to make sure to stop marking it as an implicit. (So that
// another redefinition provokes an error.) // another redefinition provokes an error.)
// //
// Note that since it has already been defined (as a hash), we don't // Note that since it has already been defined (as a hash), we don't
// want to overwrite it. So our business is done. // want to overwrite it. So our business is done.
if p.isArray(keyContext) {
p.removeImplicit(keyContext)
hash[key] = value
return
}
if p.isImplicit(keyContext) { if p.isImplicit(keyContext) {
p.removeImplicit(keyContext) p.removeImplicit(keyContext)
return return
@ -449,6 +569,7 @@ func (p *parser) setValue(key string, value interface{}) {
// key, which is *always* wrong. // key, which is *always* wrong.
p.panicf("Key '%s' has already been defined.", keyContext) p.panicf("Key '%s' has already been defined.", keyContext)
} }
hash[key] = value hash[key] = value
} }
@ -468,21 +589,15 @@ func (p *parser) setType(key string, typ tomlType) {
p.types[keyContext.String()] = typ p.types[keyContext.String()] = typ
} }
// addImplicit sets the given Key as having been created implicitly. // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
func (p *parser) addImplicit(key Key) { // "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
p.implicits[key.String()] = true func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = true }
} func (p *parser) removeImplicit(key Key) { p.implicits[key.String()] = false }
func (p *parser) isImplicit(key Key) bool { return p.implicits[key.String()] }
// removeImplicit stops tagging the given key as having been implicitly func (p *parser) isArray(key Key) bool { return p.types[key.String()] == tomlArray }
// created. func (p *parser) addImplicitContext(key Key) {
func (p *parser) removeImplicit(key Key) { p.addImplicit(key)
p.implicits[key.String()] = false p.addContext(key, false)
}
// isImplicit returns true if the key group pointed to by the key was created
// implicitly.
func (p *parser) isImplicit(key Key) bool {
return p.implicits[key.String()]
} }
// current returns the full key name of the current context. // current returns the full key name of the current context.
@ -497,20 +612,54 @@ func (p *parser) current() string {
} }
func stripFirstNewline(s string) string { func stripFirstNewline(s string) string {
if len(s) == 0 || s[0] != '\n' { if len(s) > 0 && s[0] == '\n' {
return s return s[1:]
}
if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
return s[2:]
} }
return s[1:] return s
} }
func stripEscapedWhitespace(s string) string { // Remove newlines inside triple-quoted strings if a line ends with "\".
esc := strings.Split(s, "\\\n") func stripEscapedNewlines(s string) string {
if len(esc) > 1 { split := strings.Split(s, "\n")
for i := 1; i < len(esc); i++ { if len(split) < 1 {
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) return s
}
escNL := false // Keep track of the last non-blank line was escaped.
for i, line := range split {
line = strings.TrimRight(line, " \t\r")
if len(line) == 0 || line[len(line)-1] != '\\' {
split[i] = strings.TrimRight(split[i], "\r")
if !escNL && i != len(split)-1 {
split[i] += "\n"
}
continue
}
escBS := true
for j := len(line) - 1; j >= 0 && line[j] == '\\'; j-- {
escBS = !escBS
}
if escNL {
line = strings.TrimLeft(line, " \t\r")
}
escNL = !escBS
if escBS {
split[i] += "\n"
continue
}
split[i] = line[:len(line)-1] // Remove \
if len(split)-1 > i {
split[i+1] = strings.TrimLeft(split[i+1], " \t\r")
} }
} }
return strings.Join(esc, "") return strings.Join(split, "")
} }
func (p *parser) replaceEscapes(str string) string { func (p *parser) replaceEscapes(str string) string {
@ -533,6 +682,9 @@ func (p *parser) replaceEscapes(str string) string {
default: default:
p.bug("Expected valid escape code after \\, but got %q.", s[r]) p.bug("Expected valid escape code after \\, but got %q.", s[r])
return "" return ""
case ' ', '\t':
p.panicf("invalid escape: '\\%c'", s[r])
return ""
case 'b': case 'b':
replaced = append(replaced, rune(0x0008)) replaced = append(replaced, rune(0x0008))
r += 1 r += 1
@ -585,8 +737,3 @@ func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
} }
return rune(hex) return rune(hex)
} }
func isStringType(ty itemType) bool {
return ty == itemString || ty == itemMultilineString ||
ty == itemRawString || ty == itemRawMultilineString
}

@ -1 +0,0 @@
au BufWritePost *.go silent!make tags > /dev/null 2>&1

@ -68,24 +68,3 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType {
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
panic("unreachable") panic("unreachable")
} }
// typeOfArray returns a tomlType for an array given a list of types of its
// values.
//
// In the current spec, if an array is homogeneous, then its type is always
// "Array". If the array is not homogeneous, an error is generated.
func (p *parser) typeOfArray(types []tomlType) tomlType {
// Empty arrays are cool.
if len(types) == 0 {
return tomlArray
}
theType := types[0]
for _, t := range types[1:] {
if !typeEqual(theType, t) {
p.panicf("Array contains values of type '%s' and '%s', but "+
"arrays must be homogeneous.", theType, t)
}
}
return tomlArray
}

@ -1,15 +0,0 @@
## v1.1
### Added
- Cookies can be added to the HTTP request, either via the `Cookies` map or the `Cookie()` function
- Function `GetWithClient()` provides the ability to send the request with a custom HTTP client
- Function `FindStrict()` finds the first instance of the mentioned tag with the exact matching values of the provided attribute (previously `Find()`)
- Function `FindAllStrict()` finds all the instances of the mentioned tag with the exact matching values of the attributes (previously `FindAll()`)
## Changed
- Function `Find()` now finds the first instance of the mentioned tag with any matching values of the provided attribute.
- Function `FindAll()` now finds all the instances of the mentioned tag with any matching values of the provided attribute.
---

@ -7,31 +7,23 @@
*soup* is a small web scraper package for Go, with its interface highly similar to that of BeautifulSoup. *soup* is a small web scraper package for Go, with its interface highly similar to that of BeautifulSoup.
Exported variables and functions implemented till now : Functions implemented till now :
```go ```go
var Headers map[string]string // Set headers as a map of key-value pairs, an alternative to calling Header() individually func Get(string) (string,error) // Takes the url as an argument, returns HTML string
var Cookies map[string]string // Set cookies as a map of key-value pairs, an alternative to calling Cookie() individually func Header(string, string) // Takes key,value pair to set as headers for the HTTP request made in Get(), refer to PR #11 for more usage
func Get(string) (string,error){} // Takes the url as an argument, returns HTML string func HTMLParse(string) struct{} // Takes the HTML string as an argument, returns a pointer to the DOM constructed
func GetWithClient(string, *http.Client){} // Takes the url and a custom HTTP client as arguments, returns HTML string func Find([]string) struct{} // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned
func Header(string, string){} // Takes key,value pair to set as headers for the HTTP request made in Get() func FindAll([]string) []struct{} // Same as Find(), but pointers to all occurrences returned
func Cookie(string, string){} // Takes key, value pair to set as cookies to be sent with the HTTP request in Get() func FindNextSibling() struct{} // Pointer to the next sibling of the Element in the DOM returned
func HTMLParse(string) Root {} // Takes the HTML string as an argument, returns a pointer to the DOM constructed func FindNextElementSibling() struct{} // Pointer to the next element sibling of the Element in the DOM returned
func Find([]string) Root {} // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned func FindPrevSibling() struct{} // Pointer to the previous sibling of the Element in the DOM returned
func FindAll([]string) []Root {} // Same as Find(), but pointers to all occurrences returned func FindPrevElementSibling() struct{} // Pointer to the previous element sibling of the Element in the DOM returned
func FindStrict([]string) Root {} // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned with exact matching values func Attrs() map[string]string // Map returned with all the attributes of the Element as lookup to their respective values
func FindAllStrict([]string) []Root {} // Same as FindStrict(), but pointers to all occurrences returned func Text() string // Full text inside a non-nested tag returned
func FindNextSibling() Root {} // Pointer to the next sibling of the Element in the DOM returned func SetDebug(bool) // Sets the debug mode to true or false; false by default
func FindNextElementSibling() Root {} // Pointer to the next element sibling of the Element in the DOM returned
func FindPrevSibling() Root {} // Pointer to the previous sibling of the Element in the DOM returned
func FindPrevElementSibling() Root {} // Pointer to the previous element sibling of the Element in the DOM returned
func Children() []Root {} // Find all direct children of this DOM element
func Attrs() map[string]string {} // Map returned with all the attributes of the Element as lookup to their respective values
func Text() string {} // Full text inside a non-nested tag returned, first half returned in a non-nested one
func FullText() string {} // Full text inside a nested/non-nested tag returned
func SetDebug(bool) {} // Sets the debug mode to true or false; false by default
``` ```
`Root` is a struct, containing three fields : The struct returned by the functions has three fields :
* `Pointer` containing the pointer to the current html node * `Pointer` containing the pointer to the current html node
* `NodeValue` containing the current html node's value, i.e. the tag name for an ElementNode, or the text in case of a TextNode * `NodeValue` containing the current html node's value, i.e. the tag name for an ElementNode, or the text in case of a TextNode
* `Error` containing an error if one occurrs, else `nil` is returned. * `Error` containing an error if one occurrs, else `nil` is returned.
@ -69,4 +61,4 @@ func main() {
``` ```
## Contributions ## Contributions
This package was developed in my free time. However, contributions from everybody in the community are welcome, to make it a better web scraper. If you think there should be a particular feature or function included in the package, feel free to open up a new issue or pull request. This package was developed in my free time. However, contributions from everybody in the community are welcome, to make it a better web scraper. If you feel there should be a particular new feature or function in the package, feel free to open up a new issue or pull request.

@ -5,9 +5,9 @@ keeping it as similar as possible to BeautifulSoup
package soup package soup
import ( import (
"bytes"
"errors" "errors"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
@ -27,9 +27,6 @@ var debug = false
// Headers contains all HTTP headers to send // Headers contains all HTTP headers to send
var Headers = make(map[string]string) var Headers = make(map[string]string)
// Cookies contains all HTTP cookies to send
var Cookies = make(map[string]string)
// SetDebug sets the debug status // SetDebug sets the debug status
// Setting this to true causes the panics to be thrown and logged onto the console. // Setting this to true causes the panics to be thrown and logged onto the console.
// Setting this to false causes the errors to be saved in the Error field in the returned struct. // Setting this to false causes the errors to be saved in the Error field in the returned struct.
@ -42,37 +39,29 @@ func Header(n string, v string) {
Headers[n] = v Headers[n] = v
} }
func Cookie(n string, v string) { // Get returns the HTML returned by the url in string
Cookies[n] = v func Get(url string) (string, error) {
} defer catchPanic("Get()")
// Init a new HTTP client
// GetWithClient returns the HTML returned by the url using a provided HTTP client client := &http.Client{}
func GetWithClient(url string, client *http.Client) (string, error) {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
if debug { if debug {
panic("Couldn't perform GET request to " + url) panic("Couldn't perform GET request to " + url)
} }
return "", errors.New("couldn't perform GET request to " + url) return "", errors.New("Couldn't perform GET request to " + url)
} }
// Set headers // Set headers
for hName, hValue := range Headers { for hName, hValue := range Headers {
req.Header.Set(hName, hValue) req.Header.Set(hName, hValue)
} }
// Set cookies
for cName, cValue := range Cookies {
req.AddCookie(&http.Cookie{
Name: cName,
Value: cValue,
})
}
// Perform request // Perform request
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
if debug { if debug {
panic("Couldn't perform GET request to " + url) panic("Couldn't perform GET request to " + url)
} }
return "", errors.New("couldn't perform GET request to " + url) return "", errors.New("Couldn't perform GET request to " + url)
} }
defer resp.Body.Close() defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body) bytes, err := ioutil.ReadAll(resp.Body)
@ -80,26 +69,20 @@ func GetWithClient(url string, client *http.Client) (string, error) {
if debug { if debug {
panic("Unable to read the response body") panic("Unable to read the response body")
} }
return "", errors.New("unable to read the response body") return "", errors.New("Unable to read the response body")
} }
return string(bytes), nil return string(bytes), nil
} }
// Get returns the HTML returned by the url in string using the default HTTP client
func Get(url string) (string, error) {
// Init a new HTTP client
client := &http.Client{}
return GetWithClient(url, client)
}
// HTMLParse parses the HTML returning a start pointer to the DOM // HTMLParse parses the HTML returning a start pointer to the DOM
func HTMLParse(s string) Root { func HTMLParse(s string) Root {
defer catchPanic("HTMLParse()")
r, err := html.Parse(strings.NewReader(s)) r, err := html.Parse(strings.NewReader(s))
if err != nil { if err != nil {
if debug { if debug {
panic("Unable to parse the HTML") panic("Unable to parse the HTML")
} }
return Root{nil, "", errors.New("unable to parse the HTML")} return Root{nil, "", errors.New("Unable to parse the HTML")}
} }
for r.Type != html.ElementNode { for r.Type != html.ElementNode {
switch r.Type { switch r.Type {
@ -118,12 +101,13 @@ func HTMLParse(s string) Root {
// with or without attribute key and value specified, // with or without attribute key and value specified,
// and returns a struct with a pointer to it // and returns a struct with a pointer to it
func (r Root) Find(args ...string) Root { func (r Root) Find(args ...string) Root {
temp, ok := findOnce(r.Pointer, args, false, false) defer catchPanic("Find()")
temp, ok := findOnce(r.Pointer, args, false)
if ok == false { if ok == false {
if debug { if debug {
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found") panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
} }
return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")} return Root{nil, "", errors.New("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
} }
return Root{temp, temp.Data, nil} return Root{temp, temp.Data, nil}
} }
@ -133,44 +117,15 @@ func (r Root) Find(args ...string) Root {
// and returns an array of structs, each having // and returns an array of structs, each having
// the respective pointers // the respective pointers
func (r Root) FindAll(args ...string) []Root { func (r Root) FindAll(args ...string) []Root {
temp := findAllofem(r.Pointer, args, false) defer catchPanic("FindAll()")
if len(temp) == 0 { temp := findAllofem(r.Pointer, args)
if debug {
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
}
return []Root{}
}
pointers := make([]Root, 0, len(temp))
for i := 0; i < len(temp); i++ {
pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
}
return pointers
}
// FindStrict finds the first occurrence of the given tag name
// only if all the values of the provided attribute are an exact match
func (r Root) FindStrict(args ...string) Root {
temp, ok := findOnce(r.Pointer, args, false, true)
if ok == false {
if debug {
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
}
return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
}
return Root{temp, temp.Data, nil}
}
// FindAllStrict finds all occurrences of the given tag name
// only if all the values of the provided attribute are an exact match
func (r Root) FindAllStrict(args ...string) []Root {
temp := findAllofem(r.Pointer, args, true)
if len(temp) == 0 { if len(temp) == 0 {
if debug { if debug {
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found") panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
} }
return []Root{} return []Root{}
} }
pointers := make([]Root, 0, len(temp)) pointers := make([]Root, 0, 10)
for i := 0; i < len(temp); i++ { for i := 0; i < len(temp); i++ {
pointers = append(pointers, Root{temp[i], temp[i].Data, nil}) pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
} }
@ -180,12 +135,13 @@ func (r Root) FindAllStrict(args ...string) []Root {
// FindNextSibling finds the next sibling of the pointer in the DOM // FindNextSibling finds the next sibling of the pointer in the DOM
// returning a struct with a pointer to it // returning a struct with a pointer to it
func (r Root) FindNextSibling() Root { func (r Root) FindNextSibling() Root {
defer catchPanic("FindNextSibling()")
nextSibling := r.Pointer.NextSibling nextSibling := r.Pointer.NextSibling
if nextSibling == nil { if nextSibling == nil {
if debug { if debug {
panic("No next sibling found") panic("No next sibling found")
} }
return Root{nil, "", errors.New("no next sibling found")} return Root{nil, "", errors.New("No next sibling found")}
} }
return Root{nextSibling, nextSibling.Data, nil} return Root{nextSibling, nextSibling.Data, nil}
} }
@ -193,12 +149,13 @@ func (r Root) FindNextSibling() Root {
// FindPrevSibling finds the previous sibling of the pointer in the DOM // FindPrevSibling finds the previous sibling of the pointer in the DOM
// returning a struct with a pointer to it // returning a struct with a pointer to it
func (r Root) FindPrevSibling() Root { func (r Root) FindPrevSibling() Root {
defer catchPanic("FindPrevSibling()")
prevSibling := r.Pointer.PrevSibling prevSibling := r.Pointer.PrevSibling
if prevSibling == nil { if prevSibling == nil {
if debug { if debug {
panic("No previous sibling found") panic("No previous sibling found")
} }
return Root{nil, "", errors.New("no previous sibling found")} return Root{nil, "", errors.New("No previous sibling found")}
} }
return Root{prevSibling, prevSibling.Data, nil} return Root{prevSibling, prevSibling.Data, nil}
} }
@ -206,12 +163,13 @@ func (r Root) FindPrevSibling() Root {
// FindNextElementSibling finds the next element sibling of the pointer in the DOM // FindNextElementSibling finds the next element sibling of the pointer in the DOM
// returning a struct with a pointer to it // returning a struct with a pointer to it
func (r Root) FindNextElementSibling() Root { func (r Root) FindNextElementSibling() Root {
defer catchPanic("FindNextElementSibling()")
nextSibling := r.Pointer.NextSibling nextSibling := r.Pointer.NextSibling
if nextSibling == nil { if nextSibling == nil {
if debug { if debug {
panic("No next element sibling found") panic("No next element sibling found")
} }
return Root{nil, "", errors.New("no next element sibling found")} return Root{nil, "", errors.New("No next element sibling found")}
} }
if nextSibling.Type == html.ElementNode { if nextSibling.Type == html.ElementNode {
return Root{nextSibling, nextSibling.Data, nil} return Root{nextSibling, nextSibling.Data, nil}
@ -223,12 +181,13 @@ func (r Root) FindNextElementSibling() Root {
// FindPrevElementSibling finds the previous element sibling of the pointer in the DOM // FindPrevElementSibling finds the previous element sibling of the pointer in the DOM
// returning a struct with a pointer to it // returning a struct with a pointer to it
func (r Root) FindPrevElementSibling() Root { func (r Root) FindPrevElementSibling() Root {
defer catchPanic("FindPrevElementSibling()")
prevSibling := r.Pointer.PrevSibling prevSibling := r.Pointer.PrevSibling
if prevSibling == nil { if prevSibling == nil {
if debug { if debug {
panic("No previous element sibling found") panic("No previous element sibling found")
} }
return Root{nil, "", errors.New("no previous element sibling found")} return Root{nil, "", errors.New("No previous element sibling found")}
} }
if prevSibling.Type == html.ElementNode { if prevSibling.Type == html.ElementNode {
return Root{prevSibling, prevSibling.Data, nil} return Root{prevSibling, prevSibling.Data, nil}
@ -237,19 +196,9 @@ func (r Root) FindPrevElementSibling() Root {
return p.FindPrevElementSibling() return p.FindPrevElementSibling()
} }
// Children retuns all direct children of this DOME element.
func (r Root) Children() []Root {
child := r.Pointer.FirstChild
var children []Root
for child != nil {
children = append(children, Root{child, child.Data, nil})
child = child.NextSibling
}
return children
}
// Attrs returns a map containing all attributes // Attrs returns a map containing all attributes
func (r Root) Attrs() map[string]string { func (r Root) Attrs() map[string]string {
defer catchPanic("Attrs()")
if r.Pointer.Type != html.ElementNode { if r.Pointer.Type != html.ElementNode {
if debug { if debug {
panic("Not an ElementNode") panic("Not an ElementNode")
@ -264,9 +213,10 @@ func (r Root) Attrs() map[string]string {
// Text returns the string inside a non-nested element // Text returns the string inside a non-nested element
func (r Root) Text() string { func (r Root) Text() string {
defer catchPanic("Text()")
k := r.Pointer.FirstChild k := r.Pointer.FirstChild
checkNode: checkNode:
if k != nil && k.Type != html.TextNode { if k.Type != html.TextNode {
k = k.NextSibling k = k.NextSibling
if k == nil { if k == nil {
if debug { if debug {
@ -293,39 +243,13 @@ checkNode:
return "" return ""
} }
// FullText returns the string inside even a nested element
func (r Root) FullText() string {
var buf bytes.Buffer
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.TextNode {
buf.WriteString(n.Data)
}
if n.Type == html.ElementNode {
f(n.FirstChild)
}
if n.NextSibling != nil {
f(n.NextSibling)
}
}
f(r.Pointer.FirstChild)
return buf.String()
}
// Using depth first search to find the first occurrence and return // Using depth first search to find the first occurrence and return
func findOnce(n *html.Node, args []string, uni bool, strict bool) (*html.Node, bool) { func findOnce(n *html.Node, args []string, uni bool) (*html.Node, bool) {
if uni == true { if uni == true {
if n.Type == html.ElementNode && n.Data == args[0] { if n.Type == html.ElementNode && n.Data == args[0] {
if len(args) > 1 && len(args) < 4 { if len(args) > 1 && len(args) < 4 {
for i := 0; i < len(n.Attr); i++ { for i := 0; i < len(n.Attr); i++ {
attr := n.Attr[i] if n.Attr[i].Key == args[1] && n.Attr[i].Val == args[2] {
searchAttrName := args[1]
searchAttrVal := args[2]
if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
(!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
return n, true return n, true
} }
} }
@ -336,7 +260,7 @@ func findOnce(n *html.Node, args []string, uni bool, strict bool) (*html.Node, b
} }
uni = true uni = true
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {
p, q := findOnce(c, args, true, strict) p, q := findOnce(c, args, true)
if q != false { if q != false {
return p, q return p, q
} }
@ -345,7 +269,7 @@ func findOnce(n *html.Node, args []string, uni bool, strict bool) (*html.Node, b
} }
// Using depth first search to find all occurrences and return // Using depth first search to find all occurrences and return
func findAllofem(n *html.Node, args []string, strict bool) []*html.Node { func findAllofem(n *html.Node, args []string) []*html.Node {
var nodeLinks = make([]*html.Node, 0, 10) var nodeLinks = make([]*html.Node, 0, 10)
var f func(*html.Node, []string, bool) var f func(*html.Node, []string, bool)
f = func(n *html.Node, args []string, uni bool) { f = func(n *html.Node, args []string, uni bool) {
@ -353,11 +277,7 @@ func findAllofem(n *html.Node, args []string, strict bool) []*html.Node {
if n.Data == args[0] { if n.Data == args[0] {
if len(args) > 1 && len(args) < 4 { if len(args) > 1 && len(args) < 4 {
for i := 0; i < len(n.Attr); i++ { for i := 0; i < len(n.Attr); i++ {
attr := n.Attr[i] if n.Attr[i].Key == args[1] && n.Attr[i].Val == args[2] {
searchAttrName := args[1]
searchAttrVal := args[2]
if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
(!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
nodeLinks = append(nodeLinks, n) nodeLinks = append(nodeLinks, n)
} }
} }
@ -375,25 +295,6 @@ func findAllofem(n *html.Node, args []string, strict bool) []*html.Node {
return nodeLinks return nodeLinks
} }
// attributeAndValueEquals reports when the html.Attribute attr has the same attribute name and value as from
// provided arguments
func attributeAndValueEquals(attr html.Attribute, attribute, value string) bool {
return attr.Key == attribute && attr.Val == value
}
// attributeContainsValue reports when the html.Attribute attr has the same attribute name as from provided
// attribute argument and compares if it has the same value in its values parameter
func attributeContainsValue(attr html.Attribute, attribute, value string) bool {
if attr.Key == attribute {
for _, attrVal := range strings.Fields(attr.Val) {
if attrVal == value {
return true
}
}
}
return false
}
// Returns a key pair value (like a dictionary) for each attribute // Returns a key pair value (like a dictionary) for each attribute
func getKeyValue(attributes []html.Attribute) map[string]string { func getKeyValue(attributes []html.Attribute) map[string]string {
var keyvalues = make(map[string]string) var keyvalues = make(map[string]string)
@ -405,3 +306,10 @@ func getKeyValue(attributes []html.Attribute) map[string]string {
} }
return keyvalues return keyvalues
} }
// Catch panics when they occur
func catchPanic(fnName string) {
if r := recover(); r != nil {
log.Println("Error occurred in", fnName, ":", r)
}
}

@ -1,3 +0,0 @@
module github.com/anmitsu/go-shlex
go 1.13

@ -1,10 +0,0 @@
module github.com/antonmedv/expr
go 1.13
require (
github.com/gdamore/tcell v1.3.0
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498
github.com/sanity-io/litter v1.2.0
github.com/stretchr/testify v1.5.1
)

@ -1,38 +0,0 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/rivo/tview v0.0.0-20200219210816-cd38d7432498 h1:4CFNy7/q7P06AsIONZzuWy7jcdqEmYQvOZ9FAFZdbls=
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M=
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=

@ -0,0 +1,17 @@
ARG GOVERSION=1.14
FROM golang:${GOVERSION}
# Set base env.
ARG GOOS=linux
ARG GOARCH=amd64
ENV GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
# Pre compile the stdlib for 386/arm (32bits).
RUN go build -a std
# Add the code to the image.
WORKDIR pty
ADD . .
# Build the lib.
RUN go build

@ -1,3 +1,4 @@
# NOTE: Using 1.13 as a base to build the RISCV compiler, the resulting version is based on go1.6.
FROM golang:1.13 FROM golang:1.13
# Clone and complie a riscv compatible version of the go compiler. # Clone and complie a riscv compatible version of the go compiler.
@ -8,7 +9,15 @@ ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
ENV GOROOT=/riscv-go ENV GOROOT=/riscv-go
# Make sure we compile. # Set the base env.
ENV GOOS=linux GOARCH=riscv CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
# Pre compile the stdlib.
RUN go build -a std
# Add the code to the image.
WORKDIR pty WORKDIR pty
ADD . . ADD . .
RUN GOOS=linux GOARCH=riscv go build
# Build the lib.
RUN go build

@ -4,9 +4,13 @@ Pty is a Go package for using unix pseudo-terminals.
## Install ## Install
go get github.com/creack/pty ```sh
go get github.com/creack/pty
```
## Examples
## Example Note that those examples are for demonstration purpose only, to showcase how to use the library. They are not meant to be used in any kind of production environment.
### Command ### Command
@ -14,10 +18,11 @@ Pty is a Go package for using unix pseudo-terminals.
package main package main
import ( import (
"github.com/creack/pty"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"github.com/creack/pty"
) )
func main() { func main() {
@ -51,7 +56,7 @@ import (
"syscall" "syscall"
"github.com/creack/pty" "github.com/creack/pty"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
) )
func test() error { func test() error {
@ -77,15 +82,17 @@ func test() error {
} }
}() }()
ch <- syscall.SIGWINCH // Initial resize. ch <- syscall.SIGWINCH // Initial resize.
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
// Set stdin in raw mode. // Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout. // Copy stdin to the pty and the pty to stdout.
// NOTE: The goroutine will keep reading until the next keystroke before returning.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }() go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, ptmx) _, _ = io.Copy(os.Stdout, ptmx)

@ -0,0 +1,18 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc
//+build gc
#include "textflag.h"
//
// System calls for amd64, Solaris are implemented in runtime/syscall_solaris.go
//
TEXT ·sysvicall6(SB),NOSPLIT,$0-88
JMP syscall·sysvicall6(SB)
TEXT ·rawSysvicall6(SB),NOSPLIT,$0-88
JMP syscall·rawSysvicall6(SB)

@ -10,7 +10,7 @@ import (
// available on the current platform. // available on the current platform.
var ErrUnsupported = errors.New("unsupported") var ErrUnsupported = errors.New("unsupported")
// Opens a pty and its corresponding tty. // Open a pty and its corresponding tty.
func Open() (pty, tty *os.File, err error) { func Open() (pty, tty *os.File, err error) {
return open() return open()
} }

@ -1,4 +0,0 @@
module github.com/creack/pty
go 1.13

@ -1,9 +1,15 @@
// +build !windows,!solaris //go:build !windows && !solaris
//+build !windows,!solaris
package pty package pty
import "syscall" import "syscall"
const (
TIOCGWINSZ = syscall.TIOCGWINSZ
TIOCSWINSZ = syscall.TIOCSWINSZ
)
func ioctl(fd, cmd, ptr uintptr) error { func ioctl(fd, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if e != 0 { if e != 0 {

@ -1,4 +1,5 @@
// +build darwin dragonfly freebsd netbsd openbsd //go:build (darwin || dragonfly || freebsd || netbsd || openbsd)
//+build darwin dragonfly freebsd netbsd openbsd
package pty package pty

@ -1,30 +1,48 @@
//go:build solaris
//+build solaris
package pty package pty
import ( import (
"golang.org/x/sys/unix" "syscall"
"unsafe" "unsafe"
) )
//go:cgo_import_dynamic libc_ioctl ioctl "libc.so"
//go:linkname procioctl libc_ioctl
var procioctl uintptr
const ( const (
// see /usr/include/sys/stropts.h // see /usr/include/sys/stropts.h
I_PUSH = uintptr((int32('S')<<8 | 002)) I_PUSH = uintptr((int32('S')<<8 | 002))
I_STR = uintptr((int32('S')<<8 | 010)) I_STR = uintptr((int32('S')<<8 | 010))
I_FIND = uintptr((int32('S')<<8 | 013)) I_FIND = uintptr((int32('S')<<8 | 013))
// see /usr/include/sys/ptms.h // see /usr/include/sys/ptms.h
ISPTM = (int32('P') << 8) | 1 ISPTM = (int32('P') << 8) | 1
UNLKPT = (int32('P') << 8) | 2 UNLKPT = (int32('P') << 8) | 2
PTSSTTY = (int32('P') << 8) | 3 PTSSTTY = (int32('P') << 8) | 3
ZONEPT = (int32('P') << 8) | 4 ZONEPT = (int32('P') << 8) | 4
OWNERPT = (int32('P') << 8) | 5 OWNERPT = (int32('P') << 8) | 5
// see /usr/include/sys/termios.h
TIOCSWINSZ = (uint32('T') << 8) | 103
TIOCGWINSZ = (uint32('T') << 8) | 104
) )
type strioctl struct { type strioctl struct {
ic_cmd int32 icCmd int32
ic_timout int32 icTimeout int32
ic_len int32 icLen int32
ic_dp unsafe.Pointer icDP unsafe.Pointer
} }
// Defined in asm_solaris_amd64.s.
func sysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func ioctl(fd, cmd, ptr uintptr) error { func ioctl(fd, cmd, ptr uintptr) error {
return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr)) if _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procioctl)), 3, fd, cmd, ptr, 0, 0, 0); errno != 0 {
return errno
}
return nil
} }

@ -13,7 +13,7 @@ GODEFS="go tool cgo -godefs"
$GODEFS types.go |gofmt > ztypes_$GOARCH.go $GODEFS types.go |gofmt > ztypes_$GOARCH.go
case $GOOS in case $GOOS in
freebsd|dragonfly|openbsd) freebsd|dragonfly|netbsd|openbsd)
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
;; ;;
esac esac

@ -1,3 +1,6 @@
//go:build darwin
//+build darwin
package pty package pty
import ( import (
@ -33,7 +36,7 @@ func open() (pty, tty *os.File, err error) {
return nil, nil, err return nil, nil, err
} }
t, err := os.OpenFile(sname, os.O_RDWR, 0) t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -1,3 +1,6 @@
//go:build dragonfly
//+build dragonfly
package pty package pty
import ( import (

@ -1,3 +1,6 @@
//go:build freebsd
//+build freebsd
package pty package pty
import ( import (

@ -1,3 +1,6 @@
//go:build linux
//+build linux
package pty package pty
import ( import (
@ -28,7 +31,7 @@ func open() (pty, tty *os.File, err error) {
return nil, nil, err return nil, nil, err
} }
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) //nolint:gosec // Expected Open from a variable.
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -37,7 +40,7 @@ func open() (pty, tty *os.File, err error) {
func ptsname(f *os.File) (string, error) { func ptsname(f *os.File) (string, error) {
var n _C_uint var n _C_uint
err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) //nolint:gosec // Expected unsafe pointer for Syscall call.
if err != nil { if err != nil {
return "", err return "", err
} }
@ -47,5 +50,5 @@ func ptsname(f *os.File) (string, error) {
func unlockpt(f *os.File) error { func unlockpt(f *os.File) error {
var u _C_int var u _C_int
// use TIOCSPTLCK with a pointer to zero to clear the lock // use TIOCSPTLCK with a pointer to zero to clear the lock
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) //nolint:gosec // Expected unsafe pointer for Syscall call.
} }

@ -0,0 +1,69 @@
//go:build netbsd
//+build netbsd
package pty
import (
"errors"
"os"
"syscall"
"unsafe"
)
func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
// In NetBSD unlockpt() does nothing, so it isn't called here.
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func ptsname(f *os.File) (string, error) {
/*
* from ptsname(3): The ptsname() function is equivalent to:
* struct ptmget pm;
* ioctl(fd, TIOCPTSNAME, &pm) == -1 ? NULL : pm.sn;
*/
var ptm ptmget
if err := ioctl(f.Fd(), uintptr(ioctl_TIOCPTSNAME), uintptr(unsafe.Pointer(&ptm))); err != nil {
return "", err
}
name := make([]byte, len(ptm.Sn))
for i, c := range ptm.Sn {
name[i] = byte(c)
if c == 0 {
return string(name[:i]), nil
}
}
return "", errors.New("TIOCPTSNAME string not NUL-terminated")
}
func grantpt(f *os.File) error {
/*
* from grantpt(3): Calling grantpt() is equivalent to:
* ioctl(fd, TIOCGRANTPT, 0);
*/
return ioctl(f.Fd(), uintptr(ioctl_TIOCGRANTPT), 0)
}

@ -1,3 +1,6 @@
//go:build openbsd
//+build openbsd
package pty package pty
import ( import (

@ -1,3 +1,6 @@
//go:build solaris
//+build solaris
package pty package pty
/* based on: /* based on:
@ -6,122 +9,134 @@ http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
import ( import (
"errors" "errors"
"golang.org/x/sys/unix"
"os" "os"
"strconv" "strconv"
"syscall" "syscall"
"unsafe" "unsafe"
) )
const NODEV = ^uint64(0)
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0) ptmxfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY, 0)
//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
p := os.NewFile(uintptr(masterfd), "/dev/ptmx") p := os.NewFile(uintptr(ptmxfd), "/dev/ptmx")
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p) sname, err := ptsname(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = grantpt(p) if err := grantpt(p); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
err = unlockpt(p) if err := unlockpt(p); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0) ptsfd, err := syscall.Open(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
t := os.NewFile(uintptr(slavefd), sname) t := os.NewFile(uintptr(ptsfd), sname)
// pushing terminal driver STREAMS modules as per pts(7) // In case of error after this point, make sure we close the pts fd.
for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) { defer func() {
err = streams_push(t, mod)
if err != nil { if err != nil {
_ = t.Close() // Best effort.
}
}()
// pushing terminal driver STREAMS modules as per pts(7)
for _, mod := range []string{"ptem", "ldterm", "ttcompat"} {
if err := streamsPush(t, mod); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
return p, t, nil
}
func minor(x uint64) uint64 { return p, t, nil
return x & 0377
} }
func ptsdev(fd uintptr) uint64 { func ptsname(f *os.File) (string, error) {
istr := strioctl{ISPTM, 0, 0, nil} dev, err := ptsdev(f.Fd())
err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
if err != nil { if err != nil {
return NODEV return "", err
} }
var status unix.Stat_t fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
err = unix.Fstat(int(fd), &status)
if err != nil { if err := syscall.Access(fn, 0); err != nil {
return NODEV return "", err
} }
return uint64(minor(status.Rdev)) return fn, nil
} }
func ptsname(f *os.File) (string, error) { func unlockpt(f *os.File) error {
dev := ptsdev(f.Fd()) istr := strioctl{
if dev == NODEV { icCmd: UNLKPT,
return "", errors.New("not a master pty") icTimeout: 0,
icLen: 0,
icDP: nil,
} }
fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10) return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
// access(2) creates the slave device (if the pty exists) }
// F_OK == 0 (unistd.h)
err := unix.Access(fn, 0) func minor(x uint64) uint64 { return x & 0377 }
if err != nil {
return "", err func ptsdev(fd uintptr) (uint64, error) {
istr := strioctl{
icCmd: ISPTM,
icTimeout: 0,
icLen: 0,
icDP: nil,
} }
return fn, nil
if err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr))); err != nil {
return 0, err
}
var status syscall.Stat_t
if err := syscall.Fstat(int(fd), &status); err != nil {
return 0, err
}
return uint64(minor(status.Rdev)), nil
} }
type pt_own struct { type ptOwn struct {
pto_ruid int32 rUID int32
pto_rgid int32 rGID int32
} }
func grantpt(f *os.File) error { func grantpt(f *os.File) error {
if ptsdev(f.Fd()) == NODEV { if _, err := ptsdev(f.Fd()); err != nil {
return errors.New("not a master pty") return err
} }
var pto pt_own pto := ptOwn{
pto.pto_ruid = int32(os.Getuid()) rUID: int32(os.Getuid()),
// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty" // XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
pto.pto_rgid = int32(os.Getgid()) rGID: int32(os.Getgid()),
var istr strioctl }
istr.ic_cmd = OWNERPT istr := strioctl{
istr.ic_timout = 0 icCmd: OWNERPT,
istr.ic_len = int32(unsafe.Sizeof(istr)) icTimeout: 0,
istr.ic_dp = unsafe.Pointer(&pto) icLen: int32(unsafe.Sizeof(strioctl{})),
err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))) icDP: unsafe.Pointer(&pto),
if err != nil { }
if err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))); err != nil {
return errors.New("access denied") return errors.New("access denied")
} }
return nil return nil
} }
func unlockpt(f *os.File) error { // streamsPush pushes STREAMS modules if not already done so.
istr := strioctl{UNLKPT, 0, 0, nil} func streamsPush(f *os.File, mod string) error {
return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
}
// push STREAMS modules if not already done so
func streams_push(f *os.File, mod string) error {
var err error
buf := []byte(mod) buf := []byte(mod)
// XXX I_FIND is not returning an error when the module // XXX I_FIND is not returning an error when the module
// is already pushed even though truss reports a return // is already pushed even though truss reports a return
// value of 1. A bug in the Go Solaris syscall interface? // value of 1. A bug in the Go Solaris syscall interface?
@ -129,11 +144,9 @@ func streams_push(f *os.File, mod string) error {
// https://www.illumos.org/issues/9042 // https://www.illumos.org/issues/9042
// but since we are not using libc or XPG4.2, we should not be // but since we are not using libc or XPG4.2, we should not be
// double-pushing modules // double-pushing modules
err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0]))) if err := ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0]))); err != nil {
if err != nil {
return nil return nil
} }
err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0]))) return ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
return err
} }

@ -1,4 +1,5 @@
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris //go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris
//+build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris
package pty package pty

@ -1,4 +1,5 @@
// +build !windows //go:build !windows
//+build !windows
package pty package pty
@ -13,8 +14,8 @@ import (
// corresponding pty. // corresponding pty.
// //
// Starts the process in a new session and sets the controlling terminal. // Starts the process in a new session and sets the controlling terminal.
func Start(c *exec.Cmd) (pty *os.File, err error) { func Start(cmd *exec.Cmd) (*os.File, error) {
return StartWithSize(c, nil) return StartWithSize(cmd, nil)
} }
// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, // StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
@ -23,13 +24,13 @@ func Start(c *exec.Cmd) (pty *os.File, err error) {
// //
// This will resize the pty to the specified size before starting the command. // This will resize the pty to the specified size before starting the command.
// Starts the process in a new session and sets the controlling terminal. // Starts the process in a new session and sets the controlling terminal.
func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) { func StartWithSize(cmd *exec.Cmd, ws *Winsize) (*os.File, error) {
if c.SysProcAttr == nil { if cmd.SysProcAttr == nil {
c.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr = &syscall.SysProcAttr{}
} }
c.SysProcAttr.Setsid = true cmd.SysProcAttr.Setsid = true
c.SysProcAttr.Setctty = true cmd.SysProcAttr.Setctty = true
return StartWithAttrs(c, sz, c.SysProcAttr) return StartWithAttrs(cmd, ws, cmd.SysProcAttr)
} }
// StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, // StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
@ -41,16 +42,16 @@ func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
// //
// This should generally not be needed. Used in some edge cases where it is needed to create a pty // This should generally not be needed. Used in some edge cases where it is needed to create a pty
// without a controlling terminal. // without a controlling terminal.
func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *os.File, err error) { func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (*os.File, error) {
pty, tty, err := Open() pty, tty, err := Open()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer tty.Close() defer func() { _ = tty.Close() }() // Best effort.
if sz != nil { if sz != nil {
if err := Setsize(pty, sz); err != nil { if err := Setsize(pty, sz); err != nil {
pty.Close() _ = pty.Close() // Best effort.
return nil, err return nil, err
} }
} }
@ -67,7 +68,7 @@ func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *
c.SysProcAttr = attrs c.SysProcAttr = attrs
if err := c.Start(); err != nil { if err := c.Start(); err != nil {
_ = pty.Close() _ = pty.Close() // Best effort.
return nil, err return nil, err
} }
return pty, err return pty, err

@ -17,8 +17,8 @@ cross() {
shift shift
echo2 "Build for $os." echo2 "Build for $os."
for arch in $@; do for arch in $@; do
echo2 " - $os/$arch" echo2 " - $os/$arch"
GOOS=$os GOARCH=$arch go build GOOS=$os GOARCH=$arch go build
done done
echo2 echo2
} }
@ -26,9 +26,9 @@ cross() {
set -e set -e
cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le
cross darwin amd64 386 arm arm64 cross darwin amd64 arm64
cross freebsd amd64 386 arm cross freebsd amd64 386 arm arm64
cross netbsd amd64 386 arm cross netbsd amd64 386 arm arm64
cross openbsd amd64 386 arm arm64 cross openbsd amd64 386 arm arm64
cross dragonfly amd64 cross dragonfly amd64
cross solaris amd64 cross solaris amd64
@ -47,4 +47,18 @@ fi
echo2 "Build for linux." echo2 "Build for linux."
echo2 " - linux/riscv" echo2 " - linux/riscv"
docker build -t test -f Dockerfile.riscv . docker build -t creack-pty-test -f Dockerfile.riscv .
# Golang dropped support for darwin 32bits since go1.15. Make sure the lib still compile with go1.14 on those archs.
echo2 "Build for darwin (32bits)."
echo2 " - darwin/386"
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.14 --build-arg=GOOS=darwin --build-arg=GOARCH=386 .
echo2 " - darwin/arm"
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.14 --build-arg=GOOS=darwin --build-arg=GOARCH=arm .
# Run a single test for an old go version. Would be best with go1.0, but not available on Dockerhub.
# Using 1.6 as it is the base version for the RISCV compiler.
# Would also be better to run all the tests, not just one, need to refactor this file to allow for specifc archs per version.
echo2 "Build for linux - go1.6."
echo2 " - linux/amd64"
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.6 --build-arg=GOOS=linux --build-arg=GOARCH=amd64 .

@ -1,64 +0,0 @@
// +build !windows,!solaris
package pty
import (
"os"
"syscall"
"unsafe"
)
// InheritSize applies the terminal size of pty to tty. This should be run
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
// the pty receives a window size change notification.
func InheritSize(pty, tty *os.File) error {
size, err := GetsizeFull(pty)
if err != nil {
return err
}
err = Setsize(tty, size)
if err != nil {
return err
}
return nil
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var ws Winsize
err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
return &ws, err
}
// Getsize returns the number of rows (lines) and cols (positions
// in each line) in terminal t.
func Getsize(t *os.File) (rows, cols int, err error) {
ws, err := GetsizeFull(t)
return int(ws.Rows), int(ws.Cols), err
}
// Winsize describes the terminal size.
type Winsize struct {
Rows uint16 // ws_row: Number of rows (in cells)
Cols uint16 // ws_col: Number of columns (in cells)
X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
}
func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
a2,
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}

@ -1,51 +0,0 @@
//
package pty
import (
"os"
"golang.org/x/sys/unix"
)
const (
TIOCGWINSZ = 21608 // 'T' << 8 | 104
TIOCSWINSZ = 21607 // 'T' << 8 | 103
)
// Winsize describes the terminal size.
type Winsize struct {
Rows uint16 // ws_row: Number of rows (in cells)
Cols uint16 // ws_col: Number of columns (in cells)
X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var wsz *unix.Winsize
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
if err != nil {
return nil, err
} else {
return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil
}
}
// Get Windows Size
func Getsize(t *os.File) (rows, cols int, err error) {
var wsz *unix.Winsize
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
if err != nil {
return 80, 25, err
} else {
return int(wsz.Row), int(wsz.Col), nil
}
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y}
return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz)
}

@ -0,0 +1,24 @@
package pty
import "os"
// InheritSize applies the terminal size of pty to tty. This should be run
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
// the pty receives a window size change notification.
func InheritSize(pty, tty *os.File) error {
size, err := GetsizeFull(pty)
if err != nil {
return err
}
if err := Setsize(tty, size); err != nil {
return err
}
return nil
}
// Getsize returns the number of rows (lines) and cols (positions
// in each line) in terminal t.
func Getsize(t *os.File) (rows, cols int, err error) {
ws, err := GetsizeFull(t)
return int(ws.Rows), int(ws.Cols), err
}

@ -0,0 +1,35 @@
//go:build !windows
//+build !windows
package pty
import (
"os"
"syscall"
"unsafe"
)
// Winsize describes the terminal size.
type Winsize struct {
Rows uint16 // ws_row: Number of rows (in cells)
Cols uint16 // ws_col: Number of columns (in cells)
X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
//nolint:gosec // Expected unsafe pointer for Syscall call.
return ioctl(t.Fd(), syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(ws)))
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var ws Winsize
//nolint:gosec // Expected unsafe pointer for Syscall call.
if err := ioctl(t.Fd(), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))); err != nil {
return nil, err
}
return &ws, nil
}

@ -0,0 +1,23 @@
//go:build windows
//+build windows
package pty
import (
"os"
)
// Winsize is a dummy struct to enable compilation on unsupported platforms.
type Winsize struct {
Rows, Cols, X, Y uint
}
// Setsize resizes t to s.
func Setsize(*os.File, *Winsize) error {
return ErrUnsupported
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(*os.File) (*Winsize, error) {
return nil, ErrUnsupported
}

@ -1,3 +1,6 @@
//go:build 386
//+build 386
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,3 +1,6 @@
//go:build amd64
//+build amd64
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,3 +1,6 @@
//go:build arm
//+build arm
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,8 +1,9 @@
//go:build arm64
//+build arm64
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go
// +build arm64
package pty package pty
type ( type (

@ -1,3 +1,6 @@
//go:build amd64 && dragonfly
//+build amd64,dragonfly
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_dragonfly.go // cgo -godefs types_dragonfly.go

@ -1,3 +1,6 @@
//go:build 386 && freebsd
//+build 386,freebsd
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go // cgo -godefs types_freebsd.go

@ -1,3 +1,6 @@
//go:build amd64 && freebsd
//+build amd64,freebsd
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go // cgo -godefs types_freebsd.go

@ -1,3 +1,6 @@
//go:build arm && freebsd
//+build arm,freebsd
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go // cgo -godefs types_freebsd.go

@ -1,3 +1,6 @@
//go:build arm64 && freebsd
//+build arm64,freebsd
// Code generated by cmd/cgo -godefs; DO NOT EDIT. // Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_freebsd.go // cgo -godefs types_freebsd.go

@ -0,0 +1,13 @@
//go:build (loongarch32 || loongarch64) && linux
//+build linux
//+build loongarch32 loongarch64
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go
package pty
type (
_C_int int32
_C_uint uint32
)

@ -1,9 +1,10 @@
//go:build (mips || mipsle || mips64 || mips64le) && linux
//+build linux
//+build mips mipsle mips64 mips64le
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go
// +build linux
// +build mips mipsle mips64 mips64le
package pty package pty
type ( type (

@ -0,0 +1,17 @@
//go:build (386 || amd64 || arm || arm64) && netbsd
//+build netbsd
//+build 386 amd64 arm arm64
package pty
type ptmget struct {
Cfd int32
Sfd int32
Cn [1024]int8
Sn [1024]int8
}
var (
ioctl_TIOCPTSNAME = 0x48087448
ioctl_TIOCGRANTPT = 0x20007447
)

@ -1,13 +1,14 @@
// +build openbsd //go:build (386 || amd64 || arm || arm64 || mips64) && openbsd
// +build 386 amd64 arm arm64 //+build openbsd
//+build 386 amd64 arm arm64 mips64
package pty package pty
type ptmget struct { type ptmget struct {
Cfd int32 Cfd int32
Sfd int32 Sfd int32
Cn [16]int8 Cn [16]int8
Sn [16]int8 Sn [16]int8
} }
var ioctl_PTMGET = 0x40287401 var ioctl_PTMGET = 0x40287401

@ -1,4 +1,5 @@
// +build ppc64 //go:build ppc64
//+build ppc64
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,4 +1,5 @@
// +build ppc64le //go:build ppc64le
//+build ppc64le
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,8 +1,9 @@
//go:build riscv || riscv64
//+build riscv riscv64
// Code generated by cmd/cgo -godefs; DO NOT EDIT. // Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types.go // cgo -godefs types.go
// +build riscv riscv64
package pty package pty
type ( type (

@ -1,4 +1,5 @@
// +build s390x //go:build s390x
//+build s390x
// Created by cgo -godefs - DO NOT EDIT // Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go // cgo -godefs types.go

@ -1,20 +1,11 @@
# Archived project. No maintenance. # color [![](https://github.com/fatih/color/workflows/build/badge.svg)](https://github.com/fatih/color/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/fatih/color)](https://pkg.go.dev/github.com/fatih/color)
This project is not maintained anymore and is archived. Feel free to fork and
make your own changes if needed. For more detail read my blog post: [Taking an indefinite sabbatical from my projects](https://arslan.io/2018/10/09/taking-an-indefinite-sabbatical-from-my-projects/)
Thanks to everyone for their valuable feedback and contributions.
# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color)
Color lets you use colorized outputs in terms of [ANSI Escape Color lets you use colorized outputs in terms of [ANSI Escape
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
has support for Windows too! The API can be used in several ways, pick one that has support for Windows too! The API can be used in several ways, pick one that
suits you. suits you.
![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg)
![Color](https://i.imgur.com/c1JI0lA.png)
## Install ## Install
@ -136,14 +127,16 @@ fmt.Println("All text will now be bold magenta.")
There might be a case where you want to explicitly disable/enable color output. the There might be a case where you want to explicitly disable/enable color output. the
`go-isatty` package will automatically disable color output for non-tty output streams `go-isatty` package will automatically disable color output for non-tty output streams
(for example if the output were piped directly to `less`) (for example if the output were piped directly to `less`).
`Color` has support to disable/enable colors both globally and for single color The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You variable is set (regardless of its value).
can easily disable the color output with:
```go `Color` has support to disable/enable colors programatically both globally and
for single color definitions. For example suppose you have a CLI app and a
`--no-color` bool flag. You can easily disable the color output with:
```go
var flagNoColor = flag.Bool("no-color", false, "Disable color output") var flagNoColor = flag.Bool("no-color", false, "Disable color output")
if *flagNoColor { if *flagNoColor {
@ -165,6 +158,10 @@ c.EnableColor()
c.Println("This prints again cyan...") c.Println("This prints again cyan...")
``` ```
## GitHub Actions
To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
## Todo ## Todo
* Save/Return previous values * Save/Return previous values
@ -179,4 +176,3 @@ c.Println("This prints again cyan...")
## License ## License
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details

@ -15,9 +15,11 @@ import (
var ( var (
// NoColor defines if the output is colorized or not. It's dynamically set to // NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal // false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control // or not. It's also set to true if the NO_COLOR environment variable is
// over each color block use the methods DisableColor() individually. // set (regardless of its value). This is a global option and affects all
NoColor = os.Getenv("TERM") == "dumb" || // colors. For more control over each color block use the methods
// DisableColor() individually.
NoColor = noColorExists() || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
// Output defines the standard output of the print functions. By default // Output defines the standard output of the print functions. By default
@ -33,6 +35,12 @@ var (
colorsCacheMu sync.Mutex // protects colorsCache colorsCacheMu sync.Mutex // protects colorsCache
) )
// noColorExists returns true if the environment variable NO_COLOR exists.
func noColorExists() bool {
_, exists := os.LookupEnv("NO_COLOR")
return exists
}
// Color defines a custom color object which is defined by SGR parameters. // Color defines a custom color object which is defined by SGR parameters.
type Color struct { type Color struct {
params []Attribute params []Attribute
@ -108,7 +116,14 @@ const (
// New returns a newly created color object. // New returns a newly created color object.
func New(value ...Attribute) *Color { func New(value ...Attribute) *Color {
c := &Color{params: make([]Attribute, 0)} c := &Color{
params: make([]Attribute, 0),
}
if noColorExists() {
c.noColor = boolPtr(true)
}
c.Add(value...) c.Add(value...)
return c return c
} }
@ -387,7 +402,7 @@ func (c *Color) EnableColor() {
} }
func (c *Color) isNoColorSet() bool { func (c *Color) isNoColorSet() bool {
// check first if we have user setted action // check first if we have user set action
if c.noColor != nil { if c.noColor != nil {
return *c.noColor return *c.noColor
} }

@ -118,6 +118,8 @@ the color output with:
color.NoColor = true // disables colorized output color.NoColor = true // disables colorized output
} }
You can also disable the color by setting the NO_COLOR environment variable to any value.
It also has support for single color definitions (local). You can It also has support for single color definitions (local). You can
disable/enable color output on the fly: disable/enable color output on the fly:

@ -1,8 +0,0 @@
module github.com/fatih/color
go 1.13
require (
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-isatty v0.0.11
)

@ -1,8 +0,0 @@
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

@ -1,12 +0,0 @@
module github.com/gen2brain/beeep
go 1.14
require (
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/godbus/dbus/v5 v5.0.3
github.com/gopherjs/gopherwasm v1.1.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
)

@ -1,14 +0,0 @@
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

@ -102,7 +102,7 @@ func toastNotification(title, message, appIcon string) toast.Notification {
func appID() string { func appID() string {
defID := "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe" defID := "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe"
cmd := exec.Command("powershell", "Get-StartApps") cmd := exec.Command("powershell", "-NoProfile", "Get-StartApps")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {

@ -9,9 +9,9 @@ jobs:
- run: go get - run: go get
- run: go test -v -race - run: go test -v -race
build-go-1.9: build-go-1.12:
docker: docker:
- image: golang:1.9 - image: golang:1.12
working_directory: /go/src/github.com/gliderlabs/ssh working_directory: /go/src/github.com/gliderlabs/ssh
steps: steps:
- checkout - checkout
@ -23,4 +23,4 @@ workflows:
build: build:
jobs: jobs:
- build-go-latest - build-go-latest
- build-go-1.9 - build-go-1.12

@ -140,7 +140,10 @@ func (ctx *sshContext) ServerVersion() string {
} }
func (ctx *sshContext) RemoteAddr() net.Addr { func (ctx *sshContext) RemoteAddr() net.Addr {
return ctx.Value(ContextKeyRemoteAddr).(net.Addr) if addr, ok := ctx.Value(ContextKeyRemoteAddr).(net.Addr); ok {
return addr
}
return nil
} }
func (ctx *sshContext) LocalAddr() net.Addr { func (ctx *sshContext) LocalAddr() net.Addr {

@ -42,6 +42,13 @@ func HostKeyFile(filepath string) Option {
} }
} }
func KeyboardInteractiveAuth(fn KeyboardInteractiveHandler) Option {
return func(srv *Server) error {
srv.KeyboardInteractiveHandler = fn
return nil
}
}
// HostKeyPEM returns a functional option that adds HostSigners to the server // HostKeyPEM returns a functional option that adds HostSigners to the server
// from a PEM file as bytes. // from a PEM file as bytes.
func HostKeyPEM(bytes []byte) Option { func HostKeyPEM(bytes []byte) Option {

@ -15,6 +15,10 @@ import (
// and ListenAndServeTLS methods after a call to Shutdown or Close. // and ListenAndServeTLS methods after a call to Shutdown or Close.
var ErrServerClosed = errors.New("ssh: Server closed") var ErrServerClosed = errors.New("ssh: Server closed")
type SubsystemHandler func(s Session)
var DefaultSubsystemHandlers = map[string]SubsystemHandler{}
type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte) type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte)
var DefaultRequestHandlers = map[string]RequestHandler{} var DefaultRequestHandlers = map[string]RequestHandler{}
@ -44,6 +48,8 @@ type Server struct {
ServerConfigCallback ServerConfigCallback // callback for configuring detailed SSH options ServerConfigCallback ServerConfigCallback // callback for configuring detailed SSH options
SessionRequestCallback SessionRequestCallback // callback for allowing or denying SSH sessions SessionRequestCallback SessionRequestCallback // callback for allowing or denying SSH sessions
ConnectionFailedCallback ConnectionFailedCallback // callback to report connection failures
IdleTimeout time.Duration // connection timeout when no activity, none if empty IdleTimeout time.Duration // connection timeout when no activity, none if empty
MaxTimeout time.Duration // absolute connection timeout, none if empty MaxTimeout time.Duration // absolute connection timeout, none if empty
@ -57,6 +63,10 @@ type Server struct {
// no handlers are enabled. // no handlers are enabled.
RequestHandlers map[string]RequestHandler RequestHandlers map[string]RequestHandler
// SubsystemHandlers are handlers which are similar to the usual SSH command
// handlers, but handle named subsystems.
SubsystemHandlers map[string]SubsystemHandler
listenerWg sync.WaitGroup listenerWg sync.WaitGroup
mu sync.RWMutex mu sync.RWMutex
listeners map[net.Listener]struct{} listeners map[net.Listener]struct{}
@ -95,6 +105,12 @@ func (srv *Server) ensureHandlers() {
srv.ChannelHandlers[k] = v srv.ChannelHandlers[k] = v
} }
} }
if srv.SubsystemHandlers == nil {
srv.SubsystemHandlers = map[string]SubsystemHandler{}
for k, v := range DefaultSubsystemHandlers {
srv.SubsystemHandlers[k] = v
}
}
} }
func (srv *Server) config(ctx Context) *gossh.ServerConfig { func (srv *Server) config(ctx Context) *gossh.ServerConfig {
@ -110,7 +126,7 @@ func (srv *Server) config(ctx Context) *gossh.ServerConfig {
for _, signer := range srv.HostSigners { for _, signer := range srv.HostSigners {
config.AddHostKey(signer) config.AddHostKey(signer)
} }
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil { if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil && srv.KeyboardInteractiveHandler == nil {
config.NoClientAuth = true config.NoClientAuth = true
} }
if srv.Version != "" { if srv.Version != "" {
@ -264,7 +280,9 @@ func (srv *Server) HandleConn(newConn net.Conn) {
defer conn.Close() defer conn.Close()
sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx)) sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx))
if err != nil { if err != nil {
// TODO: trigger event callback if srv.ConnectionFailedCallback != nil {
srv.ConnectionFailedCallback(conn, err)
}
return return
} }

@ -14,7 +14,7 @@ import (
// Session provides access to information about an SSH session and methods // Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from // to read and write to the SSH channel with an embedded Channel interface from
// cypto/ssh. // crypto/ssh.
// //
// When Command() returns an empty slice, the user requested a shell. Otherwise // When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments. // the user is performing an exec with those command arguments.
@ -47,6 +47,9 @@ type Session interface {
// RawCommand returns the exact command that was provided by the user. // RawCommand returns the exact command that was provided by the user.
RawCommand() string RawCommand() string
// Subsystem returns the subsystem requested by the user.
Subsystem() string
// PublicKey returns the PublicKey used to authenticate. If a public key was not // PublicKey returns the PublicKey used to authenticate. If a public key was not
// used it will return nil. // used it will return nil.
PublicKey() PublicKey PublicKey() PublicKey
@ -74,6 +77,12 @@ type Session interface {
// If there are buffered signals when a channel is registered, they will be // If there are buffered signals when a channel is registered, they will be
// sent in order on the channel immediately after registering. // sent in order on the channel immediately after registering.
Signals(c chan<- Signal) Signals(c chan<- Signal)
// Break regisers a channel to receive notifications of break requests sent
// from the client. The channel must handle break requests, or it will block
// the request handling loop. Registering nil will unregister the channel.
// During the time that no channel is registered, breaks are ignored.
Break(c chan<- bool)
} }
// maxSigBufSize is how many signals will be buffered // maxSigBufSize is how many signals will be buffered
@ -87,12 +96,13 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
return return
} }
sess := &session{ sess := &session{
Channel: ch, Channel: ch,
conn: conn, conn: conn,
handler: srv.Handler, handler: srv.Handler,
ptyCb: srv.PtyCallback, ptyCb: srv.PtyCallback,
sessReqCb: srv.SessionRequestCallback, sessReqCb: srv.SessionRequestCallback,
ctx: ctx, subsystemHandlers: srv.SubsystemHandlers,
ctx: ctx,
} }
sess.handleRequests(reqs) sess.handleRequests(reqs)
} }
@ -100,19 +110,22 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
type session struct { type session struct {
sync.Mutex sync.Mutex
gossh.Channel gossh.Channel
conn *gossh.ServerConn conn *gossh.ServerConn
handler Handler handler Handler
handled bool subsystemHandlers map[string]SubsystemHandler
exited bool handled bool
pty *Pty exited bool
winch chan Window pty *Pty
env []string winch chan Window
ptyCb PtyCallback env []string
sessReqCb SessionRequestCallback ptyCb PtyCallback
rawCmd string sessReqCb SessionRequestCallback
ctx Context rawCmd string
sigCh chan<- Signal subsystem string
sigBuf []Signal ctx Context
sigCh chan<- Signal
sigBuf []Signal
breakCh chan<- bool
} }
func (sess *session) Write(p []byte) (n int, err error) { func (sess *session) Write(p []byte) (n int, err error) {
@ -191,6 +204,10 @@ func (sess *session) Command() []string {
return append([]string(nil), cmd...) return append([]string(nil), cmd...)
} }
func (sess *session) Subsystem() string {
return sess.subsystem
}
func (sess *session) Pty() (Pty, <-chan Window, bool) { func (sess *session) Pty() (Pty, <-chan Window, bool) {
if sess.pty != nil { if sess.pty != nil {
return *sess.pty, sess.winch, true return *sess.pty, sess.winch, true
@ -211,6 +228,12 @@ func (sess *session) Signals(c chan<- Signal) {
} }
} }
func (sess *session) Break(c chan<- bool) {
sess.Lock()
defer sess.Unlock()
sess.breakCh = c
}
func (sess *session) handleRequests(reqs <-chan *gossh.Request) { func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
for req := range reqs { for req := range reqs {
switch req.Type { switch req.Type {
@ -239,6 +262,40 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
sess.handler(sess) sess.handler(sess)
sess.Exit(0) sess.Exit(0)
}() }()
case "subsystem":
if sess.handled {
req.Reply(false, nil)
continue
}
var payload = struct{ Value string }{}
gossh.Unmarshal(req.Payload, &payload)
sess.subsystem = payload.Value
// If there's a session policy callback, we need to confirm before
// accepting the session.
if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
sess.rawCmd = ""
req.Reply(false, nil)
continue
}
handler := sess.subsystemHandlers[payload.Value]
if handler == nil {
handler = sess.subsystemHandlers["default"]
}
if handler == nil {
req.Reply(false, nil)
continue
}
sess.handled = true
req.Reply(true, nil)
go func() {
handler(sess)
sess.Exit(0)
}()
case "env": case "env":
if sess.handled { if sess.handled {
req.Reply(false, nil) req.Reply(false, nil)
@ -300,6 +357,15 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
// TODO: option/callback to allow agent forwarding // TODO: option/callback to allow agent forwarding
SetAgentRequested(sess.ctx) SetAgentRequested(sess.ctx)
req.Reply(true, nil) req.Reply(true, nil)
case "break":
ok := false
sess.Lock()
if sess.breakCh != nil {
sess.breakCh <- true
ok = true
}
req.Reply(ok, nil)
sess.Unlock()
default: default:
// TODO: debug log // TODO: debug log
req.Reply(false, nil) req.Reply(false, nil)

@ -64,6 +64,10 @@ type ReversePortForwardingCallback func(ctx Context, bindHost string, bindPort u
// ServerConfigCallback is a hook for creating custom default server configs // ServerConfigCallback is a hook for creating custom default server configs
type ServerConfigCallback func(ctx Context) *gossh.ServerConfig type ServerConfigCallback func(ctx Context) *gossh.ServerConfig
// ConnectionFailedCallback is a hook for reporting failed connections
// Please note: the net.Conn is likely to be closed at this point
type ConnectionFailedCallback func(conn net.Conn, err error)
// Window represents the size of a PTY window. // Window represents the size of a PTY window.
type Window struct { type Window struct {
Width int Width int

@ -1,50 +0,0 @@
dist: bionic
language: go
go_import_path: github.com/godbus/dbus
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
matrix:
fast_finish: true
allow_failures:
- go: tip
addons:
apt:
packages:
- dbus
- dbus-x11
before_install:
- export GO111MODULE=on
script:
- go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled
- go vet ./... # go vet is the official Go static analyzer
jobs:
include:
# The build matrix doesn't cover build stages, so manually expand
# the jobs with anchors
- &multiarch
stage: "Multiarch Test"
go: 1.11.x
env: TARGETS="386 arm arm64 ppc64le"
before_install:
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
script:
- |
set -e
for target in $TARGETS; do
printf "\e[1mRunning test suite under ${target}.\e[0m\n"
GOARCH="$target" go test -v ./...
printf "\n\n"
done
- <<: *multiarch
go: 1.12.x
- <<: *multiarch
go: 1.13.x

@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus) ![Build Status](https://github.com/godbus/dbus/workflows/Go/badge.svg)
dbus dbus
---- ----
@ -32,6 +32,8 @@ gives a short overview over the basic usage.
#### Projects using godbus #### Projects using godbus
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library. - [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API. - [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
Please note that the API is considered unstable for now and may change without Please note that the API is considered unstable for now and may change without
further notice. further notice.

@ -37,7 +37,7 @@ const (
// Auth defines the behaviour of an authentication mechanism. // Auth defines the behaviour of an authentication mechanism.
type Auth interface { type Auth interface {
// Return the name of the mechnism, the argument to the first AUTH command // Return the name of the mechanism, the argument to the first AUTH command
// and the next status. // and the next status.
FirstData() (name, resp []byte, status AuthStatus) FirstData() (name, resp []byte, status AuthStatus)

@ -24,6 +24,15 @@ type Call struct {
// Holds the response once the call is done. // Holds the response once the call is done.
Body []interface{} Body []interface{}
// ResponseSequence stores the sequence number of the DBus message containing
// the call response (or error). This can be compared to the sequence number
// of other call responses and signals on this connection to determine their
// relative ordering on the underlying DBus connection.
// For errors, ResponseSequence is populated only if the error came from a
// DBusMessage that was received or if there was an error receiving. In case of
// failure to make the call, ResponseSequence will be NoSequence.
ResponseSequence Sequence
// tracks context and canceler // tracks context and canceler
ctx context.Context ctx context.Context
ctxCanceler context.CancelFunc ctxCanceler context.CancelFunc

@ -45,6 +45,7 @@ type Conn struct {
serialGen SerialGenerator serialGen SerialGenerator
inInt Interceptor inInt Interceptor
outInt Interceptor outInt Interceptor
auth []Auth
names *nameTracker names *nameTracker
calls *callTracker calls *callTracker
@ -59,7 +60,8 @@ type Conn struct {
func SessionBus() (conn *Conn, err error) { func SessionBus() (conn *Conn, err error) {
sessionBusLck.Lock() sessionBusLck.Lock()
defer sessionBusLck.Unlock() defer sessionBusLck.Unlock()
if sessionBus != nil { if sessionBus != nil &&
sessionBus.Connected() {
return sessionBus, nil return sessionBus, nil
} }
defer func() { defer func() {
@ -67,19 +69,7 @@ func SessionBus() (conn *Conn, err error) {
sessionBus = conn sessionBus = conn
} }
}() }()
conn, err = SessionBusPrivate() conn, err = ConnectSessionBus()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return return
} }
@ -116,7 +106,8 @@ func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Co
func SystemBus() (conn *Conn, err error) { func SystemBus() (conn *Conn, err error) {
systemBusLck.Lock() systemBusLck.Lock()
defer systemBusLck.Unlock() defer systemBusLck.Unlock()
if systemBus != nil { if systemBus != nil &&
systemBus.Connected() {
return systemBus, nil return systemBus, nil
} }
defer func() { defer func() {
@ -124,20 +115,42 @@ func SystemBus() (conn *Conn, err error) {
systemBus = conn systemBus = conn
} }
}() }()
conn, err = SystemBusPrivate() conn, err = ConnectSystemBus()
return
}
// ConnectSessionBus connects to the session bus.
func ConnectSessionBus(opts ...ConnOption) (*Conn, error) {
address, err := getSessionBusAddress()
if err != nil { if err != nil {
return return nil, err
} }
if err = conn.Auth(nil); err != nil { return Connect(address, opts...)
conn.Close() }
conn = nil
return // ConnectSystemBus connects to the system bus.
func ConnectSystemBus(opts ...ConnOption) (*Conn, error) {
return Connect(getSystemBusPlatformAddress(), opts...)
}
// Connect connects to the given address.
//
// Returned connection is ready to use and doesn't require calling
// Auth and Hello methods to make it usable.
func Connect(address string, opts ...ConnOption) (*Conn, error) {
conn, err := Dial(address, opts...)
if err != nil {
return nil, err
}
if err = conn.Auth(conn.auth); err != nil {
_ = conn.Close()
return nil, err
} }
if err = conn.Hello(); err != nil { if err = conn.Hello(); err != nil {
conn.Close() _ = conn.Close()
conn = nil return nil, err
} }
return return conn, nil
} }
// SystemBusPrivate returns a new private connection to the system bus. // SystemBusPrivate returns a new private connection to the system bus.
@ -197,6 +210,14 @@ func WithSerialGenerator(gen SerialGenerator) ConnOption {
} }
} }
// WithAuth sets authentication methods for the auth conversation.
func WithAuth(methods ...Auth) ConnOption {
return func(conn *Conn) error {
conn.auth = methods
return nil
}
}
// Interceptor intercepts incoming and outgoing messages. // Interceptor intercepts incoming and outgoing messages.
type Interceptor func(msg *Message) type Interceptor func(msg *Message)
@ -309,6 +330,11 @@ func (conn *Conn) Context() context.Context {
return conn.ctx return conn.ctx
} }
// Connected returns whether conn is connected
func (conn *Conn) Connected() bool {
return conn.ctx.Err() == nil
}
// Eavesdrop causes conn to send all incoming messages to the given channel // Eavesdrop causes conn to send all incoming messages to the given channel
// without further processing. Method replies, errors and signals will not be // without further processing. Method replies, errors and signals will not be
// sent to the appropriate channels and method calls will not be handled. If nil // sent to the appropriate channels and method calls will not be handled. If nil
@ -342,8 +368,9 @@ func (conn *Conn) Hello() error {
} }
// inWorker runs in an own goroutine, reading incoming messages from the // inWorker runs in an own goroutine, reading incoming messages from the
// transport and dispatching them appropiately. // transport and dispatching them appropriately.
func (conn *Conn) inWorker() { func (conn *Conn) inWorker() {
sequenceGen := newSequenceGenerator()
for { for {
msg, err := conn.ReadMessage() msg, err := conn.ReadMessage()
if err != nil { if err != nil {
@ -352,7 +379,7 @@ func (conn *Conn) inWorker() {
// anything but to shut down all stuff and returns errors to all // anything but to shut down all stuff and returns errors to all
// pending replies. // pending replies.
conn.Close() conn.Close()
conn.calls.finalizeAllWithError(err) conn.calls.finalizeAllWithError(sequenceGen, err)
return return
} }
// invalid messages are ignored // invalid messages are ignored
@ -381,13 +408,14 @@ func (conn *Conn) inWorker() {
if conn.inInt != nil { if conn.inInt != nil {
conn.inInt(msg) conn.inInt(msg)
} }
sequence := sequenceGen.next()
switch msg.Type { switch msg.Type {
case TypeError: case TypeError:
conn.serialGen.RetireSerial(conn.calls.handleDBusError(msg)) conn.serialGen.RetireSerial(conn.calls.handleDBusError(sequence, msg))
case TypeMethodReply: case TypeMethodReply:
conn.serialGen.RetireSerial(conn.calls.handleReply(msg)) conn.serialGen.RetireSerial(conn.calls.handleReply(sequence, msg))
case TypeSignal: case TypeSignal:
conn.handleSignal(msg) conn.handleSignal(sequence, msg)
case TypeMethodCall: case TypeMethodCall:
go conn.handleCall(msg) go conn.handleCall(msg)
} }
@ -395,7 +423,7 @@ func (conn *Conn) inWorker() {
} }
} }
func (conn *Conn) handleSignal(msg *Message) { func (conn *Conn) handleSignal(sequence Sequence, msg *Message) {
iface := msg.Headers[FieldInterface].value.(string) iface := msg.Headers[FieldInterface].value.(string)
member := msg.Headers[FieldMember].value.(string) member := msg.Headers[FieldMember].value.(string)
// as per http://dbus.freedesktop.org/doc/dbus-specification.html , // as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
@ -421,10 +449,11 @@ func (conn *Conn) handleSignal(msg *Message) {
} }
} }
signal := &Signal{ signal := &Signal{
Sender: sender, Sender: sender,
Path: msg.Headers[FieldPath].value.(ObjectPath), Path: msg.Headers[FieldPath].value.(ObjectPath),
Name: iface + "." + member, Name: iface + "." + member,
Body: msg.Body, Body: msg.Body,
Sequence: sequence,
} }
conn.signalHandler.DeliverSignal(iface, member, signal) conn.signalHandler.DeliverSignal(iface, member, signal)
} }
@ -442,6 +471,9 @@ func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
} }
func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) { func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
if msg.serial == 0 {
msg.serial = conn.getSerial()
}
if conn.outInt != nil { if conn.outInt != nil {
conn.outInt(msg) conn.outInt(msg)
} }
@ -473,16 +505,16 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call {
if ctx == nil { if ctx == nil {
panic("nil context") panic("nil context")
} }
if ch == nil {
ch = make(chan *Call, 1)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
var call *Call var call *Call
ctx, canceler := context.WithCancel(ctx) ctx, canceler := context.WithCancel(ctx)
msg.serial = conn.getSerial() msg.serial = conn.getSerial()
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 5)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
call = new(Call) call = new(Call)
call.Destination, _ = msg.Headers[FieldDestination].value.(string) call.Destination, _ = msg.Headers[FieldDestination].value.(string)
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
@ -504,7 +536,8 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call {
}) })
} else { } else {
canceler() canceler()
call = &Call{Err: nil} call = &Call{Err: nil, Done: ch}
ch <- call
conn.sendMessageAndIfClosed(msg, func() { conn.sendMessageAndIfClosed(msg, func() {
call = &Call{Err: ErrClosed} call = &Call{Err: ErrClosed}
}) })
@ -529,7 +562,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) {
} }
msg := new(Message) msg := new(Message)
msg.Type = TypeError msg.Type = TypeError
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant) msg.Headers = make(map[HeaderField]Variant)
if dest != "" { if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest) msg.Headers[FieldDestination] = MakeVariant(dest)
@ -548,7 +580,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) {
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
msg := new(Message) msg := new(Message)
msg.Type = TypeMethodReply msg.Type = TypeMethodReply
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant) msg.Headers = make(map[HeaderField]Variant)
if dest != "" { if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest) msg.Headers[FieldDestination] = MakeVariant(dest)
@ -564,8 +595,14 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
// AddMatchSignal registers the given match rule to receive broadcast // AddMatchSignal registers the given match rule to receive broadcast
// signals based on their contents. // signals based on their contents.
func (conn *Conn) AddMatchSignal(options ...MatchOption) error { func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
return conn.AddMatchSignalContext(context.Background(), options...)
}
// AddMatchSignalContext acts like AddMatchSignal but takes a context.
func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error {
options = append([]MatchOption{withMatchType("signal")}, options...) options = append([]MatchOption{withMatchType("signal")}, options...)
return conn.busObj.Call( return conn.busObj.CallWithContext(
ctx,
"org.freedesktop.DBus.AddMatch", 0, "org.freedesktop.DBus.AddMatch", 0,
formatMatchOptions(options), formatMatchOptions(options),
).Store() ).Store()
@ -573,8 +610,14 @@ func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
// RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal. // RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal.
func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error { func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error {
return conn.RemoveMatchSignalContext(context.Background(), options...)
}
// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context.
func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error {
options = append([]MatchOption{withMatchType("signal")}, options...) options = append([]MatchOption{withMatchType("signal")}, options...)
return conn.busObj.Call( return conn.busObj.CallWithContext(
ctx,
"org.freedesktop.DBus.RemoveMatch", 0, "org.freedesktop.DBus.RemoveMatch", 0,
formatMatchOptions(options), formatMatchOptions(options),
).Store() ).Store()
@ -639,10 +682,11 @@ func (e Error) Error() string {
// Signal represents a D-Bus message of type Signal. The name member is given in // Signal represents a D-Bus message of type Signal. The name member is given in
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. // "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
type Signal struct { type Signal struct {
Sender string Sender string
Path ObjectPath Path ObjectPath
Name string Name string
Body []interface{} Body []interface{}
Sequence Sequence
} }
// transport is a D-Bus transport. // transport is a D-Bus transport.
@ -825,25 +869,25 @@ func (tracker *callTracker) track(sn uint32, call *Call) {
tracker.lck.Unlock() tracker.lck.Unlock()
} }
func (tracker *callTracker) handleReply(msg *Message) uint32 { func (tracker *callTracker) handleReply(sequence Sequence, msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32) serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock() tracker.lck.RLock()
_, ok := tracker.calls[serial] _, ok := tracker.calls[serial]
tracker.lck.RUnlock() tracker.lck.RUnlock()
if ok { if ok {
tracker.finalizeWithBody(serial, msg.Body) tracker.finalizeWithBody(serial, sequence, msg.Body)
} }
return serial return serial
} }
func (tracker *callTracker) handleDBusError(msg *Message) uint32 { func (tracker *callTracker) handleDBusError(sequence Sequence, msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32) serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock() tracker.lck.RLock()
_, ok := tracker.calls[serial] _, ok := tracker.calls[serial]
tracker.lck.RUnlock() tracker.lck.RUnlock()
if ok { if ok {
name, _ := msg.Headers[FieldErrorName].value.(string) name, _ := msg.Headers[FieldErrorName].value.(string)
tracker.finalizeWithError(serial, Error{name, msg.Body}) tracker.finalizeWithError(serial, sequence, Error{name, msg.Body})
} }
return serial return serial
} }
@ -856,7 +900,7 @@ func (tracker *callTracker) handleSendError(msg *Message, err error) {
_, ok := tracker.calls[msg.serial] _, ok := tracker.calls[msg.serial]
tracker.lck.RUnlock() tracker.lck.RUnlock()
if ok { if ok {
tracker.finalizeWithError(msg.serial, err) tracker.finalizeWithError(msg.serial, NoSequence, err)
} }
} }
@ -871,7 +915,7 @@ func (tracker *callTracker) finalize(sn uint32) {
} }
} }
func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) { func (tracker *callTracker) finalizeWithBody(sn uint32, sequence Sequence, body []interface{}) {
tracker.lck.Lock() tracker.lck.Lock()
c, ok := tracker.calls[sn] c, ok := tracker.calls[sn]
if ok { if ok {
@ -880,11 +924,12 @@ func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) {
tracker.lck.Unlock() tracker.lck.Unlock()
if ok { if ok {
c.Body = body c.Body = body
c.ResponseSequence = sequence
c.done() c.done()
} }
} }
func (tracker *callTracker) finalizeWithError(sn uint32, err error) { func (tracker *callTracker) finalizeWithError(sn uint32, sequence Sequence, err error) {
tracker.lck.Lock() tracker.lck.Lock()
c, ok := tracker.calls[sn] c, ok := tracker.calls[sn]
if ok { if ok {
@ -893,11 +938,12 @@ func (tracker *callTracker) finalizeWithError(sn uint32, err error) {
tracker.lck.Unlock() tracker.lck.Unlock()
if ok { if ok {
c.Err = err c.Err = err
c.ResponseSequence = sequence
c.done() c.done()
} }
} }
func (tracker *callTracker) finalizeAllWithError(err error) { func (tracker *callTracker) finalizeAllWithError(sequenceGen *sequenceGenerator, err error) {
tracker.lck.Lock() tracker.lck.Lock()
closedCalls := make([]*Call, 0, len(tracker.calls)) closedCalls := make([]*Call, 0, len(tracker.calls))
for sn := range tracker.calls { for sn := range tracker.calls {
@ -907,6 +953,7 @@ func (tracker *callTracker) finalizeAllWithError(err error) {
tracker.lck.Unlock() tracker.lck.Unlock()
for _, call := range closedCalls { for _, call := range closedCalls {
call.Err = err call.Err = err
call.ResponseSequence = sequenceGen.next()
call.done() call.done()
} }
} }

@ -28,6 +28,7 @@ var (
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
unixFDType = reflect.TypeOf(UnixFD(0)) unixFDType = reflect.TypeOf(UnixFD(0))
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
errType = reflect.TypeOf((*error)(nil)).Elem()
) )
// An InvalidTypeError signals that a value which cannot be represented in the // An InvalidTypeError signals that a value which cannot be represented in the
@ -63,6 +64,9 @@ func storeInterfaces(src, dest interface{}) error {
func store(dest, src reflect.Value) error { func store(dest, src reflect.Value) error {
if dest.Kind() == reflect.Ptr { if dest.Kind() == reflect.Ptr {
if dest.IsNil() {
dest.Set(reflect.New(dest.Type().Elem()))
}
return store(dest.Elem(), src) return store(dest.Elem(), src)
} }
switch src.Kind() { switch src.Kind() {

@ -126,14 +126,28 @@ func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
} }
ret := m.Value.Call(params) ret := m.Value.Call(params)
var err error
err := ret[t.NumOut()-1].Interface().(*Error) nilErr := false // The reflection will find almost-nils, let's only pass back clean ones!
ret = ret[:t.NumOut()-1] if t.NumOut() > 0 {
if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error
nilErr = ret[t.NumOut()-1].IsNil()
ret = ret[:t.NumOut()-1]
err = e
} else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error
i := ret[t.NumOut()-1].Interface()
if i == nil {
nilErr = ret[t.NumOut()-1].IsNil()
} else {
err = i.(error)
}
ret = ret[:t.NumOut()-1]
}
}
out := make([]interface{}, len(ret)) out := make([]interface{}, len(ret))
for i, val := range ret { for i, val := range ret {
out[i] = val.Interface() out[i] = val.Interface()
} }
if err == nil { if nilErr || err == nil {
//concrete type to interface nil is a special case //concrete type to interface nil is a special case
return out, nil return out, nil
} }

@ -69,6 +69,22 @@ func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Va
return methods return methods
} }
func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
if in == nil {
return nil
}
methods := make(map[string]reflect.Value)
val := reflect.ValueOf(in)
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
methtype := typ.Method(i)
method := val.Method(i)
// map names while building table
methods[computeMethodName(methtype.Name, mapping)] = method
}
return methods
}
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) { func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
pointers := make([]interface{}, m.NumArguments()) pointers := make([]interface{}, m.NumArguments())
decode := make([]interface{}, 0, len(body)) decode := make([]interface{}, 0, len(body))
@ -159,7 +175,6 @@ func (conn *Conn) handleCall(msg *Message) {
if msg.Flags&FlagNoReplyExpected == 0 { if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message) reply := new(Message)
reply.Type = TypeMethodReply reply.Type = TypeMethodReply
reply.serial = conn.getSerial()
reply.Headers = make(map[HeaderField]Variant) reply.Headers = make(map[HeaderField]Variant)
if hasSender { if hasSender {
reply.Headers[FieldDestination] = msg.Headers[FieldSender] reply.Headers[FieldDestination] = msg.Headers[FieldSender]
@ -195,7 +210,6 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
} }
msg := new(Message) msg := new(Message)
msg.Type = TypeSignal msg.Type = TypeSignal
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant) msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldInterface] = MakeVariant(iface) msg.Headers[FieldInterface] = MakeVariant(iface)
msg.Headers[FieldMember] = MakeVariant(member) msg.Headers[FieldMember] = MakeVariant(member)
@ -247,6 +261,18 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
return conn.ExportWithMap(v, nil, path, iface) return conn.ExportWithMap(v, nil, path, iface)
} }
// ExportAll registers all exported methods defined by the given object on
// the message bus.
//
// Unlike Export there is no requirement to have the last parameter as type
// *Error. If you want to be able to return error then you can append an error
// type parameter to your method signature. If the error returned is not nil,
// it is sent back to the caller as an error. Otherwise, a method reply is
// sent with the other return values as its body.
func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error {
return conn.export(getAllMethods(v, nil), path, iface, false)
}
// ExportWithMap works exactly like Export but provides the ability to remap // ExportWithMap works exactly like Export but provides the ability to remap
// method names (e.g. export a lower-case method). // method names (e.g. export a lower-case method).
// //
@ -299,19 +325,22 @@ func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path
} }
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error { func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
out := make(map[string]reflect.Value) var out map[string]reflect.Value
for name, method := range methods { if methods != nil {
rval := reflect.ValueOf(method) out = make(map[string]reflect.Value)
if rval.Kind() != reflect.Func { for name, method := range methods {
continue rval := reflect.ValueOf(method)
} if rval.Kind() != reflect.Func {
t := rval.Type() continue
// only track valid methods must return *Error as last arg }
if t.NumOut() == 0 || t := rval.Type()
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { // only track valid methods must return *Error as last arg
continue if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
continue
}
out[name] = rval
} }
out[name] = rval
} }
return conn.export(out, path, iface, includeSubtree) return conn.export(out, path, iface, includeSubtree)
} }
@ -327,12 +356,12 @@ func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) err
return nil return nil
} }
// exportWithMap is the worker function for all exports/registrations. // export is the worker function for all exports/registrations.
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error { func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
h, ok := conn.handler.(*defaultHandler) h, ok := conn.handler.(*defaultHandler)
if !ok { if !ok {
return fmt.Errorf( return fmt.Errorf(
`dbus: export only allowed on the default hander handler have %T"`, `dbus: export only allowed on the default handler. Received: %T"`,
conn.handler) conn.handler)
} }

@ -1,3 +0,0 @@
module github.com/godbus/dbus/v5
go 1.12

@ -1,6 +1,7 @@
package dbus package dbus
import ( import (
"strconv"
"strings" "strings"
) )
@ -60,3 +61,29 @@ func WithMatchPathNamespace(namespace ObjectPath) MatchOption {
func WithMatchDestination(destination string) MatchOption { func WithMatchDestination(destination string) MatchOption {
return WithMatchOption("destination", destination) return WithMatchOption("destination", destination)
} }
// WithMatchArg sets argN match option, range of N is 0 to 63.
func WithMatchArg(argIdx int, value string) MatchOption {
if argIdx < 0 || argIdx > 63 {
panic("range of argument index is 0 to 63")
}
return WithMatchOption("arg"+strconv.Itoa(argIdx), value)
}
// WithMatchArgPath sets argN path match option, range of N is 0 to 63.
func WithMatchArgPath(argIdx int, path string) MatchOption {
if argIdx < 0 || argIdx > 63 {
panic("range of argument index is 0 to 63")
}
return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path)
}
// WithMatchArg0Namespace sets arg0namespace match option.
func WithMatchArg0Namespace(arg0Namespace string) MatchOption {
return WithMatchOption("arg0namespace", arg0Namespace)
}
// WithMatchEavesdrop sets eavesdrop match option.
func WithMatchEavesdrop(eavesdrop bool) MatchOption {
return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop))
}

@ -16,6 +16,7 @@ type BusObject interface {
AddMatchSignal(iface, member string, options ...MatchOption) *Call AddMatchSignal(iface, member string, options ...MatchOption) *Call
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
GetProperty(p string) (Variant, error) GetProperty(p string) (Variant, error)
StoreProperty(p string, value interface{}) error
SetProperty(p string, v interface{}) error SetProperty(p string, v interface{}) error
Destination() string Destination() string
Path() ObjectPath Path() ObjectPath
@ -109,7 +110,6 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch
method = method[i+1:] method = method[i+1:]
msg := new(Message) msg := new(Message)
msg.Type = TypeMethodCall msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant) msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path) msg.Headers[FieldPath] = MakeVariant(o.path)
@ -122,68 +122,31 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch
if len(args) > 0 { if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
} }
if msg.Flags&FlagNoReplyExpected == 0 { return o.conn.SendWithContext(ctx, msg, ch)
if ch == nil {
ch = make(chan *Call, 1)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
ctx, cancel := context.WithCancel(ctx)
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
ctxCanceler: cancel,
ctx: ctx,
}
o.conn.calls.track(msg.serial, call)
o.conn.sendMessageAndIfClosed(msg, func() {
o.conn.calls.handleSendError(msg, ErrClosed)
cancel()
})
go func() {
<-ctx.Done()
o.conn.calls.handleSendError(msg, ctx.Err())
}()
return call
}
done := make(chan *Call, 1)
call := &Call{
Err: nil,
Done: done,
}
defer func() {
call.Done <- call
close(done)
}()
o.conn.sendMessageAndIfClosed(msg, func() {
call.Err = ErrClosed
})
return call
} }
// GetProperty calls org.freedesktop.DBus.Properties.Get on the given // GetProperty calls org.freedesktop.DBus.Properties.Get on the given
// object. The property name must be given in interface.member notation. // object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) { func (o *Object) GetProperty(p string) (Variant, error) {
var result Variant
err := o.StoreProperty(p, &result)
return result, err
}
// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given
// object. The property name must be given in interface.member notation.
// It stores the returned property into the provided value.
func (o *Object) StoreProperty(p string, value interface{}) error {
idx := strings.LastIndex(p, ".") idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) { if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p) return errors.New("dbus: invalid property " + p)
} }
iface := p[:idx] iface := p[:idx]
prop := p[idx+1:] prop := p[idx+1:]
result := Variant{} return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) Store(value)
if err != nil {
return Variant{}, err
}
return result, nil
} }
// SetProperty calls org.freedesktop.DBus.Properties.Set on the given // SetProperty calls org.freedesktop.DBus.Properties.Set on the given

@ -0,0 +1,24 @@
package dbus
// Sequence represents the value of a monotonically increasing counter.
type Sequence uint64
const (
// NoSequence indicates the absence of a sequence value.
NoSequence Sequence = 0
)
// sequenceGenerator represents a monotonically increasing counter.
type sequenceGenerator struct {
nextSequence Sequence
}
func (generator *sequenceGenerator) next() Sequence {
result := generator.nextSequence
generator.nextSequence++
return result
}
func newSequenceGenerator() *sequenceGenerator {
return &sequenceGenerator{nextSequence: 1}
}

@ -0,0 +1,125 @@
package dbus
import (
"sync"
)
// NewSequentialSignalHandler returns an instance of a new
// signal handler that guarantees sequential processing of signals. It is a
// guarantee of this signal handler that signals will be written to
// channels in the order they are received on the DBus connection.
func NewSequentialSignalHandler() SignalHandler {
return &sequentialSignalHandler{}
}
type sequentialSignalHandler struct {
mu sync.RWMutex
closed bool
signals []*sequentialSignalChannelData
}
func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
sh.mu.RLock()
defer sh.mu.RUnlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.deliver(signal)
}
}
func (sh *sequentialSignalHandler) Terminate() {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.close()
close(scd.ch)
}
sh.closed = true
sh.signals = nil
}
func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
sh.signals = append(sh.signals, newSequentialSignalChannelData(ch))
}
func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for i := len(sh.signals) - 1; i >= 0; i-- {
if ch == sh.signals[i].ch {
sh.signals[i].close()
copy(sh.signals[i:], sh.signals[i+1:])
sh.signals[len(sh.signals)-1] = nil
sh.signals = sh.signals[:len(sh.signals)-1]
}
}
}
type sequentialSignalChannelData struct {
ch chan<- *Signal
in chan *Signal
done chan struct{}
}
func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData {
scd := &sequentialSignalChannelData{
ch: ch,
in: make(chan *Signal),
done: make(chan struct{}),
}
go scd.bufferSignals()
return scd
}
func (scd *sequentialSignalChannelData) bufferSignals() {
defer close(scd.done)
// Ensure that signals are delivered to scd.ch in the same
// order they are received from scd.in.
var queue []*Signal
for {
if len(queue) == 0 {
signal, ok := <- scd.in
if !ok {
return
}
queue = append(queue, signal)
}
select {
case scd.ch <- queue[0]:
copy(queue, queue[1:])
queue[len(queue)-1] = nil
queue = queue[:len(queue)-1]
case signal, ok := <-scd.in:
if !ok {
return
}
queue = append(queue, signal)
}
}
}
func (scd *sequentialSignalChannelData) deliver(signal *Signal) {
scd.in <- signal
}
func (scd *sequentialSignalChannelData) close() {
close(scd.in)
// Ensure that bufferSignals() has exited and won't attempt
// any future sends on scd.ch
<-scd.done
}

@ -137,7 +137,7 @@ func ParseSignatureMust(s string) Signature {
return sig return sig
} }
// Empty retruns whether the signature is the empty signature. // Empty returns whether the signature is the empty signature.
func (s Signature) Empty() bool { func (s Signature) Empty() bool {
return s.str == "" return s.str == ""
} }

@ -10,6 +10,7 @@ package dbus
/* /*
const int sizeofPtr = sizeof(void*); const int sizeofPtr = sizeof(void*);
#define _WANT_UCRED #define _WANT_UCRED
#include <sys/types.h>
#include <sys/ucred.h> #include <sys/ucred.h>
*/ */
import "C" import "C"

@ -142,3 +142,9 @@ func (v Variant) String() string {
func (v Variant) Value() interface{} { func (v Variant) Value() interface{} {
return v.value return v.value
} }
// Store converts the variant into a native go type using the same
// mechanism as the "Store" function.
func (v Variant) Store(value interface{}) error {
return storeInterfaces(v.value, value)
}

@ -719,7 +719,7 @@ loop:
n256setup() n256setup()
} }
attr &= backgroundMask attr &= backgroundMask
attr |= n256foreAttr[n256] attr |= n256foreAttr[n256%len(n256foreAttr)]
i += 2 i += 2
} }
} else if len(token) == 5 && token[i+1] == "2" { } else if len(token) == 5 && token[i+1] == "2" {
@ -761,7 +761,7 @@ loop:
n256setup() n256setup()
} }
attr &= foregroundMask attr &= foregroundMask
attr |= n256backAttr[n256] attr |= n256backAttr[n256%len(n256backAttr)]
i += 2 i += 2
} }
} else if len(token) == 5 && token[i+1] == "2" { } else if len(token) == 5 && token[i+1] == "2" {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save