mirror of
				https://github.com/adnanh/webhook.git
				synced 2025-10-25 02:30:58 +00:00 
			
		
		
		
	Merge ac897f59aa into dbc6565c35
				
					
				
			This commit is contained in:
		
						commit
						296746ad82
					
				
					 457 changed files with 161 additions and 276813 deletions
				
			
		
							
								
								
									
										10
									
								
								.github/workflows/build.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/build.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -4,20 +4,20 @@ jobs: | ||||||
|   build: |   build: | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         go-version: [1.14.x, 1.15.x] |         go-version: [1.19.x, 1.20.x] | ||||||
|         os: [ubuntu-latest, macos-latest, windows-latest] |         os: [ubuntu-latest, macos-latest, windows-latest] | ||||||
| 
 | 
 | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
| 
 | 
 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-go@v2 |       - uses: actions/setup-go@v4 | ||||||
|         with: |         with: | ||||||
|           go-version: ${{ matrix.go-version }} |           go-version: ${{ matrix.go-version }} | ||||||
|         id: go |         id: go | ||||||
| 
 | 
 | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: go build -v |         run: make build | ||||||
| 
 | 
 | ||||||
|       - name: Test |       - name: Test | ||||||
|         run: go test -v ./... |         run: make test | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -10,7 +10,7 @@ help: | ||||||
| all: build release release-windows | all: build release release-windows | ||||||
| 
 | 
 | ||||||
| build: deps ## Build the project
 | build: deps ## Build the project
 | ||||||
| 	go build | 	go build -ldflags "-linkmode external -extldflags=-static" | ||||||
| 
 | 
 | ||||||
| release: clean deps ## Generate releases for unix systems
 | release: clean deps ## Generate releases for unix systems
 | ||||||
| 	@for arch in $(ARCHS);\
 | 	@for arch in $(ARCHS);\
 | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -4,16 +4,14 @@ go 1.14 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/clbanning/mxj v1.8.4 | 	github.com/clbanning/mxj v1.8.4 | ||||||
| 	github.com/dustin/go-humanize v1.0.0 | 	github.com/dustin/go-humanize v1.0.1 | ||||||
| 	github.com/fsnotify/fsnotify v1.4.7 // indirect | 	github.com/fsnotify/fsnotify v1.6.0 // indirect | ||||||
| 	github.com/ghodss/yaml v1.0.0 | 	github.com/ghodss/yaml v1.0.0 | ||||||
| 	github.com/go-chi/chi v4.0.2+incompatible | 	github.com/go-chi/chi v4.1.2+incompatible | ||||||
| 	github.com/gofrs/uuid v3.2.0+incompatible | 	github.com/gofrs/uuid v4.4.0+incompatible | ||||||
| 	github.com/gorilla/mux v1.7.3 | 	github.com/gorilla/mux v1.8.0 | ||||||
| 	github.com/kr/pretty v0.1.0 // indirect | 	golang.org/x/net v0.14.0 // indirect | ||||||
| 	golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect | 	golang.org/x/sys v0.11.0 | ||||||
| 	golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 | 	gopkg.in/fsnotify.v1 v1.4.7 | ||||||
| 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
| 	gopkg.in/fsnotify.v1 v1.4.2 |  | ||||||
| 	gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 // indirect |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										83
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										83
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -1,34 +1,63 @@ | ||||||
| github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= | github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= | ||||||
| github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= | github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= | ||||||
| github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | ||||||
| github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= | ||||||
| github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= | ||||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= | ||||||
| github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | ||||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||||
| github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= | github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= | ||||||
| github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | ||||||
| github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= | github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= | ||||||
| github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= | github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= | ||||||
| github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||||
| github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| 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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |  | ||||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |  | ||||||
| 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/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||||
| golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
|  | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
|  | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
|  | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
|  | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||||
|  | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | ||||||
|  | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= | ||||||
|  | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| 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-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | ||||||
|  | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
|  | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
|  | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||||
|  | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= | ||||||
|  | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= | ||||||
| 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= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
| gopkg.in/fsnotify.v1 v1.4.2 h1:AwZiD/bIUttYJ+n/k1UwlSUsM+VSE6id7UAnSKqQ+Tc= | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
| gopkg.in/fsnotify.v1 v1.4.2/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | ||||||
| gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk= | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= | ||||||
| gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
|  | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||||
|  | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | ||||||
|  | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
|  |  | ||||||
|  | @ -434,10 +434,11 @@ func ExtractParameterAsString(s string, params interface{}) (string, error) { | ||||||
| // Argument type specifies the parameter key name and the source it should | // Argument type specifies the parameter key name and the source it should | ||||||
| // be extracted from | // be extracted from | ||||||
| type Argument struct { | type Argument struct { | ||||||
| 	Source       string `json:"source,omitempty"` | 	Source                  string   `json:"source,omitempty"` | ||||||
| 	Name         string `json:"name,omitempty"` | 	Name                    string   `json:"name,omitempty"` | ||||||
| 	EnvName      string `json:"envname,omitempty"` | 	EnvName                 string   `json:"envname,omitempty"` | ||||||
| 	Base64Decode bool   `json:"base64decode,omitempty"` | 	Base64Decode            bool     `json:"base64decode,omitempty"` | ||||||
|  | 	SignaturePayloadHeaders []string `json:"headers-to-add-to-signature-payload,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Get Argument method returns the value for the Argument's key name | // Get Argument method returns the value for the Argument's key name | ||||||
|  | @ -919,6 +920,7 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) { | ||||||
| 
 | 
 | ||||||
| 	arg, err := r.Parameter.Get(req) | 	arg, err := r.Parameter.Get(req) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
|  | 		payload := r.signaturePayload(req.Body, req.Headers) | ||||||
| 		switch r.Type { | 		switch r.Type { | ||||||
| 		case MatchValue: | 		case MatchValue: | ||||||
| 			return compare(arg, r.Value), nil | 			return compare(arg, r.Value), nil | ||||||
|  | @ -928,25 +930,38 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) { | ||||||
| 			log.Print(`warn: use of deprecated option payload-hash-sha1; use payload-hmac-sha1 instead`) | 			log.Print(`warn: use of deprecated option payload-hash-sha1; use payload-hmac-sha1 instead`) | ||||||
| 			fallthrough | 			fallthrough | ||||||
| 		case MatchHMACSHA1: | 		case MatchHMACSHA1: | ||||||
| 			_, err := CheckPayloadSignature(req.Body, r.Secret, arg) | 			_, err := CheckPayloadSignature(payload, r.Secret, arg) | ||||||
| 			return err == nil, err | 			return err == nil, err | ||||||
| 		case MatchHashSHA256: | 		case MatchHashSHA256: | ||||||
| 			log.Print(`warn: use of deprecated option payload-hash-sha256: use payload-hmac-sha256 instead`) | 			log.Print(`warn: use of deprecated option payload-hash-sha256: use payload-hmac-sha256 instead`) | ||||||
| 			fallthrough | 			fallthrough | ||||||
| 		case MatchHMACSHA256: | 		case MatchHMACSHA256: | ||||||
| 			_, err := CheckPayloadSignature256(req.Body, r.Secret, arg) | 			_, err := CheckPayloadSignature256(payload, r.Secret, arg) | ||||||
| 			return err == nil, err | 			return err == nil, err | ||||||
| 		case MatchHashSHA512: | 		case MatchHashSHA512: | ||||||
| 			log.Print(`warn: use of deprecated option payload-hash-sha512: use payload-hmac-sha512 instead`) | 			log.Print(`warn: use of deprecated option payload-hash-sha512: use payload-hmac-sha512 instead`) | ||||||
| 			fallthrough | 			fallthrough | ||||||
| 		case MatchHMACSHA512: | 		case MatchHMACSHA512: | ||||||
| 			_, err := CheckPayloadSignature512(req.Body, r.Secret, arg) | 			_, err := CheckPayloadSignature512(payload, r.Secret, arg) | ||||||
| 			return err == nil, err | 			return err == nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return false, err | 	return false, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // signaturePayload will concatenate the request body with header values from headers specified in r.Parameter.SignaturePayloadHeaders. | ||||||
|  | func (r MatchRule) signaturePayload(body []byte, headers map[string]interface{}) []byte { | ||||||
|  | 	for _, elem := range r.Parameter.SignaturePayloadHeaders { | ||||||
|  | 		h, found := headers[elem] | ||||||
|  | 		header := fmt.Sprintf("%s", h) | ||||||
|  | 		if found && header != "" { | ||||||
|  | 			body = append(body, []byte(header)...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return body | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // compare is a helper function for constant time string comparisons. | // compare is a helper function for constant time string comparisons. | ||||||
| func compare(a, b string) bool { | func compare(a, b string) bool { | ||||||
| 	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 | 	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 | ||||||
|  |  | ||||||
|  | @ -274,7 +274,7 @@ var argumentGetTests = []struct { | ||||||
| 
 | 
 | ||||||
| func TestArgumentGet(t *testing.T) { | func TestArgumentGet(t *testing.T) { | ||||||
| 	for _, tt := range argumentGetTests { | 	for _, tt := range argumentGetTests { | ||||||
| 		a := Argument{tt.source, tt.name, "", false} | 		a := Argument{tt.source, tt.name, "", false, nil} | ||||||
| 		r := &Request{ | 		r := &Request{ | ||||||
| 			Headers:    tt.headers, | 			Headers:    tt.headers, | ||||||
| 			Query:      tt.query, | 			Query:      tt.query, | ||||||
|  | @ -294,14 +294,14 @@ var hookParseJSONParametersTests = []struct { | ||||||
| 	rheaders, rquery, rpayload map[string]interface{} | 	rheaders, rquery, rpayload map[string]interface{} | ||||||
| 	ok                         bool | 	ok                         bool | ||||||
| }{ | }{ | ||||||
| 	{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true}, | 	{[]Argument{Argument{"header", "a", "", false, nil}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true}, | ||||||
| 	{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, | 	{[]Argument{Argument{"url", "a", "", false, nil}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, | ||||||
| 	{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, | 	{[]Argument{Argument{"payload", "a", "", false, nil}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, | ||||||
| 	{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true}, | 	{[]Argument{Argument{"header", "z", "", false, nil}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true}, | ||||||
| 	// failures | 	// failures | ||||||
| 	{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // empty string | 	{[]Argument{Argument{"header", "z", "", false, nil}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // empty string | ||||||
| 	{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter | 	{[]Argument{Argument{"header", "y", "", false, nil}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter | ||||||
| 	{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // invalid argument source | 	{[]Argument{Argument{"string", "z", "", false, nil}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false},     // invalid argument source | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHookParseJSONParameters(t *testing.T) { | func TestHookParseJSONParameters(t *testing.T) { | ||||||
|  | @ -326,9 +326,9 @@ var hookExtractCommandArgumentsTests = []struct { | ||||||
| 	value                   []string | 	value                   []string | ||||||
| 	ok                      bool | 	ok                      bool | ||||||
| }{ | }{ | ||||||
| 	{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true}, | 	{"test", []Argument{Argument{"header", "a", "", false, nil}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true}, | ||||||
| 	// failures | 	// failures | ||||||
| 	{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false}, | 	{"fail", []Argument{Argument{"payload", "a", "", false, nil}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHookExtractCommandArguments(t *testing.T) { | func TestHookExtractCommandArguments(t *testing.T) { | ||||||
|  | @ -351,20 +351,21 @@ func TestHookExtractCommandArguments(t *testing.T) { | ||||||
| // we test both cases where the name of the data is used as the name of the | // we test both cases where the name of the data is used as the name of the | ||||||
| // env key & the case where the hook definition sets the env var name to a | // env key & the case where the hook definition sets the env var name to a | ||||||
| // fixed value using the envname construct like so:: | // fixed value using the envname construct like so:: | ||||||
| //    [ | // | ||||||
| //      { | //	[ | ||||||
| //        "id": "push", | //	  { | ||||||
| //        "execute-command": "bb2mm", | //	    "id": "push", | ||||||
| //        "command-working-directory": "/tmp", | //	    "execute-command": "bb2mm", | ||||||
| //        "pass-environment-to-command": | //	    "command-working-directory": "/tmp", | ||||||
| //        [ | //	    "pass-environment-to-command": | ||||||
| //          { | //	    [ | ||||||
| //            "source": "entire-payload", | //	      { | ||||||
| //            "envname": "PAYLOAD" | //	        "source": "entire-payload", | ||||||
| //          }, | //	        "envname": "PAYLOAD" | ||||||
| //        ] | //	      }, | ||||||
| //      } | //	    ] | ||||||
| //    ] | //	  } | ||||||
|  | //	] | ||||||
| var hookExtractCommandArgumentsForEnvTests = []struct { | var hookExtractCommandArgumentsForEnvTests = []struct { | ||||||
| 	exec                    string | 	exec                    string | ||||||
| 	args                    []Argument | 	args                    []Argument | ||||||
|  | @ -375,14 +376,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct { | ||||||
| 	// successes | 	// successes | ||||||
| 	{ | 	{ | ||||||
| 		"test", | 		"test", | ||||||
| 		[]Argument{Argument{"header", "a", "", false}}, | 		[]Argument{Argument{"header", "a", "", false, nil}}, | ||||||
| 		map[string]interface{}{"A": "z"}, nil, nil, | 		map[string]interface{}{"A": "z"}, nil, nil, | ||||||
| 		[]string{"HOOK_a=z"}, | 		[]string{"HOOK_a=z"}, | ||||||
| 		true, | 		true, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"test", | 		"test", | ||||||
| 		[]Argument{Argument{"header", "a", "MYKEY", false}}, | 		[]Argument{Argument{"header", "a", "MYKEY", false, nil}}, | ||||||
| 		map[string]interface{}{"A": "z"}, nil, nil, | 		map[string]interface{}{"A": "z"}, nil, nil, | ||||||
| 		[]string{"MYKEY=z"}, | 		[]string{"MYKEY=z"}, | ||||||
| 		true, | 		true, | ||||||
|  | @ -390,7 +391,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct { | ||||||
| 	// failures | 	// failures | ||||||
| 	{ | 	{ | ||||||
| 		"fail", | 		"fail", | ||||||
| 		[]Argument{Argument{"payload", "a", "", false}}, | 		[]Argument{Argument{"payload", "a", "", false, nil}}, | ||||||
| 		map[string]interface{}{"A": "z"}, nil, nil, | 		map[string]interface{}{"A": "z"}, nil, nil, | ||||||
| 		[]string{}, | 		[]string{}, | ||||||
| 		false, | 		false, | ||||||
|  | @ -489,24 +490,27 @@ var matchRuleTests = []struct { | ||||||
| 	ok                                 bool | 	ok                                 bool | ||||||
| 	err                                bool | 	err                                bool | ||||||
| }{ | }{ | ||||||
| 	{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false}, | 	{"value", "", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false}, | ||||||
| 	{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false}, | 	{"regex", "^z", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false}, | ||||||
| 	{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | 	{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
| 	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | 	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
| 	{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | 	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false, []string{"X-Header"}}, map[string]interface{}{"A": "63bfb5c18198b324c22a2349796ac1657cec24e5", "X-Header": "custom-value"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
| 	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | 	{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
|  | 	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
|  | 	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false, []string{"X-Header"}}, map[string]interface{}{"A": "0a817ee9c28421c89e30792534fbe2dff5c246fe3a2e31d624ddab20f0227268", "X-Header": "custom-value"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
|  | 	{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false, []string{"X-Header"}}, map[string]interface{}{"A": "08ed79bb565b6848666d40c874e2818552f897923cd43765dcd4983bfa6fa4d2443c3be0d3addda1cd416bcc538c48b0d328a9c4af6ce7a7fecd4ba7641a6412", "X-Header": "custom-value"}, nil, nil, []byte(`{"a": "z"}`), "", true, false}, | ||||||
| 	// failures | 	// failures | ||||||
| 	{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false}, | 	{"value", "", "", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false}, | ||||||
| 	{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false}, | 	{"regex", "^X", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false}, | ||||||
| 	{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header | 	{"value", "", "2", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header | ||||||
| 	// errors | 	// errors | ||||||
| 	{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true},                   // invalid regex | 	{"regex", "*", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true},                   // invalid regex | ||||||
| 	{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac | 	{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac | ||||||
| 	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac | 	{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true},   // invalid hmac | ||||||
| 	{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | 	{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | ||||||
| 	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | 	{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | ||||||
| 	{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | 	{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | ||||||
| 	{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | 	{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac | ||||||
| 	// IP whitelisting, valid cases | 	// IP whitelisting, valid cases | ||||||
| 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range | 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range | ||||||
| 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range | 	{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range | ||||||
|  | @ -552,8 +556,8 @@ var andRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=z, b=y): a=z && b=y", | 		"(a=z, b=y): a=z && b=y", | ||||||
| 		AndRule{ | 		AndRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"A": "z", "B": "y"}, nil, nil, | 		map[string]interface{}{"A": "z", "B": "y"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -562,8 +566,8 @@ var andRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=z, b=Y): a=z && b=y", | 		"(a=z, b=Y): a=z && b=y", | ||||||
| 		AndRule{ | 		AndRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, | 		map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -573,22 +577,22 @@ var andRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=z, b=y, c=x, d=w=, e=X, f=X): a=z && (b=y && c=x) && (d=w || e=v) && !f=u", | 		"(a=z, b=y, c=x, d=w=, e=X, f=X): a=z && (b=y && c=x) && (d=w || e=v) && !f=u", | ||||||
| 		AndRule{ | 		AndRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{ | 			{ | ||||||
| 				And: &AndRule{ | 				And: &AndRule{ | ||||||
| 					{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 					{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 					{Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", "", false}, ""}}, | 					{Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", "", false, nil}, ""}}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				Or: &OrRule{ | 				Or: &OrRule{ | ||||||
| 					{Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", "", false}, ""}}, | 					{Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", "", false, nil}, ""}}, | ||||||
| 					{Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", "", false}, ""}}, | 					{Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", "", false, nil}, ""}}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				Not: &NotRule{ | 				Not: &NotRule{ | ||||||
| 					Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", "", false}, ""}, | 					Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", "", false, nil}, ""}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -600,7 +604,7 @@ var andRuleTests = []struct { | ||||||
| 	// failures | 	// failures | ||||||
| 	{ | 	{ | ||||||
| 		"invalid rule", | 		"invalid rule", | ||||||
| 		AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}}, | 		AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false, nil}, ""}}}, | ||||||
| 		map[string]interface{}{"Y": "z"}, nil, nil, nil, | 		map[string]interface{}{"Y": "z"}, nil, nil, nil, | ||||||
| 		false, true, | 		false, true, | ||||||
| 	}, | 	}, | ||||||
|  | @ -632,8 +636,8 @@ var orRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=z, b=X): a=z || b=y", | 		"(a=z, b=X): a=z || b=y", | ||||||
| 		OrRule{ | 		OrRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"A": "z", "B": "X"}, nil, nil, | 		map[string]interface{}{"A": "z", "B": "X"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -642,8 +646,8 @@ var orRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=X, b=y): a=z || b=y", | 		"(a=X, b=y): a=z || b=y", | ||||||
| 		OrRule{ | 		OrRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"A": "X", "B": "y"}, nil, nil, | 		map[string]interface{}{"A": "X", "B": "y"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -652,8 +656,8 @@ var orRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"(a=Z, b=Y): a=z || b=y", | 		"(a=Z, b=Y): a=z || b=y", | ||||||
| 		OrRule{ | 		OrRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, | 		map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -663,7 +667,7 @@ var orRuleTests = []struct { | ||||||
| 	{ | 	{ | ||||||
| 		"missing parameter node", | 		"missing parameter node", | ||||||
| 		OrRule{ | 		OrRule{ | ||||||
| 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, | 			{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, | ||||||
| 		}, | 		}, | ||||||
| 		map[string]interface{}{"Y": "Z"}, nil, nil, | 		map[string]interface{}{"Y": "Z"}, nil, nil, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
|  | @ -694,8 +698,8 @@ var notRuleTests = []struct { | ||||||
| 	ok                      bool | 	ok                      bool | ||||||
| 	err                     bool | 	err                     bool | ||||||
| }{ | }{ | ||||||
| 	{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false}, | 	{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false, nil}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false}, | ||||||
| 	{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false}, | 	{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false, nil}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNotRule(t *testing.T) { | func TestNotRule(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ type LogEntry struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Write constructs and writes the final log entry. | // Write constructs and writes the final log entry. | ||||||
| func (l *LogEntry) Write(status, totalBytes int, elapsed time.Duration) { | func (l *LogEntry) Write(status, totalBytes int, header http.Header, elapsed time.Duration, extra interface{}) { | ||||||
| 	rid := GetReqID(l.req.Context()) | 	rid := GetReqID(l.req.Context()) | ||||||
| 	if rid != "" { | 	if rid != "" { | ||||||
| 		fmt.Fprintf(l.buf, "[%s] ", rid) | 		fmt.Fprintf(l.buf, "[%s] ", rid) | ||||||
|  |  | ||||||
							
								
								
									
										55
									
								
								vendor/github.com/clbanning/mxj/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/clbanning/mxj/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,55 +0,0 @@ | ||||||
| Copyright (c) 2012-2016 Charles Banning <clbanning@gmail.com>.  All rights reserved. |  | ||||||
| 
 |  | ||||||
| The MIT License (MIT) |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
| 
 |  | ||||||
| =============================================================================== |  | ||||||
| 
 |  | ||||||
| Go Language Copyright & License -  |  | ||||||
| 
 |  | ||||||
| Copyright 2009 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. |  | ||||||
| 
 |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are |  | ||||||
| met: |  | ||||||
| 
 |  | ||||||
|    * Redistributions of source code must retain the above copyright |  | ||||||
| notice, this list of conditions and the following disclaimer. |  | ||||||
|    * Redistributions in binary form must reproduce the above |  | ||||||
| copyright notice, this list of conditions and the following disclaimer |  | ||||||
| in the documentation and/or other materials provided with the |  | ||||||
| distribution. |  | ||||||
|    * Neither the name of Google Inc. nor the names of its |  | ||||||
| contributors may be used to endorse or promote products derived from |  | ||||||
| this software without specific prior written permission. |  | ||||||
| 
 |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |  | ||||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | ||||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |  | ||||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |  | ||||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |  | ||||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
							
								
								
									
										189
									
								
								vendor/github.com/clbanning/mxj/anyxml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										189
									
								
								vendor/github.com/clbanning/mxj/anyxml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,189 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/xml" |  | ||||||
| 	"reflect" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	DefaultElementTag = "element" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Encode arbitrary value as XML. |  | ||||||
| // |  | ||||||
| // Note: unmarshaling the resultant |  | ||||||
| // XML may not return the original value, since tag labels may have been injected |  | ||||||
| // to create the XML representation of the value. |  | ||||||
| /* |  | ||||||
|  Encode an arbitrary JSON object. |  | ||||||
| 	package main |  | ||||||
| 
 |  | ||||||
| 	import ( |  | ||||||
| 		"encoding/json" |  | ||||||
| 		"fmt" |  | ||||||
| 		"github.com/clbanning/mxj" |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	func main() { |  | ||||||
| 		jsondata := []byte(`[ |  | ||||||
| 			{ "somekey":"somevalue" }, |  | ||||||
| 			"string", |  | ||||||
| 			3.14159265, |  | ||||||
| 			true |  | ||||||
| 		]`) |  | ||||||
| 		var i interface{} |  | ||||||
| 		err := json.Unmarshal(jsondata, &i) |  | ||||||
| 		if err != nil { |  | ||||||
| 			// do something |  | ||||||
| 		} |  | ||||||
| 		x, err := mxj.AnyXmlIndent(i, "", "  ", "mydoc") |  | ||||||
| 		if err != nil { |  | ||||||
| 			// do something else |  | ||||||
| 		} |  | ||||||
| 		fmt.Println(string(x)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	output: |  | ||||||
| 		<mydoc> |  | ||||||
| 		  <somekey>somevalue</somekey> |  | ||||||
| 		  <element>string</element> |  | ||||||
| 		  <element>3.14159265</element> |  | ||||||
| 		  <element>true</element> |  | ||||||
| 		</mydoc> |  | ||||||
| */ |  | ||||||
| // Alternative values for DefaultRootTag and DefaultElementTag can be set as: |  | ||||||
| // AnyXml( v, myRootTag, myElementTag). |  | ||||||
| func AnyXml(v interface{}, tags ...string) ([]byte, error) { |  | ||||||
| 	var rt, et string |  | ||||||
| 	if len(tags) == 1 || len(tags) == 2 { |  | ||||||
| 		rt = tags[0] |  | ||||||
| 	} else { |  | ||||||
| 		rt = DefaultRootTag |  | ||||||
| 	} |  | ||||||
| 	if len(tags) == 2 { |  | ||||||
| 		et = tags[1] |  | ||||||
| 	} else { |  | ||||||
| 		et = DefaultElementTag |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if v == nil { |  | ||||||
| 		if useGoXmlEmptyElemSyntax { |  | ||||||
| 			return []byte("<" + rt + "></" + rt + ">"), nil |  | ||||||
| 		} |  | ||||||
| 		return []byte("<" + rt + "/>"), nil |  | ||||||
| 	} |  | ||||||
| 	if reflect.TypeOf(v).Kind() == reflect.Struct { |  | ||||||
| 		return xml.Marshal(v) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var err error |  | ||||||
| 	s := new(string) |  | ||||||
| 	p := new(pretty) |  | ||||||
| 
 |  | ||||||
| 	var ss string |  | ||||||
| 	var b []byte |  | ||||||
| 	switch v.(type) { |  | ||||||
| 	case []interface{}: |  | ||||||
| 		ss = "<" + rt + ">" |  | ||||||
| 		for _, vv := range v.([]interface{}) { |  | ||||||
| 			switch vv.(type) { |  | ||||||
| 			case map[string]interface{}: |  | ||||||
| 				m := vv.(map[string]interface{}) |  | ||||||
| 				if len(m) == 1 { |  | ||||||
| 					for tag, val := range m { |  | ||||||
| 						err = mapToXmlIndent(false, s, tag, val, p) |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					err = mapToXmlIndent(false, s, et, vv, p) |  | ||||||
| 				} |  | ||||||
| 			default: |  | ||||||
| 				err = mapToXmlIndent(false, s, et, vv, p) |  | ||||||
| 			} |  | ||||||
| 			if err != nil { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		ss += *s + "</" + rt + ">" |  | ||||||
| 		b = []byte(ss) |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		m := Map(v.(map[string]interface{})) |  | ||||||
| 		b, err = m.Xml(rt) |  | ||||||
| 	default: |  | ||||||
| 		err = mapToXmlIndent(false, s, rt, v, p) |  | ||||||
| 		b = []byte(*s) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Encode an arbitrary value as a pretty XML string. |  | ||||||
| // Alternative values for DefaultRootTag and DefaultElementTag can be set as: |  | ||||||
| // AnyXmlIndent( v, "", "  ", myRootTag, myElementTag). |  | ||||||
| func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte, error) { |  | ||||||
| 	var rt, et string |  | ||||||
| 	if len(tags) == 1 || len(tags) == 2 { |  | ||||||
| 		rt = tags[0] |  | ||||||
| 	} else { |  | ||||||
| 		rt = DefaultRootTag |  | ||||||
| 	} |  | ||||||
| 	if len(tags) == 2 { |  | ||||||
| 		et = tags[1] |  | ||||||
| 	} else { |  | ||||||
| 		et = DefaultElementTag |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if v == nil { |  | ||||||
| 		if useGoXmlEmptyElemSyntax { |  | ||||||
| 			return []byte(prefix + "<" + rt + "></" + rt + ">"), nil |  | ||||||
| 		} |  | ||||||
| 		return []byte(prefix + "<" + rt + "/>"), nil |  | ||||||
| 	} |  | ||||||
| 	if reflect.TypeOf(v).Kind() == reflect.Struct { |  | ||||||
| 		return xml.MarshalIndent(v, prefix, indent) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var err error |  | ||||||
| 	s := new(string) |  | ||||||
| 	p := new(pretty) |  | ||||||
| 	p.indent = indent |  | ||||||
| 	p.padding = prefix |  | ||||||
| 
 |  | ||||||
| 	var ss string |  | ||||||
| 	var b []byte |  | ||||||
| 	switch v.(type) { |  | ||||||
| 	case []interface{}: |  | ||||||
| 		ss = "<" + rt + ">\n" |  | ||||||
| 		p.Indent() |  | ||||||
| 		for _, vv := range v.([]interface{}) { |  | ||||||
| 			switch vv.(type) { |  | ||||||
| 			case map[string]interface{}: |  | ||||||
| 				m := vv.(map[string]interface{}) |  | ||||||
| 				if len(m) == 1 { |  | ||||||
| 					for tag, val := range m { |  | ||||||
| 						err = mapToXmlIndent(true, s, tag, val, p) |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					p.start = 1 // we 1 tag in |  | ||||||
| 					err = mapToXmlIndent(true, s, et, vv, p) |  | ||||||
| 					*s += "\n" |  | ||||||
| 				} |  | ||||||
| 			default: |  | ||||||
| 				p.start = 0 // in case trailing p.start = 1 |  | ||||||
| 				err = mapToXmlIndent(true, s, et, vv, p) |  | ||||||
| 			} |  | ||||||
| 			if err != nil { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		ss += *s + "</" + rt + ">" |  | ||||||
| 		b = []byte(ss) |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		m := Map(v.(map[string]interface{})) |  | ||||||
| 		b, err = m.XmlIndent(prefix, indent, rt) |  | ||||||
| 	default: |  | ||||||
| 		err = mapToXmlIndent(true, s, rt, v, p) |  | ||||||
| 		b = []byte(*s) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
							
								
								
									
										54
									
								
								vendor/github.com/clbanning/mxj/atomFeedString.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/clbanning/mxj/atomFeedString.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,54 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> |  | ||||||
| <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld<></name></author><entry><title>rietveld: an attempt at pubsubhubbub |  | ||||||
| </title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html"> |  | ||||||
|   An attempt at adding pubsubhubbub support to Rietveld. |  | ||||||
| http://code.google.com/p/pubsubhubbub |  | ||||||
| http://code.google.com/p/rietveld/issues/detail?id=155 |  | ||||||
| 
 |  | ||||||
| The server side of the protocol is trivial: |  | ||||||
|   1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all |  | ||||||
|      feeds that will be pubsubhubbubbed. |  | ||||||
|   2. every time one of those feeds changes, tell the hub |  | ||||||
|      with a simple POST request. |  | ||||||
| 
 |  | ||||||
| I have tested this by adding debug prints to a local hub |  | ||||||
| server and checking that the server got the right publish |  | ||||||
| requests. |  | ||||||
| 
 |  | ||||||
| I can&#39;t quite get the server to work, but I think the bug |  | ||||||
| is not in my code.  I think that the server expects to be |  | ||||||
| able to grab the feed and see the feed&#39;s actual URL in |  | ||||||
| the link rel=&quot;self&quot;, but the default value for that drops |  | ||||||
| the :port from the URL, and I cannot for the life of me |  | ||||||
| figure out how to get the Atom generator deep inside |  | ||||||
| django not to do that, or even where it is doing that, |  | ||||||
| or even what code is running to generate the Atom feed. |  | ||||||
| (I thought I knew but I added some assert False statements |  | ||||||
| and it kept running!) |  | ||||||
| 
 |  | ||||||
| Ignoring that particular problem, I would appreciate |  | ||||||
| feedback on the right way to get the two values at |  | ||||||
| the top of feeds.py marked NOTE(rsc). |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </summary></entry><entry><title>rietveld: correct tab handling |  | ||||||
| </title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html"> |  | ||||||
|   This fixes the buggy tab rendering that can be seen at |  | ||||||
| http://codereview.appspot.com/116075/diff/1/2 |  | ||||||
| 
 |  | ||||||
| The fundamental problem was that the tab code was |  | ||||||
| not being told what column the text began in, so it |  | ||||||
| didn&#39;t know where to put the tab stops.  Another problem |  | ||||||
| was that some of the code assumed that string byte |  | ||||||
| offsets were the same as column offsets, which is only |  | ||||||
| true if there are no tabs. |  | ||||||
| 
 |  | ||||||
| In the process of fixing this, I cleaned up the arguments |  | ||||||
| to Fold and ExpandTabs and renamed them Break and |  | ||||||
| _ExpandTabs so that I could be sure that I found all the |  | ||||||
| call sites.  I also wanted to verify that ExpandTabs was |  | ||||||
| not being used from outside intra_region_diff.py. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </summary></entry></feed> 	   ` |  | ||||||
| 
 |  | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/clbanning/mxj/doc.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/clbanning/mxj/doc.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,134 +0,0 @@ | ||||||
| // mxj - A collection of map[string]interface{} and associated XML and JSON utilities. |  | ||||||
| // Copyright 2012-2015, 2018 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| Marshal/Unmarshal XML to/from map[string]interface{} values (and JSON); extract/modify values from maps by key or key-path, including wildcards. |  | ||||||
| 
 |  | ||||||
| mxj supplants the legacy x2j and j2x packages. The subpackage x2j-wrapper is provided to facilitate migrating from the x2j package.  The x2j and j2x subpackages provide similar functionality of the old packages but are not function-name compatible with them. |  | ||||||
| 
 |  | ||||||
| Note: this library was designed for processing ad hoc anonymous messages.  Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly. |  | ||||||
| 
 |  | ||||||
| Related Packages: |  | ||||||
| 	checkxml: github.com/clbanning/checkxml provides functions for validating XML data. |  | ||||||
| 
 |  | ||||||
| Notes: |  | ||||||
| 	2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc. |  | ||||||
| 	2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps. |  | ||||||
| 	2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package. |  | ||||||
| 	2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing. |  | ||||||
| 	2017.02.21: github.com/clbanning/checkxml provides functions for validating XML data. |  | ||||||
| 	2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods. |  | ||||||
| 	2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag(). |  | ||||||
| 	2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc. |  | ||||||
| 	2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix(). |  | ||||||
| 	2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable. |  | ||||||
| 	2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars(). |  | ||||||
| 	2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf". |  | ||||||
| 	            To cast them to float64, first set flag with CastNanInf(true). |  | ||||||
| 	2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure. |  | ||||||
| 	2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization. |  | ||||||
| 	2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM). |  | ||||||
| 	2015-12-02: NewMapXmlSeq() with mv.XmlSeq() & co. will try to preserve structure of XML doc when re-encoding. |  | ||||||
| 	2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML. |  | ||||||
| 
 |  | ||||||
| SUMMARY |  | ||||||
| 
 |  | ||||||
|    type Map map[string]interface{} |  | ||||||
| 
 |  | ||||||
|    Create a Map value, 'mv', from any map[string]interface{} value, 'v': |  | ||||||
|       mv := Map(v) |  | ||||||
| 
 |  | ||||||
|    Unmarshal / marshal XML as a Map value, 'mv': |  | ||||||
|       mv, err := NewMapXml(xmlValue) // unmarshal |  | ||||||
|       xmlValue, err := mv.Xml()      // marshal |  | ||||||
| 
 |  | ||||||
|    Unmarshal XML from an io.Reader as a Map value, 'mv': |  | ||||||
|       mv, err := NewMapXmlReader(xmlReader)         // repeated calls, as with an os.File Reader, will process stream |  | ||||||
|       mv, raw, err := NewMapXmlReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded |  | ||||||
| 
 |  | ||||||
|    Marshal Map value, 'mv', to an XML Writer (io.Writer): |  | ||||||
|       err := mv.XmlWriter(xmlWriter) |  | ||||||
|       raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter |  | ||||||
| 
 |  | ||||||
|    Also, for prettified output: |  | ||||||
|       xmlValue, err := mv.XmlIndent(prefix, indent, ...) |  | ||||||
|       err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...) |  | ||||||
|       raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...) |  | ||||||
| 
 |  | ||||||
|    Bulk process XML with error handling (note: handlers must return a boolean value): |  | ||||||
|       err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error)) |  | ||||||
|       err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte)) |  | ||||||
| 
 |  | ||||||
|    Converting XML to JSON: see Examples for NewMapXml and HandleXmlReader. |  | ||||||
| 
 |  | ||||||
|    There are comparable functions and methods for JSON processing. |  | ||||||
| 
 |  | ||||||
|    Arbitrary structure values can be decoded to / encoded from Map values: |  | ||||||
|       mv, err := NewMapStruct(structVal) |  | ||||||
|       err := mv.Struct(structPointer) |  | ||||||
| 
 |  | ||||||
|    To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON |  | ||||||
|    or structure to a Map value, 'mv', or cast a map[string]interface{} value to a Map value, 'mv', then: |  | ||||||
|       paths := mv.PathsForKey(key) |  | ||||||
|       path := mv.PathForKeyShortest(key) |  | ||||||
|       values, err := mv.ValuesForKey(key, subkeys) |  | ||||||
|       values, err := mv.ValuesForPath(path, subkeys) // 'path' can be dot-notation with wildcards and indexed arrays. |  | ||||||
|       count, err := mv.UpdateValuesForPath(newVal, path, subkeys) |  | ||||||
| 
 |  | ||||||
|    Get everything at once, irrespective of path depth: |  | ||||||
|       leafnodes := mv.LeafNodes() |  | ||||||
|       leafvalues := mv.LeafValues() |  | ||||||
| 
 |  | ||||||
|    A new Map with whatever keys are desired can be created from the current Map and then encoded in XML |  | ||||||
|    or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.) |  | ||||||
|       newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N") |  | ||||||
|       newMap, err := mv.NewMap("oldKey1", "oldKey3", "oldKey5") // a subset of 'mv'; see "examples/partial.go" |  | ||||||
|       newXml, err := newMap.Xml()   // for example |  | ||||||
|       newJson, err := newMap.Json() // ditto |  | ||||||
| 
 |  | ||||||
| XML PARSING CONVENTIONS |  | ||||||
| 
 |  | ||||||
|    Using NewMapXml() |  | ||||||
| 
 |  | ||||||
|    - Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`, |  | ||||||
|      to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or |  | ||||||
|      `SetAttrPrefix()`.) |  | ||||||
|    - If the element is a simple element and has attributes, the element value |  | ||||||
|      is given the key `#text` for its `map[string]interface{}` representation.  (See |  | ||||||
|      the 'atomFeedString.xml' test data, below.) |  | ||||||
|    - XML comments, directives, and process instructions are ignored. |  | ||||||
|    - If CoerceKeysToLower() has been called, then the resultant keys will be lower case. |  | ||||||
| 
 |  | ||||||
|    Using NewMapXmlSeq() |  | ||||||
| 
 |  | ||||||
|    - Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values |  | ||||||
|      where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the  |  | ||||||
|      value for `<attr_label>`. |  | ||||||
|    - All elements, except for the root, have a "#seq" key. |  | ||||||
|    - Comments, directives, and process instructions are unmarshalled into the Map using the |  | ||||||
|      keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more |  | ||||||
|      specifics.) |  | ||||||
|    - Name space syntax is preserved:  |  | ||||||
|       - <ns:key>something</ns.key> parses to map["ns:key"]interface{}{"something"} |  | ||||||
|       - xmlns:ns="http://myns.com/ns" parses to map["xmlns:ns"]interface{}{"http://myns.com/ns"} |  | ||||||
| 
 |  | ||||||
|    Both |  | ||||||
| 
 |  | ||||||
|    - By default, "Nan", "Inf", and "-Inf" values are not cast to float64.  If you want them |  | ||||||
|      to be cast, set a flag to cast them  using CastNanInf(true). |  | ||||||
| 
 |  | ||||||
| XML ENCODING CONVENTIONS |  | ||||||
|     |  | ||||||
|    - 'nil' Map values, which may represent 'null' JSON values, are encoded as "<tag/>". |  | ||||||
|      NOTE: the operation is not symmetric as "<tag/>" elements are decoded as 'tag:""' Map values, |  | ||||||
|            which, then, encode in JSON as '"tag":""' values.. |  | ||||||
|    - ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one.  (Go |  | ||||||
|            randomizes the walk through map[string]interface{} values.) If you plan to re-encode the |  | ||||||
|            Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and |  | ||||||
|            mv.XmlSeq() - these try to preserve the element sequencing but with added complexity when |  | ||||||
|            working with the Map representation. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| package mxj |  | ||||||
							
								
								
									
										54
									
								
								vendor/github.com/clbanning/mxj/escapechars.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/clbanning/mxj/escapechars.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,54 +0,0 @@ | ||||||
| // Copyright 2016 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var xmlEscapeChars bool |  | ||||||
| 
 |  | ||||||
| // XMLEscapeChars(true) forces escaping invalid characters in attribute and element values. |  | ||||||
| // NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is |  | ||||||
| // then '&' will be re-escaped as '&amp;'. |  | ||||||
| //   |  | ||||||
| /* |  | ||||||
| 	The values are: |  | ||||||
| 	"   " |  | ||||||
| 	'   ' |  | ||||||
| 	<   < |  | ||||||
| 	>   > |  | ||||||
| 	&   & |  | ||||||
| */ |  | ||||||
| func XMLEscapeChars(b bool) { |  | ||||||
| 	xmlEscapeChars = b |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Scan for '&' first, since 's' may contain "&" that is parsed to "&amp;"  |  | ||||||
| // - or "<" that is parsed to "&lt;". |  | ||||||
| var escapechars = [][2][]byte{ |  | ||||||
| 	{[]byte(`&`), []byte(`&`)}, |  | ||||||
| 	{[]byte(`<`), []byte(`<`)}, |  | ||||||
| 	{[]byte(`>`), []byte(`>`)}, |  | ||||||
| 	{[]byte(`"`), []byte(`"`)}, |  | ||||||
| 	{[]byte(`'`), []byte(`'`)}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func escapeChars(s string) string { |  | ||||||
| 	if len(s) == 0 { |  | ||||||
| 		return s |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b := []byte(s) |  | ||||||
| 	for _, v := range escapechars { |  | ||||||
| 		n := bytes.Count(b, v[0]) |  | ||||||
| 		if n == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		b = bytes.Replace(b, v[0], v[1], n) |  | ||||||
| 	} |  | ||||||
| 	return string(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
							
								
								
									
										7
									
								
								vendor/github.com/clbanning/mxj/exists.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/clbanning/mxj/exists.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,7 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| // Checks whether the path exists |  | ||||||
| func (mv Map) Exists(path string, subkeys ...string) bool { |  | ||||||
| 	v, err := mv.ValuesForPath(path, subkeys...) |  | ||||||
| 	return err == nil && len(v) > 0 |  | ||||||
| } |  | ||||||
							
								
								
									
										287
									
								
								vendor/github.com/clbanning/mxj/files.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										287
									
								
								vendor/github.com/clbanning/mxj/files.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,287 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Maps []Map |  | ||||||
| 
 |  | ||||||
| func NewMaps() Maps { |  | ||||||
| 	return make(Maps, 0) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type MapRaw struct { |  | ||||||
| 	M Map |  | ||||||
| 	R []byte |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMapsFromXmlFile - creates an array from a file of JSON values. |  | ||||||
| func NewMapsFromJsonFile(name string) (Maps, error) { |  | ||||||
| 	fi, err := os.Stat(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if !fi.Mode().IsRegular() { |  | ||||||
| 		return nil, fmt.Errorf("file %s is not a regular file", name) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fh, err := os.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 
 |  | ||||||
| 	am := make([]Map, 0) |  | ||||||
| 	for { |  | ||||||
| 		m, raw, err := NewMapJsonReaderRaw(fh) |  | ||||||
| 		if err != nil && err != io.EOF { |  | ||||||
| 			return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw)) |  | ||||||
| 		} |  | ||||||
| 		if len(m) > 0 { |  | ||||||
| 			am = append(am, m) |  | ||||||
| 		} |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return am, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ReadMapsFromJsonFileRaw - creates an array of MapRaw from a file of JSON values. |  | ||||||
| func NewMapsFromJsonFileRaw(name string) ([]MapRaw, error) { |  | ||||||
| 	fi, err := os.Stat(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if !fi.Mode().IsRegular() { |  | ||||||
| 		return nil, fmt.Errorf("file %s is not a regular file", name) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fh, err := os.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 
 |  | ||||||
| 	am := make([]MapRaw, 0) |  | ||||||
| 	for { |  | ||||||
| 		mr := new(MapRaw) |  | ||||||
| 		mr.M, mr.R, err = NewMapJsonReaderRaw(fh) |  | ||||||
| 		if err != nil && err != io.EOF { |  | ||||||
| 			return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R)) |  | ||||||
| 		} |  | ||||||
| 		if len(mr.M) > 0 { |  | ||||||
| 			am = append(am, *mr) |  | ||||||
| 		} |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return am, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMapsFromXmlFile - creates an array from a file of XML values. |  | ||||||
| func NewMapsFromXmlFile(name string) (Maps, error) { |  | ||||||
| 	fi, err := os.Stat(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if !fi.Mode().IsRegular() { |  | ||||||
| 		return nil, fmt.Errorf("file %s is not a regular file", name) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fh, err := os.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 
 |  | ||||||
| 	am := make([]Map, 0) |  | ||||||
| 	for { |  | ||||||
| 		m, raw, err := NewMapXmlReaderRaw(fh) |  | ||||||
| 		if err != nil && err != io.EOF { |  | ||||||
| 			return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw)) |  | ||||||
| 		} |  | ||||||
| 		if len(m) > 0 { |  | ||||||
| 			am = append(am, m) |  | ||||||
| 		} |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return am, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMapsFromXmlFileRaw - creates an array of MapRaw from a file of XML values. |  | ||||||
| // NOTE: the slice with the raw XML is clean with no extra capacity - unlike NewMapXmlReaderRaw(). |  | ||||||
| // It is slow at parsing a file from disk and is intended for relatively small utility files. |  | ||||||
| func NewMapsFromXmlFileRaw(name string) ([]MapRaw, error) { |  | ||||||
| 	fi, err := os.Stat(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if !fi.Mode().IsRegular() { |  | ||||||
| 		return nil, fmt.Errorf("file %s is not a regular file", name) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fh, err := os.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 
 |  | ||||||
| 	am := make([]MapRaw, 0) |  | ||||||
| 	for { |  | ||||||
| 		mr := new(MapRaw) |  | ||||||
| 		mr.M, mr.R, err = NewMapXmlReaderRaw(fh) |  | ||||||
| 		if err != nil && err != io.EOF { |  | ||||||
| 			return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R)) |  | ||||||
| 		} |  | ||||||
| 		if len(mr.M) > 0 { |  | ||||||
| 			am = append(am, *mr) |  | ||||||
| 		} |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return am, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ------------------------ Maps writing ------------------------- |  | ||||||
| // These are handy-dandy methods for dumping configuration data, etc. |  | ||||||
| 
 |  | ||||||
| // JsonString - analogous to mv.Json() |  | ||||||
| func (mvs Maps) JsonString(safeEncoding ...bool) (string, error) { |  | ||||||
| 	var s string |  | ||||||
| 	for _, v := range mvs { |  | ||||||
| 		j, err := v.Json() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return s, err |  | ||||||
| 		} |  | ||||||
| 		s += string(j) |  | ||||||
| 	} |  | ||||||
| 	return s, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JsonStringIndent - analogous to mv.JsonIndent() |  | ||||||
| func (mvs Maps) JsonStringIndent(prefix, indent string, safeEncoding ...bool) (string, error) { |  | ||||||
| 	var s string |  | ||||||
| 	var haveFirst bool |  | ||||||
| 	for _, v := range mvs { |  | ||||||
| 		j, err := v.JsonIndent(prefix, indent) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return s, err |  | ||||||
| 		} |  | ||||||
| 		if haveFirst { |  | ||||||
| 			s += "\n" |  | ||||||
| 		} else { |  | ||||||
| 			haveFirst = true |  | ||||||
| 		} |  | ||||||
| 		s += string(j) |  | ||||||
| 	} |  | ||||||
| 	return s, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // XmlString - analogous to mv.Xml() |  | ||||||
| func (mvs Maps) XmlString() (string, error) { |  | ||||||
| 	var s string |  | ||||||
| 	for _, v := range mvs { |  | ||||||
| 		x, err := v.Xml() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return s, err |  | ||||||
| 		} |  | ||||||
| 		s += string(x) |  | ||||||
| 	} |  | ||||||
| 	return s, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // XmlStringIndent - analogous to mv.XmlIndent() |  | ||||||
| func (mvs Maps) XmlStringIndent(prefix, indent string) (string, error) { |  | ||||||
| 	var s string |  | ||||||
| 	for _, v := range mvs { |  | ||||||
| 		x, err := v.XmlIndent(prefix, indent) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return s, err |  | ||||||
| 		} |  | ||||||
| 		s += string(x) |  | ||||||
| 	} |  | ||||||
| 	return s, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JsonFile - write Maps to named file as JSON |  | ||||||
| // Note: the file will be created, if necessary; if it exists it will be truncated. |  | ||||||
| // If you need to append to a file, open it and use JsonWriter method. |  | ||||||
| func (mvs Maps) JsonFile(file string, safeEncoding ...bool) error { |  | ||||||
| 	var encoding bool |  | ||||||
| 	if len(safeEncoding) == 1 { |  | ||||||
| 		encoding = safeEncoding[0] |  | ||||||
| 	} |  | ||||||
| 	s, err := mvs.JsonString(encoding) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fh, err := os.Create(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 	fh.WriteString(s) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // JsonFileIndent - write Maps to named file as pretty JSON |  | ||||||
| // Note: the file will be created, if necessary; if it exists it will be truncated. |  | ||||||
| // If you need to append to a file, open it and use JsonIndentWriter method. |  | ||||||
| func (mvs Maps) JsonFileIndent(file, prefix, indent string, safeEncoding ...bool) error { |  | ||||||
| 	var encoding bool |  | ||||||
| 	if len(safeEncoding) == 1 { |  | ||||||
| 		encoding = safeEncoding[0] |  | ||||||
| 	} |  | ||||||
| 	s, err := mvs.JsonStringIndent(prefix, indent, encoding) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fh, err := os.Create(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 	fh.WriteString(s) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // XmlFile - write Maps to named file as XML |  | ||||||
| // Note: the file will be created, if necessary; if it exists it will be truncated. |  | ||||||
| // If you need to append to a file, open it and use XmlWriter method. |  | ||||||
| func (mvs Maps) XmlFile(file string) error { |  | ||||||
| 	s, err := mvs.XmlString() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fh, err := os.Create(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 	fh.WriteString(s) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // XmlFileIndent - write Maps to named file as pretty XML |  | ||||||
| // Note: the file will be created,if necessary; if it exists it will be truncated. |  | ||||||
| // If you need to append to a file, open it and use XmlIndentWriter method. |  | ||||||
| func (mvs Maps) XmlFileIndent(file, prefix, indent string) error { |  | ||||||
| 	s, err := mvs.XmlStringIndent(prefix, indent) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fh, err := os.Create(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer fh.Close() |  | ||||||
| 	fh.WriteString(s) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										2
									
								
								vendor/github.com/clbanning/mxj/files_test.badjson
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/clbanning/mxj/files_test.badjson
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,2 +0,0 @@ | ||||||
| { "this":"is", "a":"test", "file":"for", "files_test.go":"case" } |  | ||||||
| { "with":"some", "bad":JSON, "in":"it" } |  | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/clbanning/mxj/files_test.badxml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/clbanning/mxj/files_test.badxml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,9 +0,0 @@ | ||||||
| <doc> |  | ||||||
| 	<some>test</some> |  | ||||||
| 	<data>for files.go</data> |  | ||||||
| </doc> |  | ||||||
| <msg> |  | ||||||
| 	<just>some</just> |  | ||||||
| 	<another>doc</other> |  | ||||||
| 	<for>test case</for> |  | ||||||
| </msg> |  | ||||||
							
								
								
									
										2
									
								
								vendor/github.com/clbanning/mxj/files_test.json
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/clbanning/mxj/files_test.json
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,2 +0,0 @@ | ||||||
| { "this":"is", "a":"test", "file":"for", "files_test.go":"case" } |  | ||||||
| { "with":"just", "two":2, "JSON":"values", "true":true } |  | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/clbanning/mxj/files_test.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/clbanning/mxj/files_test.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,9 +0,0 @@ | ||||||
| <doc> |  | ||||||
| 	<some>test</some> |  | ||||||
| 	<data>for files.go</data> |  | ||||||
| </doc> |  | ||||||
| <msg> |  | ||||||
| 	<just>some</just> |  | ||||||
| 	<another>doc</another> |  | ||||||
| 	<for>test case</for> |  | ||||||
| </msg> |  | ||||||
							
								
								
									
										1
									
								
								vendor/github.com/clbanning/mxj/files_test_dup.json
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/clbanning/mxj/files_test_dup.json
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1 +0,0 @@ | ||||||
| {"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"} |  | ||||||
							
								
								
									
										1
									
								
								vendor/github.com/clbanning/mxj/files_test_dup.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/clbanning/mxj/files_test_dup.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1 +0,0 @@ | ||||||
| <doc><data>for files.go</data><some>test</some></doc><msg><another>doc</another><for>test case</for><just>some</just></msg> |  | ||||||
							
								
								
									
										12
									
								
								vendor/github.com/clbanning/mxj/files_test_indent.json
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/clbanning/mxj/files_test_indent.json
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,12 +0,0 @@ | ||||||
| { |  | ||||||
|   "a": "test", |  | ||||||
|   "file": "for", |  | ||||||
|   "files_test.go": "case", |  | ||||||
|   "this": "is" |  | ||||||
| } |  | ||||||
| { |  | ||||||
|   "JSON": "values", |  | ||||||
|   "true": true, |  | ||||||
|   "two": 2, |  | ||||||
|   "with": "just" |  | ||||||
| } |  | ||||||
							
								
								
									
										8
									
								
								vendor/github.com/clbanning/mxj/files_test_indent.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/clbanning/mxj/files_test_indent.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,8 +0,0 @@ | ||||||
| <doc> |  | ||||||
|   <data>for files.go</data> |  | ||||||
|   <some>test</some> |  | ||||||
| </doc><msg> |  | ||||||
|   <another>doc</another> |  | ||||||
|   <for>test case</for> |  | ||||||
|   <just>some</just> |  | ||||||
| </msg> |  | ||||||
							
								
								
									
										35
									
								
								vendor/github.com/clbanning/mxj/gob.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/clbanning/mxj/gob.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,35 +0,0 @@ | ||||||
| // gob.go - Encode/Decode a Map into a gob object. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/gob" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewMapGob returns a Map value for a gob object that has been |  | ||||||
| // encoded from a map[string]interface{} (or compatible type) value. |  | ||||||
| // It is intended to provide symmetric handling of Maps that have |  | ||||||
| // been encoded using mv.Gob. |  | ||||||
| func NewMapGob(gobj []byte) (Map, error) { |  | ||||||
| 	m := make(map[string]interface{}, 0) |  | ||||||
| 	if len(gobj) == 0 { |  | ||||||
| 		return m, nil |  | ||||||
| 	} |  | ||||||
| 	r := bytes.NewReader(gobj) |  | ||||||
| 	dec := gob.NewDecoder(r) |  | ||||||
| 	if err := dec.Decode(&m); err != nil { |  | ||||||
| 		return m, err |  | ||||||
| 	} |  | ||||||
| 	return m, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Gob returns a gob-encoded value for the Map 'mv'. |  | ||||||
| func (mv Map) Gob() ([]byte, error) { |  | ||||||
| 	var buf bytes.Buffer |  | ||||||
| 	enc := gob.NewEncoder(&buf) |  | ||||||
| 	if err := enc.Encode(map[string]interface{}(mv)); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return buf.Bytes(), nil |  | ||||||
| } |  | ||||||
							
								
								
									
										323
									
								
								vendor/github.com/clbanning/mxj/json.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										323
									
								
								vendor/github.com/clbanning/mxj/json.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,323 +0,0 @@ | ||||||
| // Copyright 2012-2014 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ------------------------------ write JSON ----------------------- |  | ||||||
| 
 |  | ||||||
| // Just a wrapper on json.Marshal. |  | ||||||
| // If option safeEncoding is'true' then safe encoding of '<', '>' and '&' |  | ||||||
| // is preserved. (see encoding/json#Marshal, encoding/json#Encode) |  | ||||||
| func (mv Map) Json(safeEncoding ...bool) ([]byte, error) { |  | ||||||
| 	var s bool |  | ||||||
| 	if len(safeEncoding) == 1 { |  | ||||||
| 		s = safeEncoding[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b, err := json.Marshal(mv) |  | ||||||
| 
 |  | ||||||
| 	if !s { |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) |  | ||||||
| 	} |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Just a wrapper on json.MarshalIndent. |  | ||||||
| // If option safeEncoding is'true' then safe encoding of '<' , '>' and '&' |  | ||||||
| // is preserved. (see encoding/json#Marshal, encoding/json#Encode) |  | ||||||
| func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) { |  | ||||||
| 	var s bool |  | ||||||
| 	if len(safeEncoding) == 1 { |  | ||||||
| 		s = safeEncoding[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	b, err := json.MarshalIndent(mv, prefix, indent) |  | ||||||
| 	if !s { |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) |  | ||||||
| 		b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) |  | ||||||
| 	} |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // The following implementation is provided for symmetry with NewMapJsonReader[Raw] |  | ||||||
| // The names will also provide a key for the number of return arguments. |  | ||||||
| 
 |  | ||||||
| // Writes the Map as JSON on the Writer. |  | ||||||
| // If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. |  | ||||||
| func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error { |  | ||||||
| 	b, err := mv.Json(safeEncoding...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = jsonWriter.Write(b) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Writes the Map as JSON on the Writer. []byte is the raw JSON that was written. |  | ||||||
| // If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. |  | ||||||
| func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) { |  | ||||||
| 	b, err := mv.Json(safeEncoding...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return b, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = jsonWriter.Write(b) |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Writes the Map as pretty JSON on the Writer. |  | ||||||
| // If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. |  | ||||||
| func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error { |  | ||||||
| 	b, err := mv.JsonIndent(prefix, indent, safeEncoding...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = jsonWriter.Write(b) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written. |  | ||||||
| // If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. |  | ||||||
| func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) { |  | ||||||
| 	b, err := mv.JsonIndent(prefix, indent, safeEncoding...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return b, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = jsonWriter.Write(b) |  | ||||||
| 	return b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // --------------------------- read JSON ----------------------------- |  | ||||||
| 
 |  | ||||||
| // Decode numericvalues as json.Number type Map values - see encoding/json#Number. |  | ||||||
| // NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(),  |  | ||||||
| // etc.; it does not affect NewMapXml(), etc.  The XML encoders mv.Xml() and mv.XmlIndent() |  | ||||||
| // do recognize json.Number types; a JSON object can be decoded to a Map with json.Number |  | ||||||
| // value types and the resulting Map can be correctly encoded into a XML object. |  | ||||||
| var JsonUseNumber bool |  | ||||||
| 
 |  | ||||||
| // Just a wrapper on json.Unmarshal |  | ||||||
| //	Converting JSON to XML is a simple as: |  | ||||||
| //		... |  | ||||||
| //		mapVal, merr := mxj.NewMapJson(jsonVal) |  | ||||||
| //		if merr != nil { |  | ||||||
| //			// handle error |  | ||||||
| //		} |  | ||||||
| //		xmlVal, xerr := mapVal.Xml() |  | ||||||
| //		if xerr != nil { |  | ||||||
| //			// handle error |  | ||||||
| //		} |  | ||||||
| // NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}], |  | ||||||
| // will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map. |  | ||||||
| // See mxj/j2x/j2x_test.go. |  | ||||||
| func NewMapJson(jsonVal []byte) (Map, error) { |  | ||||||
| 	// empty or nil begets empty |  | ||||||
| 	if len(jsonVal) == 0 { |  | ||||||
| 		m := make(map[string]interface{}, 0) |  | ||||||
| 		return m, nil |  | ||||||
| 	} |  | ||||||
| 	// handle a goofy case ... |  | ||||||
| 	if jsonVal[0] == '[' { |  | ||||||
| 		jsonVal = []byte(`{"object":` + string(jsonVal) + `}`) |  | ||||||
| 	} |  | ||||||
| 	m := make(map[string]interface{}) |  | ||||||
| 	// err := json.Unmarshal(jsonVal, &m) |  | ||||||
| 	buf := bytes.NewReader(jsonVal) |  | ||||||
| 	dec := json.NewDecoder(buf) |  | ||||||
| 	if JsonUseNumber { |  | ||||||
| 		dec.UseNumber() |  | ||||||
| 	} |  | ||||||
| 	err := dec.Decode(&m) |  | ||||||
| 	return m, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Retrieve a Map value from an io.Reader. |  | ||||||
| //  NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an |  | ||||||
| //        os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte |  | ||||||
| //        value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal |  | ||||||
| //        a JSON object. |  | ||||||
| func NewMapJsonReader(jsonReader io.Reader) (Map, error) { |  | ||||||
| 	jb, err := getJson(jsonReader) |  | ||||||
| 	if err != nil || len(*jb) == 0 { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Unmarshal the 'presumed' JSON string |  | ||||||
| 	return NewMapJson(*jb) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Retrieve a Map value and raw JSON - []byte - from an io.Reader. |  | ||||||
| //  NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an |  | ||||||
| //        os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte |  | ||||||
| //        value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal |  | ||||||
| //        a JSON object and retrieve the raw JSON in a single call. |  | ||||||
| func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) { |  | ||||||
| 	jb, err := getJson(jsonReader) |  | ||||||
| 	if err != nil || len(*jb) == 0 { |  | ||||||
| 		return nil, *jb, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Unmarshal the 'presumed' JSON string |  | ||||||
| 	m, merr := NewMapJson(*jb) |  | ||||||
| 	return m, *jb, merr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Pull the next JSON string off the stream: just read from first '{' to its closing '}'. |  | ||||||
| // Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package. |  | ||||||
| func getJson(rdr io.Reader) (*[]byte, error) { |  | ||||||
| 	bval := make([]byte, 1) |  | ||||||
| 	jb := make([]byte, 0) |  | ||||||
| 	var inQuote, inJson bool |  | ||||||
| 	var parenCnt int |  | ||||||
| 	var previous byte |  | ||||||
| 
 |  | ||||||
| 	// scan the input for a matched set of {...} |  | ||||||
| 	// json.Unmarshal will handle syntax checking. |  | ||||||
| 	for { |  | ||||||
| 		_, err := rdr.Read(bval) |  | ||||||
| 		if err != nil { |  | ||||||
| 			if err == io.EOF && inJson && parenCnt > 0 { |  | ||||||
| 				return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb)) |  | ||||||
| 			} |  | ||||||
| 			return &jb, err |  | ||||||
| 		} |  | ||||||
| 		switch bval[0] { |  | ||||||
| 		case '{': |  | ||||||
| 			if !inQuote { |  | ||||||
| 				parenCnt++ |  | ||||||
| 				inJson = true |  | ||||||
| 			} |  | ||||||
| 		case '}': |  | ||||||
| 			if !inQuote { |  | ||||||
| 				parenCnt-- |  | ||||||
| 			} |  | ||||||
| 			if parenCnt < 0 { |  | ||||||
| 				return nil, fmt.Errorf("closing } without opening {: %s", string(jb)) |  | ||||||
| 			} |  | ||||||
| 		case '"': |  | ||||||
| 			if inQuote { |  | ||||||
| 				if previous == '\\' { |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 				inQuote = false |  | ||||||
| 			} else { |  | ||||||
| 				inQuote = true |  | ||||||
| 			} |  | ||||||
| 		case '\n', '\r', '\t', ' ': |  | ||||||
| 			if !inQuote { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if inJson { |  | ||||||
| 			jb = append(jb, bval[0]) |  | ||||||
| 			if parenCnt == 0 { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		previous = bval[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &jb, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ------------------------------- JSON Reader handler via Map values  ----------------------- |  | ||||||
| 
 |  | ||||||
| // Default poll delay to keep Handler from spinning on an open stream |  | ||||||
| // like sitting on os.Stdin waiting for imput. |  | ||||||
| var jhandlerPollInterval = time.Duration(1e6) |  | ||||||
| 
 |  | ||||||
| // While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader(). |  | ||||||
| // This avoids treating one or other as a special case and discussing the underlying stdlib logic. |  | ||||||
| 
 |  | ||||||
| // Bulk process JSON using handlers that process a Map value. |  | ||||||
| //	'rdr' is an io.Reader for the JSON (stream). |  | ||||||
| //	'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing. |  | ||||||
| //	'errHandler' is the error processor. Return of 'false' stops io.Reader  processing and returns the error. |  | ||||||
| //	Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized. |  | ||||||
| //	      This means that you can stop reading the file on error or after processing a particular message. |  | ||||||
| //	      To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'. |  | ||||||
| func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error { |  | ||||||
| 	var n int |  | ||||||
| 	for { |  | ||||||
| 		m, merr := NewMapJsonReader(jsonReader) |  | ||||||
| 		n++ |  | ||||||
| 
 |  | ||||||
| 		// handle error condition with errhandler |  | ||||||
| 		if merr != nil && merr != io.EOF { |  | ||||||
| 			merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error()) |  | ||||||
| 			if ok := errHandler(merr); !ok { |  | ||||||
| 				// caused reader termination |  | ||||||
| 				return merr |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// pass to maphandler |  | ||||||
| 		if len(m) != 0 { |  | ||||||
| 			if ok := mapHandler(m); !ok { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} else if merr != io.EOF { |  | ||||||
| 			<-time.After(jhandlerPollInterval) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if merr == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Bulk process JSON using handlers that process a Map value and the raw JSON. |  | ||||||
| //	'rdr' is an io.Reader for the JSON (stream). |  | ||||||
| //	'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing. |  | ||||||
| //	'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error. |  | ||||||
| //	Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized. |  | ||||||
| //	      This means that you can stop reading the file on error or after processing a particular message. |  | ||||||
| //	      To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'. |  | ||||||
| func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error { |  | ||||||
| 	var n int |  | ||||||
| 	for { |  | ||||||
| 		m, raw, merr := NewMapJsonReaderRaw(jsonReader) |  | ||||||
| 		n++ |  | ||||||
| 
 |  | ||||||
| 		// handle error condition with errhandler |  | ||||||
| 		if merr != nil && merr != io.EOF { |  | ||||||
| 			merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error()) |  | ||||||
| 			if ok := errHandler(merr, raw); !ok { |  | ||||||
| 				// caused reader termination |  | ||||||
| 				return merr |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// pass to maphandler |  | ||||||
| 		if len(m) != 0 { |  | ||||||
| 			if ok := mapHandler(m, raw); !ok { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} else if merr != io.EOF { |  | ||||||
| 			<-time.After(jhandlerPollInterval) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if merr == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										671
									
								
								vendor/github.com/clbanning/mxj/keyvalues.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										671
									
								
								vendor/github.com/clbanning/mxj/keyvalues.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,671 +0,0 @@ | ||||||
| // Copyright 2012-2014 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| //	keyvalues.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ----------------------------- get everything FOR a single key ------------------------- |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	minArraySize = 32 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var defaultArraySize int = minArraySize |  | ||||||
| 
 |  | ||||||
| // Adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath(). |  | ||||||
| // This can have the effect of significantly reducing memory allocation-copy functions for large data sets. |  | ||||||
| // Returns the initial buffer size. |  | ||||||
| func SetArraySize(size int) int { |  | ||||||
| 	if size > minArraySize { |  | ||||||
| 		defaultArraySize = size |  | ||||||
| 	} else { |  | ||||||
| 		defaultArraySize = minArraySize |  | ||||||
| 	} |  | ||||||
| 	return defaultArraySize |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match. |  | ||||||
| // On error, the returned slice is 'nil'. NOTE: 'key' can be wildcard, "*". |  | ||||||
| //   'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list. |  | ||||||
| //             - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them. |  | ||||||
| //             - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3". |  | ||||||
| //             - If the 'key' refers to a list, then "key:value" could select a list member of the list. |  | ||||||
| //             - The subkey can be wildcarded - "key:*" - to require that it's there with some value. |  | ||||||
| //             - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an |  | ||||||
| //               exclusion critera - e.g., "!author:William T. Gaddis". |  | ||||||
| //             - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|". |  | ||||||
| func (mv Map) ValuesForKey(key string, subkeys ...string) ([]interface{}, error) { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	var subKeyMap map[string]interface{} |  | ||||||
| 	if len(subkeys) > 0 { |  | ||||||
| 		var err error |  | ||||||
| 		subKeyMap, err = getSubKeyMap(subkeys...) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ret := make([]interface{}, 0, defaultArraySize) |  | ||||||
| 	var cnt int |  | ||||||
| 	hasKey(m, key, &ret, &cnt, subKeyMap) |  | ||||||
| 	return ret[:cnt], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var KeyNotExistError = errors.New("Key does not exist") |  | ||||||
| 
 |  | ||||||
| // ValueForKey is a wrapper on ValuesForKey.  It returns the first member of []interface{}, if any. |  | ||||||
| // If there is no value, "nil, nil" is returned. |  | ||||||
| func (mv Map) ValueForKey(key string, subkeys ...string) (interface{}, error) { |  | ||||||
| 	vals, err := mv.ValuesForKey(key, subkeys...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if len(vals) == 0 { |  | ||||||
| 		return nil, KeyNotExistError |  | ||||||
| 	} |  | ||||||
| 	return vals[0], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // hasKey - if the map 'key' exists append it to array |  | ||||||
| //          if it doesn't do nothing except scan array and map values |  | ||||||
| func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys map[string]interface{}) { |  | ||||||
| 	// func hasKey(iv interface{}, key string, ret *[]interface{}, subkeys map[string]interface{}) { |  | ||||||
| 	switch iv.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		vv := iv.(map[string]interface{}) |  | ||||||
| 		// see if the current value is of interest |  | ||||||
| 		if v, ok := vv[key]; ok { |  | ||||||
| 			switch v.(type) { |  | ||||||
| 			case map[string]interface{}: |  | ||||||
| 				if hasSubKeys(v, subkeys) { |  | ||||||
| 					*ret = append(*ret, v) |  | ||||||
| 					*cnt++ |  | ||||||
| 				} |  | ||||||
| 			case []interface{}: |  | ||||||
| 				for _, av := range v.([]interface{}) { |  | ||||||
| 					if hasSubKeys(av, subkeys) { |  | ||||||
| 						*ret = append(*ret, av) |  | ||||||
| 						*cnt++ |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			default: |  | ||||||
| 				if len(subkeys) == 0 { |  | ||||||
| 					*ret = append(*ret, v) |  | ||||||
| 					*cnt++ |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// wildcard case |  | ||||||
| 		if key == "*" { |  | ||||||
| 			for _, v := range vv { |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					if hasSubKeys(v, subkeys) { |  | ||||||
| 						*ret = append(*ret, v) |  | ||||||
| 						*cnt++ |  | ||||||
| 					} |  | ||||||
| 				case []interface{}: |  | ||||||
| 					for _, av := range v.([]interface{}) { |  | ||||||
| 						if hasSubKeys(av, subkeys) { |  | ||||||
| 							*ret = append(*ret, av) |  | ||||||
| 							*cnt++ |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				default: |  | ||||||
| 					if len(subkeys) == 0 { |  | ||||||
| 						*ret = append(*ret, v) |  | ||||||
| 						*cnt++ |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// scan the rest |  | ||||||
| 		for _, v := range vv { |  | ||||||
| 			hasKey(v, key, ret, cnt, subkeys) |  | ||||||
| 		} |  | ||||||
| 	case []interface{}: |  | ||||||
| 		for _, v := range iv.([]interface{}) { |  | ||||||
| 			hasKey(v, key, ret, cnt, subkeys) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // -----------------------  get everything for a node in the Map --------------------------- |  | ||||||
| 
 |  | ||||||
| // Allow indexed arrays in "path" specification. (Request from Abhijit Kadam - abhijitk100@gmail.com.) |  | ||||||
| // 2014.04.28 - implementation note. |  | ||||||
| // Implemented as a wrapper of (old)ValuesForPath() because we need look-ahead logic to handle expansion |  | ||||||
| // of wildcards and unindexed arrays.  Embedding such logic into valuesForKeyPath() would have made the |  | ||||||
| // code much more complicated; this wrapper is straightforward, easy to debug, and doesn't add significant overhead. |  | ||||||
| 
 |  | ||||||
| // Retrieve all values for a path from the Map.  If len(returned_values) == 0, then no match. |  | ||||||
| // On error, the returned array is 'nil'. |  | ||||||
| //   'path' is a dot-separated path of key values. |  | ||||||
| //          - If a node in the path is '*', then everything beyond is walked. |  | ||||||
| //          - 'path' can contain indexed array references, such as, "*.data[1]" and "msgs[2].data[0].field" - |  | ||||||
| //            even "*[2].*[0].field". |  | ||||||
| //   'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list. |  | ||||||
| //             - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them. |  | ||||||
| //             - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3". |  | ||||||
| //             - If the 'path' refers to a list, then "tag:value" would return member of the list. |  | ||||||
| //             - The subkey can be wildcarded - "key:*" - to require that it's there with some value. |  | ||||||
| //             - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an |  | ||||||
| //               exclusion critera - e.g., "!author:William T. Gaddis". |  | ||||||
| //             - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|". |  | ||||||
| func (mv Map) ValuesForPath(path string, subkeys ...string) ([]interface{}, error) { |  | ||||||
| 	// If there are no array indexes in path, use legacy ValuesForPath() logic. |  | ||||||
| 	if strings.Index(path, "[") < 0 { |  | ||||||
| 		return mv.oldValuesForPath(path, subkeys...) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var subKeyMap map[string]interface{} |  | ||||||
| 	if len(subkeys) > 0 { |  | ||||||
| 		var err error |  | ||||||
| 		subKeyMap, err = getSubKeyMap(subkeys...) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	keys, kerr := parsePath(path) |  | ||||||
| 	if kerr != nil { |  | ||||||
| 		return nil, kerr |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	vals, verr := valuesForArray(keys, mv) |  | ||||||
| 	if verr != nil { |  | ||||||
| 		return nil, verr // Vals may be nil, but return empty array. |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Need to handle subkeys ... only return members of vals that satisfy conditions. |  | ||||||
| 	retvals := make([]interface{}, 0) |  | ||||||
| 	for _, v := range vals { |  | ||||||
| 		if hasSubKeys(v, subKeyMap) { |  | ||||||
| 			retvals = append(retvals, v) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return retvals, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func valuesForArray(keys []*key, m Map) ([]interface{}, error) { |  | ||||||
| 	var tmppath string |  | ||||||
| 	var haveFirst bool |  | ||||||
| 	var vals []interface{} |  | ||||||
| 	var verr error |  | ||||||
| 
 |  | ||||||
| 	lastkey := len(keys) - 1 |  | ||||||
| 	for i := 0; i <= lastkey; i++ { |  | ||||||
| 		if !haveFirst { |  | ||||||
| 			tmppath = keys[i].name |  | ||||||
| 			haveFirst = true |  | ||||||
| 		} else { |  | ||||||
| 			tmppath += "." + keys[i].name |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Look-ahead: explode wildcards and unindexed arrays. |  | ||||||
| 		// Need to handle un-indexed list recursively: |  | ||||||
| 		// e.g., path is "stuff.data[0]" rather than "stuff[0].data[0]". |  | ||||||
| 		// Need to treat it as "stuff[0].data[0]", "stuff[1].data[0]", ... |  | ||||||
| 		if !keys[i].isArray && i < lastkey && keys[i+1].isArray { |  | ||||||
| 			// Can't pass subkeys because we may not be at literal end of path. |  | ||||||
| 			vv, vverr := m.oldValuesForPath(tmppath) |  | ||||||
| 			if vverr != nil { |  | ||||||
| 				return nil, vverr |  | ||||||
| 			} |  | ||||||
| 			for _, v := range vv { |  | ||||||
| 				// See if we can walk the value. |  | ||||||
| 				am, ok := v.(map[string]interface{}) |  | ||||||
| 				if !ok { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				// Work the backend. |  | ||||||
| 				nvals, nvalserr := valuesForArray(keys[i+1:], Map(am)) |  | ||||||
| 				if nvalserr != nil { |  | ||||||
| 					return nil, nvalserr |  | ||||||
| 				} |  | ||||||
| 				vals = append(vals, nvals...) |  | ||||||
| 			} |  | ||||||
| 			break // have recursed the whole path - return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if keys[i].isArray || i == lastkey { |  | ||||||
| 			// Don't pass subkeys because may not be at literal end of path. |  | ||||||
| 			vals, verr = m.oldValuesForPath(tmppath) |  | ||||||
| 		} else { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if verr != nil { |  | ||||||
| 			return nil, verr |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == lastkey && !keys[i].isArray { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Now we're looking at an array - supposedly. |  | ||||||
| 		// Is index in range of vals? |  | ||||||
| 		if len(vals) <= keys[i].position { |  | ||||||
| 			vals = nil |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Return the array member of interest, if at end of path. |  | ||||||
| 		if i == lastkey { |  | ||||||
| 			vals = vals[keys[i].position:(keys[i].position + 1)] |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Extract the array member of interest. |  | ||||||
| 		am := vals[keys[i].position:(keys[i].position + 1)] |  | ||||||
| 
 |  | ||||||
| 		// must be a map[string]interface{} value so we can keep walking the path |  | ||||||
| 		amm, ok := am[0].(map[string]interface{}) |  | ||||||
| 		if !ok { |  | ||||||
| 			vals = nil |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		m = Map(amm) |  | ||||||
| 		haveFirst = false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return vals, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type key struct { |  | ||||||
| 	name     string |  | ||||||
| 	isArray  bool |  | ||||||
| 	position int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parsePath(s string) ([]*key, error) { |  | ||||||
| 	keys := strings.Split(s, ".") |  | ||||||
| 
 |  | ||||||
| 	ret := make([]*key, 0) |  | ||||||
| 
 |  | ||||||
| 	for i := 0; i < len(keys); i++ { |  | ||||||
| 		if keys[i] == "" { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		newkey := new(key) |  | ||||||
| 		if strings.Index(keys[i], "[") < 0 { |  | ||||||
| 			newkey.name = keys[i] |  | ||||||
| 			ret = append(ret, newkey) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		p := strings.Split(keys[i], "[") |  | ||||||
| 		newkey.name = p[0] |  | ||||||
| 		p = strings.Split(p[1], "]") |  | ||||||
| 		if p[0] == "" { // no right bracket |  | ||||||
| 			return nil, fmt.Errorf("no right bracket on key index: %s", keys[i]) |  | ||||||
| 		} |  | ||||||
| 		// convert p[0] to a int value |  | ||||||
| 		pos, nerr := strconv.ParseInt(p[0], 10, 32) |  | ||||||
| 		if nerr != nil { |  | ||||||
| 			return nil, fmt.Errorf("cannot convert index to int value: %s", p[0]) |  | ||||||
| 		} |  | ||||||
| 		newkey.position = int(pos) |  | ||||||
| 		newkey.isArray = true |  | ||||||
| 		ret = append(ret, newkey) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return ret, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // legacy ValuesForPath() - now wrapped to handle special case of indexed arrays in 'path'. |  | ||||||
| func (mv Map) oldValuesForPath(path string, subkeys ...string) ([]interface{}, error) { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	var subKeyMap map[string]interface{} |  | ||||||
| 	if len(subkeys) > 0 { |  | ||||||
| 		var err error |  | ||||||
| 		subKeyMap, err = getSubKeyMap(subkeys...) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	keys := strings.Split(path, ".") |  | ||||||
| 	if keys[len(keys)-1] == "" { |  | ||||||
| 		keys = keys[:len(keys)-1] |  | ||||||
| 	} |  | ||||||
| 	ivals := make([]interface{}, 0, defaultArraySize) |  | ||||||
| 	var cnt int |  | ||||||
| 	valuesForKeyPath(&ivals, &cnt, m, keys, subKeyMap) |  | ||||||
| 	return ivals[:cnt], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func valuesForKeyPath(ret *[]interface{}, cnt *int, m interface{}, keys []string, subkeys map[string]interface{}) { |  | ||||||
| 	lenKeys := len(keys) |  | ||||||
| 
 |  | ||||||
| 	// load 'm' values into 'ret' |  | ||||||
| 	// expand any lists |  | ||||||
| 	if lenKeys == 0 { |  | ||||||
| 		switch m.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			if subkeys != nil { |  | ||||||
| 				if ok := hasSubKeys(m, subkeys); !ok { |  | ||||||
| 					return |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			*ret = append(*ret, m) |  | ||||||
| 			*cnt++ |  | ||||||
| 		case []interface{}: |  | ||||||
| 			for i, v := range m.([]interface{}) { |  | ||||||
| 				if subkeys != nil { |  | ||||||
| 					if ok := hasSubKeys(v, subkeys); !ok { |  | ||||||
| 						continue // only load list members with subkeys |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				*ret = append(*ret, (m.([]interface{}))[i]) |  | ||||||
| 				*cnt++ |  | ||||||
| 			} |  | ||||||
| 		default: |  | ||||||
| 			if subkeys != nil { |  | ||||||
| 				return // must be map[string]interface{} if there are subkeys |  | ||||||
| 			} |  | ||||||
| 			*ret = append(*ret, m) |  | ||||||
| 			*cnt++ |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// key of interest |  | ||||||
| 	key := keys[0] |  | ||||||
| 	switch key { |  | ||||||
| 	case "*": // wildcard - scan all values |  | ||||||
| 		switch m.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			for _, v := range m.(map[string]interface{}) { |  | ||||||
| 				// valuesForKeyPath(ret, v, keys[1:], subkeys) |  | ||||||
| 				valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) |  | ||||||
| 			} |  | ||||||
| 		case []interface{}: |  | ||||||
| 			for _, v := range m.([]interface{}) { |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				// flatten out a list of maps - keys are processed |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					for _, vv := range v.(map[string]interface{}) { |  | ||||||
| 						// valuesForKeyPath(ret, vv, keys[1:], subkeys) |  | ||||||
| 						valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys) |  | ||||||
| 					} |  | ||||||
| 				default: |  | ||||||
| 					// valuesForKeyPath(ret, v, keys[1:], subkeys) |  | ||||||
| 					valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	default: // key - must be map[string]interface{} |  | ||||||
| 		switch m.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			if v, ok := m.(map[string]interface{})[key]; ok { |  | ||||||
| 				// valuesForKeyPath(ret, v, keys[1:], subkeys) |  | ||||||
| 				valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) |  | ||||||
| 			} |  | ||||||
| 		case []interface{}: // may be buried in list |  | ||||||
| 			for _, v := range m.([]interface{}) { |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					if vv, ok := v.(map[string]interface{})[key]; ok { |  | ||||||
| 						// valuesForKeyPath(ret, vv, keys[1:], subkeys) |  | ||||||
| 						valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys) |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // hasSubKeys() - interface{} equality works for string, float64, bool |  | ||||||
| // 'v' must be a map[string]interface{} value to have subkeys |  | ||||||
| // 'a' can have k:v pairs with v.(string) == "*", which is treated like a wildcard. |  | ||||||
| func hasSubKeys(v interface{}, subkeys map[string]interface{}) bool { |  | ||||||
| 	if len(subkeys) == 0 { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch v.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		// do all subKey name:value pairs match? |  | ||||||
| 		mv := v.(map[string]interface{}) |  | ||||||
| 		for skey, sval := range subkeys { |  | ||||||
| 			isNotKey := false |  | ||||||
| 			if skey[:1] == "!" { // a NOT-key |  | ||||||
| 				skey = skey[1:] |  | ||||||
| 				isNotKey = true |  | ||||||
| 			} |  | ||||||
| 			vv, ok := mv[skey] |  | ||||||
| 			if !ok { // key doesn't exist |  | ||||||
| 				if isNotKey { // key not there, but that's what we want |  | ||||||
| 					if kv, ok := sval.(string); ok && kv == "*" { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 			// wildcard check |  | ||||||
| 			if kv, ok := sval.(string); ok && kv == "*" { |  | ||||||
| 				if isNotKey { // key is there, and we don't want it |  | ||||||
| 					return false |  | ||||||
| 				} |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			switch sval.(type) { |  | ||||||
| 			case string: |  | ||||||
| 				if s, ok := vv.(string); ok && s == sval.(string) { |  | ||||||
| 					if isNotKey { |  | ||||||
| 						return false |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			case bool: |  | ||||||
| 				if b, ok := vv.(bool); ok && b == sval.(bool) { |  | ||||||
| 					if isNotKey { |  | ||||||
| 						return false |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			case float64: |  | ||||||
| 				if f, ok := vv.(float64); ok && f == sval.(float64) { |  | ||||||
| 					if isNotKey { |  | ||||||
| 						return false |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// key there but didn't match subkey value |  | ||||||
| 			if isNotKey { // that's what we want |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		// all subkeys matched |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// not a map[string]interface{} value, can't have subkeys |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Generate map of key:value entries as map[string]string. |  | ||||||
| //	'kv' arguments are "name:value" pairs: attribute keys are designated with prepended hyphen, '-'. |  | ||||||
| //	If len(kv) == 0, the return is (nil, nil). |  | ||||||
| func getSubKeyMap(kv ...string) (map[string]interface{}, error) { |  | ||||||
| 	if len(kv) == 0 { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| 	m := make(map[string]interface{}, 0) |  | ||||||
| 	for _, v := range kv { |  | ||||||
| 		vv := strings.Split(v, fieldSep) |  | ||||||
| 		switch len(vv) { |  | ||||||
| 		case 2: |  | ||||||
| 			m[vv[0]] = interface{}(vv[1]) |  | ||||||
| 		case 3: |  | ||||||
| 			switch vv[2] { |  | ||||||
| 			case "string", "char", "text": |  | ||||||
| 				m[vv[0]] = interface{}(vv[1]) |  | ||||||
| 			case "bool", "boolean": |  | ||||||
| 				// ParseBool treats "1"==true & "0"==false |  | ||||||
| 				b, err := strconv.ParseBool(vv[1]) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return nil, fmt.Errorf("can't convert subkey value to bool: %s", vv[1]) |  | ||||||
| 				} |  | ||||||
| 				m[vv[0]] = interface{}(b) |  | ||||||
| 			case "float", "float64", "num", "number", "numeric": |  | ||||||
| 				f, err := strconv.ParseFloat(vv[1], 64) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return nil, fmt.Errorf("can't convert subkey value to float: %s", vv[1]) |  | ||||||
| 				} |  | ||||||
| 				m[vv[0]] = interface{}(f) |  | ||||||
| 			default: |  | ||||||
| 				return nil, fmt.Errorf("unknown subkey conversion spec: %s", v) |  | ||||||
| 			} |  | ||||||
| 		default: |  | ||||||
| 			return nil, fmt.Errorf("unknown subkey spec: %s", v) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return m, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // -------------------------------  END of valuesFor ... ---------------------------- |  | ||||||
| 
 |  | ||||||
| // ----------------------- locate where a key value is in the tree ------------------- |  | ||||||
| 
 |  | ||||||
| //----------------------------- find all paths to a key -------------------------------- |  | ||||||
| 
 |  | ||||||
| // Get all paths through Map, 'mv', (in dot-notation) that terminate with the specified key. |  | ||||||
| // Results can be used with ValuesForPath. |  | ||||||
| func (mv Map) PathsForKey(key string) []string { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	breadbasket := make(map[string]bool, 0) |  | ||||||
| 	breadcrumbs := "" |  | ||||||
| 
 |  | ||||||
| 	hasKeyPath(breadcrumbs, m, key, breadbasket) |  | ||||||
| 	if len(breadbasket) == 0 { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// unpack map keys to return |  | ||||||
| 	res := make([]string, len(breadbasket)) |  | ||||||
| 	var i int |  | ||||||
| 	for k := range breadbasket { |  | ||||||
| 		res[i] = k |  | ||||||
| 		i++ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Extract the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'.. |  | ||||||
| // Paths are strings using dot-notation. |  | ||||||
| func (mv Map) PathForKeyShortest(key string) string { |  | ||||||
| 	paths := mv.PathsForKey(key) |  | ||||||
| 
 |  | ||||||
| 	lp := len(paths) |  | ||||||
| 	if lp == 0 { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	if lp == 1 { |  | ||||||
| 		return paths[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	shortest := paths[0] |  | ||||||
| 	shortestLen := len(strings.Split(shortest, ".")) |  | ||||||
| 
 |  | ||||||
| 	for i := 1; i < len(paths); i++ { |  | ||||||
| 		vlen := len(strings.Split(paths[i], ".")) |  | ||||||
| 		if vlen < shortestLen { |  | ||||||
| 			shortest = paths[i] |  | ||||||
| 			shortestLen = vlen |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return shortest |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth |  | ||||||
| // This is really just a breadcrumber that saves all trails that hit the prescribed 'key'. |  | ||||||
| func hasKeyPath(crumbs string, iv interface{}, key string, basket map[string]bool) { |  | ||||||
| 	switch iv.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		vv := iv.(map[string]interface{}) |  | ||||||
| 		if _, ok := vv[key]; ok { |  | ||||||
| 			// create a new breadcrumb, intialized with the one we have |  | ||||||
| 			var nbc string |  | ||||||
| 			if crumbs == "" { |  | ||||||
| 				nbc = key |  | ||||||
| 			} else { |  | ||||||
| 				nbc = crumbs + "." + key |  | ||||||
| 			} |  | ||||||
| 			basket[nbc] = true |  | ||||||
| 		} |  | ||||||
| 		// walk on down the path, key could occur again at deeper node |  | ||||||
| 		for k, v := range vv { |  | ||||||
| 			// create a new breadcrumb, intialized with the one we have |  | ||||||
| 			var nbc string |  | ||||||
| 			if crumbs == "" { |  | ||||||
| 				nbc = k |  | ||||||
| 			} else { |  | ||||||
| 				nbc = crumbs + "." + k |  | ||||||
| 			} |  | ||||||
| 			hasKeyPath(nbc, v, key, basket) |  | ||||||
| 		} |  | ||||||
| 	case []interface{}: |  | ||||||
| 		// crumb-trail doesn't change, pass it on |  | ||||||
| 		for _, v := range iv.([]interface{}) { |  | ||||||
| 			hasKeyPath(crumbs, v, key, basket) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var PathNotExistError = errors.New("Path does not exist") |  | ||||||
| 
 |  | ||||||
| // ValueForPath wrap ValuesFor Path and returns the first value returned. |  | ||||||
| // If no value is found it returns 'nil' and PathNotExistError. |  | ||||||
| func (mv Map) ValueForPath(path string) (interface{}, error) { |  | ||||||
| 	vals, err := mv.ValuesForPath(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if len(vals) == 0 { |  | ||||||
| 		return nil, PathNotExistError |  | ||||||
| 	} |  | ||||||
| 	return vals[0], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the first found value for the path as a string. |  | ||||||
| func (mv Map) ValueForPathString(path string) (string, error) { |  | ||||||
| 	vals, err := mv.ValuesForPath(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if len(vals) == 0 { |  | ||||||
| 		return "", errors.New("ValueForPath: path not found") |  | ||||||
| 	} |  | ||||||
| 	val := vals[0] |  | ||||||
| 	switch str := val.(type) { |  | ||||||
| 	case string: |  | ||||||
| 		return str, nil |  | ||||||
| 	default: |  | ||||||
| 		return "", fmt.Errorf("ValueForPath: unsupported type: %T", str) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the first found value for the path as a string. |  | ||||||
| // If the path is not found then it returns an empty string. |  | ||||||
| func (mv Map) ValueOrEmptyForPathString(path string) string { |  | ||||||
| 	str, _ := mv.ValueForPathString(path) |  | ||||||
| 	return str |  | ||||||
| } |  | ||||||
							
								
								
									
										112
									
								
								vendor/github.com/clbanning/mxj/leafnode.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										112
									
								
								vendor/github.com/clbanning/mxj/leafnode.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,112 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| // leafnode.go - return leaf nodes with paths and values for the Map |  | ||||||
| // inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	NoAttributes = true // suppress LeafNode values that are attributes |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // LeafNode - a terminal path value in a Map. |  | ||||||
| // For XML Map values it represents an attribute or simple element value  - of type |  | ||||||
| // string unless Map was created using Cast flag. For JSON Map values it represents |  | ||||||
| // a string, numeric, boolean, or null value. |  | ||||||
| type LeafNode struct { |  | ||||||
| 	Path  string      // a dot-notation representation of the path with array subscripting |  | ||||||
| 	Value interface{} // the value at the path termination |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LeafNodes - returns an array of all LeafNode values for the Map. |  | ||||||
| // The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-') |  | ||||||
| // as well as the "#text" key for the associated simple element value. |  | ||||||
| // |  | ||||||
| // PrependAttrWithHypen(false) will result in attributes having .attr-name as  |  | ||||||
| // terminal node in 'path' while the path for the element value, itself, will be  |  | ||||||
| // the base path w/o "#text".  |  | ||||||
| // |  | ||||||
| // LeafUseDotNotation(true) causes list members to be identified using ".N" syntax |  | ||||||
| // rather than "[N]" syntax. |  | ||||||
| func (mv Map) LeafNodes(no_attr ...bool) []LeafNode { |  | ||||||
| 	var a bool |  | ||||||
| 	if len(no_attr) == 1 { |  | ||||||
| 		a = no_attr[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	l := make([]LeafNode, 0) |  | ||||||
| 	getLeafNodes("", "", map[string]interface{}(mv), &l, a) |  | ||||||
| 	return l |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) { |  | ||||||
| 	// if stripping attributes, then also strip "#text" key |  | ||||||
| 	if !noattr || node != "#text" { |  | ||||||
| 		if path != "" && node[:1] != "[" { |  | ||||||
| 			path += "." |  | ||||||
| 		} |  | ||||||
| 		path += node |  | ||||||
| 	} |  | ||||||
| 	switch mv.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		for k, v := range mv.(map[string]interface{}) { |  | ||||||
| 			// if noattr && k[:1] == "-" { |  | ||||||
| 			if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			getLeafNodes(path, k, v, l, noattr) |  | ||||||
| 		} |  | ||||||
| 	case []interface{}: |  | ||||||
| 		for i, v := range mv.([]interface{}) { |  | ||||||
| 			if useDotNotation { |  | ||||||
| 				getLeafNodes(path, strconv.Itoa(i), v, l, noattr) |  | ||||||
| 			} else { |  | ||||||
| 				getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		// can't walk any further, so create leaf |  | ||||||
| 		n := LeafNode{path, mv} |  | ||||||
| 		*l = append(*l, n) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LeafPaths - all paths that terminate in LeafNode values. |  | ||||||
| func (mv Map) LeafPaths(no_attr ...bool) []string { |  | ||||||
| 	ln := mv.LeafNodes() |  | ||||||
| 	ss := make([]string, len(ln)) |  | ||||||
| 	for i := 0; i < len(ln); i++ { |  | ||||||
| 		ss[i] = ln[i].Path |  | ||||||
| 	} |  | ||||||
| 	return ss |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LeafValues - all terminal values in the Map. |  | ||||||
| func (mv Map) LeafValues(no_attr ...bool) []interface{} { |  | ||||||
| 	ln := mv.LeafNodes() |  | ||||||
| 	vv := make([]interface{}, len(ln)) |  | ||||||
| 	for i := 0; i < len(ln); i++ { |  | ||||||
| 		vv[i] = ln[i].Value |  | ||||||
| 	} |  | ||||||
| 	return vv |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ====================== utilities ====================== |  | ||||||
| 
 |  | ||||||
| // https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I |  | ||||||
| var useDotNotation bool |  | ||||||
| 
 |  | ||||||
| // LeafUseDotNotation sets a flag that list members in LeafNode paths |  | ||||||
| // should be identified using ".N" syntax rather than the default "[N]" |  | ||||||
| // syntax.  Calling LeafUseDotNotation with no arguments toggles the  |  | ||||||
| // flag on/off; otherwise, the argument sets the flag value 'true'/'false'. |  | ||||||
| func LeafUseDotNotation(b ...bool) { |  | ||||||
| 	if len(b) == 0 { |  | ||||||
| 		useDotNotation = !useDotNotation |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	useDotNotation = b[0] |  | ||||||
| } |  | ||||||
							
								
								
									
										86
									
								
								vendor/github.com/clbanning/mxj/misc.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										86
									
								
								vendor/github.com/clbanning/mxj/misc.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,86 +0,0 @@ | ||||||
| // Copyright 2016 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| // misc.go - mimic functions (+others) called out in: |  | ||||||
| //          https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ |  | ||||||
| // Primarily these methods let you retrive XML structure information. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Return the root element of the Map. If there is not a single key in Map, |  | ||||||
| // then an error is returned. |  | ||||||
| func (mv Map) Root() (string, error) { |  | ||||||
| 	mm := map[string]interface{}(mv) |  | ||||||
| 	if len(mm) != 1 { |  | ||||||
| 		return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm)) |  | ||||||
| 	} |  | ||||||
| 	for k, _ := range mm { |  | ||||||
| 		return k, nil |  | ||||||
| 	} |  | ||||||
| 	return "", nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // If the path is an element with sub-elements, return a list of the sub-element |  | ||||||
| // keys.  (The list is alphabeticly sorted.)  NOTE: Map keys that are prefixed with |  | ||||||
| // '-', a hyphen, are considered attributes; see m.Attributes(path). |  | ||||||
| func (mv Map) Elements(path string) ([]string, error) { |  | ||||||
| 	e, err := mv.ValueForPath(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	switch e.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		ee := e.(map[string]interface{}) |  | ||||||
| 		elems := make([]string, len(ee)) |  | ||||||
| 		var i int |  | ||||||
| 		for k, _ := range ee { |  | ||||||
| 			if len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { |  | ||||||
| 				continue // skip attributes |  | ||||||
| 			} |  | ||||||
| 			elems[i] = k |  | ||||||
| 			i++ |  | ||||||
| 		} |  | ||||||
| 		elems = elems[:i] |  | ||||||
| 		// alphabetic sort keeps things tidy |  | ||||||
| 		sort.Strings(elems) |  | ||||||
| 		return elems, nil |  | ||||||
| 	} |  | ||||||
| 	return nil, fmt.Errorf("no elements for path: %s", path) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // If the path is an element with attributes, return a list of the attribute |  | ||||||
| // keys.  (The list is alphabeticly sorted.)  NOTE: Map keys that are not prefixed with |  | ||||||
| // '-', a hyphen, are not treated as attributes; see m.Elements(path). Also, if the |  | ||||||
| // attribute prefix is "" - SetAttrPrefix("") or PrependAttrWithHyphen(false) - then |  | ||||||
| // there are no identifiable attributes. |  | ||||||
| func (mv Map) Attributes(path string) ([]string, error) { |  | ||||||
| 	a, err := mv.ValueForPath(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	switch a.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		aa := a.(map[string]interface{}) |  | ||||||
| 		attrs := make([]string, len(aa)) |  | ||||||
| 		var i int |  | ||||||
| 		for k, _ := range aa { |  | ||||||
| 			if len(attrPrefix) == 0 || strings.Index(k, attrPrefix) != 0 { |  | ||||||
| 				continue // skip non-attributes |  | ||||||
| 			} |  | ||||||
| 			attrs[i] = k[len(attrPrefix):] |  | ||||||
| 			i++ |  | ||||||
| 		} |  | ||||||
| 		attrs = attrs[:i] |  | ||||||
| 		// alphabetic sort keeps things tidy |  | ||||||
| 		sort.Strings(attrs) |  | ||||||
| 		return attrs, nil |  | ||||||
| 	} |  | ||||||
| 	return nil, fmt.Errorf("no attributes for path: %s", path) |  | ||||||
| } |  | ||||||
							
								
								
									
										128
									
								
								vendor/github.com/clbanning/mxj/mxj.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										128
									
								
								vendor/github.com/clbanning/mxj/mxj.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,128 +0,0 @@ | ||||||
| // mxj - A collection of map[string]interface{} and associated XML and JSON utilities. |  | ||||||
| // Copyright 2012-2014 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"sort" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	Cast         = true // for clarity - e.g., mxj.NewMapXml(doc, mxj.Cast) |  | ||||||
| 	SafeEncoding = true // ditto - e.g., mv.Json(mxj.SafeEncoding) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Map map[string]interface{} |  | ||||||
| 
 |  | ||||||
| // Allocate a Map. |  | ||||||
| func New() Map { |  | ||||||
| 	m := make(map[string]interface{}, 0) |  | ||||||
| 	return m |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Cast a Map to map[string]interface{} |  | ||||||
| func (mv Map) Old() map[string]interface{} { |  | ||||||
| 	return mv |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Return a copy of mv as a newly allocated Map.  If the Map only contains string, |  | ||||||
| // numeric, map[string]interface{}, and []interface{} values, then it can be thought |  | ||||||
| // of as a "deep copy."  Copying a structure (or structure reference) value is subject |  | ||||||
| // to the noted restrictions. |  | ||||||
| //	NOTE: If 'mv' includes structure values with, possibly, JSON encoding tags |  | ||||||
| //	      then only public fields of the structure are in the new Map - and with |  | ||||||
| //	      keys that conform to any encoding tag instructions. The structure itself will |  | ||||||
| //	      be represented as a map[string]interface{} value. |  | ||||||
| func (mv Map) Copy() (Map, error) { |  | ||||||
| 	// this is the poor-man's deep copy |  | ||||||
| 	// not efficient, but it works |  | ||||||
| 	j, jerr := mv.Json() |  | ||||||
| 	// must handle, we don't know how mv got built |  | ||||||
| 	if jerr != nil { |  | ||||||
| 		return nil, jerr |  | ||||||
| 	} |  | ||||||
| 	return NewMapJson(j) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // --------------- StringIndent ... from x2j.WriteMap ------------- |  | ||||||
| 
 |  | ||||||
| // Pretty print a Map. |  | ||||||
| func (mv Map) StringIndent(offset ...int) string { |  | ||||||
| 	return writeMap(map[string]interface{}(mv), true, true, offset...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Pretty print a Map without the value type information - just key:value entries. |  | ||||||
| func (mv Map) StringIndentNoTypeInfo(offset ...int) string { |  | ||||||
| 	return writeMap(map[string]interface{}(mv), false, true, offset...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // writeMap - dumps the map[string]interface{} for examination. |  | ||||||
| // 'typeInfo' causes value type to be printed. |  | ||||||
| //	'offset' is initial indentation count; typically: Write(m). |  | ||||||
| func writeMap(m interface{}, typeInfo, root bool, offset ...int) string { |  | ||||||
| 	var indent int |  | ||||||
| 	if len(offset) == 1 { |  | ||||||
| 		indent = offset[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var s string |  | ||||||
| 	switch m.(type) { |  | ||||||
| 	case []interface{}: |  | ||||||
| 		if typeInfo { |  | ||||||
| 			s += "[[]interface{}]" |  | ||||||
| 		} |  | ||||||
| 		for _, v := range m.([]interface{}) { |  | ||||||
| 			s += "\n" |  | ||||||
| 			for i := 0; i < indent; i++ { |  | ||||||
| 				s += "  " |  | ||||||
| 			} |  | ||||||
| 			s += writeMap(v, typeInfo, false, indent+1) |  | ||||||
| 		} |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		list := make([][2]string, len(m.(map[string]interface{}))) |  | ||||||
| 		var n int |  | ||||||
| 		for k, v := range m.(map[string]interface{}) { |  | ||||||
| 			list[n][0] = k |  | ||||||
| 			list[n][1] = writeMap(v, typeInfo, false, indent+1) |  | ||||||
| 			n++ |  | ||||||
| 		} |  | ||||||
| 		sort.Sort(mapList(list)) |  | ||||||
| 		for _, v := range list { |  | ||||||
| 			if root { |  | ||||||
| 				root = false |  | ||||||
| 			} else { |  | ||||||
| 				s += "\n" |  | ||||||
| 			} |  | ||||||
| 			for i := 0; i < indent; i++ { |  | ||||||
| 				s += "  " |  | ||||||
| 			} |  | ||||||
| 			s += v[0] + " : " + v[1] |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		if typeInfo { |  | ||||||
| 			s += fmt.Sprintf("[%T] %+v", m, m) |  | ||||||
| 		} else { |  | ||||||
| 			s += fmt.Sprintf("%+v", m) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return s |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ======================== utility =============== |  | ||||||
| 
 |  | ||||||
| type mapList [][2]string |  | ||||||
| 
 |  | ||||||
| func (ml mapList) Len() int { |  | ||||||
| 	return len(ml) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ml mapList) Swap(i, j int) { |  | ||||||
| 	ml[i], ml[j] = ml[j], ml[i] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ml mapList) Less(i, j int) bool { |  | ||||||
| 	return ml[i][0] <= ml[j][0] |  | ||||||
| } |  | ||||||
							
								
								
									
										184
									
								
								vendor/github.com/clbanning/mxj/newmap.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										184
									
								
								vendor/github.com/clbanning/mxj/newmap.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,184 +0,0 @@ | ||||||
| // mxj - A collection of map[string]interface{} and associated XML and JSON utilities. |  | ||||||
| // Copyright 2012-2014, 2018 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| // remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings |  | ||||||
| //            keys can use dot-notation, keyOld can use wildcard, '*' |  | ||||||
| // |  | ||||||
| // Computational strategy - |  | ||||||
| // Using the key path - []string - traverse a new map[string]interface{} and |  | ||||||
| // insert the oldVal as the newVal when we arrive at the end of the path. |  | ||||||
| // If the type at the end is nil, then that is newVal |  | ||||||
| // If the type at the end is a singleton (string, float64, bool) an array is created. |  | ||||||
| // If the type at the end is an array, newVal is just appended. |  | ||||||
| // If the type at the end is a map, it is inserted if possible or the map value |  | ||||||
| //    is converted into an array if necessary. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // (Map)NewMap - create a new Map from data in the current Map. |  | ||||||
| //	'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey' |  | ||||||
| //	should be the value for 'newKey' in the returned Map. |  | ||||||
| //		- 'oldKey' supports dot-notation as described for (Map)ValuesForPath() |  | ||||||
| //		- 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays |  | ||||||
| //		- "oldKey" is shorthand for the keypair value "oldKey:oldKey" |  | ||||||
| //		- "oldKey:" and ":newKey" are invalid keypair values |  | ||||||
| //		- if 'oldKey' does not exist in the current Map, it is not written to the new Map. |  | ||||||
| //		  "null" is not supported unless it is the current Map. |  | ||||||
| //		- see newmap_test.go for several syntax examples |  | ||||||
| // 	- mv.NewMap() == mxj.New() |  | ||||||
| // |  | ||||||
| //	NOTE: "examples/partial.go" shows how to create arbitrary sub-docs of an XML doc. |  | ||||||
| func (mv Map) NewMap(keypairs ...string) (Map, error) { |  | ||||||
| 	n := make(map[string]interface{}, 0) |  | ||||||
| 	if len(keypairs) == 0 { |  | ||||||
| 		return n, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// loop through the pairs |  | ||||||
| 	var oldKey, newKey string |  | ||||||
| 	var path []string |  | ||||||
| 	for _, v := range keypairs { |  | ||||||
| 		if len(v) == 0 { |  | ||||||
| 			continue // just skip over empty keypair arguments |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// initialize oldKey, newKey and check |  | ||||||
| 		vv := strings.Split(v, ":") |  | ||||||
| 		if len(vv) > 2 { |  | ||||||
| 			return n, errors.New("oldKey:newKey keypair value not valid - " + v) |  | ||||||
| 		} |  | ||||||
| 		if len(vv) == 1 { |  | ||||||
| 			oldKey, newKey = vv[0], vv[0] |  | ||||||
| 		} else { |  | ||||||
| 			oldKey, newKey = vv[0], vv[1] |  | ||||||
| 		} |  | ||||||
| 		strings.TrimSpace(oldKey) |  | ||||||
| 		strings.TrimSpace(newKey) |  | ||||||
| 		if i := strings.Index(newKey, "*"); i > -1 { |  | ||||||
| 			return n, errors.New("newKey value cannot contain wildcard character - " + v) |  | ||||||
| 		} |  | ||||||
| 		if i := strings.Index(newKey, "["); i > -1 { |  | ||||||
| 			return n, errors.New("newKey value cannot contain indexed arrays - " + v) |  | ||||||
| 		} |  | ||||||
| 		if oldKey == "" || newKey == "" { |  | ||||||
| 			return n, errors.New("oldKey or newKey is not specified - " + v) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// get oldKey value |  | ||||||
| 		oldVal, err := mv.ValuesForPath(oldKey) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return n, err |  | ||||||
| 		} |  | ||||||
| 		if len(oldVal) == 0 { |  | ||||||
| 			continue // oldKey has no value, may not exist in mv |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// break down path |  | ||||||
| 		path = strings.Split(newKey, ".") |  | ||||||
| 		if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec |  | ||||||
| 			path = path[:len(path)-1] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		addNewVal(&n, path, oldVal) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return n, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // navigate 'n' to end of path and add val |  | ||||||
| func addNewVal(n *map[string]interface{}, path []string, val []interface{}) { |  | ||||||
| 	// newVal - either singleton or array |  | ||||||
| 	var newVal interface{} |  | ||||||
| 	if len(val) == 1 { |  | ||||||
| 		newVal = val[0] // is type interface{} |  | ||||||
| 	} else { |  | ||||||
| 		newVal = interface{}(val) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// walk to the position of interest, create it if necessary |  | ||||||
| 	m := (*n)           // initialize map walker |  | ||||||
| 	var k string        // key for m |  | ||||||
| 	lp := len(path) - 1 // when to stop looking |  | ||||||
| 	for i := 0; i < len(path); i++ { |  | ||||||
| 		k = path[i] |  | ||||||
| 		if i == lp { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		var nm map[string]interface{} // holds position of next-map |  | ||||||
| 		switch m[k].(type) { |  | ||||||
| 		case nil: // need a map for next node in path, so go there |  | ||||||
| 			nm = make(map[string]interface{}, 0) |  | ||||||
| 			m[k] = interface{}(nm) |  | ||||||
| 			m = m[k].(map[string]interface{}) |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			// OK - got somewhere to walk to, go there |  | ||||||
| 			m = m[k].(map[string]interface{}) |  | ||||||
| 		case []interface{}: |  | ||||||
| 			// add a map and nm points to new map unless there's already |  | ||||||
| 			// a map in the array, then nm points there |  | ||||||
| 			// The placement of the next value in the array is dependent |  | ||||||
| 			// on the sequence of members - could land on a map or a nil |  | ||||||
| 			// value first.  TODO: how to test this. |  | ||||||
| 			a := make([]interface{}, 0) |  | ||||||
| 			var foundmap bool |  | ||||||
| 			for _, vv := range m[k].([]interface{}) { |  | ||||||
| 				switch vv.(type) { |  | ||||||
| 				case nil: // doesn't appear that this occurs, need a test case |  | ||||||
| 					if foundmap { // use the first one in array |  | ||||||
| 						a = append(a, vv) |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 					nm = make(map[string]interface{}, 0) |  | ||||||
| 					a = append(a, interface{}(nm)) |  | ||||||
| 					foundmap = true |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					if foundmap { // use the first one in array |  | ||||||
| 						a = append(a, vv) |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 					nm = vv.(map[string]interface{}) |  | ||||||
| 					a = append(a, vv) |  | ||||||
| 					foundmap = true |  | ||||||
| 				default: |  | ||||||
| 					a = append(a, vv) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// no map found in array |  | ||||||
| 			if !foundmap { |  | ||||||
| 				nm = make(map[string]interface{}, 0) |  | ||||||
| 				a = append(a, interface{}(nm)) |  | ||||||
| 			} |  | ||||||
| 			m[k] = interface{}(a) // must insert in map |  | ||||||
| 			m = nm |  | ||||||
| 		default: // it's a string, float, bool, etc. |  | ||||||
| 			aa := make([]interface{}, 0) |  | ||||||
| 			nm = make(map[string]interface{}, 0) |  | ||||||
| 			aa = append(aa, m[k], nm) |  | ||||||
| 			m[k] = interface{}(aa) |  | ||||||
| 			m = nm |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// value is nil, array or a singleton of some kind |  | ||||||
| 	// initially m.(type) == map[string]interface{} |  | ||||||
| 	v := m[k] |  | ||||||
| 	switch v.(type) { |  | ||||||
| 	case nil: // initialized |  | ||||||
| 		m[k] = newVal |  | ||||||
| 	case []interface{}: |  | ||||||
| 		a := m[k].([]interface{}) |  | ||||||
| 		a = append(a, newVal) |  | ||||||
| 		m[k] = interface{}(a) |  | ||||||
| 	default: // v exists:string, float64, bool, map[string]interface, etc. |  | ||||||
| 		a := make([]interface{}, 0) |  | ||||||
| 		a = append(a, v, newVal) |  | ||||||
| 		m[k] = interface{}(a) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										179
									
								
								vendor/github.com/clbanning/mxj/readme.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										179
									
								
								vendor/github.com/clbanning/mxj/readme.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,179 +0,0 @@ | ||||||
| <h2>mxj - to/from maps, XML and JSON</h2> |  | ||||||
| Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/modify values from maps by key or key-path, including wildcards. |  | ||||||
| 
 |  | ||||||
| mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use mxj/x2j and mxj/j2x packages. |  | ||||||
| 
 |  | ||||||
| <h4>Related Packages</h4> |  | ||||||
| 
 |  | ||||||
| https://github.com/clbanning/checkxml provides functions for validating XML data. |  | ||||||
| 
 |  | ||||||
| <h4>Refactor Decoder - 2015.11.15</h4> |  | ||||||
| For over a year I've wanted to refactor the XML-to-map[string]interface{} decoder to make it more performant.  I recently took the time to do that, since we were using github.com/clbanning/mxj in a production system that could be deployed on a Raspberry Pi.  Now the decoder is comparable to the stdlib JSON-to-map[string]interface{} decoder in terms of its additional processing overhead relative to decoding to a structure value.  As shown by: |  | ||||||
| 
 |  | ||||||
| 	BenchmarkNewMapXml-4         	  100000	     18043 ns/op |  | ||||||
| 	BenchmarkNewStructXml-4      	  100000	     14892 ns/op |  | ||||||
| 	BenchmarkNewMapJson-4        	  300000	      4633 ns/op |  | ||||||
| 	BenchmarkNewStructJson-4     	  300000	      3427 ns/op |  | ||||||
| 	BenchmarkNewMapXmlBooks-4    	   20000	     82850 ns/op |  | ||||||
| 	BenchmarkNewStructXmlBooks-4 	   20000	     67822 ns/op |  | ||||||
| 	BenchmarkNewMapJsonBooks-4   	  100000	     17222 ns/op |  | ||||||
| 	BenchmarkNewStructJsonBooks-4	  100000	     15309 ns/op |  | ||||||
| 
 |  | ||||||
| <h4>Notices</h4> |  | ||||||
| 
 |  | ||||||
| 	2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc. |  | ||||||
| 	2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps. |  | ||||||
| 	2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package. |  | ||||||
| 	2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing. |  | ||||||
| 	2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods. |  | ||||||
| 	2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag(). |  | ||||||
| 	2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc. |  | ||||||
| 	2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix(). |  | ||||||
| 	2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable. |  | ||||||
| 	2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars(). |  | ||||||
| 	2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf". |  | ||||||
| 	            To cast them to float64, first set flag with CastNanInf(true). |  | ||||||
| 	2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure. |  | ||||||
| 	2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization. |  | ||||||
| 	2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM). |  | ||||||
| 	2015.12.02: XML decoding/encoding that preserves original structure of document. See NewMapXmlSeq() |  | ||||||
| 	            and mv.XmlSeq() / mv.XmlSeqIndent(). |  | ||||||
| 	2015-05-20: New: mv.StringIndentNoTypeInfo(). |  | ||||||
| 	            Also, alphabetically sort map[string]interface{} values by key to prettify output for mv.Xml(), |  | ||||||
| 	            mv.XmlIndent(), mv.StringIndent(), mv.StringIndentNoTypeInfo(). |  | ||||||
| 	2014-11-09: IncludeTagSeqNum() adds "_seq" key with XML doc positional information. |  | ||||||
| 	            (NOTE: PreserveXmlList() is similar and will be here soon.) |  | ||||||
| 	2014-09-18: inspired by NYTimes fork, added PrependAttrWithHyphen() to allow stripping hyphen from attribute tag. |  | ||||||
| 	2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML. |  | ||||||
| 	2014-04-28: ValuesForPath() and NewMap() now accept path with indexed array references. |  | ||||||
| 
 |  | ||||||
| <h4>Basic Unmarshal XML to map[string]interface{}</h4> |  | ||||||
| <pre>type Map map[string]interface{}</pre> |  | ||||||
| 
 |  | ||||||
| Create a `Map` value, 'mv', from any `map[string]interface{}` value, 'v': |  | ||||||
| <pre>mv := Map(v)</pre> |  | ||||||
| 
 |  | ||||||
| Unmarshal / marshal XML as a `Map` value, 'mv': |  | ||||||
| <pre>mv, err := NewMapXml(xmlValue) // unmarshal |  | ||||||
| xmlValue, err := mv.Xml()      // marshal</pre> |  | ||||||
| 
 |  | ||||||
| Unmarshal XML from an `io.Reader` as a `Map` value, 'mv': |  | ||||||
| <pre>mv, err := NewMapXmlReader(xmlReader)         // repeated calls, as with an os.File Reader, will process stream |  | ||||||
| mv, raw, err := NewMapXmlReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded</pre> |  | ||||||
| 
 |  | ||||||
| Marshal `Map` value, 'mv', to an XML Writer (`io.Writer`): |  | ||||||
| <pre>err := mv.XmlWriter(xmlWriter) |  | ||||||
| raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter</pre> |  | ||||||
|     |  | ||||||
| Also, for prettified output: |  | ||||||
| <pre>xmlValue, err := mv.XmlIndent(prefix, indent, ...) |  | ||||||
| err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...) |  | ||||||
| raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...)</pre> |  | ||||||
| 
 |  | ||||||
| Bulk process XML with error handling (note: handlers must return a boolean value): |  | ||||||
| <pre>err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error)) |  | ||||||
| err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte))</pre> |  | ||||||
| 
 |  | ||||||
| Converting XML to JSON: see Examples for `NewMapXml` and `HandleXmlReader`. |  | ||||||
| 
 |  | ||||||
| There are comparable functions and methods for JSON processing. |  | ||||||
| 
 |  | ||||||
| Arbitrary structure values can be decoded to / encoded from `Map` values: |  | ||||||
| <pre>mv, err := NewMapStruct(structVal) |  | ||||||
| err := mv.Struct(structPointer)</pre> |  | ||||||
| 
 |  | ||||||
| <h4>Extract / modify Map values</h4> |  | ||||||
| To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON |  | ||||||
| or structure to a `Map` value, 'mv', or cast a `map[string]interface{}` value to a `Map` value, 'mv', then: |  | ||||||
| <pre>paths := mv.PathsForKey(key) |  | ||||||
| path := mv.PathForKeyShortest(key) |  | ||||||
| values, err := mv.ValuesForKey(key, subkeys) |  | ||||||
| values, err := mv.ValuesForPath(path, subkeys) |  | ||||||
| count, err := mv.UpdateValuesForPath(newVal, path, subkeys)</pre> |  | ||||||
| 
 |  | ||||||
| Get everything at once, irrespective of path depth: |  | ||||||
| <pre>leafnodes := mv.LeafNodes() |  | ||||||
| leafvalues := mv.LeafValues()</pre> |  | ||||||
| 
 |  | ||||||
| A new `Map` with whatever keys are desired can be created from the current `Map` and then encoded in XML |  | ||||||
| or JSON. (Note: keys can use dot-notation.) |  | ||||||
| <pre>newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N") |  | ||||||
| newMap, err := mv.NewMap("oldKey1", "oldKey3", "oldKey5") // a subset of 'mv'; see "examples/partial.go" |  | ||||||
| newXml, err := newMap.Xml()   // for example |  | ||||||
| newJson, err := newMap.Json() // ditto</pre> |  | ||||||
| 
 |  | ||||||
| <h4>Usage</h4> |  | ||||||
| 
 |  | ||||||
| The package is fairly well [self-documented with examples](http://godoc.org/github.com/clbanning/mxj). |  | ||||||
| 
 |  | ||||||
| Also, the subdirectory "examples" contains a wide range of examples, several taken from golang-nuts discussions. |  | ||||||
| 
 |  | ||||||
| <h4>XML parsing conventions</h4> |  | ||||||
| 
 |  | ||||||
| Using NewMapXml() |  | ||||||
| 
 |  | ||||||
|    - Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`, |  | ||||||
|      to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or |  | ||||||
|      `SetAttrPrefix()`.) |  | ||||||
|    - If the element is a simple element and has attributes, the element value |  | ||||||
|      is given the key `#text` for its `map[string]interface{}` representation.  (See |  | ||||||
|      the 'atomFeedString.xml' test data, below.) |  | ||||||
|    - XML comments, directives, and process instructions are ignored. |  | ||||||
|    - If CoerceKeysToLower() has been called, then the resultant keys will be lower case. |  | ||||||
| 
 |  | ||||||
| Using NewMapXmlSeq() |  | ||||||
| 
 |  | ||||||
|    - Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values |  | ||||||
|      where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the  |  | ||||||
|      value for `<attr_label>`. |  | ||||||
|    - All elements, except for the root, have a "#seq" key. |  | ||||||
|    - Comments, directives, and process instructions are unmarshalled into the Map using the |  | ||||||
|      keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more |  | ||||||
|      specifics.) |  | ||||||
|    - Name space syntax is preserved:  |  | ||||||
|       - `<ns:key>something</ns.key>` parses to `map["ns:key"]interface{}{"something"}` |  | ||||||
|       - `xmlns:ns="http://myns.com/ns"` parses to `map["xmlns:ns"]interface{}{"http://myns.com/ns"}` |  | ||||||
| 
 |  | ||||||
| Both |  | ||||||
| 
 |  | ||||||
|    - By default, "Nan", "Inf", and "-Inf" values are not cast to float64.  If you want them |  | ||||||
|      to be cast, set a flag to cast them  using CastNanInf(true). |  | ||||||
| 
 |  | ||||||
| <h4>XML encoding conventions</h4> |  | ||||||
| 
 |  | ||||||
|    - 'nil' `Map` values, which may represent 'null' JSON values, are encoded as `<tag/>`. |  | ||||||
|      NOTE: the operation is not symmetric as `<tag/>` elements are decoded as `tag:""` `Map` values, |  | ||||||
|            which, then, encode in JSON as `"tag":""` values. |  | ||||||
|    - ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one.  (Go |  | ||||||
|            randomizes the walk through map[string]interface{} values.) If you plan to re-encode the |  | ||||||
|            Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and |  | ||||||
|            mv.XmlSeq() - these try to preserve the element sequencing but with added complexity when |  | ||||||
|            working with the Map representation. |  | ||||||
| 
 |  | ||||||
| <h4>Running "go test"</h4> |  | ||||||
| 
 |  | ||||||
| Because there are no guarantees on the sequence map elements are retrieved, the tests have been  |  | ||||||
| written for visual verification in most cases.  One advantage is that you can easily use the  |  | ||||||
| output from running "go test" as examples of calling the various functions and methods. |  | ||||||
| 
 |  | ||||||
| <h4>Motivation</h4> |  | ||||||
| 
 |  | ||||||
| I make extensive use of JSON for messaging and typically unmarshal the messages into |  | ||||||
| `map[string]interface{}` values.  This is easily done using `json.Unmarshal` from the |  | ||||||
| standard Go libraries.  Unfortunately, many legacy solutions use structured |  | ||||||
| XML messages; in those environments the applications would have to be refactored to |  | ||||||
| interoperate with my components. |  | ||||||
| 
 |  | ||||||
| The better solution is to just provide an alternative HTTP handler that receives |  | ||||||
| XML messages and parses it into a `map[string]interface{}` value and then reuse |  | ||||||
| all the JSON-based code.  The Go `xml.Unmarshal()` function does not provide the same |  | ||||||
| option of unmarshaling XML messages into `map[string]interface{}` values. So I wrote |  | ||||||
| a couple of small functions to fill this gap and released them as the x2j package. |  | ||||||
| 
 |  | ||||||
| Over the next year and a half additional features were added, and the companion j2x |  | ||||||
| package was released to address XML encoding of arbitrary JSON and `map[string]interface{}` |  | ||||||
| values.  As part of a refactoring of our production system and looking at how we had been |  | ||||||
| using the x2j and j2x packages we found that we rarely performed direct XML-to-JSON or |  | ||||||
| JSON-to_XML conversion and that working with the XML or JSON as `map[string]interface{}` |  | ||||||
| values was the primary value.  Thus, everything was refactored into the mxj package. |  | ||||||
| 
 |  | ||||||
							
								
								
									
										37
									
								
								vendor/github.com/clbanning/mxj/remove.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/clbanning/mxj/remove.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,37 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import "strings" |  | ||||||
| 
 |  | ||||||
| // Removes the path. |  | ||||||
| func (mv Map) Remove(path string) error { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	return remove(m, path) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func remove(m interface{}, path string) error { |  | ||||||
| 	val, err := prevValueByPath(m, path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	lastKey := lastKey(path) |  | ||||||
| 	delete(val, lastKey) |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // returns the last key of the path. |  | ||||||
| // lastKey("a.b.c") would had returned "c" |  | ||||||
| func lastKey(path string) string { |  | ||||||
| 	keys := strings.Split(path, ".") |  | ||||||
| 	key := keys[len(keys)-1] |  | ||||||
| 	return key |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // returns the path without the last key |  | ||||||
| // parentPath("a.b.c") whould had returned "a.b" |  | ||||||
| func parentPath(path string) string { |  | ||||||
| 	keys := strings.Split(path, ".") |  | ||||||
| 	parentPath := strings.Join(keys[0:len(keys)-1], ".") |  | ||||||
| 	return parentPath |  | ||||||
| } |  | ||||||
							
								
								
									
										54
									
								
								vendor/github.com/clbanning/mxj/rename.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/clbanning/mxj/rename.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,54 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // RenameKey renames a key in a Map. |  | ||||||
| // It works only for nested maps. It doesn't work for cases when it buried in a list. |  | ||||||
| func (mv Map) RenameKey(path string, newName string) error { |  | ||||||
| 	if !mv.Exists(path) { |  | ||||||
| 		return errors.New("RenameKey: path not found: " + path) |  | ||||||
| 	} |  | ||||||
| 	if mv.Exists(parentPath(path) + "." + newName) { |  | ||||||
| 		return errors.New("RenameKey: key already exists: " + newName) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	return renameKey(m, path, newName) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func renameKey(m interface{}, path string, newName string) error { |  | ||||||
| 	val, err := prevValueByPath(m, path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	oldName := lastKey(path) |  | ||||||
| 	val[newName] = val[oldName] |  | ||||||
| 	delete(val, oldName) |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // returns a value which contains a last key in the path |  | ||||||
| // For example: prevValueByPath("a.b.c", {a{b{c: 3}}}) returns {c: 3} |  | ||||||
| func prevValueByPath(m interface{}, path string) (map[string]interface{}, error) { |  | ||||||
| 	keys := strings.Split(path, ".") |  | ||||||
| 
 |  | ||||||
| 	switch mValue := m.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		for key, value := range mValue { |  | ||||||
| 			if key == keys[0] { |  | ||||||
| 				if len(keys) == 1 { |  | ||||||
| 					return mValue, nil |  | ||||||
| 				} else { |  | ||||||
| 					// keep looking for the full path to the key |  | ||||||
| 					return prevValueByPath(value, strings.Join(keys[1:], ".")) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil, errors.New("prevValueByPath: didn't find path – " + path) |  | ||||||
| } |  | ||||||
							
								
								
									
										26
									
								
								vendor/github.com/clbanning/mxj/set.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/clbanning/mxj/set.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,26 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Sets the value for the path |  | ||||||
| func (mv Map) SetValueForPath(value interface{}, path string) error { |  | ||||||
| 	pathAry := strings.Split(path, ".") |  | ||||||
| 	parentPathAry := pathAry[0 : len(pathAry)-1] |  | ||||||
| 	parentPath := strings.Join(parentPathAry, ".") |  | ||||||
| 
 |  | ||||||
| 	val, err := mv.ValueForPath(parentPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if val == nil { |  | ||||||
| 		return nil // we just ignore the request if there's no val |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key := pathAry[len(pathAry)-1] |  | ||||||
| 	cVal := val.(map[string]interface{}) |  | ||||||
| 	cVal[key] = value |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/clbanning/mxj/setfieldsep.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/clbanning/mxj/setfieldsep.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,20 +0,0 @@ | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| // Per: https://github.com/clbanning/mxj/issues/37#issuecomment-278651862 |  | ||||||
| var fieldSep string = ":" |  | ||||||
| 
 |  | ||||||
| // SetFieldSeparator changes the default field separator, ":", for the |  | ||||||
| // newVal argument in mv.UpdateValuesForPath and the optional 'subkey' arguments |  | ||||||
| // in mv.ValuesForKey and mv.ValuesForPath.  |  | ||||||
| //  |  | ||||||
| // E.g., if the newVal value is "http://blah/blah", setting the field separator |  | ||||||
| // to "|" will allow the newVal specification, "<key>|http://blah/blah" to parse |  | ||||||
| // properly.  If called with no argument or an empty string value, the field |  | ||||||
| // separator is set to the default, ":". |  | ||||||
| func SetFieldSeparator(s ...string) { |  | ||||||
| 	if len(s) == 0 || s[0] == "" { |  | ||||||
| 		fieldSep = ":" // the default |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	fieldSep = s[0] |  | ||||||
| } |  | ||||||
							
								
								
									
										29
									
								
								vendor/github.com/clbanning/mxj/songtext.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/clbanning/mxj/songtext.xml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,29 +0,0 @@ | ||||||
| <msg mtype="alert" mpriority="1"> |  | ||||||
| 	<text>help me!</text> |  | ||||||
| 	<song title="A Long Time" author="Mayer Hawthorne"> |  | ||||||
| 		<verses> |  | ||||||
| 			<verse name="verse 1" no="1"> |  | ||||||
| 				<line no="1">Henry was a renegade</line> |  | ||||||
| 				<line no="2">Didn't like to play it safe</line> |  | ||||||
| 				<line no="3">One component at a time</line> |  | ||||||
| 				<line no="4">There's got to be a better way</line> |  | ||||||
| 				<line no="5">Oh, people came from miles around</line> |  | ||||||
| 				<line no="6">Searching for a steady job</line> |  | ||||||
| 				<line no="7">Welcome to the Motor Town</line> |  | ||||||
| 				<line no="8">Booming like an atom bomb</line> |  | ||||||
| 			</verse> |  | ||||||
| 			<verse name="verse 2" no="2"> |  | ||||||
| 				<line no="1">Oh, Henry was the end of the story</line> |  | ||||||
| 				<line no="2">Then everything went wrong</line> |  | ||||||
| 				<line no="3">And we'll return it to its former glory</line> |  | ||||||
| 				<line no="4">But it just takes so long</line> |  | ||||||
| 			</verse> |  | ||||||
| 		</verses> |  | ||||||
| 		<chorus> |  | ||||||
| 			<line no="1">It's going to take a long time</line> |  | ||||||
| 			<line no="2">It's going to take it, but we'll make it one day</line> |  | ||||||
| 			<line no="3">It's going to take a long time</line> |  | ||||||
| 			<line no="4">It's going to take it, but we'll make it one day</line> |  | ||||||
| 		</chorus> |  | ||||||
| 	</song> |  | ||||||
| </msg> |  | ||||||
							
								
								
									
										30
									
								
								vendor/github.com/clbanning/mxj/strict.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/clbanning/mxj/strict.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,30 +0,0 @@ | ||||||
| // Copyright 2016 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| // strict.go actually addresses setting xml.Decoder attribute |  | ||||||
| // values.  This'll let you parse non-standard XML. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/xml" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // CustomDecoder can be used to specify xml.Decoder attribute |  | ||||||
| // values, e.g., Strict:false, to be used.  By default CustomDecoder |  | ||||||
| // is nil.  If CustomeDecoder != nil, then mxj.XmlCharsetReader variable is |  | ||||||
| // ignored and must be set as part of the CustomDecoder value, if needed. |  | ||||||
| //	Usage: |  | ||||||
| //		mxj.CustomDecoder = &xml.Decoder{Strict:false} |  | ||||||
| var CustomDecoder *xml.Decoder |  | ||||||
| 
 |  | ||||||
| // useCustomDecoder copy over public attributes from customDecoder |  | ||||||
| func useCustomDecoder(d *xml.Decoder) { |  | ||||||
| 	d.Strict = CustomDecoder.Strict |  | ||||||
| 	d.AutoClose = CustomDecoder.AutoClose |  | ||||||
| 	d.Entity = CustomDecoder.Entity |  | ||||||
| 	d.CharsetReader = CustomDecoder.CharsetReader |  | ||||||
| 	d.DefaultSpace = CustomDecoder.DefaultSpace |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
							
								
								
									
										54
									
								
								vendor/github.com/clbanning/mxj/struct.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/clbanning/mxj/struct.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,54 +0,0 @@ | ||||||
| // Copyright 2012-2017 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"reflect" |  | ||||||
| 
 |  | ||||||
| 	// "github.com/fatih/structs" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Create a new Map value from a structure.  Error returned if argument is not a structure. |  | ||||||
| // Only public structure fields are decoded in the Map value. See github.com/fatih/structs#Map |  | ||||||
| // for handling of "structs" tags. |  | ||||||
| 
 |  | ||||||
| // DEPRECATED - import github.com/fatih/structs and cast result of structs.Map to mxj.Map. |  | ||||||
| //	import "github.com/fatih/structs" |  | ||||||
| //	... |  | ||||||
| //	   sm, err := structs.Map(<some struct>) |  | ||||||
| //	   if err != nil { |  | ||||||
| //	      // handle error |  | ||||||
| //	   } |  | ||||||
| //	   m := mxj.Map(sm) |  | ||||||
| // Alernatively uncomment the old source and import in struct.go. |  | ||||||
| func NewMapStruct(structVal interface{}) (Map, error) { |  | ||||||
| 	return nil, errors.New("deprecated - see package documentation") |  | ||||||
| 	/* |  | ||||||
| 		if !structs.IsStruct(structVal) { |  | ||||||
| 			return nil, errors.New("NewMapStruct() error: argument is not type Struct") |  | ||||||
| 		} |  | ||||||
| 		return structs.Map(structVal), nil |  | ||||||
| 	*/ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Marshal a map[string]interface{} into a structure referenced by 'structPtr'. Error returned |  | ||||||
| // if argument is not a pointer or if json.Unmarshal returns an error. |  | ||||||
| //	json.Unmarshal structure encoding rules are followed to encode public structure fields. |  | ||||||
| func (mv Map) Struct(structPtr interface{}) error { |  | ||||||
| 	// should check that we're getting a pointer. |  | ||||||
| 	if reflect.ValueOf(structPtr).Kind() != reflect.Ptr { |  | ||||||
| 		return errors.New("mv.Struct() error: argument is not type Ptr") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	j, err := json.Marshal(m) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return json.Unmarshal(j, structPtr) |  | ||||||
| } |  | ||||||
							
								
								
									
										256
									
								
								vendor/github.com/clbanning/mxj/updatevalues.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										256
									
								
								vendor/github.com/clbanning/mxj/updatevalues.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,256 +0,0 @@ | ||||||
| // Copyright 2012-2014, 2017 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| // updatevalues.go - modify a value based on path and possibly sub-keys |  | ||||||
| // TODO(clb): handle simple elements with attributes and NewMapXmlSeq Map values. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Update value based on path and possible sub-key values. |  | ||||||
| // A count of the number of values changed and any error are returned. |  | ||||||
| // If the count == 0, then no path (and subkeys) matched. |  | ||||||
| //	'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified |  | ||||||
| //	             or a string value "key:value[:type]" where type is "bool" or "num" to cast the value. |  | ||||||
| //	'path' is dot-notation list of keys to traverse; last key in path can be newVal key |  | ||||||
| //	       NOTE: 'path' spec does not currently support indexed array references. |  | ||||||
| //	'subkeys' are "key:value[:type]" entries that must match for path node |  | ||||||
| //	            The subkey can be wildcarded - "key:*" - to require that it's there with some value. |  | ||||||
| //	            If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an |  | ||||||
| //	            exclusion critera - e.g., "!author:William T. Gaddis". |  | ||||||
| // |  | ||||||
| //	NOTES: |  | ||||||
| //		1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value. |  | ||||||
| //		2. Values in Maps created using NewMapXmlSeq are map[string]interface{} values with a "#text" key. |  | ||||||
| //		3. If values in 'newVal' or 'subkeys' args contain ":", use SetFieldSeparator to an unused symbol, |  | ||||||
| //	      perhaps "|". |  | ||||||
| func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 
 |  | ||||||
| 	// extract the subkeys |  | ||||||
| 	var subKeyMap map[string]interface{} |  | ||||||
| 	if len(subkeys) > 0 { |  | ||||||
| 		var err error |  | ||||||
| 		subKeyMap, err = getSubKeyMap(subkeys...) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return 0, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// extract key and value from newVal |  | ||||||
| 	var key string |  | ||||||
| 	var val interface{} |  | ||||||
| 	switch newVal.(type) { |  | ||||||
| 	case map[string]interface{}, Map: |  | ||||||
| 		switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec) |  | ||||||
| 		case Map: |  | ||||||
| 			newVal = newVal.(Map).Old() |  | ||||||
| 		} |  | ||||||
| 		if len(newVal.(map[string]interface{})) != 1 { |  | ||||||
| 			return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal) |  | ||||||
| 		} |  | ||||||
| 		for key, val = range newVal.(map[string]interface{}) { |  | ||||||
| 		} |  | ||||||
| 	case string: // split it as a key:value pair |  | ||||||
| 		ss := strings.Split(newVal.(string), fieldSep) |  | ||||||
| 		n := len(ss) |  | ||||||
| 		if n < 2 || n > 3 { |  | ||||||
| 			return 0, fmt.Errorf("unknown newVal spec - %+v", newVal) |  | ||||||
| 		} |  | ||||||
| 		key = ss[0] |  | ||||||
| 		if n == 2 { |  | ||||||
| 			val = interface{}(ss[1]) |  | ||||||
| 		} else if n == 3 { |  | ||||||
| 			switch ss[2] { |  | ||||||
| 			case "bool", "boolean": |  | ||||||
| 				nv, err := strconv.ParseBool(ss[1]) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal) |  | ||||||
| 				} |  | ||||||
| 				val = interface{}(nv) |  | ||||||
| 			case "num", "numeric", "float", "int": |  | ||||||
| 				nv, err := strconv.ParseFloat(ss[1], 64) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal) |  | ||||||
| 				} |  | ||||||
| 				val = interface{}(nv) |  | ||||||
| 			default: |  | ||||||
| 				return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	default: |  | ||||||
| 		return 0, fmt.Errorf("invalid newVal type - %+v", newVal) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// parse path |  | ||||||
| 	keys := strings.Split(path, ".") |  | ||||||
| 
 |  | ||||||
| 	var count int |  | ||||||
| 	updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count) |  | ||||||
| 
 |  | ||||||
| 	return count, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // navigate the path |  | ||||||
| func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) { |  | ||||||
| 	// ----- at end node: looking at possible node to get 'key' ---- |  | ||||||
| 	if len(keys) == 1 { |  | ||||||
| 		updateValue(key, value, m, keys[0], subkeys, cnt) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// ----- here we are navigating the path thru the penultimate node -------- |  | ||||||
| 	// key of interest is keys[0] - the next in the path |  | ||||||
| 	switch keys[0] { |  | ||||||
| 	case "*": // wildcard - scan all values |  | ||||||
| 		switch m.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			for _, v := range m.(map[string]interface{}) { |  | ||||||
| 				updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt) |  | ||||||
| 			} |  | ||||||
| 		case []interface{}: |  | ||||||
| 			for _, v := range m.([]interface{}) { |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				// flatten out a list of maps - keys are processed |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					for _, vv := range v.(map[string]interface{}) { |  | ||||||
| 						updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt) |  | ||||||
| 					} |  | ||||||
| 				default: |  | ||||||
| 					updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	default: // key - must be map[string]interface{} |  | ||||||
| 		switch m.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			if v, ok := m.(map[string]interface{})[keys[0]]; ok { |  | ||||||
| 				updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt) |  | ||||||
| 			} |  | ||||||
| 		case []interface{}: // may be buried in list |  | ||||||
| 			for _, v := range m.([]interface{}) { |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				case map[string]interface{}: |  | ||||||
| 					if vv, ok := v.(map[string]interface{})[keys[0]]; ok { |  | ||||||
| 						updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt) |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // change value if key and subkeys are present |  | ||||||
| func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) { |  | ||||||
| 	// there are two possible options for the value of 'keys0': map[string]interface, []interface{} |  | ||||||
| 	// and 'key' is a key in the map or is a key in a map in a list. |  | ||||||
| 	switch m.(type) { |  | ||||||
| 	case map[string]interface{}: // gotta have the last key |  | ||||||
| 		if keys0 == "*" { |  | ||||||
| 			for k := range m.(map[string]interface{}) { |  | ||||||
| 				updateValue(key, value, m, k, subkeys, cnt) |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		endVal, _ := m.(map[string]interface{})[keys0] |  | ||||||
| 
 |  | ||||||
| 		// if newV key is the end of path, replace the value for path-end |  | ||||||
| 		// may be []interface{} - means replace just an entry w/ subkeys |  | ||||||
| 		// otherwise replace the keys0 value if subkeys are there |  | ||||||
| 		// NOTE: this will replace the subkeys, also |  | ||||||
| 		if key == keys0 { |  | ||||||
| 			switch endVal.(type) { |  | ||||||
| 			case map[string]interface{}: |  | ||||||
| 				if hasSubKeys(m, subkeys) { |  | ||||||
| 					(m.(map[string]interface{}))[keys0] = value |  | ||||||
| 					(*cnt)++ |  | ||||||
| 				} |  | ||||||
| 			case []interface{}: |  | ||||||
| 				// without subkeys can't select list member to modify |  | ||||||
| 				// so key:value spec is it ... |  | ||||||
| 				if hasSubKeys(m, subkeys) { |  | ||||||
| 					(m.(map[string]interface{}))[keys0] = value |  | ||||||
| 					(*cnt)++ |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 				nv := make([]interface{}, 0) |  | ||||||
| 				var valmodified bool |  | ||||||
| 				for _, v := range endVal.([]interface{}) { |  | ||||||
| 					// check entry subkeys |  | ||||||
| 					if hasSubKeys(v, subkeys) { |  | ||||||
| 						// replace v with value |  | ||||||
| 						nv = append(nv, value) |  | ||||||
| 						valmodified = true |  | ||||||
| 						(*cnt)++ |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 					nv = append(nv, v) |  | ||||||
| 				} |  | ||||||
| 				if valmodified { |  | ||||||
| 					(m.(map[string]interface{}))[keys0] = interface{}(nv) |  | ||||||
| 				} |  | ||||||
| 			default: // anything else is a strict replacement |  | ||||||
| 				if hasSubKeys(m, subkeys) { |  | ||||||
| 					(m.(map[string]interface{}))[keys0] = value |  | ||||||
| 					(*cnt)++ |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// so value is for an element of endVal |  | ||||||
| 		// if endVal is a map then 'key' must be there w/ subkeys |  | ||||||
| 		// if endVal is a list then 'key' must be in a list member w/ subkeys |  | ||||||
| 		switch endVal.(type) { |  | ||||||
| 		case map[string]interface{}: |  | ||||||
| 			if !hasSubKeys(endVal, subkeys) { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			if _, ok := (endVal.(map[string]interface{}))[key]; ok { |  | ||||||
| 				(endVal.(map[string]interface{}))[key] = value |  | ||||||
| 				(*cnt)++ |  | ||||||
| 			} |  | ||||||
| 		case []interface{}: // keys0 points to a list, check subkeys |  | ||||||
| 			for _, v := range endVal.([]interface{}) { |  | ||||||
| 				// got to be a map so we can replace value for 'key' |  | ||||||
| 				vv, vok := v.(map[string]interface{}) |  | ||||||
| 				if !vok { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				if _, ok := vv[key]; !ok { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				if !hasSubKeys(vv, subkeys) { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				vv[key] = value |  | ||||||
| 				(*cnt)++ |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	case []interface{}: // key may be in a list member |  | ||||||
| 		// don't need to handle keys0 == "*"; we're looking at everything, anyway. |  | ||||||
| 		for _, v := range m.([]interface{}) { |  | ||||||
| 			// only map values - we're looking for 'key' |  | ||||||
| 			mm, ok := v.(map[string]interface{}) |  | ||||||
| 			if !ok { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if _, ok := mm[key]; !ok { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if !hasSubKeys(mm, subkeys) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			mm[key] = value |  | ||||||
| 			(*cnt)++ |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// return |  | ||||||
| } |  | ||||||
							
								
								
									
										1139
									
								
								vendor/github.com/clbanning/mxj/xml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1139
									
								
								vendor/github.com/clbanning/mxj/xml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										828
									
								
								vendor/github.com/clbanning/mxj/xmlseq.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										828
									
								
								vendor/github.com/clbanning/mxj/xmlseq.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,828 +0,0 @@ | ||||||
| // Copyright 2012-2016 Charles Banning. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file |  | ||||||
| 
 |  | ||||||
| // xmlseq.go - version of xml.go with sequence # injection on Decoding and sorting on Encoding. |  | ||||||
| // Also, handles comments, directives and process instructions. |  | ||||||
| 
 |  | ||||||
| package mxj |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/xml" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var NoRoot = errors.New("no root key") |  | ||||||
| var NO_ROOT = NoRoot // maintain backwards compatibility |  | ||||||
| 
 |  | ||||||
| // ------------------- NewMapXmlSeq & NewMapXmlSeqReader ... ------------------------- |  | ||||||
| 
 |  | ||||||
| // This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure. |  | ||||||
| // The xml.Decoder.RawToken method is used to parse the XML, so there is no checking for appropriate xml.EndElement values; |  | ||||||
| // thus, it is assumed that the XML is valid. |  | ||||||
| // |  | ||||||
| // NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #seq. |  | ||||||
| // If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible. |  | ||||||
| // NOTE: "#seq" key/value pairs are removed on encoding with mv.XmlSeq() / mv.XmlSeqIndent(). |  | ||||||
| //	• attributes are a map - map["#attr"]map["attr_key"]map[string]interface{}{"#text":<aval>, "#seq":<num>} |  | ||||||
| //	• all simple elements are decoded as map["#text"]interface{} with a "#seq" k:v pair, as well. |  | ||||||
| //	• lists always decode as map["list_tag"][]map[string]interface{} where the array elements are maps that |  | ||||||
| //	  include a "#seq" k:v pair based on sequence they are decoded.  Thus, XML like: |  | ||||||
| //	      <doc> |  | ||||||
| //	         <ltag>value 1</ltag> |  | ||||||
| //	         <newtag>value 2</newtag> |  | ||||||
| //	         <ltag>value 3</ltag> |  | ||||||
| //	      </doc> |  | ||||||
| //	  is decoded as: |  | ||||||
| //	    doc : |  | ||||||
| //	      ltag :[[]interface{}] |  | ||||||
| //	        [item: 0] |  | ||||||
| //	          #seq :[int] 0 |  | ||||||
| //	          #text :[string] value 1 |  | ||||||
| //	        [item: 1] |  | ||||||
| //	          #seq :[int] 2 |  | ||||||
| //	          #text :[string] value 3 |  | ||||||
| //	      newtag : |  | ||||||
| //	        #seq :[int] 1 |  | ||||||
| //	        #text :[string] value 2 |  | ||||||
| //	  It will encode in proper sequence even though the Map representation merges all "ltag" elements in an array. |  | ||||||
| //	• comments - "<!--comment-->" -  are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair. |  | ||||||
| //	• directives - "<!text>" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair. |  | ||||||
| //	• process instructions  - "<?instr?>" - are decoded as map["#procinst"]interface{} where the #procinst value |  | ||||||
| //	  is of map[string]interface{} type with the following keys: #target, #inst, and #seq. |  | ||||||
| //	• comments, directives, and procinsts that are NOT part of a document with a root key will be returned as |  | ||||||
| //	  map[string]interface{} and the error value 'NoRoot'. |  | ||||||
| //	• note: "<![CDATA[" syntax is lost in xml.Decode parser - and is not handled here, either. |  | ||||||
| //	   and: "\r\n" is converted to "\n" |  | ||||||
| // |  | ||||||
| //	NOTES: |  | ||||||
| //	   1. The 'xmlVal' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other |  | ||||||
| //	      extraneous xml.CharData will be ignored unless io.EOF is reached first. |  | ||||||
| //	   2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to |  | ||||||
| //	      re-encode the message in its original structure. |  | ||||||
| //	   3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case. |  | ||||||
| // |  | ||||||
| //	NAME SPACES: |  | ||||||
| //	   1. Keys in the Map value that are parsed from a <name space prefix>:<local name> tag preserve the |  | ||||||
| //	      "<prefix>:" notation rather than stripping it as with NewMapXml(). |  | ||||||
| //	   2. Attribute keys for name space prefix declarations preserve "xmlns:<prefix>" notation. |  | ||||||
| func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) { |  | ||||||
| 	var r bool |  | ||||||
| 	if len(cast) == 1 { |  | ||||||
| 		r = cast[0] |  | ||||||
| 	} |  | ||||||
| 	return xmlSeqToMap(xmlVal, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure. |  | ||||||
| // |  | ||||||
| // Get next XML doc from an io.Reader as a Map value.  Returns Map value. |  | ||||||
| //	NOTES: |  | ||||||
| //	   1. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other |  | ||||||
| //	      extraneous xml.CharData will be ignored unless io.EOF is reached first. |  | ||||||
| //	   2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to |  | ||||||
| //	      re-encode the message in its original structure. |  | ||||||
| //	   3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case. |  | ||||||
| func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) { |  | ||||||
| 	var r bool |  | ||||||
| 	if len(cast) == 1 { |  | ||||||
| 		r = cast[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// We need to put an *os.File reader in a ByteReader or the xml.NewDecoder |  | ||||||
| 	// will wrap it in a bufio.Reader and seek on the file beyond where the |  | ||||||
| 	// xml.Decoder parses! |  | ||||||
| 	if _, ok := xmlReader.(io.ByteReader); !ok { |  | ||||||
| 		xmlReader = myByteReader(xmlReader) // see code at EOF |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// build the map |  | ||||||
| 	return xmlSeqReaderToMap(xmlReader, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure. |  | ||||||
| // |  | ||||||
| // Get next XML doc from an io.Reader as a Map value.  Returns Map value and slice with the raw XML. |  | ||||||
| //	NOTES: |  | ||||||
| //	   1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte |  | ||||||
| //	      using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact. |  | ||||||
| //	      See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large |  | ||||||
| //	      data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body |  | ||||||
| //	      you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call. |  | ||||||
| //	    2. The 'raw' return value may be larger than the XML text value. |  | ||||||
| //	    3. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other |  | ||||||
| //	       extraneous xml.CharData will be ignored unless io.EOF is reached first. |  | ||||||
| //	    4. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to |  | ||||||
| //	       re-encode the message in its original structure. |  | ||||||
| //	    5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case. |  | ||||||
| func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) { |  | ||||||
| 	var r bool |  | ||||||
| 	if len(cast) == 1 { |  | ||||||
| 		r = cast[0] |  | ||||||
| 	} |  | ||||||
| 	// create TeeReader so we can retrieve raw XML |  | ||||||
| 	buf := make([]byte, 0) |  | ||||||
| 	wb := bytes.NewBuffer(buf) |  | ||||||
| 	trdr := myTeeReader(xmlReader, wb) |  | ||||||
| 
 |  | ||||||
| 	m, err := xmlSeqReaderToMap(trdr, r) |  | ||||||
| 
 |  | ||||||
| 	// retrieve the raw XML that was decoded |  | ||||||
| 	b := wb.Bytes() |  | ||||||
| 
 |  | ||||||
| 	// err may be NoRoot |  | ||||||
| 	return m, b, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // xmlSeqReaderToMap() - parse a XML io.Reader to a map[string]interface{} value |  | ||||||
| func xmlSeqReaderToMap(rdr io.Reader, r bool) (map[string]interface{}, error) { |  | ||||||
| 	// parse the Reader |  | ||||||
| 	p := xml.NewDecoder(rdr) |  | ||||||
| 	if CustomDecoder != nil { |  | ||||||
| 		useCustomDecoder(p) |  | ||||||
| 	} else { |  | ||||||
| 		p.CharsetReader = XmlCharsetReader |  | ||||||
| 	} |  | ||||||
| 	return xmlSeqToMapParser("", nil, p, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // xmlSeqToMap - convert a XML doc into map[string]interface{} value |  | ||||||
| func xmlSeqToMap(doc []byte, r bool) (map[string]interface{}, error) { |  | ||||||
| 	b := bytes.NewReader(doc) |  | ||||||
| 	p := xml.NewDecoder(b) |  | ||||||
| 	if CustomDecoder != nil { |  | ||||||
| 		useCustomDecoder(p) |  | ||||||
| 	} else { |  | ||||||
| 		p.CharsetReader = XmlCharsetReader |  | ||||||
| 	} |  | ||||||
| 	return xmlSeqToMapParser("", nil, p, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ===================================== where the work happens ============================= |  | ||||||
| 
 |  | ||||||
| // xmlSeqToMapParser - load a 'clean' XML doc into a map[string]interface{} directly. |  | ||||||
| // Add #seq tag value for each element decoded - to be used for Encoding later. |  | ||||||
| func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[string]interface{}, error) { |  | ||||||
| 	if snakeCaseKeys { |  | ||||||
| 		skey = strings.Replace(skey, "-", "_", -1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// NOTE: all attributes and sub-elements parsed into 'na', 'na' is returned as value for 'skey' in 'n'. |  | ||||||
| 	var n, na map[string]interface{} |  | ||||||
| 	var seq int // for including seq num when decoding |  | ||||||
| 
 |  | ||||||
| 	// Allocate maps and load attributes, if any. |  | ||||||
| 	// NOTE: on entry from NewMapXml(), etc., skey=="", and we fall through |  | ||||||
| 	//       to get StartElement then recurse with skey==xml.StartElement.Name.Local |  | ||||||
| 	//       where we begin allocating map[string]interface{} values 'n' and 'na'. |  | ||||||
| 	if skey != "" { |  | ||||||
| 		// 'n' only needs one slot - save call to runtime•hashGrow() |  | ||||||
| 		// 'na' we don't know |  | ||||||
| 		n = make(map[string]interface{}, 1) |  | ||||||
| 		na = make(map[string]interface{}) |  | ||||||
| 		if len(a) > 0 { |  | ||||||
| 			// xml.Attr is decoded into: map["#attr"]map[<attr_label>]interface{} |  | ||||||
| 			// where interface{} is map[string]interface{}{"#text":<attr_val>, "#seq":<attr_seq>} |  | ||||||
| 			aa := make(map[string]interface{}, len(a)) |  | ||||||
| 			for i, v := range a { |  | ||||||
| 				if snakeCaseKeys { |  | ||||||
| 					v.Name.Local = strings.Replace(v.Name.Local, "-", "_", -1) |  | ||||||
| 				} |  | ||||||
| 				if len(v.Name.Space) > 0 { |  | ||||||
| 					aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i} |  | ||||||
| 				} else { |  | ||||||
| 					aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			na["#attr"] = aa |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Return XMPP <stream:stream> message. |  | ||||||
| 	if handleXMPPStreamTag && skey == "stream:stream" { |  | ||||||
| 		n[skey] = na |  | ||||||
| 		return n, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for { |  | ||||||
| 		t, err := p.RawToken() |  | ||||||
| 		if err != nil { |  | ||||||
| 			if err != io.EOF { |  | ||||||
| 				return nil, errors.New("xml.Decoder.Token() - " + err.Error()) |  | ||||||
| 			} |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		switch t.(type) { |  | ||||||
| 		case xml.StartElement: |  | ||||||
| 			tt := t.(xml.StartElement) |  | ||||||
| 
 |  | ||||||
| 			// First call to xmlSeqToMapParser() doesn't pass xml.StartElement - the map key. |  | ||||||
| 			// So when the loop is first entered, the first token is the root tag along |  | ||||||
| 			// with any attributes, which we process here. |  | ||||||
| 			// |  | ||||||
| 			// Subsequent calls to xmlSeqToMapParser() will pass in tag+attributes for |  | ||||||
| 			// processing before getting the next token which is the element value, |  | ||||||
| 			// which is done above. |  | ||||||
| 			if skey == "" { |  | ||||||
| 				if len(tt.Name.Space) > 0 { |  | ||||||
| 					return xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r) |  | ||||||
| 				} else { |  | ||||||
| 					return xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// If not initializing the map, parse the element. |  | ||||||
| 			// len(nn) == 1, necessarily - it is just an 'n'. |  | ||||||
| 			var nn map[string]interface{} |  | ||||||
| 			if len(tt.Name.Space) > 0 { |  | ||||||
| 				nn, err = xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r) |  | ||||||
| 			} else { |  | ||||||
| 				nn, err = xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r) |  | ||||||
| 			} |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// The nn map[string]interface{} value is a na[nn_key] value. |  | ||||||
| 			// We need to see if nn_key already exists - means we're parsing a list. |  | ||||||
| 			// This may require converting na[nn_key] value into []interface{} type. |  | ||||||
| 			// First, extract the key:val for the map - it's a singleton. |  | ||||||
| 			var key string |  | ||||||
| 			var val interface{} |  | ||||||
| 			for key, val = range nn { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// add "#seq" k:v pair - |  | ||||||
| 			// Sequence number included even in list elements - this should allow us |  | ||||||
| 			// to properly resequence even something goofy like: |  | ||||||
| 			//     <list>item 1</list> |  | ||||||
| 			//     <subelement>item 2</subelement> |  | ||||||
| 			//     <list>item 3</list> |  | ||||||
| 			// where all the "list" subelements are decoded into an array. |  | ||||||
| 			switch val.(type) { |  | ||||||
| 			case map[string]interface{}: |  | ||||||
| 				val.(map[string]interface{})["#seq"] = seq |  | ||||||
| 				seq++ |  | ||||||
| 			case interface{}: // a non-nil simple element: string, float64, bool |  | ||||||
| 				v := map[string]interface{}{"#text": val, "#seq": seq} |  | ||||||
| 				seq++ |  | ||||||
| 				val = v |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// 'na' holding sub-elements of n. |  | ||||||
| 			// See if 'key' already exists. |  | ||||||
| 			// If 'key' exists, then this is a list, if not just add key:val to na. |  | ||||||
| 			if v, ok := na[key]; ok { |  | ||||||
| 				var a []interface{} |  | ||||||
| 				switch v.(type) { |  | ||||||
| 				case []interface{}: |  | ||||||
| 					a = v.([]interface{}) |  | ||||||
| 				default: // anything else - note: v.(type) != nil |  | ||||||
| 					a = []interface{}{v} |  | ||||||
| 				} |  | ||||||
| 				a = append(a, val) |  | ||||||
| 				na[key] = a |  | ||||||
| 			} else { |  | ||||||
| 				na[key] = val // save it as a singleton |  | ||||||
| 			} |  | ||||||
| 		case xml.EndElement: |  | ||||||
| 			if skey != "" { |  | ||||||
| 				tt := t.(xml.EndElement) |  | ||||||
| 				if snakeCaseKeys { |  | ||||||
| 					tt.Name.Local = strings.Replace(tt.Name.Local, "-", "_", -1) |  | ||||||
| 				} |  | ||||||
| 				var name string |  | ||||||
| 				if len(tt.Name.Space) > 0 { |  | ||||||
| 					name = tt.Name.Space + `:` + tt.Name.Local |  | ||||||
| 				} else { |  | ||||||
| 					name = tt.Name.Local |  | ||||||
| 				} |  | ||||||
| 				if skey != name { |  | ||||||
| 					return nil, fmt.Errorf("element %s not properly terminated, got %s at #%d", |  | ||||||
| 						skey, name, p.InputOffset()) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case. |  | ||||||
| 			if len(n) == 0 { |  | ||||||
| 				// If len(na)==0 we have an empty element == ""; |  | ||||||
| 				// it has no xml.Attr nor xml.CharData. |  | ||||||
| 				// Empty element content will be  map["etag"]map["#text"]"" |  | ||||||
| 				// after #seq injection - map["etag"]map["#seq"]seq - after return. |  | ||||||
| 				if len(na) > 0 { |  | ||||||
| 					n[skey] = na |  | ||||||
| 				} else { |  | ||||||
| 					n[skey] = "" // empty element |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			return n, nil |  | ||||||
| 		case xml.CharData: |  | ||||||
| 			// clean up possible noise |  | ||||||
| 			tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ") |  | ||||||
| 			if skey == "" { |  | ||||||
| 				// per Adrian (http://www.adrianlungu.com/) catch stray text |  | ||||||
| 				// in decoder stream - |  | ||||||
| 				// https://github.com/clbanning/mxj/pull/14#issuecomment-182816374 |  | ||||||
| 				// NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get |  | ||||||
| 				// a p.Token() decoding error when the BOM is UTF-16 or UTF-32. |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if len(tt) > 0 { |  | ||||||
| 				// every simple element is a #text and has #seq associated with it |  | ||||||
| 				na["#text"] = cast(tt, r) |  | ||||||
| 				na["#seq"] = seq |  | ||||||
| 				seq++ |  | ||||||
| 			} |  | ||||||
| 		case xml.Comment: |  | ||||||
| 			if n == nil { // no root 'key' |  | ||||||
| 				n = map[string]interface{}{"#comment": string(t.(xml.Comment))} |  | ||||||
| 				return n, NoRoot |  | ||||||
| 			} |  | ||||||
| 			cm := make(map[string]interface{}, 2) |  | ||||||
| 			cm["#text"] = string(t.(xml.Comment)) |  | ||||||
| 			cm["#seq"] = seq |  | ||||||
| 			seq++ |  | ||||||
| 			na["#comment"] = cm |  | ||||||
| 		case xml.Directive: |  | ||||||
| 			if n == nil { // no root 'key' |  | ||||||
| 				n = map[string]interface{}{"#directive": string(t.(xml.Directive))} |  | ||||||
| 				return n, NoRoot |  | ||||||
| 			} |  | ||||||
| 			dm := make(map[string]interface{}, 2) |  | ||||||
| 			dm["#text"] = string(t.(xml.Directive)) |  | ||||||
| 			dm["#seq"] = seq |  | ||||||
| 			seq++ |  | ||||||
| 			na["#directive"] = dm |  | ||||||
| 		case xml.ProcInst: |  | ||||||
| 			if n == nil { |  | ||||||
| 				na = map[string]interface{}{"#target": t.(xml.ProcInst).Target, "#inst": string(t.(xml.ProcInst).Inst)} |  | ||||||
| 				n = map[string]interface{}{"#procinst": na} |  | ||||||
| 				return n, NoRoot |  | ||||||
| 			} |  | ||||||
| 			pm := make(map[string]interface{}, 3) |  | ||||||
| 			pm["#target"] = t.(xml.ProcInst).Target |  | ||||||
| 			pm["#inst"] = string(t.(xml.ProcInst).Inst) |  | ||||||
| 			pm["#seq"] = seq |  | ||||||
| 			seq++ |  | ||||||
| 			na["#procinst"] = pm |  | ||||||
| 		default: |  | ||||||
| 			// noop - shouldn't ever get here, now, since we handle all token types |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ------------------ END: NewMapXml & NewMapXmlReader ------------------------- |  | ||||||
| 
 |  | ||||||
| // --------------------- mv.XmlSeq & mv.XmlSeqWriter ------------------------- |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Encode a Map as XML with elements sorted on #seq.  The companion of NewMapXmlSeq(). |  | ||||||
| // The following rules apply. |  | ||||||
| //    - The key label "#text" is treated as the value for a simple element with attributes. |  | ||||||
| //    - The "#seq" key is used to seqence the subelements or attributes but is ignored for writing. |  | ||||||
| //    - The "#attr" map key identifies the map of attribute map[string]interface{} values with "#text" key. |  | ||||||
| //    - The "#comment" map key identifies a comment in the value "#text" map entry - <!--comment-->. |  | ||||||
| //    - The "#directive" map key identifies a directive in the value "#text" map entry - <!directive>. |  | ||||||
| //    - The "#procinst" map key identifies a process instruction in the value "#target" and "#inst" |  | ||||||
| //      map entries - <?target inst?>. |  | ||||||
| //    - Value type encoding: |  | ||||||
| //          > string, bool, float64, int, int32, int64, float32: per "%v" formating |  | ||||||
| //          > []bool, []uint8: by casting to string |  | ||||||
| //          > structures, etc.: handed to xml.Marshal() - if there is an error, the element |  | ||||||
| //            value is "UNKNOWN" |  | ||||||
| //    - Elements with only attribute values or are null are terminated using "/>" unless XmlGoEmptyElemSystax() called. |  | ||||||
| //    - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible. |  | ||||||
| //      Thus, `{ "key":"value" }` encodes as "<key>value</key>". |  | ||||||
| func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 	var err error |  | ||||||
| 	s := new(string) |  | ||||||
| 	p := new(pretty) // just a stub |  | ||||||
| 
 |  | ||||||
| 	if len(m) == 1 && len(rootTag) == 0 { |  | ||||||
| 		for key, value := range m { |  | ||||||
| 			// if it's an array, see if all values are map[string]interface{} |  | ||||||
| 			// we force a new root tag if we'll end up with no key:value in the list |  | ||||||
| 			// so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></doc> |  | ||||||
| 			switch value.(type) { |  | ||||||
| 			case []interface{}: |  | ||||||
| 				for _, v := range value.([]interface{}) { |  | ||||||
| 					switch v.(type) { |  | ||||||
| 					case map[string]interface{}: // noop |  | ||||||
| 					default: // anything else |  | ||||||
| 						err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p) |  | ||||||
| 						goto done |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			err = mapToXmlSeqIndent(false, s, key, value, p) |  | ||||||
| 		} |  | ||||||
| 	} else if len(rootTag) == 1 { |  | ||||||
| 		err = mapToXmlSeqIndent(false, s, rootTag[0], m, p) |  | ||||||
| 	} else { |  | ||||||
| 		err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p) |  | ||||||
| 	} |  | ||||||
| done: |  | ||||||
| 	return []byte(*s), err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // The following implementation is provided only for symmetry with NewMapXmlReader[Raw] |  | ||||||
| // The names will also provide a key for the number of return arguments. |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Writes the Map as  XML on the Writer. |  | ||||||
| // See XmlSeq() for encoding rules. |  | ||||||
| func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error { |  | ||||||
| 	x, err := mv.XmlSeq(rootTag...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = xmlWriter.Write(x) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Writes the Map as  XML on the Writer. []byte is the raw XML that was written. |  | ||||||
| // See XmlSeq() for encoding rules. |  | ||||||
| func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) { |  | ||||||
| 	x, err := mv.XmlSeq(rootTag...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return x, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = xmlWriter.Write(x) |  | ||||||
| 	return x, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Writes the Map as pretty XML on the Writer. |  | ||||||
| // See Xml() for encoding rules. |  | ||||||
| func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error { |  | ||||||
| 	x, err := mv.XmlSeqIndent(prefix, indent, rootTag...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = xmlWriter.Write(x) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written. |  | ||||||
| // See XmlSeq() for encoding rules. |  | ||||||
| func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) { |  | ||||||
| 	x, err := mv.XmlSeqIndent(prefix, indent, rootTag...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return x, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, err = xmlWriter.Write(x) |  | ||||||
| 	return x, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // -------------------- END: mv.Xml & mv.XmlWriter ------------------------------- |  | ||||||
| 
 |  | ||||||
| // ---------------------- XmlSeqIndent ---------------------------- |  | ||||||
| 
 |  | ||||||
| // This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co. |  | ||||||
| // |  | ||||||
| // Encode a map[string]interface{} as a pretty XML string. |  | ||||||
| // See mv.XmlSeq() for encoding rules. |  | ||||||
| func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, error) { |  | ||||||
| 	m := map[string]interface{}(mv) |  | ||||||
| 
 |  | ||||||
| 	var err error |  | ||||||
| 	s := new(string) |  | ||||||
| 	p := new(pretty) |  | ||||||
| 	p.indent = indent |  | ||||||
| 	p.padding = prefix |  | ||||||
| 
 |  | ||||||
| 	if len(m) == 1 && len(rootTag) == 0 { |  | ||||||
| 		// this can extract the key for the single map element |  | ||||||
| 		// use it if it isn't a key for a list |  | ||||||
| 		for key, value := range m { |  | ||||||
| 			if _, ok := value.([]interface{}); ok { |  | ||||||
| 				err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p) |  | ||||||
| 			} else { |  | ||||||
| 				err = mapToXmlSeqIndent(true, s, key, value, p) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} else if len(rootTag) == 1 { |  | ||||||
| 		err = mapToXmlSeqIndent(true, s, rootTag[0], m, p) |  | ||||||
| 	} else { |  | ||||||
| 		err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p) |  | ||||||
| 	} |  | ||||||
| 	return []byte(*s), err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // where the work actually happens |  | ||||||
| // returns an error if an attribute is not atomic |  | ||||||
| func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error { |  | ||||||
| 	var endTag bool |  | ||||||
| 	var isSimple bool |  | ||||||
| 	var noEndTag bool |  | ||||||
| 	var elen int |  | ||||||
| 	var ss string |  | ||||||
| 	p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start} |  | ||||||
| 
 |  | ||||||
| 	switch value.(type) { |  | ||||||
| 	case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32: |  | ||||||
| 		if doIndent { |  | ||||||
| 			*s += p.padding |  | ||||||
| 		} |  | ||||||
| 		if key != "#comment" && key != "#directive" && key != "#procinst" { |  | ||||||
| 			*s += `<` + key |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	switch value.(type) { |  | ||||||
| 	case map[string]interface{}: |  | ||||||
| 		val := value.(map[string]interface{}) |  | ||||||
| 
 |  | ||||||
| 		if key == "#comment" { |  | ||||||
| 			*s += `<!--` + val["#text"].(string) + `-->` |  | ||||||
| 			noEndTag = true |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if key == "#directive" { |  | ||||||
| 			*s += `<!` + val["#text"].(string) + `>` |  | ||||||
| 			noEndTag = true |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if key == "#procinst" { |  | ||||||
| 			*s += `<?` + val["#target"].(string) + ` ` + val["#inst"].(string) + `?>` |  | ||||||
| 			noEndTag = true |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		haveAttrs := false |  | ||||||
| 		// process attributes first |  | ||||||
| 		if v, ok := val["#attr"].(map[string]interface{}); ok { |  | ||||||
| 			// First, unroll the map[string]interface{} into a []keyval array. |  | ||||||
| 			// Then sequence it. |  | ||||||
| 			kv := make([]keyval, len(v)) |  | ||||||
| 			n := 0 |  | ||||||
| 			for ak, av := range v { |  | ||||||
| 				kv[n] = keyval{ak, av} |  | ||||||
| 				n++ |  | ||||||
| 			} |  | ||||||
| 			sort.Sort(elemListSeq(kv)) |  | ||||||
| 			// Now encode the attributes in original decoding sequence, using keyval array. |  | ||||||
| 			for _, a := range kv { |  | ||||||
| 				vv := a.v.(map[string]interface{}) |  | ||||||
| 				switch vv["#text"].(type) { |  | ||||||
| 				case string: |  | ||||||
| 					if xmlEscapeChars { |  | ||||||
| 						ss = escapeChars(vv["#text"].(string)) |  | ||||||
| 					} else { |  | ||||||
| 						ss = vv["#text"].(string) |  | ||||||
| 					} |  | ||||||
| 					*s += ` ` + a.k + `="` + ss + `"` |  | ||||||
| 				case float64, bool, int, int32, int64, float32: |  | ||||||
| 					*s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"` |  | ||||||
| 				case []byte: |  | ||||||
| 					if xmlEscapeChars { |  | ||||||
| 						ss = escapeChars(string(vv["#text"].([]byte))) |  | ||||||
| 					} else { |  | ||||||
| 						ss = string(vv["#text"].([]byte)) |  | ||||||
| 					} |  | ||||||
| 					*s += ` ` + a.k + `="` + ss + `"` |  | ||||||
| 				default: |  | ||||||
| 					return fmt.Errorf("invalid attribute value for: %s", a.k) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			haveAttrs = true |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// simple element? |  | ||||||
| 		// every map value has, at least, "#seq" and, perhaps, "#text" and/or "#attr" |  | ||||||
| 		_, seqOK := val["#seq"] // have key |  | ||||||
| 		if v, ok := val["#text"]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK { |  | ||||||
| 			if stmp, ok := v.(string); ok && stmp != "" { |  | ||||||
| 				if xmlEscapeChars { |  | ||||||
| 					stmp = escapeChars(stmp) |  | ||||||
| 				} |  | ||||||
| 				*s += ">" + stmp |  | ||||||
| 				endTag = true |  | ||||||
| 				elen = 1 |  | ||||||
| 			} |  | ||||||
| 			isSimple = true |  | ||||||
| 			break |  | ||||||
| 		} else if !ok && ((len(val) == 2 && haveAttrs) || (len(val) == 1 && !haveAttrs)) && seqOK { |  | ||||||
| 			// here no #text but have #seq or #seq+#attr |  | ||||||
| 			endTag = false |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// we now need to sequence everything except attributes |  | ||||||
| 		// 'kv' will hold everything that needs to be written |  | ||||||
| 		kv := make([]keyval, 0) |  | ||||||
| 		for k, v := range val { |  | ||||||
| 			if k == "#attr" { // already processed |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if k == "#seq" { // ignore - just for sorting |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			switch v.(type) { |  | ||||||
| 			case []interface{}: |  | ||||||
| 				// unwind the array as separate entries |  | ||||||
| 				for _, vv := range v.([]interface{}) { |  | ||||||
| 					kv = append(kv, keyval{k, vv}) |  | ||||||
| 				} |  | ||||||
| 			default: |  | ||||||
| 				kv = append(kv, keyval{k, v}) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// close tag with possible attributes |  | ||||||
| 		*s += ">" |  | ||||||
| 		if doIndent { |  | ||||||
| 			*s += "\n" |  | ||||||
| 		} |  | ||||||
| 		// something more complex |  | ||||||
| 		p.mapDepth++ |  | ||||||
| 		sort.Sort(elemListSeq(kv)) |  | ||||||
| 		i := 0 |  | ||||||
| 		for _, v := range kv { |  | ||||||
| 			switch v.v.(type) { |  | ||||||
| 			case []interface{}: |  | ||||||
| 			default: |  | ||||||
| 				if i == 0 && doIndent { |  | ||||||
| 					p.Indent() |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			i++ |  | ||||||
| 			if err := mapToXmlSeqIndent(doIndent, s, v.k, v.v, p); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			switch v.v.(type) { |  | ||||||
| 			case []interface{}: // handled in []interface{} case |  | ||||||
| 			default: |  | ||||||
| 				if doIndent { |  | ||||||
| 					p.Outdent() |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			i-- |  | ||||||
| 		} |  | ||||||
| 		p.mapDepth-- |  | ||||||
| 		endTag = true |  | ||||||
| 		elen = 1 // we do have some content other than attrs |  | ||||||
| 	case []interface{}: |  | ||||||
| 		for _, v := range value.([]interface{}) { |  | ||||||
| 			if doIndent { |  | ||||||
| 				p.Indent() |  | ||||||
| 			} |  | ||||||
| 			if err := mapToXmlSeqIndent(doIndent, s, key, v, p); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			if doIndent { |  | ||||||
| 				p.Outdent() |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	case nil: |  | ||||||
| 		// terminate the tag |  | ||||||
| 		if doIndent { |  | ||||||
| 			*s += p.padding |  | ||||||
| 		} |  | ||||||
| 		*s += "<" + key |  | ||||||
| 		endTag, isSimple = true, true |  | ||||||
| 		break |  | ||||||
| 	default: // handle anything - even goofy stuff |  | ||||||
| 		elen = 0 |  | ||||||
| 		switch value.(type) { |  | ||||||
| 		case string: |  | ||||||
| 			if xmlEscapeChars { |  | ||||||
| 				ss = escapeChars(value.(string)) |  | ||||||
| 			} else { |  | ||||||
| 				ss = value.(string) |  | ||||||
| 			} |  | ||||||
| 			elen = len(ss) |  | ||||||
| 			if elen > 0 { |  | ||||||
| 				*s += ">" + ss |  | ||||||
| 			} |  | ||||||
| 		case float64, bool, int, int32, int64, float32: |  | ||||||
| 			v := fmt.Sprintf("%v", value) |  | ||||||
| 			elen = len(v) |  | ||||||
| 			if elen > 0 { |  | ||||||
| 				*s += ">" + v |  | ||||||
| 			} |  | ||||||
| 		case []byte: // NOTE: byte is just an alias for uint8 |  | ||||||
| 			// similar to how xml.Marshal handles []byte structure members |  | ||||||
| 			if xmlEscapeChars { |  | ||||||
| 				ss = escapeChars(string(value.([]byte))) |  | ||||||
| 			} else { |  | ||||||
| 				ss = string(value.([]byte)) |  | ||||||
| 			} |  | ||||||
| 			elen = len(ss) |  | ||||||
| 			if elen > 0 { |  | ||||||
| 				*s += ">" + ss |  | ||||||
| 			} |  | ||||||
| 		default: |  | ||||||
| 			var v []byte |  | ||||||
| 			var err error |  | ||||||
| 			if doIndent { |  | ||||||
| 				v, err = xml.MarshalIndent(value, p.padding, p.indent) |  | ||||||
| 			} else { |  | ||||||
| 				v, err = xml.Marshal(value) |  | ||||||
| 			} |  | ||||||
| 			if err != nil { |  | ||||||
| 				*s += ">UNKNOWN" |  | ||||||
| 			} else { |  | ||||||
| 				elen = len(v) |  | ||||||
| 				if elen > 0 { |  | ||||||
| 					*s += string(v) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		isSimple = true |  | ||||||
| 		endTag = true |  | ||||||
| 	} |  | ||||||
| 	if endTag && !noEndTag { |  | ||||||
| 		if doIndent { |  | ||||||
| 			if !isSimple { |  | ||||||
| 				*s += p.padding |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		switch value.(type) { |  | ||||||
| 		case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32: |  | ||||||
| 			if elen > 0 || useGoXmlEmptyElemSyntax { |  | ||||||
| 				if elen == 0 { |  | ||||||
| 					*s += ">" |  | ||||||
| 				} |  | ||||||
| 				*s += `</` + key + ">" |  | ||||||
| 			} else { |  | ||||||
| 				*s += `/>` |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} else if !noEndTag { |  | ||||||
| 		if useGoXmlEmptyElemSyntax { |  | ||||||
| 			*s += `</` + key + ">" |  | ||||||
| 			// *s += "></" + key + ">" |  | ||||||
| 		} else { |  | ||||||
| 			*s += "/>" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if doIndent { |  | ||||||
| 		if p.cnt > p.start { |  | ||||||
| 			*s += "\n" |  | ||||||
| 		} |  | ||||||
| 		p.Outdent() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // the element sort implementation |  | ||||||
| 
 |  | ||||||
| type keyval struct { |  | ||||||
| 	k string |  | ||||||
| 	v interface{} |  | ||||||
| } |  | ||||||
| type elemListSeq []keyval |  | ||||||
| 
 |  | ||||||
| func (e elemListSeq) Len() int { |  | ||||||
| 	return len(e) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e elemListSeq) Swap(i, j int) { |  | ||||||
| 	e[i], e[j] = e[j], e[i] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e elemListSeq) Less(i, j int) bool { |  | ||||||
| 	var iseq, jseq int |  | ||||||
| 	var ok bool |  | ||||||
| 	if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok { |  | ||||||
| 		iseq = 9999999 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok { |  | ||||||
| 		jseq = 9999999 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return iseq <= jseq |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // =============== https://groups.google.com/forum/#!topic/golang-nuts/lHPOHD-8qio |  | ||||||
| 
 |  | ||||||
| // BeautifyXml (re)formats an XML doc similar to Map.XmlIndent(). |  | ||||||
| func BeautifyXml(b []byte, prefix, indent string) ([]byte, error) { |  | ||||||
| 	x, err := NewMapXmlSeq(b) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return x.XmlSeqIndent(prefix, indent) |  | ||||||
| } |  | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/dustin/go-humanize/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/dustin/go-humanize/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,21 +0,0 @@ | ||||||
| sudo: false |  | ||||||
| language: go |  | ||||||
| go: |  | ||||||
|   - 1.3.x |  | ||||||
|   - 1.5.x |  | ||||||
|   - 1.6.x |  | ||||||
|   - 1.7.x |  | ||||||
|   - 1.8.x |  | ||||||
|   - 1.9.x |  | ||||||
|   - master |  | ||||||
| matrix: |  | ||||||
|   allow_failures: |  | ||||||
|     - go: master |  | ||||||
|   fast_finish: true |  | ||||||
| install: |  | ||||||
|   - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). |  | ||||||
| script: |  | ||||||
|   - go get -t -v ./... |  | ||||||
|   - diff -u <(echo -n) <(gofmt -d -s .) |  | ||||||
|   - go tool vet . |  | ||||||
|   - go test -v -race ./... |  | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/dustin/go-humanize/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/dustin/go-humanize/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,21 +0,0 @@ | ||||||
| Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net> |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in |  | ||||||
| all copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
| 
 |  | ||||||
| <http://www.opensource.org/licenses/mit-license.php> |  | ||||||
							
								
								
									
										124
									
								
								vendor/github.com/dustin/go-humanize/README.markdown
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										124
									
								
								vendor/github.com/dustin/go-humanize/README.markdown
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,124 +0,0 @@ | ||||||
| # Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize) |  | ||||||
| 
 |  | ||||||
| Just a few functions for helping humanize times and sizes. |  | ||||||
| 
 |  | ||||||
| `go get` it as `github.com/dustin/go-humanize`, import it as |  | ||||||
| `"github.com/dustin/go-humanize"`, use it as `humanize`. |  | ||||||
| 
 |  | ||||||
| See [godoc](https://godoc.org/github.com/dustin/go-humanize) for |  | ||||||
| complete documentation. |  | ||||||
| 
 |  | ||||||
| ## Sizes |  | ||||||
| 
 |  | ||||||
| This lets you take numbers like `82854982` and convert them to useful |  | ||||||
| strings like, `83 MB` or `79 MiB` (whichever you prefer). |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Times |  | ||||||
| 
 |  | ||||||
| This lets you take a `time.Time` and spit it out in relative terms. |  | ||||||
| For example, `12 seconds ago` or `3 days from now`. |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Thanks to Kyle Lemons for the time implementation from an IRC |  | ||||||
| conversation one day. It's pretty neat. |  | ||||||
| 
 |  | ||||||
| ## Ordinals |  | ||||||
| 
 |  | ||||||
| From a [mailing list discussion][odisc] where a user wanted to be able |  | ||||||
| to label ordinals. |  | ||||||
| 
 |  | ||||||
|     0 -> 0th |  | ||||||
|     1 -> 1st |  | ||||||
|     2 -> 2nd |  | ||||||
|     3 -> 3rd |  | ||||||
|     4 -> 4th |  | ||||||
|     [...] |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Commas |  | ||||||
| 
 |  | ||||||
| Want to shove commas into numbers? Be my guest. |  | ||||||
| 
 |  | ||||||
|     0 -> 0 |  | ||||||
|     100 -> 100 |  | ||||||
|     1000 -> 1,000 |  | ||||||
|     1000000000 -> 1,000,000,000 |  | ||||||
|     -100000 -> -100,000 |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Ftoa |  | ||||||
| 
 |  | ||||||
| Nicer float64 formatter that removes trailing zeros. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| fmt.Printf("%f", 2.24)                // 2.240000 |  | ||||||
| fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 |  | ||||||
| fmt.Printf("%f", 2.0)                 // 2.000000 |  | ||||||
| fmt.Printf("%s", humanize.Ftoa(2.0))  // 2 |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## SI notation |  | ||||||
| 
 |  | ||||||
| Format numbers with [SI notation][sinotation]. |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| humanize.SI(0.00000000223, "M") // 2.23 nM |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## English-specific functions |  | ||||||
| 
 |  | ||||||
| The following functions are in the `humanize/english` subpackage. |  | ||||||
| 
 |  | ||||||
| ### Plurals |  | ||||||
| 
 |  | ||||||
| Simple English pluralization |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| english.PluralWord(1, "object", "") // object |  | ||||||
| english.PluralWord(42, "object", "") // objects |  | ||||||
| english.PluralWord(2, "bus", "") // buses |  | ||||||
| english.PluralWord(99, "locus", "loci") // loci |  | ||||||
| 
 |  | ||||||
| english.Plural(1, "object", "") // 1 object |  | ||||||
| english.Plural(42, "object", "") // 42 objects |  | ||||||
| english.Plural(2, "bus", "") // 2 buses |  | ||||||
| english.Plural(99, "locus", "loci") // 99 loci |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Word series |  | ||||||
| 
 |  | ||||||
| Format comma-separated words lists with conjuctions: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| english.WordSeries([]string{"foo"}, "and") // foo |  | ||||||
| english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar |  | ||||||
| english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz |  | ||||||
| 
 |  | ||||||
| english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| [odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion |  | ||||||
| [sinotation]: http://en.wikipedia.org/wiki/Metric_prefix |  | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/dustin/go-humanize/big.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/dustin/go-humanize/big.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,31 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"math/big" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // order of magnitude (to a max order) |  | ||||||
| func oomm(n, b *big.Int, maxmag int) (float64, int) { |  | ||||||
| 	mag := 0 |  | ||||||
| 	m := &big.Int{} |  | ||||||
| 	for n.Cmp(b) >= 0 { |  | ||||||
| 		n.DivMod(n, b, m) |  | ||||||
| 		mag++ |  | ||||||
| 		if mag == maxmag && maxmag >= 0 { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // total order of magnitude |  | ||||||
| // (same as above, but with no upper limit) |  | ||||||
| func oom(n, b *big.Int) (float64, int) { |  | ||||||
| 	mag := 0 |  | ||||||
| 	m := &big.Int{} |  | ||||||
| 	for n.Cmp(b) >= 0 { |  | ||||||
| 		n.DivMod(n, b, m) |  | ||||||
| 		mag++ |  | ||||||
| 	} |  | ||||||
| 	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag |  | ||||||
| } |  | ||||||
							
								
								
									
										173
									
								
								vendor/github.com/dustin/go-humanize/bigbytes.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										173
									
								
								vendor/github.com/dustin/go-humanize/bigbytes.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,173 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"math/big" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	bigIECExp = big.NewInt(1024) |  | ||||||
| 
 |  | ||||||
| 	// BigByte is one byte in bit.Ints |  | ||||||
| 	BigByte = big.NewInt(1) |  | ||||||
| 	// BigKiByte is 1,024 bytes in bit.Ints |  | ||||||
| 	BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) |  | ||||||
| 	// BigMiByte is 1,024 k bytes in bit.Ints |  | ||||||
| 	BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) |  | ||||||
| 	// BigGiByte is 1,024 m bytes in bit.Ints |  | ||||||
| 	BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) |  | ||||||
| 	// BigTiByte is 1,024 g bytes in bit.Ints |  | ||||||
| 	BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) |  | ||||||
| 	// BigPiByte is 1,024 t bytes in bit.Ints |  | ||||||
| 	BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) |  | ||||||
| 	// BigEiByte is 1,024 p bytes in bit.Ints |  | ||||||
| 	BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) |  | ||||||
| 	// BigZiByte is 1,024 e bytes in bit.Ints |  | ||||||
| 	BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) |  | ||||||
| 	// BigYiByte is 1,024 z bytes in bit.Ints |  | ||||||
| 	BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	bigSIExp = big.NewInt(1000) |  | ||||||
| 
 |  | ||||||
| 	// BigSIByte is one SI byte in big.Ints |  | ||||||
| 	BigSIByte = big.NewInt(1) |  | ||||||
| 	// BigKByte is 1,000 SI bytes in big.Ints |  | ||||||
| 	BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) |  | ||||||
| 	// BigMByte is 1,000 SI k bytes in big.Ints |  | ||||||
| 	BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) |  | ||||||
| 	// BigGByte is 1,000 SI m bytes in big.Ints |  | ||||||
| 	BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) |  | ||||||
| 	// BigTByte is 1,000 SI g bytes in big.Ints |  | ||||||
| 	BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) |  | ||||||
| 	// BigPByte is 1,000 SI t bytes in big.Ints |  | ||||||
| 	BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) |  | ||||||
| 	// BigEByte is 1,000 SI p bytes in big.Ints |  | ||||||
| 	BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) |  | ||||||
| 	// BigZByte is 1,000 SI e bytes in big.Ints |  | ||||||
| 	BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) |  | ||||||
| 	// BigYByte is 1,000 SI z bytes in big.Ints |  | ||||||
| 	BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var bigBytesSizeTable = map[string]*big.Int{ |  | ||||||
| 	"b":   BigByte, |  | ||||||
| 	"kib": BigKiByte, |  | ||||||
| 	"kb":  BigKByte, |  | ||||||
| 	"mib": BigMiByte, |  | ||||||
| 	"mb":  BigMByte, |  | ||||||
| 	"gib": BigGiByte, |  | ||||||
| 	"gb":  BigGByte, |  | ||||||
| 	"tib": BigTiByte, |  | ||||||
| 	"tb":  BigTByte, |  | ||||||
| 	"pib": BigPiByte, |  | ||||||
| 	"pb":  BigPByte, |  | ||||||
| 	"eib": BigEiByte, |  | ||||||
| 	"eb":  BigEByte, |  | ||||||
| 	"zib": BigZiByte, |  | ||||||
| 	"zb":  BigZByte, |  | ||||||
| 	"yib": BigYiByte, |  | ||||||
| 	"yb":  BigYByte, |  | ||||||
| 	// Without suffix |  | ||||||
| 	"":   BigByte, |  | ||||||
| 	"ki": BigKiByte, |  | ||||||
| 	"k":  BigKByte, |  | ||||||
| 	"mi": BigMiByte, |  | ||||||
| 	"m":  BigMByte, |  | ||||||
| 	"gi": BigGiByte, |  | ||||||
| 	"g":  BigGByte, |  | ||||||
| 	"ti": BigTiByte, |  | ||||||
| 	"t":  BigTByte, |  | ||||||
| 	"pi": BigPiByte, |  | ||||||
| 	"p":  BigPByte, |  | ||||||
| 	"ei": BigEiByte, |  | ||||||
| 	"e":  BigEByte, |  | ||||||
| 	"z":  BigZByte, |  | ||||||
| 	"zi": BigZiByte, |  | ||||||
| 	"y":  BigYByte, |  | ||||||
| 	"yi": BigYiByte, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ten = big.NewInt(10) |  | ||||||
| 
 |  | ||||||
| func humanateBigBytes(s, base *big.Int, sizes []string) string { |  | ||||||
| 	if s.Cmp(ten) < 0 { |  | ||||||
| 		return fmt.Sprintf("%d B", s) |  | ||||||
| 	} |  | ||||||
| 	c := (&big.Int{}).Set(s) |  | ||||||
| 	val, mag := oomm(c, base, len(sizes)-1) |  | ||||||
| 	suffix := sizes[mag] |  | ||||||
| 	f := "%.0f %s" |  | ||||||
| 	if val < 10 { |  | ||||||
| 		f = "%.1f %s" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fmt.Sprintf(f, val, suffix) |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // BigBytes produces a human readable representation of an SI size. |  | ||||||
| // |  | ||||||
| // See also: ParseBigBytes. |  | ||||||
| // |  | ||||||
| // BigBytes(82854982) -> 83 MB |  | ||||||
| func BigBytes(s *big.Int) string { |  | ||||||
| 	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} |  | ||||||
| 	return humanateBigBytes(s, bigSIExp, sizes) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // BigIBytes produces a human readable representation of an IEC size. |  | ||||||
| // |  | ||||||
| // See also: ParseBigBytes. |  | ||||||
| // |  | ||||||
| // BigIBytes(82854982) -> 79 MiB |  | ||||||
| func BigIBytes(s *big.Int) string { |  | ||||||
| 	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} |  | ||||||
| 	return humanateBigBytes(s, bigIECExp, sizes) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseBigBytes parses a string representation of bytes into the number |  | ||||||
| // of bytes it represents. |  | ||||||
| // |  | ||||||
| // See also: BigBytes, BigIBytes. |  | ||||||
| // |  | ||||||
| // ParseBigBytes("42 MB") -> 42000000, nil |  | ||||||
| // ParseBigBytes("42 mib") -> 44040192, nil |  | ||||||
| func ParseBigBytes(s string) (*big.Int, error) { |  | ||||||
| 	lastDigit := 0 |  | ||||||
| 	hasComma := false |  | ||||||
| 	for _, r := range s { |  | ||||||
| 		if !(unicode.IsDigit(r) || r == '.' || r == ',') { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		if r == ',' { |  | ||||||
| 			hasComma = true |  | ||||||
| 		} |  | ||||||
| 		lastDigit++ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	num := s[:lastDigit] |  | ||||||
| 	if hasComma { |  | ||||||
| 		num = strings.Replace(num, ",", "", -1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	val := &big.Rat{} |  | ||||||
| 	_, err := fmt.Sscanf(num, "%f", val) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) |  | ||||||
| 	if m, ok := bigBytesSizeTable[extra]; ok { |  | ||||||
| 		mv := (&big.Rat{}).SetInt(m) |  | ||||||
| 		val.Mul(val, mv) |  | ||||||
| 		rv := &big.Int{} |  | ||||||
| 		rv.Div(val.Num(), val.Denom()) |  | ||||||
| 		return rv, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil, fmt.Errorf("unhandled size name: %v", extra) |  | ||||||
| } |  | ||||||
							
								
								
									
										143
									
								
								vendor/github.com/dustin/go-humanize/bytes.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										143
									
								
								vendor/github.com/dustin/go-humanize/bytes.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,143 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"math" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // IEC Sizes. |  | ||||||
| // kibis of bits |  | ||||||
| const ( |  | ||||||
| 	Byte = 1 << (iota * 10) |  | ||||||
| 	KiByte |  | ||||||
| 	MiByte |  | ||||||
| 	GiByte |  | ||||||
| 	TiByte |  | ||||||
| 	PiByte |  | ||||||
| 	EiByte |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // SI Sizes. |  | ||||||
| const ( |  | ||||||
| 	IByte = 1 |  | ||||||
| 	KByte = IByte * 1000 |  | ||||||
| 	MByte = KByte * 1000 |  | ||||||
| 	GByte = MByte * 1000 |  | ||||||
| 	TByte = GByte * 1000 |  | ||||||
| 	PByte = TByte * 1000 |  | ||||||
| 	EByte = PByte * 1000 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var bytesSizeTable = map[string]uint64{ |  | ||||||
| 	"b":   Byte, |  | ||||||
| 	"kib": KiByte, |  | ||||||
| 	"kb":  KByte, |  | ||||||
| 	"mib": MiByte, |  | ||||||
| 	"mb":  MByte, |  | ||||||
| 	"gib": GiByte, |  | ||||||
| 	"gb":  GByte, |  | ||||||
| 	"tib": TiByte, |  | ||||||
| 	"tb":  TByte, |  | ||||||
| 	"pib": PiByte, |  | ||||||
| 	"pb":  PByte, |  | ||||||
| 	"eib": EiByte, |  | ||||||
| 	"eb":  EByte, |  | ||||||
| 	// Without suffix |  | ||||||
| 	"":   Byte, |  | ||||||
| 	"ki": KiByte, |  | ||||||
| 	"k":  KByte, |  | ||||||
| 	"mi": MiByte, |  | ||||||
| 	"m":  MByte, |  | ||||||
| 	"gi": GiByte, |  | ||||||
| 	"g":  GByte, |  | ||||||
| 	"ti": TiByte, |  | ||||||
| 	"t":  TByte, |  | ||||||
| 	"pi": PiByte, |  | ||||||
| 	"p":  PByte, |  | ||||||
| 	"ei": EiByte, |  | ||||||
| 	"e":  EByte, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func logn(n, b float64) float64 { |  | ||||||
| 	return math.Log(n) / math.Log(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func humanateBytes(s uint64, base float64, sizes []string) string { |  | ||||||
| 	if s < 10 { |  | ||||||
| 		return fmt.Sprintf("%d B", s) |  | ||||||
| 	} |  | ||||||
| 	e := math.Floor(logn(float64(s), base)) |  | ||||||
| 	suffix := sizes[int(e)] |  | ||||||
| 	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 |  | ||||||
| 	f := "%.0f %s" |  | ||||||
| 	if val < 10 { |  | ||||||
| 		f = "%.1f %s" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fmt.Sprintf(f, val, suffix) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Bytes produces a human readable representation of an SI size. |  | ||||||
| // |  | ||||||
| // See also: ParseBytes. |  | ||||||
| // |  | ||||||
| // Bytes(82854982) -> 83 MB |  | ||||||
| func Bytes(s uint64) string { |  | ||||||
| 	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} |  | ||||||
| 	return humanateBytes(s, 1000, sizes) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // IBytes produces a human readable representation of an IEC size. |  | ||||||
| // |  | ||||||
| // See also: ParseBytes. |  | ||||||
| // |  | ||||||
| // IBytes(82854982) -> 79 MiB |  | ||||||
| func IBytes(s uint64) string { |  | ||||||
| 	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} |  | ||||||
| 	return humanateBytes(s, 1024, sizes) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseBytes parses a string representation of bytes into the number |  | ||||||
| // of bytes it represents. |  | ||||||
| // |  | ||||||
| // See Also: Bytes, IBytes. |  | ||||||
| // |  | ||||||
| // ParseBytes("42 MB") -> 42000000, nil |  | ||||||
| // ParseBytes("42 mib") -> 44040192, nil |  | ||||||
| func ParseBytes(s string) (uint64, error) { |  | ||||||
| 	lastDigit := 0 |  | ||||||
| 	hasComma := false |  | ||||||
| 	for _, r := range s { |  | ||||||
| 		if !(unicode.IsDigit(r) || r == '.' || r == ',') { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		if r == ',' { |  | ||||||
| 			hasComma = true |  | ||||||
| 		} |  | ||||||
| 		lastDigit++ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	num := s[:lastDigit] |  | ||||||
| 	if hasComma { |  | ||||||
| 		num = strings.Replace(num, ",", "", -1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	f, err := strconv.ParseFloat(num, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) |  | ||||||
| 	if m, ok := bytesSizeTable[extra]; ok { |  | ||||||
| 		f *= float64(m) |  | ||||||
| 		if f >= math.MaxUint64 { |  | ||||||
| 			return 0, fmt.Errorf("too large: %v", s) |  | ||||||
| 		} |  | ||||||
| 		return uint64(f), nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return 0, fmt.Errorf("unhandled size name: %v", extra) |  | ||||||
| } |  | ||||||
							
								
								
									
										116
									
								
								vendor/github.com/dustin/go-humanize/comma.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										116
									
								
								vendor/github.com/dustin/go-humanize/comma.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,116 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"math" |  | ||||||
| 	"math/big" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Comma produces a string form of the given number in base 10 with |  | ||||||
| // commas after every three orders of magnitude. |  | ||||||
| // |  | ||||||
| // e.g. Comma(834142) -> 834,142 |  | ||||||
| func Comma(v int64) string { |  | ||||||
| 	sign := "" |  | ||||||
| 
 |  | ||||||
| 	// Min int64 can't be negated to a usable value, so it has to be special cased. |  | ||||||
| 	if v == math.MinInt64 { |  | ||||||
| 		return "-9,223,372,036,854,775,808" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if v < 0 { |  | ||||||
| 		sign = "-" |  | ||||||
| 		v = 0 - v |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	parts := []string{"", "", "", "", "", "", ""} |  | ||||||
| 	j := len(parts) - 1 |  | ||||||
| 
 |  | ||||||
| 	for v > 999 { |  | ||||||
| 		parts[j] = strconv.FormatInt(v%1000, 10) |  | ||||||
| 		switch len(parts[j]) { |  | ||||||
| 		case 2: |  | ||||||
| 			parts[j] = "0" + parts[j] |  | ||||||
| 		case 1: |  | ||||||
| 			parts[j] = "00" + parts[j] |  | ||||||
| 		} |  | ||||||
| 		v = v / 1000 |  | ||||||
| 		j-- |  | ||||||
| 	} |  | ||||||
| 	parts[j] = strconv.Itoa(int(v)) |  | ||||||
| 	return sign + strings.Join(parts[j:], ",") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Commaf produces a string form of the given number in base 10 with |  | ||||||
| // commas after every three orders of magnitude. |  | ||||||
| // |  | ||||||
| // e.g. Commaf(834142.32) -> 834,142.32 |  | ||||||
| func Commaf(v float64) string { |  | ||||||
| 	buf := &bytes.Buffer{} |  | ||||||
| 	if v < 0 { |  | ||||||
| 		buf.Write([]byte{'-'}) |  | ||||||
| 		v = 0 - v |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	comma := []byte{','} |  | ||||||
| 
 |  | ||||||
| 	parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") |  | ||||||
| 	pos := 0 |  | ||||||
| 	if len(parts[0])%3 != 0 { |  | ||||||
| 		pos += len(parts[0]) % 3 |  | ||||||
| 		buf.WriteString(parts[0][:pos]) |  | ||||||
| 		buf.Write(comma) |  | ||||||
| 	} |  | ||||||
| 	for ; pos < len(parts[0]); pos += 3 { |  | ||||||
| 		buf.WriteString(parts[0][pos : pos+3]) |  | ||||||
| 		buf.Write(comma) |  | ||||||
| 	} |  | ||||||
| 	buf.Truncate(buf.Len() - 1) |  | ||||||
| 
 |  | ||||||
| 	if len(parts) > 1 { |  | ||||||
| 		buf.Write([]byte{'.'}) |  | ||||||
| 		buf.WriteString(parts[1]) |  | ||||||
| 	} |  | ||||||
| 	return buf.String() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // CommafWithDigits works like the Commaf but limits the resulting |  | ||||||
| // string to the given number of decimal places. |  | ||||||
| // |  | ||||||
| // e.g. CommafWithDigits(834142.32, 1) -> 834,142.3 |  | ||||||
| func CommafWithDigits(f float64, decimals int) string { |  | ||||||
| 	return stripTrailingDigits(Commaf(f), decimals) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // BigComma produces a string form of the given big.Int in base 10 |  | ||||||
| // with commas after every three orders of magnitude. |  | ||||||
| func BigComma(b *big.Int) string { |  | ||||||
| 	sign := "" |  | ||||||
| 	if b.Sign() < 0 { |  | ||||||
| 		sign = "-" |  | ||||||
| 		b.Abs(b) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	athousand := big.NewInt(1000) |  | ||||||
| 	c := (&big.Int{}).Set(b) |  | ||||||
| 	_, m := oom(c, athousand) |  | ||||||
| 	parts := make([]string, m+1) |  | ||||||
| 	j := len(parts) - 1 |  | ||||||
| 
 |  | ||||||
| 	mod := &big.Int{} |  | ||||||
| 	for b.Cmp(athousand) >= 0 { |  | ||||||
| 		b.DivMod(b, athousand, mod) |  | ||||||
| 		parts[j] = strconv.FormatInt(mod.Int64(), 10) |  | ||||||
| 		switch len(parts[j]) { |  | ||||||
| 		case 2: |  | ||||||
| 			parts[j] = "0" + parts[j] |  | ||||||
| 		case 1: |  | ||||||
| 			parts[j] = "00" + parts[j] |  | ||||||
| 		} |  | ||||||
| 		j-- |  | ||||||
| 	} |  | ||||||
| 	parts[j] = strconv.Itoa(int(b.Int64())) |  | ||||||
| 	return sign + strings.Join(parts[j:], ",") |  | ||||||
| } |  | ||||||
							
								
								
									
										40
									
								
								vendor/github.com/dustin/go-humanize/commaf.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/dustin/go-humanize/commaf.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,40 +0,0 @@ | ||||||
| // +build go1.6 |  | ||||||
| 
 |  | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"math/big" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // BigCommaf produces a string form of the given big.Float in base 10 |  | ||||||
| // with commas after every three orders of magnitude. |  | ||||||
| func BigCommaf(v *big.Float) string { |  | ||||||
| 	buf := &bytes.Buffer{} |  | ||||||
| 	if v.Sign() < 0 { |  | ||||||
| 		buf.Write([]byte{'-'}) |  | ||||||
| 		v.Abs(v) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	comma := []byte{','} |  | ||||||
| 
 |  | ||||||
| 	parts := strings.Split(v.Text('f', -1), ".") |  | ||||||
| 	pos := 0 |  | ||||||
| 	if len(parts[0])%3 != 0 { |  | ||||||
| 		pos += len(parts[0]) % 3 |  | ||||||
| 		buf.WriteString(parts[0][:pos]) |  | ||||||
| 		buf.Write(comma) |  | ||||||
| 	} |  | ||||||
| 	for ; pos < len(parts[0]); pos += 3 { |  | ||||||
| 		buf.WriteString(parts[0][pos : pos+3]) |  | ||||||
| 		buf.Write(comma) |  | ||||||
| 	} |  | ||||||
| 	buf.Truncate(buf.Len() - 1) |  | ||||||
| 
 |  | ||||||
| 	if len(parts) > 1 { |  | ||||||
| 		buf.Write([]byte{'.'}) |  | ||||||
| 		buf.WriteString(parts[1]) |  | ||||||
| 	} |  | ||||||
| 	return buf.String() |  | ||||||
| } |  | ||||||
							
								
								
									
										46
									
								
								vendor/github.com/dustin/go-humanize/ftoa.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/dustin/go-humanize/ftoa.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,46 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func stripTrailingZeros(s string) string { |  | ||||||
| 	offset := len(s) - 1 |  | ||||||
| 	for offset > 0 { |  | ||||||
| 		if s[offset] == '.' { |  | ||||||
| 			offset-- |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		if s[offset] != '0' { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		offset-- |  | ||||||
| 	} |  | ||||||
| 	return s[:offset+1] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func stripTrailingDigits(s string, digits int) string { |  | ||||||
| 	if i := strings.Index(s, "."); i >= 0 { |  | ||||||
| 		if digits <= 0 { |  | ||||||
| 			return s[:i] |  | ||||||
| 		} |  | ||||||
| 		i++ |  | ||||||
| 		if i+digits >= len(s) { |  | ||||||
| 			return s |  | ||||||
| 		} |  | ||||||
| 		return s[:i+digits] |  | ||||||
| 	} |  | ||||||
| 	return s |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Ftoa converts a float to a string with no trailing zeros. |  | ||||||
| func Ftoa(num float64) string { |  | ||||||
| 	return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FtoaWithDigits converts a float to a string but limits the resulting string |  | ||||||
| // to the given number of decimal places, and no trailing zeros. |  | ||||||
| func FtoaWithDigits(num float64, digits int) string { |  | ||||||
| 	return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits)) |  | ||||||
| } |  | ||||||
							
								
								
									
										8
									
								
								vendor/github.com/dustin/go-humanize/humanize.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/dustin/go-humanize/humanize.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,8 +0,0 @@ | ||||||
| /* |  | ||||||
| Package humanize converts boring ugly numbers to human-friendly strings and back. |  | ||||||
| 
 |  | ||||||
| Durations can be turned into strings such as "3 days ago", numbers |  | ||||||
| representing sizes like 82854982 into useful strings like, "83 MB" or |  | ||||||
| "79 MiB" (whichever you prefer). |  | ||||||
| */ |  | ||||||
| package humanize |  | ||||||
							
								
								
									
										192
									
								
								vendor/github.com/dustin/go-humanize/number.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										192
									
								
								vendor/github.com/dustin/go-humanize/number.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,192 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| Slightly adapted from the source to fit go-humanize. |  | ||||||
| 
 |  | ||||||
| Author: https://github.com/gorhill |  | ||||||
| Source: https://gist.github.com/gorhill/5285193 |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"math" |  | ||||||
| 	"strconv" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	renderFloatPrecisionMultipliers = [...]float64{ |  | ||||||
| 		1, |  | ||||||
| 		10, |  | ||||||
| 		100, |  | ||||||
| 		1000, |  | ||||||
| 		10000, |  | ||||||
| 		100000, |  | ||||||
| 		1000000, |  | ||||||
| 		10000000, |  | ||||||
| 		100000000, |  | ||||||
| 		1000000000, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	renderFloatPrecisionRounders = [...]float64{ |  | ||||||
| 		0.5, |  | ||||||
| 		0.05, |  | ||||||
| 		0.005, |  | ||||||
| 		0.0005, |  | ||||||
| 		0.00005, |  | ||||||
| 		0.000005, |  | ||||||
| 		0.0000005, |  | ||||||
| 		0.00000005, |  | ||||||
| 		0.000000005, |  | ||||||
| 		0.0000000005, |  | ||||||
| 	} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // FormatFloat produces a formatted number as string based on the following user-specified criteria: |  | ||||||
| // * thousands separator |  | ||||||
| // * decimal separator |  | ||||||
| // * decimal precision |  | ||||||
| // |  | ||||||
| // Usage: s := RenderFloat(format, n) |  | ||||||
| // The format parameter tells how to render the number n. |  | ||||||
| // |  | ||||||
| // See examples: http://play.golang.org/p/LXc1Ddm1lJ |  | ||||||
| // |  | ||||||
| // Examples of format strings, given n = 12345.6789: |  | ||||||
| // "#,###.##" => "12,345.67" |  | ||||||
| // "#,###." => "12,345" |  | ||||||
| // "#,###" => "12345,678" |  | ||||||
| // "#\u202F###,##" => "12 345,68" |  | ||||||
| // "#.###,###### => 12.345,678900 |  | ||||||
| // "" (aka default format) => 12,345.67 |  | ||||||
| // |  | ||||||
| // The highest precision allowed is 9 digits after the decimal symbol. |  | ||||||
| // There is also a version for integer number, FormatInteger(), |  | ||||||
| // which is convenient for calls within template. |  | ||||||
| func FormatFloat(format string, n float64) string { |  | ||||||
| 	// Special cases: |  | ||||||
| 	//   NaN = "NaN" |  | ||||||
| 	//   +Inf = "+Infinity" |  | ||||||
| 	//   -Inf = "-Infinity" |  | ||||||
| 	if math.IsNaN(n) { |  | ||||||
| 		return "NaN" |  | ||||||
| 	} |  | ||||||
| 	if n > math.MaxFloat64 { |  | ||||||
| 		return "Infinity" |  | ||||||
| 	} |  | ||||||
| 	if n < -math.MaxFloat64 { |  | ||||||
| 		return "-Infinity" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// default format |  | ||||||
| 	precision := 2 |  | ||||||
| 	decimalStr := "." |  | ||||||
| 	thousandStr := "," |  | ||||||
| 	positiveStr := "" |  | ||||||
| 	negativeStr := "-" |  | ||||||
| 
 |  | ||||||
| 	if len(format) > 0 { |  | ||||||
| 		format := []rune(format) |  | ||||||
| 
 |  | ||||||
| 		// If there is an explicit format directive, |  | ||||||
| 		// then default values are these: |  | ||||||
| 		precision = 9 |  | ||||||
| 		thousandStr = "" |  | ||||||
| 
 |  | ||||||
| 		// collect indices of meaningful formatting directives |  | ||||||
| 		formatIndx := []int{} |  | ||||||
| 		for i, char := range format { |  | ||||||
| 			if char != '#' && char != '0' { |  | ||||||
| 				formatIndx = append(formatIndx, i) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if len(formatIndx) > 0 { |  | ||||||
| 			// Directive at index 0: |  | ||||||
| 			//   Must be a '+' |  | ||||||
| 			//   Raise an error if not the case |  | ||||||
| 			// index: 0123456789 |  | ||||||
| 			//        +0.000,000 |  | ||||||
| 			//        +000,000.0 |  | ||||||
| 			//        +0000.00 |  | ||||||
| 			//        +0000 |  | ||||||
| 			if formatIndx[0] == 0 { |  | ||||||
| 				if format[formatIndx[0]] != '+' { |  | ||||||
| 					panic("RenderFloat(): invalid positive sign directive") |  | ||||||
| 				} |  | ||||||
| 				positiveStr = "+" |  | ||||||
| 				formatIndx = formatIndx[1:] |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// Two directives: |  | ||||||
| 			//   First is thousands separator |  | ||||||
| 			//   Raise an error if not followed by 3-digit |  | ||||||
| 			// 0123456789 |  | ||||||
| 			// 0.000,000 |  | ||||||
| 			// 000,000.00 |  | ||||||
| 			if len(formatIndx) == 2 { |  | ||||||
| 				if (formatIndx[1] - formatIndx[0]) != 4 { |  | ||||||
| 					panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") |  | ||||||
| 				} |  | ||||||
| 				thousandStr = string(format[formatIndx[0]]) |  | ||||||
| 				formatIndx = formatIndx[1:] |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// One directive: |  | ||||||
| 			//   Directive is decimal separator |  | ||||||
| 			//   The number of digit-specifier following the separator indicates wanted precision |  | ||||||
| 			// 0123456789 |  | ||||||
| 			// 0.00 |  | ||||||
| 			// 000,0000 |  | ||||||
| 			if len(formatIndx) == 1 { |  | ||||||
| 				decimalStr = string(format[formatIndx[0]]) |  | ||||||
| 				precision = len(format) - formatIndx[0] - 1 |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// generate sign part |  | ||||||
| 	var signStr string |  | ||||||
| 	if n >= 0.000000001 { |  | ||||||
| 		signStr = positiveStr |  | ||||||
| 	} else if n <= -0.000000001 { |  | ||||||
| 		signStr = negativeStr |  | ||||||
| 		n = -n |  | ||||||
| 	} else { |  | ||||||
| 		signStr = "" |  | ||||||
| 		n = 0.0 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// split number into integer and fractional parts |  | ||||||
| 	intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) |  | ||||||
| 
 |  | ||||||
| 	// generate integer part string |  | ||||||
| 	intStr := strconv.FormatInt(int64(intf), 10) |  | ||||||
| 
 |  | ||||||
| 	// add thousand separator if required |  | ||||||
| 	if len(thousandStr) > 0 { |  | ||||||
| 		for i := len(intStr); i > 3; { |  | ||||||
| 			i -= 3 |  | ||||||
| 			intStr = intStr[:i] + thousandStr + intStr[i:] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// no fractional part, we can leave now |  | ||||||
| 	if precision == 0 { |  | ||||||
| 		return signStr + intStr |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// generate fractional part |  | ||||||
| 	fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) |  | ||||||
| 	// may need padding |  | ||||||
| 	if len(fracStr) < precision { |  | ||||||
| 		fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return signStr + intStr + decimalStr + fracStr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FormatInteger produces a formatted number as string. |  | ||||||
| // See FormatFloat. |  | ||||||
| func FormatInteger(format string, n int) string { |  | ||||||
| 	return FormatFloat(format, float64(n)) |  | ||||||
| } |  | ||||||
							
								
								
									
										25
									
								
								vendor/github.com/dustin/go-humanize/ordinals.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/dustin/go-humanize/ordinals.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,25 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import "strconv" |  | ||||||
| 
 |  | ||||||
| // Ordinal gives you the input number in a rank/ordinal format. |  | ||||||
| // |  | ||||||
| // Ordinal(3) -> 3rd |  | ||||||
| func Ordinal(x int) string { |  | ||||||
| 	suffix := "th" |  | ||||||
| 	switch x % 10 { |  | ||||||
| 	case 1: |  | ||||||
| 		if x%100 != 11 { |  | ||||||
| 			suffix = "st" |  | ||||||
| 		} |  | ||||||
| 	case 2: |  | ||||||
| 		if x%100 != 12 { |  | ||||||
| 			suffix = "nd" |  | ||||||
| 		} |  | ||||||
| 	case 3: |  | ||||||
| 		if x%100 != 13 { |  | ||||||
| 			suffix = "rd" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return strconv.Itoa(x) + suffix |  | ||||||
| } |  | ||||||
							
								
								
									
										123
									
								
								vendor/github.com/dustin/go-humanize/si.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/dustin/go-humanize/si.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,123 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"math" |  | ||||||
| 	"regexp" |  | ||||||
| 	"strconv" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var siPrefixTable = map[float64]string{ |  | ||||||
| 	-24: "y", // yocto |  | ||||||
| 	-21: "z", // zepto |  | ||||||
| 	-18: "a", // atto |  | ||||||
| 	-15: "f", // femto |  | ||||||
| 	-12: "p", // pico |  | ||||||
| 	-9:  "n", // nano |  | ||||||
| 	-6:  "µ", // micro |  | ||||||
| 	-3:  "m", // milli |  | ||||||
| 	0:   "", |  | ||||||
| 	3:   "k", // kilo |  | ||||||
| 	6:   "M", // mega |  | ||||||
| 	9:   "G", // giga |  | ||||||
| 	12:  "T", // tera |  | ||||||
| 	15:  "P", // peta |  | ||||||
| 	18:  "E", // exa |  | ||||||
| 	21:  "Z", // zetta |  | ||||||
| 	24:  "Y", // yotta |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var revSIPrefixTable = revfmap(siPrefixTable) |  | ||||||
| 
 |  | ||||||
| // revfmap reverses the map and precomputes the power multiplier |  | ||||||
| func revfmap(in map[float64]string) map[string]float64 { |  | ||||||
| 	rv := map[string]float64{} |  | ||||||
| 	for k, v := range in { |  | ||||||
| 		rv[v] = math.Pow(10, k) |  | ||||||
| 	} |  | ||||||
| 	return rv |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var riParseRegex *regexp.Regexp |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	ri := `^([\-0-9.]+)\s?([` |  | ||||||
| 	for _, v := range siPrefixTable { |  | ||||||
| 		ri += v |  | ||||||
| 	} |  | ||||||
| 	ri += `]?)(.*)` |  | ||||||
| 
 |  | ||||||
| 	riParseRegex = regexp.MustCompile(ri) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ComputeSI finds the most appropriate SI prefix for the given number |  | ||||||
| // and returns the prefix along with the value adjusted to be within |  | ||||||
| // that prefix. |  | ||||||
| // |  | ||||||
| // See also: SI, ParseSI. |  | ||||||
| // |  | ||||||
| // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") |  | ||||||
| func ComputeSI(input float64) (float64, string) { |  | ||||||
| 	if input == 0 { |  | ||||||
| 		return 0, "" |  | ||||||
| 	} |  | ||||||
| 	mag := math.Abs(input) |  | ||||||
| 	exponent := math.Floor(logn(mag, 10)) |  | ||||||
| 	exponent = math.Floor(exponent/3) * 3 |  | ||||||
| 
 |  | ||||||
| 	value := mag / math.Pow(10, exponent) |  | ||||||
| 
 |  | ||||||
| 	// Handle special case where value is exactly 1000.0 |  | ||||||
| 	// Should return 1 M instead of 1000 k |  | ||||||
| 	if value == 1000.0 { |  | ||||||
| 		exponent += 3 |  | ||||||
| 		value = mag / math.Pow(10, exponent) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	value = math.Copysign(value, input) |  | ||||||
| 
 |  | ||||||
| 	prefix := siPrefixTable[exponent] |  | ||||||
| 	return value, prefix |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SI returns a string with default formatting. |  | ||||||
| // |  | ||||||
| // SI uses Ftoa to format float value, removing trailing zeros. |  | ||||||
| // |  | ||||||
| // See also: ComputeSI, ParseSI. |  | ||||||
| // |  | ||||||
| // e.g. SI(1000000, "B") -> 1 MB |  | ||||||
| // e.g. SI(2.2345e-12, "F") -> 2.2345 pF |  | ||||||
| func SI(input float64, unit string) string { |  | ||||||
| 	value, prefix := ComputeSI(input) |  | ||||||
| 	return Ftoa(value) + " " + prefix + unit |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SIWithDigits works like SI but limits the resulting string to the |  | ||||||
| // given number of decimal places. |  | ||||||
| // |  | ||||||
| // e.g. SIWithDigits(1000000, 0, "B") -> 1 MB |  | ||||||
| // e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF |  | ||||||
| func SIWithDigits(input float64, decimals int, unit string) string { |  | ||||||
| 	value, prefix := ComputeSI(input) |  | ||||||
| 	return FtoaWithDigits(value, decimals) + " " + prefix + unit |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var errInvalid = errors.New("invalid input") |  | ||||||
| 
 |  | ||||||
| // ParseSI parses an SI string back into the number and unit. |  | ||||||
| // |  | ||||||
| // See also: SI, ComputeSI. |  | ||||||
| // |  | ||||||
| // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) |  | ||||||
| func ParseSI(input string) (float64, string, error) { |  | ||||||
| 	found := riParseRegex.FindStringSubmatch(input) |  | ||||||
| 	if len(found) != 4 { |  | ||||||
| 		return 0, "", errInvalid |  | ||||||
| 	} |  | ||||||
| 	mag := revSIPrefixTable[found[2]] |  | ||||||
| 	unit := found[3] |  | ||||||
| 
 |  | ||||||
| 	base, err := strconv.ParseFloat(found[1], 64) |  | ||||||
| 	return base * mag, unit, err |  | ||||||
| } |  | ||||||
							
								
								
									
										117
									
								
								vendor/github.com/dustin/go-humanize/times.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/dustin/go-humanize/times.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,117 +0,0 @@ | ||||||
| package humanize |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"math" |  | ||||||
| 	"sort" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Seconds-based time units |  | ||||||
| const ( |  | ||||||
| 	Day      = 24 * time.Hour |  | ||||||
| 	Week     = 7 * Day |  | ||||||
| 	Month    = 30 * Day |  | ||||||
| 	Year     = 12 * Month |  | ||||||
| 	LongTime = 37 * Year |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Time formats a time into a relative string. |  | ||||||
| // |  | ||||||
| // Time(someT) -> "3 weeks ago" |  | ||||||
| func Time(then time.Time) string { |  | ||||||
| 	return RelTime(then, time.Now(), "ago", "from now") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A RelTimeMagnitude struct contains a relative time point at which |  | ||||||
| // the relative format of time will switch to a new format string.  A |  | ||||||
| // slice of these in ascending order by their "D" field is passed to |  | ||||||
| // CustomRelTime to format durations. |  | ||||||
| // |  | ||||||
| // The Format field is a string that may contain a "%s" which will be |  | ||||||
| // replaced with the appropriate signed label (e.g. "ago" or "from |  | ||||||
| // now") and a "%d" that will be replaced by the quantity. |  | ||||||
| // |  | ||||||
| // The DivBy field is the amount of time the time difference must be |  | ||||||
| // divided by in order to display correctly. |  | ||||||
| // |  | ||||||
| // e.g. if D is 2*time.Minute and you want to display "%d minutes %s" |  | ||||||
| // DivBy should be time.Minute so whatever the duration is will be |  | ||||||
| // expressed in minutes. |  | ||||||
| type RelTimeMagnitude struct { |  | ||||||
| 	D      time.Duration |  | ||||||
| 	Format string |  | ||||||
| 	DivBy  time.Duration |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var defaultMagnitudes = []RelTimeMagnitude{ |  | ||||||
| 	{time.Second, "now", time.Second}, |  | ||||||
| 	{2 * time.Second, "1 second %s", 1}, |  | ||||||
| 	{time.Minute, "%d seconds %s", time.Second}, |  | ||||||
| 	{2 * time.Minute, "1 minute %s", 1}, |  | ||||||
| 	{time.Hour, "%d minutes %s", time.Minute}, |  | ||||||
| 	{2 * time.Hour, "1 hour %s", 1}, |  | ||||||
| 	{Day, "%d hours %s", time.Hour}, |  | ||||||
| 	{2 * Day, "1 day %s", 1}, |  | ||||||
| 	{Week, "%d days %s", Day}, |  | ||||||
| 	{2 * Week, "1 week %s", 1}, |  | ||||||
| 	{Month, "%d weeks %s", Week}, |  | ||||||
| 	{2 * Month, "1 month %s", 1}, |  | ||||||
| 	{Year, "%d months %s", Month}, |  | ||||||
| 	{18 * Month, "1 year %s", 1}, |  | ||||||
| 	{2 * Year, "2 years %s", 1}, |  | ||||||
| 	{LongTime, "%d years %s", Year}, |  | ||||||
| 	{math.MaxInt64, "a long while %s", 1}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RelTime formats a time into a relative string. |  | ||||||
| // |  | ||||||
| // It takes two times and two labels.  In addition to the generic time |  | ||||||
| // delta string (e.g. 5 minutes), the labels are used applied so that |  | ||||||
| // the label corresponding to the smaller time is applied. |  | ||||||
| // |  | ||||||
| // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" |  | ||||||
| func RelTime(a, b time.Time, albl, blbl string) string { |  | ||||||
| 	return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // CustomRelTime formats a time into a relative string. |  | ||||||
| // |  | ||||||
| // It takes two times two labels and a table of relative time formats. |  | ||||||
| // In addition to the generic time delta string (e.g. 5 minutes), the |  | ||||||
| // labels are used applied so that the label corresponding to the |  | ||||||
| // smaller time is applied. |  | ||||||
| func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { |  | ||||||
| 	lbl := albl |  | ||||||
| 	diff := b.Sub(a) |  | ||||||
| 
 |  | ||||||
| 	if a.After(b) { |  | ||||||
| 		lbl = blbl |  | ||||||
| 		diff = a.Sub(b) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	n := sort.Search(len(magnitudes), func(i int) bool { |  | ||||||
| 		return magnitudes[i].D > diff |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	if n >= len(magnitudes) { |  | ||||||
| 		n = len(magnitudes) - 1 |  | ||||||
| 	} |  | ||||||
| 	mag := magnitudes[n] |  | ||||||
| 	args := []interface{}{} |  | ||||||
| 	escaped := false |  | ||||||
| 	for _, ch := range mag.Format { |  | ||||||
| 		if escaped { |  | ||||||
| 			switch ch { |  | ||||||
| 			case 's': |  | ||||||
| 				args = append(args, lbl) |  | ||||||
| 			case 'd': |  | ||||||
| 				args = append(args, diff/mag.DivBy) |  | ||||||
| 			} |  | ||||||
| 			escaped = false |  | ||||||
| 		} else { |  | ||||||
| 			escaped = ch == '%' |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf(mag.Format, args...) |  | ||||||
| } |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/ghodss/yaml/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/ghodss/yaml/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,20 +0,0 @@ | ||||||
| # OSX leaves these everywhere on SMB shares |  | ||||||
| ._* |  | ||||||
| 
 |  | ||||||
| # Eclipse files |  | ||||||
| .classpath |  | ||||||
| .project |  | ||||||
| .settings/** |  | ||||||
| 
 |  | ||||||
| # Emacs save files |  | ||||||
| *~ |  | ||||||
| 
 |  | ||||||
| # Vim-related files |  | ||||||
| [._]*.s[a-w][a-z] |  | ||||||
| [._]s[a-w][a-z] |  | ||||||
| *.un~ |  | ||||||
| Session.vim |  | ||||||
| .netrwhist |  | ||||||
| 
 |  | ||||||
| # Go test binaries |  | ||||||
| *.test |  | ||||||
							
								
								
									
										7
									
								
								vendor/github.com/ghodss/yaml/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/ghodss/yaml/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,7 +0,0 @@ | ||||||
| language: go |  | ||||||
| go: |  | ||||||
|   - 1.3 |  | ||||||
|   - 1.4 |  | ||||||
| script: |  | ||||||
|   - go test |  | ||||||
|   - go build |  | ||||||
							
								
								
									
										50
									
								
								vendor/github.com/ghodss/yaml/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								vendor/github.com/ghodss/yaml/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,50 +0,0 @@ | ||||||
| The MIT License (MIT) |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2014 Sam Ghods |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2012 The Go Authors. All rights reserved. |  | ||||||
| 
 |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are |  | ||||||
| met: |  | ||||||
| 
 |  | ||||||
|    * Redistributions of source code must retain the above copyright |  | ||||||
| notice, this list of conditions and the following disclaimer. |  | ||||||
|    * Redistributions in binary form must reproduce the above |  | ||||||
| copyright notice, this list of conditions and the following disclaimer |  | ||||||
| in the documentation and/or other materials provided with the |  | ||||||
| distribution. |  | ||||||
|    * Neither the name of Google Inc. nor the names of its |  | ||||||
| contributors may be used to endorse or promote products derived from |  | ||||||
| this software without specific prior written permission. |  | ||||||
| 
 |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |  | ||||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | ||||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |  | ||||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |  | ||||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |  | ||||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
							
								
								
									
										121
									
								
								vendor/github.com/ghodss/yaml/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								vendor/github.com/ghodss/yaml/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,121 +0,0 @@ | ||||||
| # YAML marshaling and unmarshaling support for Go |  | ||||||
| 
 |  | ||||||
| [](https://travis-ci.org/ghodss/yaml) |  | ||||||
| 
 |  | ||||||
| ## Introduction |  | ||||||
| 
 |  | ||||||
| A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs. |  | ||||||
| 
 |  | ||||||
| In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/). |  | ||||||
| 
 |  | ||||||
| ## Compatibility |  | ||||||
| 
 |  | ||||||
| This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility). |  | ||||||
| 
 |  | ||||||
| ## Caveats |  | ||||||
| 
 |  | ||||||
| **Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example: |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| BAD: |  | ||||||
| 	exampleKey: !!binary gIGC |  | ||||||
| 
 |  | ||||||
| GOOD: |  | ||||||
| 	exampleKey: gIGC |  | ||||||
| ... and decode the base64 data in your code. |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| **Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys. |  | ||||||
| 
 |  | ||||||
| ## Installation and usage |  | ||||||
| 
 |  | ||||||
| To install, run: |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| $ go get github.com/ghodss/yaml |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| And import using: |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| import "github.com/ghodss/yaml" |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Usage is very similar to the JSON library: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/ghodss/yaml" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Person struct { |  | ||||||
| 	Name string `json:"name"` // Affects YAML field names too. |  | ||||||
| 	Age  int    `json:"age"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	// Marshal a Person struct to YAML. |  | ||||||
| 	p := Person{"John", 30} |  | ||||||
| 	y, err := yaml.Marshal(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Printf("err: %v\n", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	fmt.Println(string(y)) |  | ||||||
| 	/* Output: |  | ||||||
| 	age: 30 |  | ||||||
| 	name: John |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// Unmarshal the YAML back into a Person struct. |  | ||||||
| 	var p2 Person |  | ||||||
| 	err = yaml.Unmarshal(y, &p2) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Printf("err: %v\n", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	fmt.Println(p2) |  | ||||||
| 	/* Output: |  | ||||||
| 	{John 30} |  | ||||||
| 	*/ |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| `yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/ghodss/yaml" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	j := []byte(`{"name": "John", "age": 30}`) |  | ||||||
| 	y, err := yaml.JSONToYAML(j) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Printf("err: %v\n", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	fmt.Println(string(y)) |  | ||||||
| 	/* Output: |  | ||||||
| 	name: John |  | ||||||
| 	age: 30 |  | ||||||
| 	*/ |  | ||||||
| 	j2, err := yaml.YAMLToJSON(y) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Printf("err: %v\n", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	fmt.Println(string(j2)) |  | ||||||
| 	/* Output: |  | ||||||
| 	{"age":30,"name":"John"} |  | ||||||
| 	*/ |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
							
								
								
									
										501
									
								
								vendor/github.com/ghodss/yaml/fields.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										501
									
								
								vendor/github.com/ghodss/yaml/fields.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,501 +0,0 @@ | ||||||
| // Copyright 2013 The Go Authors. All rights reserved. |  | ||||||
| // Use of this source code is governed by a BSD-style |  | ||||||
| // license that can be found in the LICENSE file. |  | ||||||
| package yaml |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"reflect" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"unicode" |  | ||||||
| 	"unicode/utf8" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // indirect walks down v allocating pointers as needed, |  | ||||||
| // until it gets to a non-pointer. |  | ||||||
| // if it encounters an Unmarshaler, indirect stops and returns that. |  | ||||||
| // if decodingNull is true, indirect stops at the last pointer so it can be set to nil. |  | ||||||
| func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { |  | ||||||
| 	// If v is a named type and is addressable, |  | ||||||
| 	// start with its address, so that if the type has pointer methods, |  | ||||||
| 	// we find them. |  | ||||||
| 	if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { |  | ||||||
| 		v = v.Addr() |  | ||||||
| 	} |  | ||||||
| 	for { |  | ||||||
| 		// Load value from interface, but only if the result will be |  | ||||||
| 		// usefully addressable. |  | ||||||
| 		if v.Kind() == reflect.Interface && !v.IsNil() { |  | ||||||
| 			e := v.Elem() |  | ||||||
| 			if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { |  | ||||||
| 				v = e |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if v.Kind() != reflect.Ptr { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		if v.IsNil() { |  | ||||||
| 			if v.CanSet() { |  | ||||||
| 				v.Set(reflect.New(v.Type().Elem())) |  | ||||||
| 			} else { |  | ||||||
| 				v = reflect.New(v.Type().Elem()) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if v.Type().NumMethod() > 0 { |  | ||||||
| 			if u, ok := v.Interface().(json.Unmarshaler); ok { |  | ||||||
| 				return u, nil, reflect.Value{} |  | ||||||
| 			} |  | ||||||
| 			if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { |  | ||||||
| 				return nil, u, reflect.Value{} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		v = v.Elem() |  | ||||||
| 	} |  | ||||||
| 	return nil, nil, v |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A field represents a single field found in a struct. |  | ||||||
| type field struct { |  | ||||||
| 	name      string |  | ||||||
| 	nameBytes []byte                 // []byte(name) |  | ||||||
| 	equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent |  | ||||||
| 
 |  | ||||||
| 	tag       bool |  | ||||||
| 	index     []int |  | ||||||
| 	typ       reflect.Type |  | ||||||
| 	omitEmpty bool |  | ||||||
| 	quoted    bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func fillField(f field) field { |  | ||||||
| 	f.nameBytes = []byte(f.name) |  | ||||||
| 	f.equalFold = foldFunc(f.nameBytes) |  | ||||||
| 	return f |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // byName sorts field by name, breaking ties with depth, |  | ||||||
| // then breaking ties with "name came from json tag", then |  | ||||||
| // breaking ties with index sequence. |  | ||||||
| type byName []field |  | ||||||
| 
 |  | ||||||
| func (x byName) Len() int { return len(x) } |  | ||||||
| 
 |  | ||||||
| func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |  | ||||||
| 
 |  | ||||||
| func (x byName) Less(i, j int) bool { |  | ||||||
| 	if x[i].name != x[j].name { |  | ||||||
| 		return x[i].name < x[j].name |  | ||||||
| 	} |  | ||||||
| 	if len(x[i].index) != len(x[j].index) { |  | ||||||
| 		return len(x[i].index) < len(x[j].index) |  | ||||||
| 	} |  | ||||||
| 	if x[i].tag != x[j].tag { |  | ||||||
| 		return x[i].tag |  | ||||||
| 	} |  | ||||||
| 	return byIndex(x).Less(i, j) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // byIndex sorts field by index sequence. |  | ||||||
| type byIndex []field |  | ||||||
| 
 |  | ||||||
| func (x byIndex) Len() int { return len(x) } |  | ||||||
| 
 |  | ||||||
| func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |  | ||||||
| 
 |  | ||||||
| func (x byIndex) Less(i, j int) bool { |  | ||||||
| 	for k, xik := range x[i].index { |  | ||||||
| 		if k >= len(x[j].index) { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		if xik != x[j].index[k] { |  | ||||||
| 			return xik < x[j].index[k] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return len(x[i].index) < len(x[j].index) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // typeFields returns a list of fields that JSON should recognize for the given type. |  | ||||||
| // The algorithm is breadth-first search over the set of structs to include - the top struct |  | ||||||
| // and then any reachable anonymous structs. |  | ||||||
| func typeFields(t reflect.Type) []field { |  | ||||||
| 	// Anonymous fields to explore at the current level and the next. |  | ||||||
| 	current := []field{} |  | ||||||
| 	next := []field{{typ: t}} |  | ||||||
| 
 |  | ||||||
| 	// Count of queued names for current level and the next. |  | ||||||
| 	count := map[reflect.Type]int{} |  | ||||||
| 	nextCount := map[reflect.Type]int{} |  | ||||||
| 
 |  | ||||||
| 	// Types already visited at an earlier level. |  | ||||||
| 	visited := map[reflect.Type]bool{} |  | ||||||
| 
 |  | ||||||
| 	// Fields found. |  | ||||||
| 	var fields []field |  | ||||||
| 
 |  | ||||||
| 	for len(next) > 0 { |  | ||||||
| 		current, next = next, current[:0] |  | ||||||
| 		count, nextCount = nextCount, map[reflect.Type]int{} |  | ||||||
| 
 |  | ||||||
| 		for _, f := range current { |  | ||||||
| 			if visited[f.typ] { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			visited[f.typ] = true |  | ||||||
| 
 |  | ||||||
| 			// Scan f.typ for fields to include. |  | ||||||
| 			for i := 0; i < f.typ.NumField(); i++ { |  | ||||||
| 				sf := f.typ.Field(i) |  | ||||||
| 				if sf.PkgPath != "" { // unexported |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				tag := sf.Tag.Get("json") |  | ||||||
| 				if tag == "-" { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				name, opts := parseTag(tag) |  | ||||||
| 				if !isValidTag(name) { |  | ||||||
| 					name = "" |  | ||||||
| 				} |  | ||||||
| 				index := make([]int, len(f.index)+1) |  | ||||||
| 				copy(index, f.index) |  | ||||||
| 				index[len(f.index)] = i |  | ||||||
| 
 |  | ||||||
| 				ft := sf.Type |  | ||||||
| 				if ft.Name() == "" && ft.Kind() == reflect.Ptr { |  | ||||||
| 					// Follow pointer. |  | ||||||
| 					ft = ft.Elem() |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// Record found field and index sequence. |  | ||||||
| 				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { |  | ||||||
| 					tagged := name != "" |  | ||||||
| 					if name == "" { |  | ||||||
| 						name = sf.Name |  | ||||||
| 					} |  | ||||||
| 					fields = append(fields, fillField(field{ |  | ||||||
| 						name:      name, |  | ||||||
| 						tag:       tagged, |  | ||||||
| 						index:     index, |  | ||||||
| 						typ:       ft, |  | ||||||
| 						omitEmpty: opts.Contains("omitempty"), |  | ||||||
| 						quoted:    opts.Contains("string"), |  | ||||||
| 					})) |  | ||||||
| 					if count[f.typ] > 1 { |  | ||||||
| 						// If there were multiple instances, add a second, |  | ||||||
| 						// so that the annihilation code will see a duplicate. |  | ||||||
| 						// It only cares about the distinction between 1 or 2, |  | ||||||
| 						// so don't bother generating any more copies. |  | ||||||
| 						fields = append(fields, fields[len(fields)-1]) |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// Record new anonymous struct to explore in next round. |  | ||||||
| 				nextCount[ft]++ |  | ||||||
| 				if nextCount[ft] == 1 { |  | ||||||
| 					next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sort.Sort(byName(fields)) |  | ||||||
| 
 |  | ||||||
| 	// Delete all fields that are hidden by the Go rules for embedded fields, |  | ||||||
| 	// except that fields with JSON tags are promoted. |  | ||||||
| 
 |  | ||||||
| 	// The fields are sorted in primary order of name, secondary order |  | ||||||
| 	// of field index length. Loop over names; for each name, delete |  | ||||||
| 	// hidden fields by choosing the one dominant field that survives. |  | ||||||
| 	out := fields[:0] |  | ||||||
| 	for advance, i := 0, 0; i < len(fields); i += advance { |  | ||||||
| 		// One iteration per name. |  | ||||||
| 		// Find the sequence of fields with the name of this first field. |  | ||||||
| 		fi := fields[i] |  | ||||||
| 		name := fi.name |  | ||||||
| 		for advance = 1; i+advance < len(fields); advance++ { |  | ||||||
| 			fj := fields[i+advance] |  | ||||||
| 			if fj.name != name { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if advance == 1 { // Only one field with this name |  | ||||||
| 			out = append(out, fi) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		dominant, ok := dominantField(fields[i : i+advance]) |  | ||||||
| 		if ok { |  | ||||||
| 			out = append(out, dominant) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fields = out |  | ||||||
| 	sort.Sort(byIndex(fields)) |  | ||||||
| 
 |  | ||||||
| 	return fields |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // dominantField looks through the fields, all of which are known to |  | ||||||
| // have the same name, to find the single field that dominates the |  | ||||||
| // others using Go's embedding rules, modified by the presence of |  | ||||||
| // JSON tags. If there are multiple top-level fields, the boolean |  | ||||||
| // will be false: This condition is an error in Go and we skip all |  | ||||||
| // the fields. |  | ||||||
| func dominantField(fields []field) (field, bool) { |  | ||||||
| 	// The fields are sorted in increasing index-length order. The winner |  | ||||||
| 	// must therefore be one with the shortest index length. Drop all |  | ||||||
| 	// longer entries, which is easy: just truncate the slice. |  | ||||||
| 	length := len(fields[0].index) |  | ||||||
| 	tagged := -1 // Index of first tagged field. |  | ||||||
| 	for i, f := range fields { |  | ||||||
| 		if len(f.index) > length { |  | ||||||
| 			fields = fields[:i] |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		if f.tag { |  | ||||||
| 			if tagged >= 0 { |  | ||||||
| 				// Multiple tagged fields at the same level: conflict. |  | ||||||
| 				// Return no field. |  | ||||||
| 				return field{}, false |  | ||||||
| 			} |  | ||||||
| 			tagged = i |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if tagged >= 0 { |  | ||||||
| 		return fields[tagged], true |  | ||||||
| 	} |  | ||||||
| 	// All remaining fields have the same length. If there's more than one, |  | ||||||
| 	// we have a conflict (two fields named "X" at the same level) and we |  | ||||||
| 	// return no field. |  | ||||||
| 	if len(fields) > 1 { |  | ||||||
| 		return field{}, false |  | ||||||
| 	} |  | ||||||
| 	return fields[0], true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var fieldCache struct { |  | ||||||
| 	sync.RWMutex |  | ||||||
| 	m map[reflect.Type][]field |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. |  | ||||||
| func cachedTypeFields(t reflect.Type) []field { |  | ||||||
| 	fieldCache.RLock() |  | ||||||
| 	f := fieldCache.m[t] |  | ||||||
| 	fieldCache.RUnlock() |  | ||||||
| 	if f != nil { |  | ||||||
| 		return f |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Compute fields without lock. |  | ||||||
| 	// Might duplicate effort but won't hold other computations back. |  | ||||||
| 	f = typeFields(t) |  | ||||||
| 	if f == nil { |  | ||||||
| 		f = []field{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fieldCache.Lock() |  | ||||||
| 	if fieldCache.m == nil { |  | ||||||
| 		fieldCache.m = map[reflect.Type][]field{} |  | ||||||
| 	} |  | ||||||
| 	fieldCache.m[t] = f |  | ||||||
| 	fieldCache.Unlock() |  | ||||||
| 	return f |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isValidTag(s string) bool { |  | ||||||
| 	if s == "" { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	for _, c := range s { |  | ||||||
| 		switch { |  | ||||||
| 		case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): |  | ||||||
| 			// Backslash and quote chars are reserved, but |  | ||||||
| 			// otherwise any punctuation chars are allowed |  | ||||||
| 			// in a tag name. |  | ||||||
| 		default: |  | ||||||
| 			if !unicode.IsLetter(c) && !unicode.IsDigit(c) { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	caseMask     = ^byte(0x20) // Mask to ignore case in ASCII. |  | ||||||
| 	kelvin       = '\u212a' |  | ||||||
| 	smallLongEss = '\u017f' |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // foldFunc returns one of four different case folding equivalence |  | ||||||
| // functions, from most general (and slow) to fastest: |  | ||||||
| // |  | ||||||
| // 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 |  | ||||||
| // 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') |  | ||||||
| // 3) asciiEqualFold, no special, but includes non-letters (including _) |  | ||||||
| // 4) simpleLetterEqualFold, no specials, no non-letters. |  | ||||||
| // |  | ||||||
| // The letters S and K are special because they map to 3 runes, not just 2: |  | ||||||
| //  * S maps to s and to U+017F 'ſ' Latin small letter long s |  | ||||||
| //  * k maps to K and to U+212A 'K' Kelvin sign |  | ||||||
| // See http://play.golang.org/p/tTxjOc0OGo |  | ||||||
| // |  | ||||||
| // The returned function is specialized for matching against s and |  | ||||||
| // should only be given s. It's not curried for performance reasons. |  | ||||||
| func foldFunc(s []byte) func(s, t []byte) bool { |  | ||||||
| 	nonLetter := false |  | ||||||
| 	special := false // special letter |  | ||||||
| 	for _, b := range s { |  | ||||||
| 		if b >= utf8.RuneSelf { |  | ||||||
| 			return bytes.EqualFold |  | ||||||
| 		} |  | ||||||
| 		upper := b & caseMask |  | ||||||
| 		if upper < 'A' || upper > 'Z' { |  | ||||||
| 			nonLetter = true |  | ||||||
| 		} else if upper == 'K' || upper == 'S' { |  | ||||||
| 			// See above for why these letters are special. |  | ||||||
| 			special = true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if special { |  | ||||||
| 		return equalFoldRight |  | ||||||
| 	} |  | ||||||
| 	if nonLetter { |  | ||||||
| 		return asciiEqualFold |  | ||||||
| 	} |  | ||||||
| 	return simpleLetterEqualFold |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // equalFoldRight is a specialization of bytes.EqualFold when s is |  | ||||||
| // known to be all ASCII (including punctuation), but contains an 's', |  | ||||||
| // 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. |  | ||||||
| // See comments on foldFunc. |  | ||||||
| func equalFoldRight(s, t []byte) bool { |  | ||||||
| 	for _, sb := range s { |  | ||||||
| 		if len(t) == 0 { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		tb := t[0] |  | ||||||
| 		if tb < utf8.RuneSelf { |  | ||||||
| 			if sb != tb { |  | ||||||
| 				sbUpper := sb & caseMask |  | ||||||
| 				if 'A' <= sbUpper && sbUpper <= 'Z' { |  | ||||||
| 					if sbUpper != tb&caseMask { |  | ||||||
| 						return false |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					return false |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			t = t[1:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		// sb is ASCII and t is not. t must be either kelvin |  | ||||||
| 		// sign or long s; sb must be s, S, k, or K. |  | ||||||
| 		tr, size := utf8.DecodeRune(t) |  | ||||||
| 		switch sb { |  | ||||||
| 		case 's', 'S': |  | ||||||
| 			if tr != smallLongEss { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		case 'k', 'K': |  | ||||||
| 			if tr != kelvin { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		default: |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		t = t[size:] |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| 	if len(t) > 0 { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // asciiEqualFold is a specialization of bytes.EqualFold for use when |  | ||||||
| // s is all ASCII (but may contain non-letters) and contains no |  | ||||||
| // special-folding letters. |  | ||||||
| // See comments on foldFunc. |  | ||||||
| func asciiEqualFold(s, t []byte) bool { |  | ||||||
| 	if len(s) != len(t) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	for i, sb := range s { |  | ||||||
| 		tb := t[i] |  | ||||||
| 		if sb == tb { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { |  | ||||||
| 			if sb&caseMask != tb&caseMask { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // simpleLetterEqualFold is a specialization of bytes.EqualFold for |  | ||||||
| // use when s is all ASCII letters (no underscores, etc) and also |  | ||||||
| // doesn't contain 'k', 'K', 's', or 'S'. |  | ||||||
| // See comments on foldFunc. |  | ||||||
| func simpleLetterEqualFold(s, t []byte) bool { |  | ||||||
| 	if len(s) != len(t) { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	for i, b := range s { |  | ||||||
| 		if b&caseMask != t[i]&caseMask { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // tagOptions is the string following a comma in a struct field's "json" |  | ||||||
| // tag, or the empty string. It does not include the leading comma. |  | ||||||
| type tagOptions string |  | ||||||
| 
 |  | ||||||
| // parseTag splits a struct field's json tag into its name and |  | ||||||
| // comma-separated options. |  | ||||||
| func parseTag(tag string) (string, tagOptions) { |  | ||||||
| 	if idx := strings.Index(tag, ","); idx != -1 { |  | ||||||
| 		return tag[:idx], tagOptions(tag[idx+1:]) |  | ||||||
| 	} |  | ||||||
| 	return tag, tagOptions("") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Contains reports whether a comma-separated list of options |  | ||||||
| // contains a particular substr flag. substr must be surrounded by a |  | ||||||
| // string boundary or commas. |  | ||||||
| func (o tagOptions) Contains(optionName string) bool { |  | ||||||
| 	if len(o) == 0 { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	s := string(o) |  | ||||||
| 	for s != "" { |  | ||||||
| 		var next string |  | ||||||
| 		i := strings.Index(s, ",") |  | ||||||
| 		if i >= 0 { |  | ||||||
| 			s, next = s[:i], s[i+1:] |  | ||||||
| 		} |  | ||||||
| 		if s == optionName { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 		s = next |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
							
								
								
									
										277
									
								
								vendor/github.com/ghodss/yaml/yaml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										277
									
								
								vendor/github.com/ghodss/yaml/yaml.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,277 +0,0 @@ | ||||||
| package yaml |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strconv" |  | ||||||
| 
 |  | ||||||
| 	"gopkg.in/yaml.v2" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Marshals the object into JSON then converts JSON to YAML and returns the |  | ||||||
| // YAML. |  | ||||||
| func Marshal(o interface{}) ([]byte, error) { |  | ||||||
| 	j, err := json.Marshal(o) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error marshaling into JSON: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	y, err := JSONToYAML(j) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error converting JSON to YAML: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return y, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Converts YAML to JSON then uses JSON to unmarshal into an object. |  | ||||||
| func Unmarshal(y []byte, o interface{}) error { |  | ||||||
| 	vo := reflect.ValueOf(o) |  | ||||||
| 	j, err := yamlToJSON(y, &vo) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("error converting YAML to JSON: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = json.Unmarshal(j, o) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("error unmarshaling JSON: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Convert JSON to YAML. |  | ||||||
| func JSONToYAML(j []byte) ([]byte, error) { |  | ||||||
| 	// Convert the JSON to an object. |  | ||||||
| 	var jsonObj interface{} |  | ||||||
| 	// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the |  | ||||||
| 	// Go JSON library doesn't try to pick the right number type (int, float, |  | ||||||
| 	// etc.) when unmarshalling to interface{}, it just picks float64 |  | ||||||
| 	// universally. go-yaml does go through the effort of picking the right |  | ||||||
| 	// number type, so we can preserve number type throughout this process. |  | ||||||
| 	err := yaml.Unmarshal(j, &jsonObj) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Marshal this object into YAML. |  | ||||||
| 	return yaml.Marshal(jsonObj) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through |  | ||||||
| // this method should be a no-op. |  | ||||||
| // |  | ||||||
| // Things YAML can do that are not supported by JSON: |  | ||||||
| // * In YAML you can have binary and null keys in your maps. These are invalid |  | ||||||
| //   in JSON. (int and float keys are converted to strings.) |  | ||||||
| // * Binary data in YAML with the !!binary tag is not supported. If you want to |  | ||||||
| //   use binary data with this library, encode the data as base64 as usual but do |  | ||||||
| //   not use the !!binary tag in your YAML. This will ensure the original base64 |  | ||||||
| //   encoded data makes it all the way through to the JSON. |  | ||||||
| func YAMLToJSON(y []byte) ([]byte, error) { |  | ||||||
| 	return yamlToJSON(y, nil) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) { |  | ||||||
| 	// Convert the YAML to an object. |  | ||||||
| 	var yamlObj interface{} |  | ||||||
| 	err := yaml.Unmarshal(y, &yamlObj) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// YAML objects are not completely compatible with JSON objects (e.g. you |  | ||||||
| 	// can have non-string keys in YAML). So, convert the YAML-compatible object |  | ||||||
| 	// to a JSON-compatible object, failing with an error if irrecoverable |  | ||||||
| 	// incompatibilties happen along the way. |  | ||||||
| 	jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Convert this object to JSON and return the data. |  | ||||||
| 	return json.Marshal(jsonObj) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { |  | ||||||
| 	var err error |  | ||||||
| 
 |  | ||||||
| 	// Resolve jsonTarget to a concrete value (i.e. not a pointer or an |  | ||||||
| 	// interface). We pass decodingNull as false because we're not actually |  | ||||||
| 	// decoding into the value, we're just checking if the ultimate target is a |  | ||||||
| 	// string. |  | ||||||
| 	if jsonTarget != nil { |  | ||||||
| 		ju, tu, pv := indirect(*jsonTarget, false) |  | ||||||
| 		// We have a JSON or Text Umarshaler at this level, so we can't be trying |  | ||||||
| 		// to decode into a string. |  | ||||||
| 		if ju != nil || tu != nil { |  | ||||||
| 			jsonTarget = nil |  | ||||||
| 		} else { |  | ||||||
| 			jsonTarget = &pv |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// If yamlObj is a number or a boolean, check if jsonTarget is a string - |  | ||||||
| 	// if so, coerce.  Else return normal. |  | ||||||
| 	// If yamlObj is a map or array, find the field that each key is |  | ||||||
| 	// unmarshaling to, and when you recurse pass the reflect.Value for that |  | ||||||
| 	// field back into this function. |  | ||||||
| 	switch typedYAMLObj := yamlObj.(type) { |  | ||||||
| 	case map[interface{}]interface{}: |  | ||||||
| 		// JSON does not support arbitrary keys in a map, so we must convert |  | ||||||
| 		// these keys to strings. |  | ||||||
| 		// |  | ||||||
| 		// From my reading of go-yaml v2 (specifically the resolve function), |  | ||||||
| 		// keys can only have the types string, int, int64, float64, binary |  | ||||||
| 		// (unsupported), or null (unsupported). |  | ||||||
| 		strMap := make(map[string]interface{}) |  | ||||||
| 		for k, v := range typedYAMLObj { |  | ||||||
| 			// Resolve the key to a string first. |  | ||||||
| 			var keyString string |  | ||||||
| 			switch typedKey := k.(type) { |  | ||||||
| 			case string: |  | ||||||
| 				keyString = typedKey |  | ||||||
| 			case int: |  | ||||||
| 				keyString = strconv.Itoa(typedKey) |  | ||||||
| 			case int64: |  | ||||||
| 				// go-yaml will only return an int64 as a key if the system |  | ||||||
| 				// architecture is 32-bit and the key's value is between 32-bit |  | ||||||
| 				// and 64-bit. Otherwise the key type will simply be int. |  | ||||||
| 				keyString = strconv.FormatInt(typedKey, 10) |  | ||||||
| 			case float64: |  | ||||||
| 				// Stolen from go-yaml to use the same conversion to string as |  | ||||||
| 				// the go-yaml library uses to convert float to string when |  | ||||||
| 				// Marshaling. |  | ||||||
| 				s := strconv.FormatFloat(typedKey, 'g', -1, 32) |  | ||||||
| 				switch s { |  | ||||||
| 				case "+Inf": |  | ||||||
| 					s = ".inf" |  | ||||||
| 				case "-Inf": |  | ||||||
| 					s = "-.inf" |  | ||||||
| 				case "NaN": |  | ||||||
| 					s = ".nan" |  | ||||||
| 				} |  | ||||||
| 				keyString = s |  | ||||||
| 			case bool: |  | ||||||
| 				if typedKey { |  | ||||||
| 					keyString = "true" |  | ||||||
| 				} else { |  | ||||||
| 					keyString = "false" |  | ||||||
| 				} |  | ||||||
| 			default: |  | ||||||
| 				return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v", |  | ||||||
| 					reflect.TypeOf(k), k, v) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// jsonTarget should be a struct or a map. If it's a struct, find |  | ||||||
| 			// the field it's going to map to and pass its reflect.Value. If |  | ||||||
| 			// it's a map, find the element type of the map and pass the |  | ||||||
| 			// reflect.Value created from that type. If it's neither, just pass |  | ||||||
| 			// nil - JSON conversion will error for us if it's a real issue. |  | ||||||
| 			if jsonTarget != nil { |  | ||||||
| 				t := *jsonTarget |  | ||||||
| 				if t.Kind() == reflect.Struct { |  | ||||||
| 					keyBytes := []byte(keyString) |  | ||||||
| 					// Find the field that the JSON library would use. |  | ||||||
| 					var f *field |  | ||||||
| 					fields := cachedTypeFields(t.Type()) |  | ||||||
| 					for i := range fields { |  | ||||||
| 						ff := &fields[i] |  | ||||||
| 						if bytes.Equal(ff.nameBytes, keyBytes) { |  | ||||||
| 							f = ff |  | ||||||
| 							break |  | ||||||
| 						} |  | ||||||
| 						// Do case-insensitive comparison. |  | ||||||
| 						if f == nil && ff.equalFold(ff.nameBytes, keyBytes) { |  | ||||||
| 							f = ff |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 					if f != nil { |  | ||||||
| 						// Find the reflect.Value of the most preferential |  | ||||||
| 						// struct field. |  | ||||||
| 						jtf := t.Field(f.index[0]) |  | ||||||
| 						strMap[keyString], err = convertToJSONableObject(v, &jtf) |  | ||||||
| 						if err != nil { |  | ||||||
| 							return nil, err |  | ||||||
| 						} |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} else if t.Kind() == reflect.Map { |  | ||||||
| 					// Create a zero value of the map's element type to use as |  | ||||||
| 					// the JSON target. |  | ||||||
| 					jtv := reflect.Zero(t.Type().Elem()) |  | ||||||
| 					strMap[keyString], err = convertToJSONableObject(v, &jtv) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			strMap[keyString], err = convertToJSONableObject(v, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return strMap, nil |  | ||||||
| 	case []interface{}: |  | ||||||
| 		// We need to recurse into arrays in case there are any |  | ||||||
| 		// map[interface{}]interface{}'s inside and to convert any |  | ||||||
| 		// numbers to strings. |  | ||||||
| 
 |  | ||||||
| 		// If jsonTarget is a slice (which it really should be), find the |  | ||||||
| 		// thing it's going to map to. If it's not a slice, just pass nil |  | ||||||
| 		// - JSON conversion will error for us if it's a real issue. |  | ||||||
| 		var jsonSliceElemValue *reflect.Value |  | ||||||
| 		if jsonTarget != nil { |  | ||||||
| 			t := *jsonTarget |  | ||||||
| 			if t.Kind() == reflect.Slice { |  | ||||||
| 				// By default slices point to nil, but we need a reflect.Value |  | ||||||
| 				// pointing to a value of the slice type, so we create one here. |  | ||||||
| 				ev := reflect.Indirect(reflect.New(t.Type().Elem())) |  | ||||||
| 				jsonSliceElemValue = &ev |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Make and use a new array. |  | ||||||
| 		arr := make([]interface{}, len(typedYAMLObj)) |  | ||||||
| 		for i, v := range typedYAMLObj { |  | ||||||
| 			arr[i], err = convertToJSONableObject(v, jsonSliceElemValue) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return arr, nil |  | ||||||
| 	default: |  | ||||||
| 		// If the target type is a string and the YAML type is a number, |  | ||||||
| 		// convert the YAML type to a string. |  | ||||||
| 		if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String { |  | ||||||
| 			// Based on my reading of go-yaml, it may return int, int64, |  | ||||||
| 			// float64, or uint64. |  | ||||||
| 			var s string |  | ||||||
| 			switch typedVal := typedYAMLObj.(type) { |  | ||||||
| 			case int: |  | ||||||
| 				s = strconv.FormatInt(int64(typedVal), 10) |  | ||||||
| 			case int64: |  | ||||||
| 				s = strconv.FormatInt(typedVal, 10) |  | ||||||
| 			case float64: |  | ||||||
| 				s = strconv.FormatFloat(typedVal, 'g', -1, 32) |  | ||||||
| 			case uint64: |  | ||||||
| 				s = strconv.FormatUint(typedVal, 10) |  | ||||||
| 			case bool: |  | ||||||
| 				if typedVal { |  | ||||||
| 					s = "true" |  | ||||||
| 				} else { |  | ||||||
| 					s = "false" |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			if len(s) > 0 { |  | ||||||
| 				yamlObj = interface{}(s) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return yamlObj, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,3 +0,0 @@ | ||||||
| .idea |  | ||||||
| *.sw? |  | ||||||
| .vscode |  | ||||||
							
								
								
									
										18
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,18 +0,0 @@ | ||||||
| language: go |  | ||||||
| 
 |  | ||||||
| go: |  | ||||||
|   - 1.10.x |  | ||||||
|   - 1.11.x |  | ||||||
|   - 1.12.x |  | ||||||
| 
 |  | ||||||
| script: |  | ||||||
|   - go get -d -t ./... |  | ||||||
|   - go vet ./... |  | ||||||
|   - go test ./... |  | ||||||
|   - > |  | ||||||
|     go_version=$(go version); |  | ||||||
|     if [ ${go_version:13:4} = "1.12" ]; then |  | ||||||
|       go get -u golang.org/x/tools/cmd/goimports; |  | ||||||
|       goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :; |  | ||||||
|     fi |  | ||||||
| 
 |  | ||||||
							
								
								
									
										139
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										139
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,139 +0,0 @@ | ||||||
| # Changelog |  | ||||||
| 
 |  | ||||||
| ## v4.0.0 (2019-01-10) |  | ||||||
| 
 |  | ||||||
| - chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 |  | ||||||
| - router: respond with 404 on router with no routes (#362) |  | ||||||
| - router: additional check to ensure wildcard is at the end of a url pattern (#333) |  | ||||||
| - middleware: deprecate use of http.CloseNotifier (#347) |  | ||||||
| - middleware: fix RedirectSlashes to include query params on redirect (#334) |  | ||||||
| - History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.4 (2019-01-07) |  | ||||||
| 
 |  | ||||||
| - Minor middleware improvements. No changes to core library/router. Moving v3 into its |  | ||||||
| - own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 |  | ||||||
| - History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.3 (2018-08-27) |  | ||||||
| 
 |  | ||||||
| - Minor release |  | ||||||
| - See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.2 (2017-12-22) |  | ||||||
| 
 |  | ||||||
| - Support to route trailing slashes on mounted sub-routers (#281) |  | ||||||
| - middleware: new `ContentCharset` to check matching charsets. Thank you |  | ||||||
|   @csucu for your community contribution! |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.1 (2017-11-20) |  | ||||||
| 
 |  | ||||||
| - middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types |  | ||||||
| - middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value |  | ||||||
| - Minor bug fixes |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.3.0 (2017-10-10) |  | ||||||
| 
 |  | ||||||
| - New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage |  | ||||||
| - Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.2.1 (2017-08-31) |  | ||||||
| 
 |  | ||||||
| - Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface |  | ||||||
|   and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path |  | ||||||
| - Add new `RouteMethod` to `*Context` |  | ||||||
| - Add new `Routes` pointer to `*Context` |  | ||||||
| - Add new `middleware.GetHead` to route missing HEAD requests to GET handler |  | ||||||
| - Updated benchmarks (see README) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.1.5 (2017-08-02) |  | ||||||
| 
 |  | ||||||
| - Setup golint and go vet for the project |  | ||||||
| - As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` |  | ||||||
|   to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.1.0 (2017-07-10) |  | ||||||
| 
 |  | ||||||
| - Fix a few minor issues after v3 release |  | ||||||
| - Move `docgen` sub-pkg to https://github.com/go-chi/docgen |  | ||||||
| - Move `render` sub-pkg to https://github.com/go-chi/render |  | ||||||
| - Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime  |  | ||||||
|   suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in |  | ||||||
|   https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v3.0.0 (2017-06-21) |  | ||||||
| 
 |  | ||||||
| - Major update to chi library with many exciting updates, but also some *breaking changes* |  | ||||||
| - URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as |  | ||||||
|   `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the |  | ||||||
|   same router |  | ||||||
| - Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: |  | ||||||
|   `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` |  | ||||||
| - Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as |  | ||||||
|   `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like |  | ||||||
|   in `_examples/custom-handler` |  | ||||||
| - Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their |  | ||||||
|   own using file handler with the stdlib, see `_examples/fileserver` for an example |  | ||||||
| - Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` |  | ||||||
| - Moved the chi project to its own organization, to allow chi-related community packages to |  | ||||||
|   be easily discovered and supported, at: https://github.com/go-chi |  | ||||||
| - *NOTE:* please update your import paths to `"github.com/go-chi/chi"` |  | ||||||
| - *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.1.0 (2017-03-30) |  | ||||||
| 
 |  | ||||||
| - Minor improvements and update to the chi core library |  | ||||||
| - Introduced a brand new `chi/render` sub-package to complete the story of building |  | ||||||
|   APIs to offer a pattern for managing well-defined request / response payloads. Please |  | ||||||
|   check out the updated `_examples/rest` example for how it works. |  | ||||||
| - Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.0.0 (2017-01-06) |  | ||||||
| 
 |  | ||||||
| - After many months of v2 being in an RC state with many companies and users running it in |  | ||||||
|   production, the inclusion of some improvements to the middlewares, we are very pleased to |  | ||||||
|   announce v2.0.0 of chi. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v2.0.0-rc1 (2016-07-26) |  | ||||||
| 
 |  | ||||||
| - Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular |  | ||||||
|   community `"net/context"` package has been included in the standard library as `"context"` and |  | ||||||
|   utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other |  | ||||||
|   request-scoped values. We're very excited about the new context addition and are proud to |  | ||||||
|   introduce chi v2, a minimal and powerful routing package for building large HTTP services, |  | ||||||
|   with zero external dependencies. Chi focuses on idiomatic design and encourages the use of  |  | ||||||
|   stdlib HTTP handlers and middlwares. |  | ||||||
| - chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` |  | ||||||
| - chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` |  | ||||||
| - chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, |  | ||||||
|   which provides direct access to URL routing parameters, the routing path and the matching |  | ||||||
|   routing patterns. |  | ||||||
| - Users upgrading from chi v1 to v2, need to: |  | ||||||
|   1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to |  | ||||||
|      the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` |  | ||||||
|   2. Use `chi.URLParam(r *http.Request, paramKey string) string` |  | ||||||
|      or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v1.0.0 (2016-07-01) |  | ||||||
| 
 |  | ||||||
| - Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## v0.9.0 (2016-03-31) |  | ||||||
| 
 |  | ||||||
| - Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) |  | ||||||
| - BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters |  | ||||||
|   has changed to: `chi.URLParam(ctx, "id")` |  | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,31 +0,0 @@ | ||||||
| # Contributing |  | ||||||
| 
 |  | ||||||
| ## Prerequisites |  | ||||||
| 
 |  | ||||||
| 1. [Install Go][go-install]. |  | ||||||
| 2. Download the sources and switch the working directory: |  | ||||||
| 
 |  | ||||||
|     ```bash |  | ||||||
|     go get -u -d github.com/go-chi/chi |  | ||||||
|     cd $GOPATH/src/github.com/go-chi/chi |  | ||||||
|     ``` |  | ||||||
| 
 |  | ||||||
| ## Submitting a Pull Request |  | ||||||
| 
 |  | ||||||
| A typical workflow is: |  | ||||||
| 
 |  | ||||||
| 1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] |  | ||||||
| 2. [Create a topic branch.][branch] |  | ||||||
| 3. Add tests for your change. |  | ||||||
| 4. Run `go test`. If your tests pass, return to the step 3. |  | ||||||
| 5. Implement the change and ensure the steps from the previous step pass. |  | ||||||
| 6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline. |  | ||||||
| 7. [Add, commit and push your changes.][git-help] |  | ||||||
| 8. [Submit a pull request.][pull-req] |  | ||||||
| 
 |  | ||||||
| [go-install]: https://golang.org/doc/install |  | ||||||
| [go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html |  | ||||||
| [fork]: https://help.github.com/articles/fork-a-repo |  | ||||||
| [branch]: http://learn.github.com/p/branching.html |  | ||||||
| [git-help]: https://guides.github.com |  | ||||||
| [pull-req]: https://help.github.com/articles/using-pull-requests |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,20 +0,0 @@ | ||||||
| Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. |  | ||||||
| 
 |  | ||||||
| MIT License |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of |  | ||||||
| this software and associated documentation files (the "Software"), to deal in |  | ||||||
| the Software without restriction, including without limitation the rights to |  | ||||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |  | ||||||
| the Software, and to permit persons to whom the Software is furnished to do so, |  | ||||||
| subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |  | ||||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |  | ||||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |  | ||||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |  | ||||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
							
								
								
									
										438
									
								
								vendor/github.com/go-chi/chi/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										438
									
								
								vendor/github.com/go-chi/chi/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,438 +0,0 @@ | ||||||
| # <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" /> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] |  | ||||||
| 
 |  | ||||||
| `chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's |  | ||||||
| especially good at helping you write large REST API services that are kept maintainable as your |  | ||||||
| project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to |  | ||||||
| handle signaling, cancelation and request-scoped values across a handler chain. |  | ||||||
| 
 |  | ||||||
| The focus of the project has been to seek out an elegant and comfortable design for writing |  | ||||||
| REST API servers, written during the development of the Pressly API service that powers our |  | ||||||
| public API service, which in turn powers all of our client-side applications. |  | ||||||
| 
 |  | ||||||
| The key considerations of chi's design are: project structure, maintainability, standard http |  | ||||||
| handlers (stdlib-only), developer productivity, and deconstructing a large system into many small |  | ||||||
| parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also |  | ||||||
| included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! |  | ||||||
| 
 |  | ||||||
| ## Install |  | ||||||
| 
 |  | ||||||
| `go get -u github.com/go-chi/chi` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Features |  | ||||||
| 
 |  | ||||||
| * **Lightweight** - cloc'd in ~1000 LOC for the chi router |  | ||||||
| * **Fast** - yes, see [benchmarks](#benchmarks) |  | ||||||
| * **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` |  | ||||||
| * **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting |  | ||||||
| * **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts |  | ||||||
| * **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) |  | ||||||
| * **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown |  | ||||||
| * **No external dependencies** - plain ol' Go stdlib + net/http |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Examples |  | ||||||
| 
 |  | ||||||
| See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **As easy as:** |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	r := chi.NewRouter() |  | ||||||
| 	r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		w.Write([]byte("welcome")) |  | ||||||
| 	}) |  | ||||||
| 	http.ListenAndServe(":3000", r) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| **REST Preview:** |  | ||||||
| 
 |  | ||||||
| Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs |  | ||||||
| in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in |  | ||||||
| Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). |  | ||||||
| 
 |  | ||||||
| I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed |  | ||||||
| above, they will show you all the features of chi and serve as a good form of documentation. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| import ( |  | ||||||
|   //... |  | ||||||
|   "context" |  | ||||||
|   "github.com/go-chi/chi" |  | ||||||
|   "github.com/go-chi/chi/middleware" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
|   r := chi.NewRouter() |  | ||||||
| 
 |  | ||||||
|   // A good base middleware stack |  | ||||||
|   r.Use(middleware.RequestID) |  | ||||||
|   r.Use(middleware.RealIP) |  | ||||||
|   r.Use(middleware.Logger) |  | ||||||
|   r.Use(middleware.Recoverer) |  | ||||||
| 
 |  | ||||||
|   // Set a timeout value on the request context (ctx), that will signal |  | ||||||
|   // through ctx.Done() that the request has timed out and further |  | ||||||
|   // processing should be stopped. |  | ||||||
|   r.Use(middleware.Timeout(60 * time.Second)) |  | ||||||
| 
 |  | ||||||
|   r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     w.Write([]byte("hi")) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   // RESTy routes for "articles" resource |  | ||||||
|   r.Route("/articles", func(r chi.Router) { |  | ||||||
|     r.With(paginate).Get("/", listArticles)                           // GET /articles |  | ||||||
|     r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 |  | ||||||
| 
 |  | ||||||
|     r.Post("/", createArticle)                                        // POST /articles |  | ||||||
|     r.Get("/search", searchArticles)                                  // GET /articles/search |  | ||||||
| 
 |  | ||||||
|     // Regexp url parameters: |  | ||||||
|     r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto |  | ||||||
| 
 |  | ||||||
|     // Subrouters: |  | ||||||
|     r.Route("/{articleID}", func(r chi.Router) { |  | ||||||
|       r.Use(ArticleCtx) |  | ||||||
|       r.Get("/", getArticle)                                          // GET /articles/123 |  | ||||||
|       r.Put("/", updateArticle)                                       // PUT /articles/123 |  | ||||||
|       r.Delete("/", deleteArticle)                                    // DELETE /articles/123 |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   // Mount the admin sub-router |  | ||||||
|   r.Mount("/admin", adminRouter()) |  | ||||||
| 
 |  | ||||||
|   http.ListenAndServe(":3333", r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ArticleCtx(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     articleID := chi.URLParam(r, "articleID") |  | ||||||
|     article, err := dbGetArticle(articleID) |  | ||||||
|     if err != nil { |  | ||||||
|       http.Error(w, http.StatusText(404), 404) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     ctx := context.WithValue(r.Context(), "article", article) |  | ||||||
|     next.ServeHTTP(w, r.WithContext(ctx)) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getArticle(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   ctx := r.Context() |  | ||||||
|   article, ok := ctx.Value("article").(*Article) |  | ||||||
|   if !ok { |  | ||||||
|     http.Error(w, http.StatusText(422), 422) |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
|   w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A completely separate router for administrator routes |  | ||||||
| func adminRouter() http.Handler { |  | ||||||
|   r := chi.NewRouter() |  | ||||||
|   r.Use(AdminOnly) |  | ||||||
|   r.Get("/", adminIndex) |  | ||||||
|   r.Get("/accounts", adminListAccounts) |  | ||||||
|   return r |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func AdminOnly(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     ctx := r.Context() |  | ||||||
|     perm, ok := ctx.Value("acl.permission").(YourPermissionType) |  | ||||||
|     if !ok || !perm.IsAdmin() { |  | ||||||
|       http.Error(w, http.StatusText(403), 403) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     next.ServeHTTP(w, r) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Router design |  | ||||||
| 
 |  | ||||||
| chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). |  | ||||||
| The router is fully compatible with `net/http`. |  | ||||||
| 
 |  | ||||||
| Built on top of the tree is the `Router` interface: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // Router consisting of the core routing methods used by chi's Mux, |  | ||||||
| // using only the standard net/http. |  | ||||||
| type Router interface { |  | ||||||
| 	http.Handler |  | ||||||
| 	Routes |  | ||||||
| 
 |  | ||||||
| 	// Use appends one of more middlewares onto the Router stack. |  | ||||||
| 	Use(middlewares ...func(http.Handler) http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// With adds inline middlewares for an endpoint handler. |  | ||||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router |  | ||||||
| 
 |  | ||||||
| 	// Group adds a new inline-Router along the current routing |  | ||||||
| 	// path, with a fresh middleware stack for the inline-Router. |  | ||||||
| 	Group(fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Route mounts a sub-Router along a `pattern`` string. |  | ||||||
| 	Route(pattern string, fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Mount attaches another http.Handler along ./pattern/* |  | ||||||
| 	Mount(pattern string, h http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches |  | ||||||
| 	// all HTTP methods. |  | ||||||
| 	Handle(pattern string, h http.Handler) |  | ||||||
| 	HandleFunc(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// Method and MethodFunc adds routes for `pattern` that matches |  | ||||||
| 	// the `method` HTTP method. |  | ||||||
| 	Method(method, pattern string, h http.Handler) |  | ||||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// HTTP-method routing along `pattern` |  | ||||||
| 	Connect(pattern string, h http.HandlerFunc) |  | ||||||
| 	Delete(pattern string, h http.HandlerFunc) |  | ||||||
| 	Get(pattern string, h http.HandlerFunc) |  | ||||||
| 	Head(pattern string, h http.HandlerFunc) |  | ||||||
| 	Options(pattern string, h http.HandlerFunc) |  | ||||||
| 	Patch(pattern string, h http.HandlerFunc) |  | ||||||
| 	Post(pattern string, h http.HandlerFunc) |  | ||||||
| 	Put(pattern string, h http.HandlerFunc) |  | ||||||
| 	Trace(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// NotFound defines a handler to respond whenever a route could |  | ||||||
| 	// not be found. |  | ||||||
| 	NotFound(h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is |  | ||||||
| 	// not allowed. |  | ||||||
| 	MethodNotAllowed(h http.HandlerFunc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes interface adds two methods for router traversal, which is also |  | ||||||
| // used by the github.com/go-chi/docgen package to generate documentation for Routers. |  | ||||||
| type Routes interface { |  | ||||||
| 	// Routes returns the routing tree in an easily traversable structure. |  | ||||||
| 	Routes() []Route |  | ||||||
| 
 |  | ||||||
| 	// Middlewares returns the list of middlewares in use by the router. |  | ||||||
| 	Middlewares() Middlewares |  | ||||||
| 
 |  | ||||||
| 	// Match searches the routing tree for a handler that matches |  | ||||||
| 	// the method/path - similar to routing a http request, but without |  | ||||||
| 	// executing the handler thereafter. |  | ||||||
| 	Match(rctx *Context, method, path string) bool |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern |  | ||||||
| supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters |  | ||||||
| can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters |  | ||||||
| and `chi.URLParam(r, "*")` for a wildcard parameter. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### Middleware handlers |  | ||||||
| 
 |  | ||||||
| chi's middlewares are just stdlib net/http middleware handlers. There is nothing special |  | ||||||
| about them, which means the router and all the tooling is designed to be compatible and |  | ||||||
| friendly with any middleware in the community. This offers much better extensibility and reuse |  | ||||||
| of packages and is at the heart of chi's purpose. |  | ||||||
| 
 |  | ||||||
| Here is an example of a standard net/http middleware handler using the new request context |  | ||||||
| available in Go. This middleware sets a hypothetical user identifier on the request |  | ||||||
| context and calls the next handler in the chain. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP middleware setting a value on the request context |  | ||||||
| func MyMiddleware(next http.Handler) http.Handler { |  | ||||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     ctx := context.WithValue(r.Context(), "user", "123") |  | ||||||
|     next.ServeHTTP(w, r.WithContext(ctx)) |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### Request handlers |  | ||||||
| 
 |  | ||||||
| chi uses standard net/http request handlers. This little snippet is an example of a http.Handler |  | ||||||
| func that reads a user identifier from the request context - hypothetically, identifying |  | ||||||
| the user sending an authenticated request, validated+set by a previous middleware handler. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP handler accessing data from the request context. |  | ||||||
| func MyRequestHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   user := r.Context().Value("user").(string) |  | ||||||
|   w.Write([]byte(fmt.Sprintf("hi %s", user))) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ### URL parameters |  | ||||||
| 
 |  | ||||||
| chi's router parses and stores URL parameters right onto the request context. Here is |  | ||||||
| an example of how to access URL params in your net/http handlers. And of course, middlewares |  | ||||||
| are able to access the same information. |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| // HTTP handler accessing the url routing parameters. |  | ||||||
| func MyRequestHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|   userID := chi.URLParam(r, "userID") // from a route like /users/{userID} |  | ||||||
| 
 |  | ||||||
|   ctx := r.Context() |  | ||||||
|   key := ctx.Value("key").(string) |  | ||||||
| 
 |  | ||||||
|   w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key))) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Middlewares |  | ||||||
| 
 |  | ||||||
| chi comes equipped with an optional `middleware` package, providing a suite of standard |  | ||||||
| `net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible |  | ||||||
| with `net/http` can be used with chi's mux. |  | ||||||
| 
 |  | ||||||
| ### Core middlewares |  | ||||||
| 
 |  | ||||||
| ----------------------------------------------------------------------------------------------------------- |  | ||||||
| | chi/middleware Handler | description                                                                     | |  | ||||||
| |:----------------------|:--------------------------------------------------------------------------------- |  | ||||||
| | AllowContentType      | Explicit whitelist of accepted request Content-Types                            | |  | ||||||
| | Compress              | Gzip compression for clients that accept compressed responses                   | |  | ||||||
| | GetHead               | Automatically route undefined HEAD requests to GET handlers                     | |  | ||||||
| | Heartbeat             | Monitoring endpoint to check the servers pulse                                  | |  | ||||||
| | Logger                | Logs the start and end of each request with the elapsed processing time         | |  | ||||||
| | NoCache               | Sets response headers to prevent clients from caching                           | |  | ||||||
| | Profiler              | Easily attach net/http/pprof to your routers                                    | |  | ||||||
| | RealIP                | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP         | |  | ||||||
| | Recoverer             | Gracefully absorb panics and prints the stack trace                             | |  | ||||||
| | RequestID             | Injects a request ID into the context of each request                           | |  | ||||||
| | RedirectSlashes       | Redirect slashes on routing paths                                               | |  | ||||||
| | SetHeader             | Short-hand middleware to set a response header key/value                        | |  | ||||||
| | StripSlashes          | Strip slashes on routing paths                                                  | |  | ||||||
| | Throttle              | Puts a ceiling on the number of concurrent requests                             | |  | ||||||
| | Timeout               | Signals to the request context when the timeout deadline is reached             | |  | ||||||
| | URLFormat             | Parse extension from url and put it on request context                          | |  | ||||||
| | WithValue             | Short-hand middleware to set a key/value on the request context                 | |  | ||||||
| ----------------------------------------------------------------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| ### Auxiliary middlewares & packages |  | ||||||
| 
 |  | ||||||
| Please see https://github.com/go-chi for additional packages. |  | ||||||
| 
 |  | ||||||
| -------------------------------------------------------------------------------------------------------------------- |  | ||||||
| | package                                            | description                                                 | |  | ||||||
| |:---------------------------------------------------|:------------------------------------------------------------- |  | ||||||
| | [cors](https://github.com/go-chi/cors)             | Cross-origin resource sharing (CORS)                        | |  | ||||||
| | [docgen](https://github.com/go-chi/docgen)         | Print chi.Router routes at runtime                          | |  | ||||||
| | [jwtauth](https://github.com/go-chi/jwtauth)       | JWT authentication                                          | |  | ||||||
| | [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing                           | |  | ||||||
| | [httpcoala](https://github.com/go-chi/httpcoala)   | HTTP request coalescer                                      | |  | ||||||
| | [chi-authz](https://github.com/casbin/chi-authz)   | Request ACL via https://github.com/hsluoyz/casbin           | |  | ||||||
| | [phi](https://github.com/fate-lovely/phi)          | Port chi to [fasthttp](https://github.com/valyala/fasthttp) | |  | ||||||
| -------------------------------------------------------------------------------------------------------------------- |  | ||||||
| 
 |  | ||||||
| please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## context? |  | ||||||
| 
 |  | ||||||
| `context` is a tiny pkg that provides simple interface to signal context across call stacks |  | ||||||
| and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani) |  | ||||||
| and is available in stdlib since go1.7. |  | ||||||
| 
 |  | ||||||
| Learn more at https://blog.golang.org/context |  | ||||||
| 
 |  | ||||||
| and.. |  | ||||||
| * Docs: https://golang.org/pkg/context |  | ||||||
| * Source: https://github.com/golang/go/tree/master/src/context |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Benchmarks |  | ||||||
| 
 |  | ||||||
| The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark |  | ||||||
| 
 |  | ||||||
| Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop |  | ||||||
| 
 |  | ||||||
| ```shell |  | ||||||
| BenchmarkChi_Param            3000000         475 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Param5           2000000         696 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Param20          1000000        1275 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParamWrite       3000000         505 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubStatic     3000000         508 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubParam      2000000         669 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GithubAll          10000      134627 ns/op     87699 B/op    609 allocs/op |  | ||||||
| BenchmarkChi_GPlusStatic      3000000         402 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlusParam       3000000         500 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlus2Params     3000000         586 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_GPlusAll          200000        7237 ns/op      5616 B/op     39 allocs/op |  | ||||||
| BenchmarkChi_ParseStatic      3000000         408 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParseParam       3000000         488 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_Parse2Params     3000000         551 ns/op       432 B/op      3 allocs/op |  | ||||||
| BenchmarkChi_ParseAll          100000       13508 ns/op     11232 B/op     78 allocs/op |  | ||||||
| BenchmarkChi_StaticAll          20000       81933 ns/op     67826 B/op    471 allocs/op |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc |  | ||||||
| 
 |  | ||||||
| NOTE: the allocs in the benchmark above are from the calls to http.Request's |  | ||||||
| `WithContext(context.Context)` method that clones the http.Request, sets the `Context()` |  | ||||||
| on the duplicated (alloc'd) request and returns it the new request object. This is just |  | ||||||
| how setting context on a request in Go works. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Credits |  | ||||||
| 
 |  | ||||||
| * Carl Jackson for https://github.com/zenazn/goji |  | ||||||
|   * Parts of chi's thinking comes from goji, and chi's middleware package |  | ||||||
|     sources from goji. |  | ||||||
| * Armon Dadgar for https://github.com/armon/go-radix |  | ||||||
| * Contributions: [@VojtechVitek](https://github.com/VojtechVitek) |  | ||||||
| 
 |  | ||||||
| We'll be more than happy to see [your contributions](./CONTRIBUTING.md)! |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Beyond REST |  | ||||||
| 
 |  | ||||||
| chi is just a http router that lets you decompose request handling into many smaller layers. |  | ||||||
| Many companies including Pressly.com (of course) use chi to write REST services for their public |  | ||||||
| APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces |  | ||||||
| required to write a complete client-server system or network of microservices. |  | ||||||
| 
 |  | ||||||
| Looking ahead beyond REST, I also recommend some newer works in the field coming from |  | ||||||
| [gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit) |  | ||||||
| and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their |  | ||||||
| own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server |  | ||||||
| communication feel like a single program on a single computer, no need to hand-write a client library |  | ||||||
| and the request/response payloads are typed contracts. NATS is pretty amazing too as a super |  | ||||||
| fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery - |  | ||||||
| an excellent combination with gRPC. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## License |  | ||||||
| 
 |  | ||||||
| Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) |  | ||||||
| 
 |  | ||||||
| Licensed under [MIT License](./LICENSE) |  | ||||||
| 
 |  | ||||||
| [GoDoc]: https://godoc.org/github.com/go-chi/chi |  | ||||||
| [GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg |  | ||||||
| [Travis]: https://travis-ci.org/go-chi/chi |  | ||||||
| [Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master |  | ||||||
							
								
								
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,49 +0,0 @@ | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import "net/http" |  | ||||||
| 
 |  | ||||||
| // Chain returns a Middlewares type from a slice of middleware handlers. |  | ||||||
| func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { |  | ||||||
| 	return Middlewares(middlewares) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handler builds and returns a http.Handler from the chain of middlewares, |  | ||||||
| // with `h http.Handler` as the final handler. |  | ||||||
| func (mws Middlewares) Handler(h http.Handler) http.Handler { |  | ||||||
| 	return &ChainHandler{mws, h, chain(mws, h)} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HandlerFunc builds and returns a http.Handler from the chain of middlewares, |  | ||||||
| // with `h http.Handler` as the final handler. |  | ||||||
| func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler { |  | ||||||
| 	return &ChainHandler{mws, h, chain(mws, h)} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ChainHandler is a http.Handler with support for handler composition and |  | ||||||
| // execution. |  | ||||||
| type ChainHandler struct { |  | ||||||
| 	Middlewares Middlewares |  | ||||||
| 	Endpoint    http.Handler |  | ||||||
| 	chain       http.Handler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	c.chain.ServeHTTP(w, r) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // chain builds a http.Handler composed of an inline middleware stack and endpoint |  | ||||||
| // handler in the order they are passed. |  | ||||||
| func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler { |  | ||||||
| 	// Return ahead of time if there aren't any middlewares for the chain |  | ||||||
| 	if len(middlewares) == 0 { |  | ||||||
| 		return endpoint |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wrap the end handler with the middleware chain |  | ||||||
| 	h := middlewares[len(middlewares)-1](endpoint) |  | ||||||
| 	for i := len(middlewares) - 2; i >= 0; i-- { |  | ||||||
| 		h = middlewares[i](h) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return h |  | ||||||
| } |  | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,134 +0,0 @@ | ||||||
| // |  | ||||||
| // Package chi is a small, idiomatic and composable router for building HTTP services. |  | ||||||
| // |  | ||||||
| // chi requires Go 1.7 or newer. |  | ||||||
| // |  | ||||||
| // Example: |  | ||||||
| //  package main |  | ||||||
| // |  | ||||||
| //  import ( |  | ||||||
| //  	"net/http" |  | ||||||
| // |  | ||||||
| //  	"github.com/go-chi/chi" |  | ||||||
| //  	"github.com/go-chi/chi/middleware" |  | ||||||
| //  ) |  | ||||||
| // |  | ||||||
| //  func main() { |  | ||||||
| //  	r := chi.NewRouter() |  | ||||||
| //  	r.Use(middleware.Logger) |  | ||||||
| //  	r.Use(middleware.Recoverer) |  | ||||||
| // |  | ||||||
| //  	r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| //  		w.Write([]byte("root.")) |  | ||||||
| //  	}) |  | ||||||
| // |  | ||||||
| //  	http.ListenAndServe(":3333", r) |  | ||||||
| //  } |  | ||||||
| // |  | ||||||
| // See github.com/go-chi/chi/_examples/ for more in-depth examples. |  | ||||||
| // |  | ||||||
| // URL patterns allow for easy matching of path components in HTTP |  | ||||||
| // requests. The matching components can then be accessed using |  | ||||||
| // chi.URLParam(). All patterns must begin with a slash. |  | ||||||
| // |  | ||||||
| // A simple named placeholder {name} matches any sequence of characters |  | ||||||
| // up to the next / or the end of the URL. Trailing slashes on paths must |  | ||||||
| // be handled explicitly. |  | ||||||
| // |  | ||||||
| // A placeholder with a name followed by a colon allows a regular |  | ||||||
| // expression match, for example {number:\\d+}. The regular expression |  | ||||||
| // syntax is Go's normal regexp RE2 syntax, except that regular expressions |  | ||||||
| // including { or } are not supported, and / will never be |  | ||||||
| // matched. An anonymous regexp pattern is allowed, using an empty string |  | ||||||
| // before the colon in the placeholder, such as {:\\d+} |  | ||||||
| // |  | ||||||
| // The special placeholder of asterisk matches the rest of the requested |  | ||||||
| // URL. Any trailing characters in the pattern are ignored. This is the only |  | ||||||
| // placeholder which will match / characters. |  | ||||||
| // |  | ||||||
| // Examples: |  | ||||||
| //  "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" |  | ||||||
| //  "/user/{name}/info" matches "/user/jsmith/info" |  | ||||||
| //  "/page/*" matches "/page/intro/latest" |  | ||||||
| //  "/page/*/index" also matches "/page/intro/latest" |  | ||||||
| //  "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" |  | ||||||
| // |  | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import "net/http" |  | ||||||
| 
 |  | ||||||
| // NewRouter returns a new Mux object that implements the Router interface. |  | ||||||
| func NewRouter() *Mux { |  | ||||||
| 	return NewMux() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Router consisting of the core routing methods used by chi's Mux, |  | ||||||
| // using only the standard net/http. |  | ||||||
| type Router interface { |  | ||||||
| 	http.Handler |  | ||||||
| 	Routes |  | ||||||
| 
 |  | ||||||
| 	// Use appends one of more middlewares onto the Router stack. |  | ||||||
| 	Use(middlewares ...func(http.Handler) http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// With adds inline middlewares for an endpoint handler. |  | ||||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router |  | ||||||
| 
 |  | ||||||
| 	// Group adds a new inline-Router along the current routing |  | ||||||
| 	// path, with a fresh middleware stack for the inline-Router. |  | ||||||
| 	Group(fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Route mounts a sub-Router along a `pattern`` string. |  | ||||||
| 	Route(pattern string, fn func(r Router)) Router |  | ||||||
| 
 |  | ||||||
| 	// Mount attaches another http.Handler along ./pattern/* |  | ||||||
| 	Mount(pattern string, h http.Handler) |  | ||||||
| 
 |  | ||||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches |  | ||||||
| 	// all HTTP methods. |  | ||||||
| 	Handle(pattern string, h http.Handler) |  | ||||||
| 	HandleFunc(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// Method and MethodFunc adds routes for `pattern` that matches |  | ||||||
| 	// the `method` HTTP method. |  | ||||||
| 	Method(method, pattern string, h http.Handler) |  | ||||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// HTTP-method routing along `pattern` |  | ||||||
| 	Connect(pattern string, h http.HandlerFunc) |  | ||||||
| 	Delete(pattern string, h http.HandlerFunc) |  | ||||||
| 	Get(pattern string, h http.HandlerFunc) |  | ||||||
| 	Head(pattern string, h http.HandlerFunc) |  | ||||||
| 	Options(pattern string, h http.HandlerFunc) |  | ||||||
| 	Patch(pattern string, h http.HandlerFunc) |  | ||||||
| 	Post(pattern string, h http.HandlerFunc) |  | ||||||
| 	Put(pattern string, h http.HandlerFunc) |  | ||||||
| 	Trace(pattern string, h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// NotFound defines a handler to respond whenever a route could |  | ||||||
| 	// not be found. |  | ||||||
| 	NotFound(h http.HandlerFunc) |  | ||||||
| 
 |  | ||||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is |  | ||||||
| 	// not allowed. |  | ||||||
| 	MethodNotAllowed(h http.HandlerFunc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes interface adds two methods for router traversal, which is also |  | ||||||
| // used by the `docgen` subpackage to generation documentation for Routers. |  | ||||||
| type Routes interface { |  | ||||||
| 	// Routes returns the routing tree in an easily traversable structure. |  | ||||||
| 	Routes() []Route |  | ||||||
| 
 |  | ||||||
| 	// Middlewares returns the list of middlewares in use by the router. |  | ||||||
| 	Middlewares() Middlewares |  | ||||||
| 
 |  | ||||||
| 	// Match searches the routing tree for a handler that matches |  | ||||||
| 	// the method/path - similar to routing a http request, but without |  | ||||||
| 	// executing the handler thereafter. |  | ||||||
| 	Match(rctx *Context, method, path string) bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Middlewares type is a slice of standard middleware handlers with methods |  | ||||||
| // to compose middleware chains and http.Handler's. |  | ||||||
| type Middlewares []func(http.Handler) http.Handler |  | ||||||
							
								
								
									
										161
									
								
								vendor/github.com/go-chi/chi/context.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										161
									
								
								vendor/github.com/go-chi/chi/context.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,161 +0,0 @@ | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// RouteCtxKey is the context.Context key to store the request context. |  | ||||||
| 	RouteCtxKey = &contextKey{"RouteContext"} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Context is the default routing context set on the root node of a |  | ||||||
| // request context to track route patterns, URL parameters and |  | ||||||
| // an optional routing path. |  | ||||||
| type Context struct { |  | ||||||
| 	Routes Routes |  | ||||||
| 
 |  | ||||||
| 	// Routing path/method override used during the route search. |  | ||||||
| 	// See Mux#routeHTTP method. |  | ||||||
| 	RoutePath   string |  | ||||||
| 	RouteMethod string |  | ||||||
| 
 |  | ||||||
| 	// Routing pattern stack throughout the lifecycle of the request, |  | ||||||
| 	// across all connected routers. It is a record of all matching |  | ||||||
| 	// patterns across a stack of sub-routers. |  | ||||||
| 	RoutePatterns []string |  | ||||||
| 
 |  | ||||||
| 	// URLParams are the stack of routeParams captured during the |  | ||||||
| 	// routing lifecycle across a stack of sub-routers. |  | ||||||
| 	URLParams RouteParams |  | ||||||
| 
 |  | ||||||
| 	// The endpoint routing pattern that matched the request URI path |  | ||||||
| 	// or `RoutePath` of the current sub-router. This value will update |  | ||||||
| 	// during the lifecycle of a request passing through a stack of |  | ||||||
| 	// sub-routers. |  | ||||||
| 	routePattern string |  | ||||||
| 
 |  | ||||||
| 	// Route parameters matched for the current sub-router. It is |  | ||||||
| 	// intentionally unexported so it cant be tampered. |  | ||||||
| 	routeParams RouteParams |  | ||||||
| 
 |  | ||||||
| 	// methodNotAllowed hint |  | ||||||
| 	methodNotAllowed bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewRouteContext returns a new routing Context object. |  | ||||||
| func NewRouteContext() *Context { |  | ||||||
| 	return &Context{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Reset a routing context to its initial state. |  | ||||||
| func (x *Context) Reset() { |  | ||||||
| 	x.Routes = nil |  | ||||||
| 	x.RoutePath = "" |  | ||||||
| 	x.RouteMethod = "" |  | ||||||
| 	x.RoutePatterns = x.RoutePatterns[:0] |  | ||||||
| 	x.URLParams.Keys = x.URLParams.Keys[:0] |  | ||||||
| 	x.URLParams.Values = x.URLParams.Values[:0] |  | ||||||
| 
 |  | ||||||
| 	x.routePattern = "" |  | ||||||
| 	x.routeParams.Keys = x.routeParams.Keys[:0] |  | ||||||
| 	x.routeParams.Values = x.routeParams.Values[:0] |  | ||||||
| 	x.methodNotAllowed = false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParam returns the corresponding URL parameter value from the request |  | ||||||
| // routing context. |  | ||||||
| func (x *Context) URLParam(key string) string { |  | ||||||
| 	for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { |  | ||||||
| 		if x.URLParams.Keys[k] == key { |  | ||||||
| 			return x.URLParams.Values[k] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RoutePattern builds the routing pattern string for the particular |  | ||||||
| // request, at the particular point during routing. This means, the value |  | ||||||
| // will change throughout the execution of a request in a router. That is |  | ||||||
| // why its advised to only use this value after calling the next handler. |  | ||||||
| // |  | ||||||
| // For example, |  | ||||||
| // |  | ||||||
| //   func Instrument(next http.Handler) http.Handler { |  | ||||||
| //     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| //       next.ServeHTTP(w, r) |  | ||||||
| //       routePattern := chi.RouteContext(r.Context()).RoutePattern() |  | ||||||
| //       measure(w, r, routePattern) |  | ||||||
| //   	 }) |  | ||||||
| //   } |  | ||||||
| func (x *Context) RoutePattern() string { |  | ||||||
| 	routePattern := strings.Join(x.RoutePatterns, "") |  | ||||||
| 	return strings.Replace(routePattern, "/*/", "/", -1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RouteContext returns chi's routing Context object from a |  | ||||||
| // http.Request Context. |  | ||||||
| func RouteContext(ctx context.Context) *Context { |  | ||||||
| 	return ctx.Value(RouteCtxKey).(*Context) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParam returns the url parameter from a http.Request object. |  | ||||||
| func URLParam(r *http.Request, key string) string { |  | ||||||
| 	if rctx := RouteContext(r.Context()); rctx != nil { |  | ||||||
| 		return rctx.URLParam(key) |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // URLParamFromCtx returns the url parameter from a http.Request Context. |  | ||||||
| func URLParamFromCtx(ctx context.Context, key string) string { |  | ||||||
| 	if rctx := RouteContext(ctx); rctx != nil { |  | ||||||
| 		return rctx.URLParam(key) |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RouteParams is a structure to track URL routing parameters efficiently. |  | ||||||
| type RouteParams struct { |  | ||||||
| 	Keys, Values []string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Add will append a URL parameter to the end of the route param |  | ||||||
| func (s *RouteParams) Add(key, value string) { |  | ||||||
| 	(*s).Keys = append((*s).Keys, key) |  | ||||||
| 	(*s).Values = append((*s).Values, value) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ServerBaseContext wraps an http.Handler to set the request context to the |  | ||||||
| // `baseCtx`. |  | ||||||
| func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler { |  | ||||||
| 	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		ctx := r.Context() |  | ||||||
| 		baseCtx := baseCtx |  | ||||||
| 
 |  | ||||||
| 		// Copy over default net/http server context keys |  | ||||||
| 		if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok { |  | ||||||
| 			baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v) |  | ||||||
| 		} |  | ||||||
| 		if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok { |  | ||||||
| 			baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		h.ServeHTTP(w, r.WithContext(baseCtx)) |  | ||||||
| 	}) |  | ||||||
| 	return fn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // contextKey is a value for use with context.WithValue. It's used as |  | ||||||
| // a pointer so it fits in an interface{} without allocation. This technique |  | ||||||
| // for defining context keys was copied from Go 1.7's new use of context in net/http. |  | ||||||
| type contextKey struct { |  | ||||||
| 	name string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *contextKey) String() string { |  | ||||||
| 	return "chi context value " + k.name |  | ||||||
| } |  | ||||||
							
								
								
									
										275
									
								
								vendor/github.com/go-chi/chi/middleware/compress.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										275
									
								
								vendor/github.com/go-chi/chi/middleware/compress.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,275 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"compress/flate" |  | ||||||
| 	"compress/gzip" |  | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var encoders = map[string]EncoderFunc{} |  | ||||||
| 
 |  | ||||||
| var encodingPrecedence = []string{"br", "gzip", "deflate"} |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	// TODO: |  | ||||||
| 	// lzma: Opera. |  | ||||||
| 	// sdch: Chrome, Android. Gzip output + dictionary header. |  | ||||||
| 	// br:   Brotli, see https://github.com/go-chi/chi/pull/326 |  | ||||||
| 
 |  | ||||||
| 	// TODO: Exception for old MSIE browsers that can't handle non-HTML? |  | ||||||
| 	// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression |  | ||||||
| 	SetEncoder("gzip", encoderGzip) |  | ||||||
| 
 |  | ||||||
| 	// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951) |  | ||||||
| 	// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32 |  | ||||||
| 	// checksum compared to CRC-32 used in "gzip" and thus is faster. |  | ||||||
| 	// |  | ||||||
| 	// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect |  | ||||||
| 	// raw DEFLATE data only, without the mentioned zlib wrapper. |  | ||||||
| 	// Because of this major confusion, most modern browsers try it |  | ||||||
| 	// both ways, first looking for zlib headers. |  | ||||||
| 	// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548 |  | ||||||
| 	// |  | ||||||
| 	// The list of browsers having problems is quite big, see: |  | ||||||
| 	// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression |  | ||||||
| 	// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results |  | ||||||
| 	// |  | ||||||
| 	// That's why we prefer gzip over deflate. It's just more reliable |  | ||||||
| 	// and not significantly slower than gzip. |  | ||||||
| 	SetEncoder("deflate", encoderDeflate) |  | ||||||
| 
 |  | ||||||
| 	// NOTE: Not implemented, intentionally: |  | ||||||
| 	// case "compress": // LZW. Deprecated. |  | ||||||
| 	// case "bzip2":    // Too slow on-the-fly. |  | ||||||
| 	// case "zopfli":   // Too slow on-the-fly. |  | ||||||
| 	// case "xz":       // Too slow on-the-fly. |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // An EncoderFunc is a function that wraps the provided ResponseWriter with a |  | ||||||
| // streaming compression algorithm and returns it. |  | ||||||
| // |  | ||||||
| // In case of failure, the function should return nil. |  | ||||||
| type EncoderFunc func(w http.ResponseWriter, level int) io.Writer |  | ||||||
| 
 |  | ||||||
| // SetEncoder can be used to set the implementation of a compression algorithm. |  | ||||||
| // |  | ||||||
| // The encoding should be a standardised identifier. See: |  | ||||||
| // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding |  | ||||||
| // |  | ||||||
| // For example, add the Brotli algortithm: |  | ||||||
| // |  | ||||||
| //  import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc" |  | ||||||
| // |  | ||||||
| //  middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer { |  | ||||||
| //    params := brotli_enc.NewBrotliParams() |  | ||||||
| //    params.SetQuality(level) |  | ||||||
| //    return brotli_enc.NewBrotliWriter(params, w) |  | ||||||
| //  }) |  | ||||||
| func SetEncoder(encoding string, fn EncoderFunc) { |  | ||||||
| 	encoding = strings.ToLower(encoding) |  | ||||||
| 	if encoding == "" { |  | ||||||
| 		panic("the encoding can not be empty") |  | ||||||
| 	} |  | ||||||
| 	if fn == nil { |  | ||||||
| 		panic("attempted to set a nil encoder function") |  | ||||||
| 	} |  | ||||||
| 	encoders[encoding] = fn |  | ||||||
| 
 |  | ||||||
| 	var e string |  | ||||||
| 	for _, v := range encodingPrecedence { |  | ||||||
| 		if v == encoding { |  | ||||||
| 			e = v |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if e == "" { |  | ||||||
| 		encodingPrecedence = append([]string{e}, encodingPrecedence...) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var defaultContentTypes = map[string]struct{}{ |  | ||||||
| 	"text/html":                {}, |  | ||||||
| 	"text/css":                 {}, |  | ||||||
| 	"text/plain":               {}, |  | ||||||
| 	"text/javascript":          {}, |  | ||||||
| 	"application/javascript":   {}, |  | ||||||
| 	"application/x-javascript": {}, |  | ||||||
| 	"application/json":         {}, |  | ||||||
| 	"application/atom+xml":     {}, |  | ||||||
| 	"application/rss+xml":      {}, |  | ||||||
| 	"image/svg+xml":            {}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DefaultCompress is a middleware that compresses response |  | ||||||
| // body of predefined content types to a data format based |  | ||||||
| // on Accept-Encoding request header. It uses a default |  | ||||||
| // compression level. |  | ||||||
| func DefaultCompress(next http.Handler) http.Handler { |  | ||||||
| 	return Compress(flate.DefaultCompression)(next) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Compress is a middleware that compresses response |  | ||||||
| // body of a given content types to a data format based |  | ||||||
| // on Accept-Encoding request header. It uses a given |  | ||||||
| // compression level. |  | ||||||
| // |  | ||||||
| // NOTE: make sure to set the Content-Type header on your response |  | ||||||
| // otherwise this middleware will not compress the response body. For ex, in |  | ||||||
| // your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) |  | ||||||
| // or set it manually. |  | ||||||
| func Compress(level int, types ...string) func(next http.Handler) http.Handler { |  | ||||||
| 	contentTypes := defaultContentTypes |  | ||||||
| 	if len(types) > 0 { |  | ||||||
| 		contentTypes = make(map[string]struct{}, len(types)) |  | ||||||
| 		for _, t := range types { |  | ||||||
| 			contentTypes[t] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			encoder, encoding := selectEncoder(r.Header) |  | ||||||
| 
 |  | ||||||
| 			cw := &compressResponseWriter{ |  | ||||||
| 				ResponseWriter: w, |  | ||||||
| 				w:              w, |  | ||||||
| 				contentTypes:   contentTypes, |  | ||||||
| 				encoder:        encoder, |  | ||||||
| 				encoding:       encoding, |  | ||||||
| 				level:          level, |  | ||||||
| 			} |  | ||||||
| 			defer cw.Close() |  | ||||||
| 
 |  | ||||||
| 			next.ServeHTTP(cw, r) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func selectEncoder(h http.Header) (EncoderFunc, string) { |  | ||||||
| 	header := h.Get("Accept-Encoding") |  | ||||||
| 
 |  | ||||||
| 	// Parse the names of all accepted algorithms from the header. |  | ||||||
| 	accepted := strings.Split(strings.ToLower(header), ",") |  | ||||||
| 
 |  | ||||||
| 	// Find supported encoder by accepted list by precedence |  | ||||||
| 	for _, name := range encodingPrecedence { |  | ||||||
| 		if fn, ok := encoders[name]; ok && matchAcceptEncoding(accepted, name) { |  | ||||||
| 			return fn, name |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// No encoder found to match the accepted encoding |  | ||||||
| 	return nil, "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func matchAcceptEncoding(accepted []string, encoding string) bool { |  | ||||||
| 	for _, v := range accepted { |  | ||||||
| 		if strings.Index(v, encoding) >= 0 { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type compressResponseWriter struct { |  | ||||||
| 	http.ResponseWriter |  | ||||||
| 	w            io.Writer |  | ||||||
| 	encoder      EncoderFunc |  | ||||||
| 	encoding     string |  | ||||||
| 	contentTypes map[string]struct{} |  | ||||||
| 	level        int |  | ||||||
| 	wroteHeader  bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) WriteHeader(code int) { |  | ||||||
| 	if cw.wroteHeader { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	cw.wroteHeader = true |  | ||||||
| 	defer cw.ResponseWriter.WriteHeader(code) |  | ||||||
| 
 |  | ||||||
| 	// Already compressed data? |  | ||||||
| 	if cw.Header().Get("Content-Encoding") != "" { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Parse the first part of the Content-Type response header. |  | ||||||
| 	contentType := "" |  | ||||||
| 	parts := strings.Split(cw.Header().Get("Content-Type"), ";") |  | ||||||
| 	if len(parts) > 0 { |  | ||||||
| 		contentType = parts[0] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Is the content type compressable? |  | ||||||
| 	if _, ok := cw.contentTypes[contentType]; !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if cw.encoder != nil && cw.encoding != "" { |  | ||||||
| 		if wr := cw.encoder(cw.ResponseWriter, cw.level); wr != nil { |  | ||||||
| 			cw.w = wr |  | ||||||
| 			cw.Header().Set("Content-Encoding", cw.encoding) |  | ||||||
| 
 |  | ||||||
| 			// The content-length after compression is unknown |  | ||||||
| 			cw.Header().Del("Content-Length") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) Write(p []byte) (int, error) { |  | ||||||
| 	if !cw.wroteHeader { |  | ||||||
| 		cw.WriteHeader(http.StatusOK) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return cw.w.Write(p) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) Flush() { |  | ||||||
| 	if f, ok := cw.w.(http.Flusher); ok { |  | ||||||
| 		f.Flush() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { |  | ||||||
| 	if hj, ok := cw.w.(http.Hijacker); ok { |  | ||||||
| 		return hj.Hijack() |  | ||||||
| 	} |  | ||||||
| 	return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error { |  | ||||||
| 	if ps, ok := cw.w.(http.Pusher); ok { |  | ||||||
| 		return ps.Push(target, opts) |  | ||||||
| 	} |  | ||||||
| 	return errors.New("chi/middleware: http.Pusher is unavailable on the writer") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (cw *compressResponseWriter) Close() error { |  | ||||||
| 	if c, ok := cw.w.(io.WriteCloser); ok { |  | ||||||
| 		return c.Close() |  | ||||||
| 	} |  | ||||||
| 	return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func encoderGzip(w http.ResponseWriter, level int) io.Writer { |  | ||||||
| 	gw, err := gzip.NewWriterLevel(w, level) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return gw |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func encoderDeflate(w http.ResponseWriter, level int) io.Writer { |  | ||||||
| 	dw, err := flate.NewWriter(w, level) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return dw |  | ||||||
| } |  | ||||||
							
								
								
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_charset.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_charset.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,51 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match. |  | ||||||
| // An empty charset will allow requests with no Content-Type header or no specified charset. |  | ||||||
| func ContentCharset(charsets ...string) func(next http.Handler) http.Handler { |  | ||||||
| 	for i, c := range charsets { |  | ||||||
| 		charsets[i] = strings.ToLower(c) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			if !contentEncoding(r.Header.Get("Content-Type"), charsets...) { |  | ||||||
| 				w.WriteHeader(http.StatusUnsupportedMediaType) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			next.ServeHTTP(w, r) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Check the content encoding against a list of acceptable values. |  | ||||||
| func contentEncoding(ce string, charsets ...string) bool { |  | ||||||
| 	_, ce = split(strings.ToLower(ce), ";") |  | ||||||
| 	_, ce = split(ce, "charset=") |  | ||||||
| 	ce, _ = split(ce, ";") |  | ||||||
| 	for _, c := range charsets { |  | ||||||
| 		if ce == c { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Split a string in two parts, cleaning any whitespace. |  | ||||||
| func split(str, sep string) (string, string) { |  | ||||||
| 	var a, b string |  | ||||||
| 	var parts = strings.SplitN(str, sep, 2) |  | ||||||
| 	a = strings.TrimSpace(parts[0]) |  | ||||||
| 	if len(parts) == 2 { |  | ||||||
| 		b = strings.TrimSpace(parts[1]) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return a, b |  | ||||||
| } |  | ||||||
							
								
								
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_type.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_type.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,51 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // SetHeader is a convenience handler to set a response header key/value |  | ||||||
| func SetHeader(key, value string) func(next http.Handler) http.Handler { |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			w.Header().Set(key, value) |  | ||||||
| 			next.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // AllowContentType enforces a whitelist of request Content-Types otherwise responds |  | ||||||
| // with a 415 Unsupported Media Type status. |  | ||||||
| func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler { |  | ||||||
| 	cT := []string{} |  | ||||||
| 	for _, t := range contentTypes { |  | ||||||
| 		cT = append(cT, strings.ToLower(t)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			if r.ContentLength == 0 { |  | ||||||
| 				// skip check for empty content body |  | ||||||
| 				next.ServeHTTP(w, r) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type"))) |  | ||||||
| 			if i := strings.Index(s, ";"); i > -1 { |  | ||||||
| 				s = s[0:i] |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			for _, t := range cT { |  | ||||||
| 				if t == s { |  | ||||||
| 					next.ServeHTTP(w, r) |  | ||||||
| 					return |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			w.WriteHeader(http.StatusUnsupportedMediaType) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/get_head.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/get_head.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,39 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // GetHead automatically route undefined HEAD requests to GET handlers. |  | ||||||
| func GetHead(next http.Handler) http.Handler { |  | ||||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		if r.Method == "HEAD" { |  | ||||||
| 			rctx := chi.RouteContext(r.Context()) |  | ||||||
| 			routePath := rctx.RoutePath |  | ||||||
| 			if routePath == "" { |  | ||||||
| 				if r.URL.RawPath != "" { |  | ||||||
| 					routePath = r.URL.RawPath |  | ||||||
| 				} else { |  | ||||||
| 					routePath = r.URL.Path |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// Temporary routing context to look-ahead before routing the request |  | ||||||
| 			tctx := chi.NewRouteContext() |  | ||||||
| 
 |  | ||||||
| 			// Attempt to find a HEAD handler for the routing path, if not found, traverse |  | ||||||
| 			// the router as through its a GET route, but proceed with the request |  | ||||||
| 			// with the HEAD method. |  | ||||||
| 			if !rctx.Routes.Match(tctx, "HEAD", routePath) { |  | ||||||
| 				rctx.RouteMethod = "GET" |  | ||||||
| 				rctx.RoutePath = routePath |  | ||||||
| 				next.ServeHTTP(w, r) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
							
								
								
									
										26
									
								
								vendor/github.com/go-chi/chi/middleware/heartbeat.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/go-chi/chi/middleware/heartbeat.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,26 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Heartbeat endpoint middleware useful to setting up a path like |  | ||||||
| // `/ping` that load balancers or uptime testing external services |  | ||||||
| // can make a request before hitting any routes. It's also convenient |  | ||||||
| // to place this above ACL middlewares as well. |  | ||||||
| func Heartbeat(endpoint string) func(http.Handler) http.Handler { |  | ||||||
| 	f := func(h http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			if r.Method == "GET" && strings.EqualFold(r.URL.Path, endpoint) { |  | ||||||
| 				w.Header().Set("Content-Type", "text/plain") |  | ||||||
| 				w.WriteHeader(http.StatusOK) |  | ||||||
| 				w.Write([]byte(".")) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			h.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| 	return f |  | ||||||
| } |  | ||||||
							
								
								
									
										158
									
								
								vendor/github.com/go-chi/chi/middleware/logger.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										158
									
								
								vendor/github.com/go-chi/chi/middleware/logger.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,158 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// LogEntryCtxKey is the context.Context key to store the request log entry. |  | ||||||
| 	LogEntryCtxKey = &contextKey{"LogEntry"} |  | ||||||
| 
 |  | ||||||
| 	// DefaultLogger is called by the Logger middleware handler to log each request. |  | ||||||
| 	// Its made a package-level variable so that it can be reconfigured for custom |  | ||||||
| 	// logging configurations. |  | ||||||
| 	DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false}) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Logger is a middleware that logs the start and end of each request, along |  | ||||||
| // with some useful data about what was requested, what the response status was, |  | ||||||
| // and how long it took to return. When standard output is a TTY, Logger will |  | ||||||
| // print in color, otherwise it will print in black and white. Logger prints a |  | ||||||
| // request ID if one is provided. |  | ||||||
| // |  | ||||||
| // Alternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger` |  | ||||||
| // middleware pkg. |  | ||||||
| func Logger(next http.Handler) http.Handler { |  | ||||||
| 	return DefaultLogger(next) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RequestLogger returns a logger handler using a custom LogFormatter. |  | ||||||
| func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler { |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			entry := f.NewLogEntry(r) |  | ||||||
| 			ww := NewWrapResponseWriter(w, r.ProtoMajor) |  | ||||||
| 
 |  | ||||||
| 			t1 := time.Now() |  | ||||||
| 			defer func() { |  | ||||||
| 				entry.Write(ww.Status(), ww.BytesWritten(), time.Since(t1)) |  | ||||||
| 			}() |  | ||||||
| 
 |  | ||||||
| 			next.ServeHTTP(ww, WithLogEntry(r, entry)) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LogFormatter initiates the beginning of a new LogEntry per request. |  | ||||||
| // See DefaultLogFormatter for an example implementation. |  | ||||||
| type LogFormatter interface { |  | ||||||
| 	NewLogEntry(r *http.Request) LogEntry |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LogEntry records the final log when a request completes. |  | ||||||
| // See defaultLogEntry for an example implementation. |  | ||||||
| type LogEntry interface { |  | ||||||
| 	Write(status, bytes int, elapsed time.Duration) |  | ||||||
| 	Panic(v interface{}, stack []byte) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetLogEntry returns the in-context LogEntry for a request. |  | ||||||
| func GetLogEntry(r *http.Request) LogEntry { |  | ||||||
| 	entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry) |  | ||||||
| 	return entry |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WithLogEntry sets the in-context LogEntry for a request. |  | ||||||
| func WithLogEntry(r *http.Request, entry LogEntry) *http.Request { |  | ||||||
| 	r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry)) |  | ||||||
| 	return r |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LoggerInterface accepts printing to stdlib logger or compatible logger. |  | ||||||
| type LoggerInterface interface { |  | ||||||
| 	Print(v ...interface{}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DefaultLogFormatter is a simple logger that implements a LogFormatter. |  | ||||||
| type DefaultLogFormatter struct { |  | ||||||
| 	Logger  LoggerInterface |  | ||||||
| 	NoColor bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewLogEntry creates a new LogEntry for the request. |  | ||||||
| func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry { |  | ||||||
| 	useColor := !l.NoColor |  | ||||||
| 	entry := &defaultLogEntry{ |  | ||||||
| 		DefaultLogFormatter: l, |  | ||||||
| 		request:             r, |  | ||||||
| 		buf:                 &bytes.Buffer{}, |  | ||||||
| 		useColor:            useColor, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	reqID := GetReqID(r.Context()) |  | ||||||
| 	if reqID != "" { |  | ||||||
| 		cW(entry.buf, useColor, nYellow, "[%s] ", reqID) |  | ||||||
| 	} |  | ||||||
| 	cW(entry.buf, useColor, nCyan, "\"") |  | ||||||
| 	cW(entry.buf, useColor, bMagenta, "%s ", r.Method) |  | ||||||
| 
 |  | ||||||
| 	scheme := "http" |  | ||||||
| 	if r.TLS != nil { |  | ||||||
| 		scheme = "https" |  | ||||||
| 	} |  | ||||||
| 	cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto) |  | ||||||
| 
 |  | ||||||
| 	entry.buf.WriteString("from ") |  | ||||||
| 	entry.buf.WriteString(r.RemoteAddr) |  | ||||||
| 	entry.buf.WriteString(" - ") |  | ||||||
| 
 |  | ||||||
| 	return entry |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type defaultLogEntry struct { |  | ||||||
| 	*DefaultLogFormatter |  | ||||||
| 	request  *http.Request |  | ||||||
| 	buf      *bytes.Buffer |  | ||||||
| 	useColor bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) { |  | ||||||
| 	switch { |  | ||||||
| 	case status < 200: |  | ||||||
| 		cW(l.buf, l.useColor, bBlue, "%03d", status) |  | ||||||
| 	case status < 300: |  | ||||||
| 		cW(l.buf, l.useColor, bGreen, "%03d", status) |  | ||||||
| 	case status < 400: |  | ||||||
| 		cW(l.buf, l.useColor, bCyan, "%03d", status) |  | ||||||
| 	case status < 500: |  | ||||||
| 		cW(l.buf, l.useColor, bYellow, "%03d", status) |  | ||||||
| 	default: |  | ||||||
| 		cW(l.buf, l.useColor, bRed, "%03d", status) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cW(l.buf, l.useColor, bBlue, " %dB", bytes) |  | ||||||
| 
 |  | ||||||
| 	l.buf.WriteString(" in ") |  | ||||||
| 	if elapsed < 500*time.Millisecond { |  | ||||||
| 		cW(l.buf, l.useColor, nGreen, "%s", elapsed) |  | ||||||
| 	} else if elapsed < 5*time.Second { |  | ||||||
| 		cW(l.buf, l.useColor, nYellow, "%s", elapsed) |  | ||||||
| 	} else { |  | ||||||
| 		cW(l.buf, l.useColor, nRed, "%s", elapsed) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	l.Logger.Print(l.buf.String()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *defaultLogEntry) Panic(v interface{}, stack []byte) { |  | ||||||
| 	panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry) |  | ||||||
| 	cW(panicEntry.buf, l.useColor, bRed, "panic: %+v", v) |  | ||||||
| 	l.Logger.Print(panicEntry.buf.String()) |  | ||||||
| 	l.Logger.Print(string(stack)) |  | ||||||
| } |  | ||||||
							
								
								
									
										12
									
								
								vendor/github.com/go-chi/chi/middleware/middleware.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/go-chi/chi/middleware/middleware.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,12 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // contextKey is a value for use with context.WithValue. It's used as |  | ||||||
| // a pointer so it fits in an interface{} without allocation. This technique |  | ||||||
| // for defining context keys was copied from Go 1.7's new use of context in net/http. |  | ||||||
| type contextKey struct { |  | ||||||
| 	name string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (k *contextKey) String() string { |  | ||||||
| 	return "chi/middleware context value " + k.name |  | ||||||
| } |  | ||||||
							
								
								
									
										58
									
								
								vendor/github.com/go-chi/chi/middleware/nocache.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/go-chi/chi/middleware/nocache.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,58 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // Ported from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Unix epoch time |  | ||||||
| var epoch = time.Unix(0, 0).Format(time.RFC1123) |  | ||||||
| 
 |  | ||||||
| // Taken from https://github.com/mytrile/nocache |  | ||||||
| var noCacheHeaders = map[string]string{ |  | ||||||
| 	"Expires":         epoch, |  | ||||||
| 	"Cache-Control":   "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", |  | ||||||
| 	"Pragma":          "no-cache", |  | ||||||
| 	"X-Accel-Expires": "0", |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var etagHeaders = []string{ |  | ||||||
| 	"ETag", |  | ||||||
| 	"If-Modified-Since", |  | ||||||
| 	"If-Match", |  | ||||||
| 	"If-None-Match", |  | ||||||
| 	"If-Range", |  | ||||||
| 	"If-Unmodified-Since", |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent |  | ||||||
| // a router (or subrouter) from being cached by an upstream proxy and/or client. |  | ||||||
| // |  | ||||||
| // As per http://wiki.nginx.org/HttpProxyModule - NoCache sets: |  | ||||||
| //      Expires: Thu, 01 Jan 1970 00:00:00 UTC |  | ||||||
| //      Cache-Control: no-cache, private, max-age=0 |  | ||||||
| //      X-Accel-Expires: 0 |  | ||||||
| //      Pragma: no-cache (for HTTP/1.0 proxies/clients) |  | ||||||
| func NoCache(h http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| 		// Delete any ETag headers that may have been set |  | ||||||
| 		for _, v := range etagHeaders { |  | ||||||
| 			if r.Header.Get(v) != "" { |  | ||||||
| 				r.Header.Del(v) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Set our NoCache headers |  | ||||||
| 		for k, v := range noCacheHeaders { |  | ||||||
| 			w.Header().Set(k, v) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		h.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
							
								
								
									
										55
									
								
								vendor/github.com/go-chi/chi/middleware/profiler.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/go-chi/chi/middleware/profiler.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,55 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"expvar" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/pprof" |  | ||||||
| 
 |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Profiler is a convenient subrouter used for mounting net/http/pprof. ie. |  | ||||||
| // |  | ||||||
| //  func MyService() http.Handler { |  | ||||||
| //    r := chi.NewRouter() |  | ||||||
| //    // ..middlewares |  | ||||||
| //    r.Mount("/debug", middleware.Profiler()) |  | ||||||
| //    // ..routes |  | ||||||
| //    return r |  | ||||||
| //  } |  | ||||||
| func Profiler() http.Handler { |  | ||||||
| 	r := chi.NewRouter() |  | ||||||
| 	r.Use(NoCache) |  | ||||||
| 
 |  | ||||||
| 	r.Get("/", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		http.Redirect(w, r, r.RequestURI+"/pprof/", 301) |  | ||||||
| 	}) |  | ||||||
| 	r.HandleFunc("/pprof", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		http.Redirect(w, r, r.RequestURI+"/", 301) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	r.HandleFunc("/pprof/*", pprof.Index) |  | ||||||
| 	r.HandleFunc("/pprof/cmdline", pprof.Cmdline) |  | ||||||
| 	r.HandleFunc("/pprof/profile", pprof.Profile) |  | ||||||
| 	r.HandleFunc("/pprof/symbol", pprof.Symbol) |  | ||||||
| 	r.HandleFunc("/pprof/trace", pprof.Trace) |  | ||||||
| 	r.HandleFunc("/vars", expVars) |  | ||||||
| 
 |  | ||||||
| 	return r |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Replicated from expvar.go as not public. |  | ||||||
| func expVars(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	first := true |  | ||||||
| 	w.Header().Set("Content-Type", "application/json") |  | ||||||
| 	fmt.Fprintf(w, "{\n") |  | ||||||
| 	expvar.Do(func(kv expvar.KeyValue) { |  | ||||||
| 		if !first { |  | ||||||
| 			fmt.Fprintf(w, ",\n") |  | ||||||
| 		} |  | ||||||
| 		first = false |  | ||||||
| 		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) |  | ||||||
| 	}) |  | ||||||
| 	fmt.Fprintf(w, "\n}\n") |  | ||||||
| } |  | ||||||
							
								
								
									
										54
									
								
								vendor/github.com/go-chi/chi/middleware/realip.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/go-chi/chi/middleware/realip.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,54 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // Ported from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") |  | ||||||
| var xRealIP = http.CanonicalHeaderKey("X-Real-IP") |  | ||||||
| 
 |  | ||||||
| // RealIP is a middleware that sets a http.Request's RemoteAddr to the results |  | ||||||
| // of parsing either the X-Forwarded-For header or the X-Real-IP header (in that |  | ||||||
| // order). |  | ||||||
| // |  | ||||||
| // This middleware should be inserted fairly early in the middleware stack to |  | ||||||
| // ensure that subsequent layers (e.g., request loggers) which examine the |  | ||||||
| // RemoteAddr will see the intended value. |  | ||||||
| // |  | ||||||
| // You should only use this middleware if you can trust the headers passed to |  | ||||||
| // you (in particular, the two headers this middleware uses), for example |  | ||||||
| // because you have placed a reverse proxy like HAProxy or nginx in front of |  | ||||||
| // chi. If your reverse proxies are configured to pass along arbitrary header |  | ||||||
| // values from the client, or if you use this middleware without a reverse |  | ||||||
| // proxy, malicious clients will be able to make you very sad (or, depending on |  | ||||||
| // how you're using RemoteAddr, vulnerable to an attack of some sort). |  | ||||||
| func RealIP(h http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		if rip := realIP(r); rip != "" { |  | ||||||
| 			r.RemoteAddr = rip |  | ||||||
| 		} |  | ||||||
| 		h.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func realIP(r *http.Request) string { |  | ||||||
| 	var ip string |  | ||||||
| 
 |  | ||||||
| 	if xff := r.Header.Get(xForwardedFor); xff != "" { |  | ||||||
| 		i := strings.Index(xff, ", ") |  | ||||||
| 		if i == -1 { |  | ||||||
| 			i = len(xff) |  | ||||||
| 		} |  | ||||||
| 		ip = xff[:i] |  | ||||||
| 	} else if xrip := r.Header.Get(xRealIP); xrip != "" { |  | ||||||
| 		ip = xrip |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return ip |  | ||||||
| } |  | ||||||
							
								
								
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/recoverer.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/recoverer.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,39 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // The original work was derived from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"runtime/debug" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Recoverer is a middleware that recovers from panics, logs the panic (and a |  | ||||||
| // backtrace), and returns a HTTP 500 (Internal Server Error) status if |  | ||||||
| // possible. Recoverer prints a request ID if one is provided. |  | ||||||
| // |  | ||||||
| // Alternatively, look at https://github.com/pressly/lg middleware pkgs. |  | ||||||
| func Recoverer(next http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		defer func() { |  | ||||||
| 			if rvr := recover(); rvr != nil { |  | ||||||
| 
 |  | ||||||
| 				logEntry := GetLogEntry(r) |  | ||||||
| 				if logEntry != nil { |  | ||||||
| 					logEntry.Panic(rvr, debug.Stack()) |  | ||||||
| 				} else { |  | ||||||
| 					fmt.Fprintf(os.Stderr, "Panic: %+v\n", rvr) |  | ||||||
| 					debug.PrintStack() |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 
 |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
							
								
								
									
										92
									
								
								vendor/github.com/go-chi/chi/middleware/request_id.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										92
									
								
								vendor/github.com/go-chi/chi/middleware/request_id.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,92 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // Ported from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync/atomic" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Key to use when setting the request ID. |  | ||||||
| type ctxKeyRequestID int |  | ||||||
| 
 |  | ||||||
| // RequestIDKey is the key that holds the unique request ID in a request context. |  | ||||||
| const RequestIDKey ctxKeyRequestID = 0 |  | ||||||
| 
 |  | ||||||
| var prefix string |  | ||||||
| var reqid uint64 |  | ||||||
| 
 |  | ||||||
| // A quick note on the statistics here: we're trying to calculate the chance that |  | ||||||
| // two randomly generated base62 prefixes will collide. We use the formula from |  | ||||||
| // http://en.wikipedia.org/wiki/Birthday_problem |  | ||||||
| // |  | ||||||
| // P[m, n] \approx 1 - e^{-m^2/2n} |  | ||||||
| // |  | ||||||
| // We ballpark an upper bound for $m$ by imagining (for whatever reason) a server |  | ||||||
| // that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$ |  | ||||||
| // |  | ||||||
| // For a $k$ character base-62 identifier, we have $n(k) = 62^k$ |  | ||||||
| // |  | ||||||
| // Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for |  | ||||||
| // our purposes, and is surely more than anyone would ever need in practice -- a |  | ||||||
| // process that is rebooted a handful of times a day for a hundred years has less |  | ||||||
| // than a millionth of a percent chance of generating two colliding IDs. |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	hostname, err := os.Hostname() |  | ||||||
| 	if hostname == "" || err != nil { |  | ||||||
| 		hostname = "localhost" |  | ||||||
| 	} |  | ||||||
| 	var buf [12]byte |  | ||||||
| 	var b64 string |  | ||||||
| 	for len(b64) < 10 { |  | ||||||
| 		rand.Read(buf[:]) |  | ||||||
| 		b64 = base64.StdEncoding.EncodeToString(buf[:]) |  | ||||||
| 		b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RequestID is a middleware that injects a request ID into the context of each |  | ||||||
| // request. A request ID is a string of the form "host.example.com/random-0001", |  | ||||||
| // where "random" is a base62 random string that uniquely identifies this go |  | ||||||
| // process, and where the last number is an atomically incremented request |  | ||||||
| // counter. |  | ||||||
| func RequestID(next http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		ctx := r.Context() |  | ||||||
| 		requestID := r.Header.Get("X-Request-Id") |  | ||||||
| 		if requestID == "" { |  | ||||||
| 			myid := atomic.AddUint64(&reqid, 1) |  | ||||||
| 			requestID = fmt.Sprintf("%s-%06d", prefix, myid) |  | ||||||
| 		} |  | ||||||
| 		ctx = context.WithValue(ctx, RequestIDKey, requestID) |  | ||||||
| 		next.ServeHTTP(w, r.WithContext(ctx)) |  | ||||||
| 	} |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetReqID returns a request ID from the given context if one is present. |  | ||||||
| // Returns the empty string if a request ID cannot be found. |  | ||||||
| func GetReqID(ctx context.Context) string { |  | ||||||
| 	if ctx == nil { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	if reqID, ok := ctx.Value(RequestIDKey).(string); ok { |  | ||||||
| 		return reqID |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NextRequestID generates the next request ID in the sequence. |  | ||||||
| func NextRequestID() uint64 { |  | ||||||
| 	return atomic.AddUint64(&reqid, 1) |  | ||||||
| } |  | ||||||
							
								
								
									
										56
									
								
								vendor/github.com/go-chi/chi/middleware/strip.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										56
									
								
								vendor/github.com/go-chi/chi/middleware/strip.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,56 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // StripSlashes is a middleware that will match request paths with a trailing |  | ||||||
| // slash, strip it from the path and continue routing through the mux, if a route |  | ||||||
| // matches, then it will serve the handler. |  | ||||||
| func StripSlashes(next http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		var path string |  | ||||||
| 		rctx := chi.RouteContext(r.Context()) |  | ||||||
| 		if rctx.RoutePath != "" { |  | ||||||
| 			path = rctx.RoutePath |  | ||||||
| 		} else { |  | ||||||
| 			path = r.URL.Path |  | ||||||
| 		} |  | ||||||
| 		if len(path) > 1 && path[len(path)-1] == '/' { |  | ||||||
| 			rctx.RoutePath = path[:len(path)-1] |  | ||||||
| 		} |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RedirectSlashes is a middleware that will match request paths with a trailing |  | ||||||
| // slash and redirect to the same path, less the trailing slash. |  | ||||||
| // |  | ||||||
| // NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer, |  | ||||||
| // see https://github.com/go-chi/chi/issues/343 |  | ||||||
| func RedirectSlashes(next http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		var path string |  | ||||||
| 		rctx := chi.RouteContext(r.Context()) |  | ||||||
| 		if rctx.RoutePath != "" { |  | ||||||
| 			path = rctx.RoutePath |  | ||||||
| 		} else { |  | ||||||
| 			path = r.URL.Path |  | ||||||
| 		} |  | ||||||
| 		if len(path) > 1 && path[len(path)-1] == '/' { |  | ||||||
| 			if r.URL.RawQuery != "" { |  | ||||||
| 				path = fmt.Sprintf("%s?%s", path[:len(path)-1], r.URL.RawQuery) |  | ||||||
| 			} else { |  | ||||||
| 				path = path[:len(path)-1] |  | ||||||
| 			} |  | ||||||
| 			http.Redirect(w, r, path, 301) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
							
								
								
									
										63
									
								
								vendor/github.com/go-chi/chi/middleware/terminal.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								vendor/github.com/go-chi/chi/middleware/terminal.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,63 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // Ported from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// Normal colors |  | ||||||
| 	nBlack   = []byte{'\033', '[', '3', '0', 'm'} |  | ||||||
| 	nRed     = []byte{'\033', '[', '3', '1', 'm'} |  | ||||||
| 	nGreen   = []byte{'\033', '[', '3', '2', 'm'} |  | ||||||
| 	nYellow  = []byte{'\033', '[', '3', '3', 'm'} |  | ||||||
| 	nBlue    = []byte{'\033', '[', '3', '4', 'm'} |  | ||||||
| 	nMagenta = []byte{'\033', '[', '3', '5', 'm'} |  | ||||||
| 	nCyan    = []byte{'\033', '[', '3', '6', 'm'} |  | ||||||
| 	nWhite   = []byte{'\033', '[', '3', '7', 'm'} |  | ||||||
| 	// Bright colors |  | ||||||
| 	bBlack   = []byte{'\033', '[', '3', '0', ';', '1', 'm'} |  | ||||||
| 	bRed     = []byte{'\033', '[', '3', '1', ';', '1', 'm'} |  | ||||||
| 	bGreen   = []byte{'\033', '[', '3', '2', ';', '1', 'm'} |  | ||||||
| 	bYellow  = []byte{'\033', '[', '3', '3', ';', '1', 'm'} |  | ||||||
| 	bBlue    = []byte{'\033', '[', '3', '4', ';', '1', 'm'} |  | ||||||
| 	bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} |  | ||||||
| 	bCyan    = []byte{'\033', '[', '3', '6', ';', '1', 'm'} |  | ||||||
| 	bWhite   = []byte{'\033', '[', '3', '7', ';', '1', 'm'} |  | ||||||
| 
 |  | ||||||
| 	reset = []byte{'\033', '[', '0', 'm'} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var isTTY bool |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	// This is sort of cheating: if stdout is a character device, we assume |  | ||||||
| 	// that means it's a TTY. Unfortunately, there are many non-TTY |  | ||||||
| 	// character devices, but fortunately stdout is rarely set to any of |  | ||||||
| 	// them. |  | ||||||
| 	// |  | ||||||
| 	// We could solve this properly by pulling in a dependency on |  | ||||||
| 	// code.google.com/p/go.crypto/ssh/terminal, for instance, but as a |  | ||||||
| 	// heuristic for whether to print in color or in black-and-white, I'd |  | ||||||
| 	// really rather not. |  | ||||||
| 	fi, err := os.Stdout.Stat() |  | ||||||
| 	if err == nil { |  | ||||||
| 		m := os.ModeDevice | os.ModeCharDevice |  | ||||||
| 		isTTY = fi.Mode()&m == m |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // colorWrite |  | ||||||
| func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) { |  | ||||||
| 	if isTTY && useColor { |  | ||||||
| 		w.Write(color) |  | ||||||
| 	} |  | ||||||
| 	fmt.Fprintf(w, s, args...) |  | ||||||
| 	if isTTY && useColor { |  | ||||||
| 		w.Write(reset) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										101
									
								
								vendor/github.com/go-chi/chi/middleware/throttle.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/go-chi/chi/middleware/throttle.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,101 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	errCapacityExceeded = "Server capacity exceeded." |  | ||||||
| 	errTimedOut         = "Timed out while waiting for a pending request to complete." |  | ||||||
| 	errContextCanceled  = "Context was canceled." |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	defaultBacklogTimeout = time.Second * 60 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Throttle is a middleware that limits number of currently processed requests |  | ||||||
| // at a time. |  | ||||||
| func Throttle(limit int) func(http.Handler) http.Handler { |  | ||||||
| 	return ThrottleBacklog(limit, 0, defaultBacklogTimeout) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ThrottleBacklog is a middleware that limits number of currently processed |  | ||||||
| // requests at a time and provides a backlog for holding a finite number of |  | ||||||
| // pending requests. |  | ||||||
| func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler { |  | ||||||
| 	if limit < 1 { |  | ||||||
| 		panic("chi/middleware: Throttle expects limit > 0") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if backlogLimit < 0 { |  | ||||||
| 		panic("chi/middleware: Throttle expects backlogLimit to be positive") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t := throttler{ |  | ||||||
| 		tokens:         make(chan token, limit), |  | ||||||
| 		backlogTokens:  make(chan token, limit+backlogLimit), |  | ||||||
| 		backlogTimeout: backlogTimeout, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Filling tokens. |  | ||||||
| 	for i := 0; i < limit+backlogLimit; i++ { |  | ||||||
| 		if i < limit { |  | ||||||
| 			t.tokens <- token{} |  | ||||||
| 		} |  | ||||||
| 		t.backlogTokens <- token{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn := func(h http.Handler) http.Handler { |  | ||||||
| 		t.h = h |  | ||||||
| 		return &t |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // token represents a request that is being processed. |  | ||||||
| type token struct{} |  | ||||||
| 
 |  | ||||||
| // throttler limits number of currently processed requests at a time. |  | ||||||
| type throttler struct { |  | ||||||
| 	h              http.Handler |  | ||||||
| 	tokens         chan token |  | ||||||
| 	backlogTokens  chan token |  | ||||||
| 	backlogTimeout time.Duration |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ServeHTTP is the primary throttler request handler |  | ||||||
| func (t *throttler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	ctx := r.Context() |  | ||||||
| 	select { |  | ||||||
| 	case <-ctx.Done(): |  | ||||||
| 		http.Error(w, errContextCanceled, http.StatusServiceUnavailable) |  | ||||||
| 		return |  | ||||||
| 	case btok := <-t.backlogTokens: |  | ||||||
| 		timer := time.NewTimer(t.backlogTimeout) |  | ||||||
| 
 |  | ||||||
| 		defer func() { |  | ||||||
| 			t.backlogTokens <- btok |  | ||||||
| 		}() |  | ||||||
| 
 |  | ||||||
| 		select { |  | ||||||
| 		case <-timer.C: |  | ||||||
| 			http.Error(w, errTimedOut, http.StatusServiceUnavailable) |  | ||||||
| 			return |  | ||||||
| 		case <-ctx.Done(): |  | ||||||
| 			http.Error(w, errContextCanceled, http.StatusServiceUnavailable) |  | ||||||
| 			return |  | ||||||
| 		case tok := <-t.tokens: |  | ||||||
| 			defer func() { |  | ||||||
| 				t.tokens <- tok |  | ||||||
| 			}() |  | ||||||
| 			t.h.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	default: |  | ||||||
| 		http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										49
									
								
								vendor/github.com/go-chi/chi/middleware/timeout.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/go-chi/chi/middleware/timeout.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,49 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net/http" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Timeout is a middleware that cancels ctx after a given timeout and return |  | ||||||
| // a 504 Gateway Timeout error to the client. |  | ||||||
| // |  | ||||||
| // It's required that you select the ctx.Done() channel to check for the signal |  | ||||||
| // if the context has reached its deadline and return, otherwise the timeout |  | ||||||
| // signal will be just ignored. |  | ||||||
| // |  | ||||||
| // ie. a route/handler may look like: |  | ||||||
| // |  | ||||||
| //  r.Get("/long", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| // 	 ctx := r.Context() |  | ||||||
| // 	 processTime := time.Duration(rand.Intn(4)+1) * time.Second |  | ||||||
| // |  | ||||||
| // 	 select { |  | ||||||
| // 	 case <-ctx.Done(): |  | ||||||
| // 	 	return |  | ||||||
| // |  | ||||||
| // 	 case <-time.After(processTime): |  | ||||||
| // 	 	 // The above channel simulates some hard work. |  | ||||||
| // 	 } |  | ||||||
| // |  | ||||||
| // 	 w.Write([]byte("done")) |  | ||||||
| //  }) |  | ||||||
| // |  | ||||||
| func Timeout(timeout time.Duration) func(next http.Handler) http.Handler { |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			ctx, cancel := context.WithTimeout(r.Context(), timeout) |  | ||||||
| 			defer func() { |  | ||||||
| 				cancel() |  | ||||||
| 				if ctx.Err() == context.DeadlineExceeded { |  | ||||||
| 					w.WriteHeader(http.StatusGatewayTimeout) |  | ||||||
| 				} |  | ||||||
| 			}() |  | ||||||
| 
 |  | ||||||
| 			r = r.WithContext(ctx) |  | ||||||
| 			next.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										72
									
								
								vendor/github.com/go-chi/chi/middleware/url_format.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										72
									
								
								vendor/github.com/go-chi/chi/middleware/url_format.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,72 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"github.com/go-chi/chi" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// URLFormatCtxKey is the context.Context key to store the URL format data |  | ||||||
| 	// for a request. |  | ||||||
| 	URLFormatCtxKey = &contextKey{"URLFormat"} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // URLFormat is a middleware that parses the url extension from a request path and stores it |  | ||||||
| // on the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will |  | ||||||
| // trim the suffix from the routing path and continue routing. |  | ||||||
| // |  | ||||||
| // Routers should not include a url parameter for the suffix when using this middleware. |  | ||||||
| // |  | ||||||
| // Sample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml` |  | ||||||
| // |  | ||||||
| //  func routes() http.Handler { |  | ||||||
| //    r := chi.NewRouter() |  | ||||||
| //    r.Use(middleware.URLFormat) |  | ||||||
| // |  | ||||||
| //    r.Get("/articles/{id}", ListArticles) |  | ||||||
| // |  | ||||||
| //    return r |  | ||||||
| //  } |  | ||||||
| // |  | ||||||
| //  func ListArticles(w http.ResponseWriter, r *http.Request) { |  | ||||||
| // 	  urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string) |  | ||||||
| // |  | ||||||
| // 	  switch urlFormat { |  | ||||||
| // 	  case "json": |  | ||||||
| // 	  	render.JSON(w, r, articles) |  | ||||||
| // 	  case "xml:" |  | ||||||
| // 	  	render.XML(w, r, articles) |  | ||||||
| // 	  default: |  | ||||||
| // 	  	render.JSON(w, r, articles) |  | ||||||
| // 	  } |  | ||||||
| // } |  | ||||||
| // |  | ||||||
| func URLFormat(next http.Handler) http.Handler { |  | ||||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		ctx := r.Context() |  | ||||||
| 
 |  | ||||||
| 		var format string |  | ||||||
| 		path := r.URL.Path |  | ||||||
| 
 |  | ||||||
| 		if strings.Index(path, ".") > 0 { |  | ||||||
| 			base := strings.LastIndex(path, "/") |  | ||||||
| 			idx := strings.Index(path[base:], ".") |  | ||||||
| 
 |  | ||||||
| 			if idx > 0 { |  | ||||||
| 				idx += base |  | ||||||
| 				format = path[idx+1:] |  | ||||||
| 
 |  | ||||||
| 				rctx := chi.RouteContext(r.Context()) |  | ||||||
| 				rctx.RoutePath = path[:idx] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		r = r.WithContext(context.WithValue(ctx, URLFormatCtxKey, format)) |  | ||||||
| 
 |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| 	return http.HandlerFunc(fn) |  | ||||||
| } |  | ||||||
							
								
								
									
										17
									
								
								vendor/github.com/go-chi/chi/middleware/value.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/go-chi/chi/middleware/value.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,17 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // WithValue is a middleware that sets a given key/value in a context chain. |  | ||||||
| func WithValue(key interface{}, val interface{}) func(next http.Handler) http.Handler { |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			r = r.WithContext(context.WithValue(r.Context(), key, val)) |  | ||||||
| 			next.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 		return http.HandlerFunc(fn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										183
									
								
								vendor/github.com/go-chi/chi/middleware/wrap_writer.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										183
									
								
								vendor/github.com/go-chi/chi/middleware/wrap_writer.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,183 +0,0 @@ | ||||||
| package middleware |  | ||||||
| 
 |  | ||||||
| // The original work was derived from Goji's middleware, source: |  | ||||||
| // https://github.com/zenazn/goji/tree/master/web/middleware |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"io" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to |  | ||||||
| // hook into various parts of the response process. |  | ||||||
| func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter { |  | ||||||
| 	_, fl := w.(http.Flusher) |  | ||||||
| 
 |  | ||||||
| 	bw := basicWriter{ResponseWriter: w} |  | ||||||
| 
 |  | ||||||
| 	if protoMajor == 2 { |  | ||||||
| 		_, ps := w.(http.Pusher) |  | ||||||
| 		if fl && ps { |  | ||||||
| 			return &http2FancyWriter{bw} |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		_, hj := w.(http.Hijacker) |  | ||||||
| 		_, rf := w.(io.ReaderFrom) |  | ||||||
| 		if fl && hj && rf { |  | ||||||
| 			return &httpFancyWriter{bw} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if fl { |  | ||||||
| 		return &flushWriter{bw} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &bw |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook |  | ||||||
| // into various parts of the response process. |  | ||||||
| type WrapResponseWriter interface { |  | ||||||
| 	http.ResponseWriter |  | ||||||
| 	// Status returns the HTTP status of the request, or 0 if one has not |  | ||||||
| 	// yet been sent. |  | ||||||
| 	Status() int |  | ||||||
| 	// BytesWritten returns the total number of bytes sent to the client. |  | ||||||
| 	BytesWritten() int |  | ||||||
| 	// Tee causes the response body to be written to the given io.Writer in |  | ||||||
| 	// addition to proxying the writes through. Only one io.Writer can be |  | ||||||
| 	// tee'd to at once: setting a second one will overwrite the first. |  | ||||||
| 	// Writes will be sent to the proxy before being written to this |  | ||||||
| 	// io.Writer. It is illegal for the tee'd writer to be modified |  | ||||||
| 	// concurrently with writes. |  | ||||||
| 	Tee(io.Writer) |  | ||||||
| 	// Unwrap returns the original proxied target. |  | ||||||
| 	Unwrap() http.ResponseWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // basicWriter wraps a http.ResponseWriter that implements the minimal |  | ||||||
| // http.ResponseWriter interface. |  | ||||||
| type basicWriter struct { |  | ||||||
| 	http.ResponseWriter |  | ||||||
| 	wroteHeader bool |  | ||||||
| 	code        int |  | ||||||
| 	bytes       int |  | ||||||
| 	tee         io.Writer |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) WriteHeader(code int) { |  | ||||||
| 	if !b.wroteHeader { |  | ||||||
| 		b.code = code |  | ||||||
| 		b.wroteHeader = true |  | ||||||
| 		b.ResponseWriter.WriteHeader(code) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) Write(buf []byte) (int, error) { |  | ||||||
| 	b.WriteHeader(http.StatusOK) |  | ||||||
| 	n, err := b.ResponseWriter.Write(buf) |  | ||||||
| 	if b.tee != nil { |  | ||||||
| 		_, err2 := b.tee.Write(buf[:n]) |  | ||||||
| 		// Prefer errors generated by the proxied writer. |  | ||||||
| 		if err == nil { |  | ||||||
| 			err = err2 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	b.bytes += n |  | ||||||
| 	return n, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) maybeWriteHeader() { |  | ||||||
| 	if !b.wroteHeader { |  | ||||||
| 		b.WriteHeader(http.StatusOK) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) Status() int { |  | ||||||
| 	return b.code |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) BytesWritten() int { |  | ||||||
| 	return b.bytes |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) Tee(w io.Writer) { |  | ||||||
| 	b.tee = w |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *basicWriter) Unwrap() http.ResponseWriter { |  | ||||||
| 	return b.ResponseWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type flushWriter struct { |  | ||||||
| 	basicWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *flushWriter) Flush() { |  | ||||||
| 	f.wroteHeader = true |  | ||||||
| 
 |  | ||||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) |  | ||||||
| 	fl.Flush() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var _ http.Flusher = &flushWriter{} |  | ||||||
| 
 |  | ||||||
| // httpFancyWriter is a HTTP writer that additionally satisfies |  | ||||||
| // http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case |  | ||||||
| // of wrapping the http.ResponseWriter that package http gives you, in order to |  | ||||||
| // make the proxied object support the full method set of the proxied object. |  | ||||||
| type httpFancyWriter struct { |  | ||||||
| 	basicWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *httpFancyWriter) Flush() { |  | ||||||
| 	f.wroteHeader = true |  | ||||||
| 
 |  | ||||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) |  | ||||||
| 	fl.Flush() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { |  | ||||||
| 	hj := f.basicWriter.ResponseWriter.(http.Hijacker) |  | ||||||
| 	return hj.Hijack() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error { |  | ||||||
| 	return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) { |  | ||||||
| 	if f.basicWriter.tee != nil { |  | ||||||
| 		n, err := io.Copy(&f.basicWriter, r) |  | ||||||
| 		f.basicWriter.bytes += int(n) |  | ||||||
| 		return n, err |  | ||||||
| 	} |  | ||||||
| 	rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) |  | ||||||
| 	f.basicWriter.maybeWriteHeader() |  | ||||||
| 	n, err := rf.ReadFrom(r) |  | ||||||
| 	f.basicWriter.bytes += int(n) |  | ||||||
| 	return n, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var _ http.Flusher = &httpFancyWriter{} |  | ||||||
| var _ http.Hijacker = &httpFancyWriter{} |  | ||||||
| var _ http.Pusher = &http2FancyWriter{} |  | ||||||
| var _ io.ReaderFrom = &httpFancyWriter{} |  | ||||||
| 
 |  | ||||||
| // http2FancyWriter is a HTTP2 writer that additionally satisfies |  | ||||||
| // http.Flusher, and io.ReaderFrom. It exists for the common case |  | ||||||
| // of wrapping the http.ResponseWriter that package http gives you, in order to |  | ||||||
| // make the proxied object support the full method set of the proxied object. |  | ||||||
| type http2FancyWriter struct { |  | ||||||
| 	basicWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *http2FancyWriter) Flush() { |  | ||||||
| 	f.wroteHeader = true |  | ||||||
| 
 |  | ||||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) |  | ||||||
| 	fl.Flush() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var _ http.Flusher = &http2FancyWriter{} |  | ||||||
							
								
								
									
										460
									
								
								vendor/github.com/go-chi/chi/mux.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										460
									
								
								vendor/github.com/go-chi/chi/mux.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,460 +0,0 @@ | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var _ Router = &Mux{} |  | ||||||
| 
 |  | ||||||
| // Mux is a simple HTTP route multiplexer that parses a request path, |  | ||||||
| // records any URL params, and executes an end handler. It implements |  | ||||||
| // the http.Handler interface and is friendly with the standard library. |  | ||||||
| // |  | ||||||
| // Mux is designed to be fast, minimal and offer a powerful API for building |  | ||||||
| // modular and composable HTTP services with a large set of handlers. It's |  | ||||||
| // particularly useful for writing large REST API services that break a handler |  | ||||||
| // into many smaller parts composed of middlewares and end handlers. |  | ||||||
| type Mux struct { |  | ||||||
| 	// The radix trie router |  | ||||||
| 	tree *node |  | ||||||
| 
 |  | ||||||
| 	// The middleware stack |  | ||||||
| 	middlewares []func(http.Handler) http.Handler |  | ||||||
| 
 |  | ||||||
| 	// Controls the behaviour of middleware chain generation when a mux |  | ||||||
| 	// is registered as an inline group inside another mux. |  | ||||||
| 	inline bool |  | ||||||
| 	parent *Mux |  | ||||||
| 
 |  | ||||||
| 	// The computed mux handler made of the chained middleware stack and |  | ||||||
| 	// the tree router |  | ||||||
| 	handler http.Handler |  | ||||||
| 
 |  | ||||||
| 	// Routing context pool |  | ||||||
| 	pool *sync.Pool |  | ||||||
| 
 |  | ||||||
| 	// Custom route not found handler |  | ||||||
| 	notFoundHandler http.HandlerFunc |  | ||||||
| 
 |  | ||||||
| 	// Custom method not allowed handler |  | ||||||
| 	methodNotAllowedHandler http.HandlerFunc |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewMux returns a newly initialized Mux object that implements the Router |  | ||||||
| // interface. |  | ||||||
| func NewMux() *Mux { |  | ||||||
| 	mux := &Mux{tree: &node{}, pool: &sync.Pool{}} |  | ||||||
| 	mux.pool.New = func() interface{} { |  | ||||||
| 		return NewRouteContext() |  | ||||||
| 	} |  | ||||||
| 	return mux |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ServeHTTP is the single method of the http.Handler interface that makes |  | ||||||
| // Mux interoperable with the standard library. It uses a sync.Pool to get and |  | ||||||
| // reuse routing contexts for each request. |  | ||||||
| func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	// Ensure the mux has some routes defined on the mux |  | ||||||
| 	if mx.handler == nil { |  | ||||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if a routing context already exists from a parent router. |  | ||||||
| 	rctx, _ := r.Context().Value(RouteCtxKey).(*Context) |  | ||||||
| 	if rctx != nil { |  | ||||||
| 		mx.handler.ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Fetch a RouteContext object from the sync pool, and call the computed |  | ||||||
| 	// mx.handler that is comprised of mx.middlewares + mx.routeHTTP. |  | ||||||
| 	// Once the request is finished, reset the routing context and put it back |  | ||||||
| 	// into the pool for reuse from another request. |  | ||||||
| 	rctx = mx.pool.Get().(*Context) |  | ||||||
| 	rctx.Reset() |  | ||||||
| 	rctx.Routes = mx |  | ||||||
| 	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx)) |  | ||||||
| 	mx.handler.ServeHTTP(w, r) |  | ||||||
| 	mx.pool.Put(rctx) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Use appends a middleware handler to the Mux middleware stack. |  | ||||||
| // |  | ||||||
| // The middleware stack for any Mux will execute before searching for a matching |  | ||||||
| // route to a specific handler, which provides opportunity to respond early, |  | ||||||
| // change the course of the request execution, or set request-scoped values for |  | ||||||
| // the next http.Handler. |  | ||||||
| func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { |  | ||||||
| 	if mx.handler != nil { |  | ||||||
| 		panic("chi: all middlewares must be defined before routes on a mux") |  | ||||||
| 	} |  | ||||||
| 	mx.middlewares = append(mx.middlewares, middlewares...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handle adds the route `pattern` that matches any http method to |  | ||||||
| // execute the `handler` http.Handler. |  | ||||||
| func (mx *Mux) Handle(pattern string, handler http.Handler) { |  | ||||||
| 	mx.handle(mALL, pattern, handler) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HandleFunc adds the route `pattern` that matches any http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mALL, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Method adds the route `pattern` that matches `method` http method to |  | ||||||
| // execute the `handler` http.Handler. |  | ||||||
| func (mx *Mux) Method(method, pattern string, handler http.Handler) { |  | ||||||
| 	m, ok := methodMap[strings.ToUpper(method)] |  | ||||||
| 	if !ok { |  | ||||||
| 		panic(fmt.Sprintf("chi: '%s' http method is not supported.", method)) |  | ||||||
| 	} |  | ||||||
| 	mx.handle(m, pattern, handler) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodFunc adds the route `pattern` that matches `method` http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.Method(method, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Connect adds the route `pattern` that matches a CONNECT http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mCONNECT, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Delete adds the route `pattern` that matches a DELETE http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mDELETE, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get adds the route `pattern` that matches a GET http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mGET, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Head adds the route `pattern` that matches a HEAD http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mHEAD, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Options adds the route `pattern` that matches a OPTIONS http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mOPTIONS, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Patch adds the route `pattern` that matches a PATCH http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPATCH, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Post adds the route `pattern` that matches a POST http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPOST, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Put adds the route `pattern` that matches a PUT http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mPUT, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Trace adds the route `pattern` that matches a TRACE http method to |  | ||||||
| // execute the `handlerFn` http.HandlerFunc. |  | ||||||
| func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) { |  | ||||||
| 	mx.handle(mTRACE, pattern, handlerFn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NotFound sets a custom http.HandlerFunc for routing paths that could |  | ||||||
| // not be found. The default 404 handler is `http.NotFound`. |  | ||||||
| func (mx *Mux) NotFound(handlerFn http.HandlerFunc) { |  | ||||||
| 	// Build NotFound handler chain |  | ||||||
| 	m := mx |  | ||||||
| 	hFn := handlerFn |  | ||||||
| 	if mx.inline && mx.parent != nil { |  | ||||||
| 		m = mx.parent |  | ||||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Update the notFoundHandler from this point forward |  | ||||||
| 	m.notFoundHandler = hFn |  | ||||||
| 	m.updateSubRoutes(func(subMux *Mux) { |  | ||||||
| 		if subMux.notFoundHandler == nil { |  | ||||||
| 			subMux.NotFound(hFn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the |  | ||||||
| // method is unresolved. The default handler returns a 405 with an empty body. |  | ||||||
| func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { |  | ||||||
| 	// Build MethodNotAllowed handler chain |  | ||||||
| 	m := mx |  | ||||||
| 	hFn := handlerFn |  | ||||||
| 	if mx.inline && mx.parent != nil { |  | ||||||
| 		m = mx.parent |  | ||||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Update the methodNotAllowedHandler from this point forward |  | ||||||
| 	m.methodNotAllowedHandler = hFn |  | ||||||
| 	m.updateSubRoutes(func(subMux *Mux) { |  | ||||||
| 		if subMux.methodNotAllowedHandler == nil { |  | ||||||
| 			subMux.MethodNotAllowed(hFn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // With adds inline middlewares for an endpoint handler. |  | ||||||
| func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { |  | ||||||
| 	// Similarly as in handle(), we must build the mux handler once further |  | ||||||
| 	// middleware registration isn't allowed for this stack, like now. |  | ||||||
| 	if !mx.inline && mx.handler == nil { |  | ||||||
| 		mx.buildRouteHandler() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Copy middlewares from parent inline muxs |  | ||||||
| 	var mws Middlewares |  | ||||||
| 	if mx.inline { |  | ||||||
| 		mws = make(Middlewares, len(mx.middlewares)) |  | ||||||
| 		copy(mws, mx.middlewares) |  | ||||||
| 	} |  | ||||||
| 	mws = append(mws, middlewares...) |  | ||||||
| 
 |  | ||||||
| 	im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws} |  | ||||||
| 
 |  | ||||||
| 	return im |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Group creates a new inline-Mux with a fresh middleware stack. It's useful |  | ||||||
| // for a group of handlers along the same routing path that use an additional |  | ||||||
| // set of middlewares. See _examples/. |  | ||||||
| func (mx *Mux) Group(fn func(r Router)) Router { |  | ||||||
| 	im := mx.With().(*Mux) |  | ||||||
| 	if fn != nil { |  | ||||||
| 		fn(im) |  | ||||||
| 	} |  | ||||||
| 	return im |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Route creates a new Mux with a fresh middleware stack and mounts it |  | ||||||
| // along the `pattern` as a subrouter. Effectively, this is a short-hand |  | ||||||
| // call to Mount. See _examples/. |  | ||||||
| func (mx *Mux) Route(pattern string, fn func(r Router)) Router { |  | ||||||
| 	subRouter := NewRouter() |  | ||||||
| 	if fn != nil { |  | ||||||
| 		fn(subRouter) |  | ||||||
| 	} |  | ||||||
| 	mx.Mount(pattern, subRouter) |  | ||||||
| 	return subRouter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Mount attaches another http.Handler or chi Router as a subrouter along a routing |  | ||||||
| // path. It's very useful to split up a large API as many independent routers and |  | ||||||
| // compose them as a single service using Mount. See _examples/. |  | ||||||
| // |  | ||||||
| // Note that Mount() simply sets a wildcard along the `pattern` that will continue |  | ||||||
| // routing at the `handler`, which in most cases is another chi.Router. As a result, |  | ||||||
| // if you define two Mount() routes on the exact same pattern the mount will panic. |  | ||||||
| func (mx *Mux) Mount(pattern string, handler http.Handler) { |  | ||||||
| 	// Provide runtime safety for ensuring a pattern isn't mounted on an existing |  | ||||||
| 	// routing pattern. |  | ||||||
| 	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { |  | ||||||
| 		panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Assign sub-Router's with the parent not found & method not allowed handler if not specified. |  | ||||||
| 	subr, ok := handler.(*Mux) |  | ||||||
| 	if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil { |  | ||||||
| 		subr.NotFound(mx.notFoundHandler) |  | ||||||
| 	} |  | ||||||
| 	if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil { |  | ||||||
| 		subr.MethodNotAllowed(mx.methodNotAllowedHandler) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wrap the sub-router in a handlerFunc to scope the request path for routing. |  | ||||||
| 	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		rctx := RouteContext(r.Context()) |  | ||||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) |  | ||||||
| 		handler.ServeHTTP(w, r) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	if pattern == "" || pattern[len(pattern)-1] != '/' { |  | ||||||
| 		mx.handle(mALL|mSTUB, pattern, mountHandler) |  | ||||||
| 		mx.handle(mALL|mSTUB, pattern+"/", mountHandler) |  | ||||||
| 		pattern += "/" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	method := mALL |  | ||||||
| 	subroutes, _ := handler.(Routes) |  | ||||||
| 	if subroutes != nil { |  | ||||||
| 		method |= mSTUB |  | ||||||
| 	} |  | ||||||
| 	n := mx.handle(method, pattern+"*", mountHandler) |  | ||||||
| 
 |  | ||||||
| 	if subroutes != nil { |  | ||||||
| 		n.subroutes = subroutes |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Routes returns a slice of routing information from the tree, |  | ||||||
| // useful for traversing available routes of a router. |  | ||||||
| func (mx *Mux) Routes() []Route { |  | ||||||
| 	return mx.tree.routes() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Middlewares returns a slice of middleware handler functions. |  | ||||||
| func (mx *Mux) Middlewares() Middlewares { |  | ||||||
| 	return mx.middlewares |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Match searches the routing tree for a handler that matches the method/path. |  | ||||||
| // It's similar to routing a http request, but without executing the handler |  | ||||||
| // thereafter. |  | ||||||
| // |  | ||||||
| // Note: the *Context state is updated during execution, so manage |  | ||||||
| // the state carefully or make a NewRouteContext(). |  | ||||||
| func (mx *Mux) Match(rctx *Context, method, path string) bool { |  | ||||||
| 	m, ok := methodMap[method] |  | ||||||
| 	if !ok { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	node, _, h := mx.tree.FindRoute(rctx, m, path) |  | ||||||
| 
 |  | ||||||
| 	if node != nil && node.subroutes != nil { |  | ||||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) |  | ||||||
| 		return node.subroutes.Match(rctx, method, rctx.RoutePath) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return h != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NotFoundHandler returns the default Mux 404 responder whenever a route |  | ||||||
| // cannot be found. |  | ||||||
| func (mx *Mux) NotFoundHandler() http.HandlerFunc { |  | ||||||
| 	if mx.notFoundHandler != nil { |  | ||||||
| 		return mx.notFoundHandler |  | ||||||
| 	} |  | ||||||
| 	return http.NotFound |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MethodNotAllowedHandler returns the default Mux 405 responder whenever |  | ||||||
| // a method cannot be resolved for a route. |  | ||||||
| func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc { |  | ||||||
| 	if mx.methodNotAllowedHandler != nil { |  | ||||||
| 		return mx.methodNotAllowedHandler |  | ||||||
| 	} |  | ||||||
| 	return methodNotAllowedHandler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // buildRouteHandler builds the single mux handler that is a chain of the middleware |  | ||||||
| // stack, as defined by calls to Use(), and the tree router (Mux) itself. After this |  | ||||||
| // point, no other middlewares can be registered on this Mux's stack. But you can still |  | ||||||
| // compose additional middlewares via Group()'s or using a chained middleware handler. |  | ||||||
| func (mx *Mux) buildRouteHandler() { |  | ||||||
| 	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // handle registers a http.Handler in the routing tree for a particular http method |  | ||||||
| // and routing pattern. |  | ||||||
| func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node { |  | ||||||
| 	if len(pattern) == 0 || pattern[0] != '/' { |  | ||||||
| 		panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Build the final routing handler for this Mux. |  | ||||||
| 	if !mx.inline && mx.handler == nil { |  | ||||||
| 		mx.buildRouteHandler() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Build endpoint handler with inline middlewares for the route |  | ||||||
| 	var h http.Handler |  | ||||||
| 	if mx.inline { |  | ||||||
| 		mx.handler = http.HandlerFunc(mx.routeHTTP) |  | ||||||
| 		h = Chain(mx.middlewares...).Handler(handler) |  | ||||||
| 	} else { |  | ||||||
| 		h = handler |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Add the endpoint to the tree and return the node |  | ||||||
| 	return mx.tree.InsertRoute(method, pattern, h) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // routeHTTP routes a http.Request through the Mux routing tree to serve |  | ||||||
| // the matching handler for a particular http method. |  | ||||||
| func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	// Grab the route context object |  | ||||||
| 	rctx := r.Context().Value(RouteCtxKey).(*Context) |  | ||||||
| 
 |  | ||||||
| 	// The request routing path |  | ||||||
| 	routePath := rctx.RoutePath |  | ||||||
| 	if routePath == "" { |  | ||||||
| 		if r.URL.RawPath != "" { |  | ||||||
| 			routePath = r.URL.RawPath |  | ||||||
| 		} else { |  | ||||||
| 			routePath = r.URL.Path |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if method is supported by chi |  | ||||||
| 	if rctx.RouteMethod == "" { |  | ||||||
| 		rctx.RouteMethod = r.Method |  | ||||||
| 	} |  | ||||||
| 	method, ok := methodMap[rctx.RouteMethod] |  | ||||||
| 	if !ok { |  | ||||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Find the route |  | ||||||
| 	if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { |  | ||||||
| 		h.ServeHTTP(w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if rctx.methodNotAllowed { |  | ||||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) |  | ||||||
| 	} else { |  | ||||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (mx *Mux) nextRoutePath(rctx *Context) string { |  | ||||||
| 	routePath := "/" |  | ||||||
| 	nx := len(rctx.routeParams.Keys) - 1 // index of last param in list |  | ||||||
| 	if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx { |  | ||||||
| 		routePath += rctx.routeParams.Values[nx] |  | ||||||
| 	} |  | ||||||
| 	return routePath |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Recursively update data on child routers. |  | ||||||
| func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { |  | ||||||
| 	for _, r := range mx.tree.routes() { |  | ||||||
| 		subMux, ok := r.SubRoutes.(*Mux) |  | ||||||
| 		if !ok { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		fn(subMux) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // methodNotAllowedHandler is a helper function to respond with a 405, |  | ||||||
| // method not allowed. |  | ||||||
| func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	w.WriteHeader(405) |  | ||||||
| 	w.Write(nil) |  | ||||||
| } |  | ||||||
							
								
								
									
										846
									
								
								vendor/github.com/go-chi/chi/tree.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										846
									
								
								vendor/github.com/go-chi/chi/tree.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,846 +0,0 @@ | ||||||
| package chi |  | ||||||
| 
 |  | ||||||
| // Radix tree implementation below is a based on the original work by |  | ||||||
| // Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go |  | ||||||
| // (MIT licensed). It's been heavily modified for use as a HTTP routing tree. |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"math" |  | ||||||
| 	"net/http" |  | ||||||
| 	"regexp" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type methodTyp int |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	mSTUB methodTyp = 1 << iota |  | ||||||
| 	mCONNECT |  | ||||||
| 	mDELETE |  | ||||||
| 	mGET |  | ||||||
| 	mHEAD |  | ||||||
| 	mOPTIONS |  | ||||||
| 	mPATCH |  | ||||||
| 	mPOST |  | ||||||
| 	mPUT |  | ||||||
| 	mTRACE |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var mALL = mCONNECT | mDELETE | mGET | mHEAD | |  | ||||||
| 	mOPTIONS | mPATCH | mPOST | mPUT | mTRACE |  | ||||||
| 
 |  | ||||||
| var methodMap = map[string]methodTyp{ |  | ||||||
| 	http.MethodConnect: mCONNECT, |  | ||||||
| 	http.MethodDelete:  mDELETE, |  | ||||||
| 	http.MethodGet:     mGET, |  | ||||||
| 	http.MethodHead:    mHEAD, |  | ||||||
| 	http.MethodOptions: mOPTIONS, |  | ||||||
| 	http.MethodPatch:   mPATCH, |  | ||||||
| 	http.MethodPost:    mPOST, |  | ||||||
| 	http.MethodPut:     mPUT, |  | ||||||
| 	http.MethodTrace:   mTRACE, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RegisterMethod adds support for custom HTTP method handlers, available |  | ||||||
| // via Router#Method and Router#MethodFunc |  | ||||||
| func RegisterMethod(method string) { |  | ||||||
| 	if method == "" { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	method = strings.ToUpper(method) |  | ||||||
| 	if _, ok := methodMap[method]; ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	n := len(methodMap) |  | ||||||
| 	if n > strconv.IntSize { |  | ||||||
| 		panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) |  | ||||||
| 	} |  | ||||||
| 	mt := methodTyp(math.Exp2(float64(n))) |  | ||||||
| 	methodMap[method] = mt |  | ||||||
| 	mALL |= mt |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type nodeTyp uint8 |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	ntStatic   nodeTyp = iota // /home |  | ||||||
| 	ntRegexp                  // /{id:[0-9]+} |  | ||||||
| 	ntParam                   // /{user} |  | ||||||
| 	ntCatchAll                // /api/v1/* |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type node struct { |  | ||||||
| 	// node type: static, regexp, param, catchAll |  | ||||||
| 	typ nodeTyp |  | ||||||
| 
 |  | ||||||
| 	// first byte of the prefix |  | ||||||
| 	label byte |  | ||||||
| 
 |  | ||||||
| 	// first byte of the child prefix |  | ||||||
| 	tail byte |  | ||||||
| 
 |  | ||||||
| 	// prefix is the common prefix we ignore |  | ||||||
| 	prefix string |  | ||||||
| 
 |  | ||||||
| 	// regexp matcher for regexp nodes |  | ||||||
| 	rex *regexp.Regexp |  | ||||||
| 
 |  | ||||||
| 	// HTTP handler endpoints on the leaf node |  | ||||||
| 	endpoints endpoints |  | ||||||
| 
 |  | ||||||
| 	// subroutes on the leaf node |  | ||||||
| 	subroutes Routes |  | ||||||
| 
 |  | ||||||
| 	// child nodes should be stored in-order for iteration, |  | ||||||
| 	// in groups of the node type. |  | ||||||
| 	children [ntCatchAll + 1]nodes |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // endpoints is a mapping of http method constants to handlers |  | ||||||
| // for a given route. |  | ||||||
| type endpoints map[methodTyp]*endpoint |  | ||||||
| 
 |  | ||||||
| type endpoint struct { |  | ||||||
| 	// endpoint handler |  | ||||||
| 	handler http.Handler |  | ||||||
| 
 |  | ||||||
| 	// pattern is the routing pattern for handler nodes |  | ||||||
| 	pattern string |  | ||||||
| 
 |  | ||||||
| 	// parameter keys recorded on handler nodes |  | ||||||
| 	paramKeys []string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s endpoints) Value(method methodTyp) *endpoint { |  | ||||||
| 	mh, ok := s[method] |  | ||||||
| 	if !ok { |  | ||||||
| 		mh = &endpoint{} |  | ||||||
| 		s[method] = mh |  | ||||||
| 	} |  | ||||||
| 	return mh |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node { |  | ||||||
| 	var parent *node |  | ||||||
| 	search := pattern |  | ||||||
| 
 |  | ||||||
| 	for { |  | ||||||
| 		// Handle key exhaustion |  | ||||||
| 		if len(search) == 0 { |  | ||||||
| 			// Insert or update the node's leaf handler |  | ||||||
| 			n.setEndpoint(method, handler, pattern) |  | ||||||
| 			return n |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// We're going to be searching for a wild node next, |  | ||||||
| 		// in this case, we need to get the tail |  | ||||||
| 		var label = search[0] |  | ||||||
| 		var segTail byte |  | ||||||
| 		var segEndIdx int |  | ||||||
| 		var segTyp nodeTyp |  | ||||||
| 		var segRexpat string |  | ||||||
| 		if label == '{' || label == '*' { |  | ||||||
| 			segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var prefix string |  | ||||||
| 		if segTyp == ntRegexp { |  | ||||||
| 			prefix = segRexpat |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Look for the edge to attach to |  | ||||||
| 		parent = n |  | ||||||
| 		n = n.getEdge(segTyp, label, segTail, prefix) |  | ||||||
| 
 |  | ||||||
| 		// No edge, create one |  | ||||||
| 		if n == nil { |  | ||||||
| 			child := &node{label: label, tail: segTail, prefix: search} |  | ||||||
| 			hn := parent.addChild(child, search) |  | ||||||
| 			hn.setEndpoint(method, handler, pattern) |  | ||||||
| 
 |  | ||||||
| 			return hn |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Found an edge to match the pattern |  | ||||||
| 
 |  | ||||||
| 		if n.typ > ntStatic { |  | ||||||
| 			// We found a param node, trim the param from the search path and continue. |  | ||||||
| 			// This param/wild pattern segment would already be on the tree from a previous |  | ||||||
| 			// call to addChild when creating a new node. |  | ||||||
| 			search = search[segEndIdx:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Static nodes fall below here. |  | ||||||
| 		// Determine longest prefix of the search key on match. |  | ||||||
| 		commonPrefix := longestPrefix(search, n.prefix) |  | ||||||
| 		if commonPrefix == len(n.prefix) { |  | ||||||
| 			// the common prefix is as long as the current node's prefix we're attempting to insert. |  | ||||||
| 			// keep the search going. |  | ||||||
| 			search = search[commonPrefix:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Split the node |  | ||||||
| 		child := &node{ |  | ||||||
| 			typ:    ntStatic, |  | ||||||
| 			prefix: search[:commonPrefix], |  | ||||||
| 		} |  | ||||||
| 		parent.replaceChild(search[0], segTail, child) |  | ||||||
| 
 |  | ||||||
| 		// Restore the existing node |  | ||||||
| 		n.label = n.prefix[commonPrefix] |  | ||||||
| 		n.prefix = n.prefix[commonPrefix:] |  | ||||||
| 		child.addChild(n, n.prefix) |  | ||||||
| 
 |  | ||||||
| 		// If the new key is a subset, set the method/handler on this node and finish. |  | ||||||
| 		search = search[commonPrefix:] |  | ||||||
| 		if len(search) == 0 { |  | ||||||
| 			child.setEndpoint(method, handler, pattern) |  | ||||||
| 			return child |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Create a new edge for the node |  | ||||||
| 		subchild := &node{ |  | ||||||
| 			typ:    ntStatic, |  | ||||||
| 			label:  search[0], |  | ||||||
| 			prefix: search, |  | ||||||
| 		} |  | ||||||
| 		hn := child.addChild(subchild, search) |  | ||||||
| 		hn.setEndpoint(method, handler, pattern) |  | ||||||
| 		return hn |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // addChild appends the new `child` node to the tree using the `pattern` as the trie key. |  | ||||||
| // For a URL router like chi's, we split the static, param, regexp and wildcard segments |  | ||||||
| // into different nodes. In addition, addChild will recursively call itself until every |  | ||||||
| // pattern segment is added to the url pattern tree as individual nodes, depending on type. |  | ||||||
| func (n *node) addChild(child *node, prefix string) *node { |  | ||||||
| 	search := prefix |  | ||||||
| 
 |  | ||||||
| 	// handler leaf node added to the tree is the child. |  | ||||||
| 	// this may be overridden later down the flow |  | ||||||
| 	hn := child |  | ||||||
| 
 |  | ||||||
| 	// Parse next segment |  | ||||||
| 	segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) |  | ||||||
| 
 |  | ||||||
| 	// Add child depending on next up segment |  | ||||||
| 	switch segTyp { |  | ||||||
| 
 |  | ||||||
| 	case ntStatic: |  | ||||||
| 		// Search prefix is all static (that is, has no params in path) |  | ||||||
| 		// noop |  | ||||||
| 
 |  | ||||||
| 	default: |  | ||||||
| 		// Search prefix contains a param, regexp or wildcard |  | ||||||
| 
 |  | ||||||
| 		if segTyp == ntRegexp { |  | ||||||
| 			rex, err := regexp.Compile(segRexpat) |  | ||||||
| 			if err != nil { |  | ||||||
| 				panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat)) |  | ||||||
| 			} |  | ||||||
| 			child.prefix = segRexpat |  | ||||||
| 			child.rex = rex |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if segStartIdx == 0 { |  | ||||||
| 			// Route starts with a param |  | ||||||
| 			child.typ = segTyp |  | ||||||
| 
 |  | ||||||
| 			if segTyp == ntCatchAll { |  | ||||||
| 				segStartIdx = -1 |  | ||||||
| 			} else { |  | ||||||
| 				segStartIdx = segEndIdx |  | ||||||
| 			} |  | ||||||
| 			if segStartIdx < 0 { |  | ||||||
| 				segStartIdx = len(search) |  | ||||||
| 			} |  | ||||||
| 			child.tail = segTail // for params, we set the tail |  | ||||||
| 
 |  | ||||||
| 			if segStartIdx != len(search) { |  | ||||||
| 				// add static edge for the remaining part, split the end. |  | ||||||
| 				// its not possible to have adjacent param nodes, so its certainly |  | ||||||
| 				// going to be a static node next. |  | ||||||
| 
 |  | ||||||
| 				search = search[segStartIdx:] // advance search position |  | ||||||
| 
 |  | ||||||
| 				nn := &node{ |  | ||||||
| 					typ:    ntStatic, |  | ||||||
| 					label:  search[0], |  | ||||||
| 					prefix: search, |  | ||||||
| 				} |  | ||||||
| 				hn = child.addChild(nn, search) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		} else if segStartIdx > 0 { |  | ||||||
| 			// Route has some param |  | ||||||
| 
 |  | ||||||
| 			// starts with a static segment |  | ||||||
| 			child.typ = ntStatic |  | ||||||
| 			child.prefix = search[:segStartIdx] |  | ||||||
| 			child.rex = nil |  | ||||||
| 
 |  | ||||||
| 			// add the param edge node |  | ||||||
| 			search = search[segStartIdx:] |  | ||||||
| 
 |  | ||||||
| 			nn := &node{ |  | ||||||
| 				typ:   segTyp, |  | ||||||
| 				label: search[0], |  | ||||||
| 				tail:  segTail, |  | ||||||
| 			} |  | ||||||
| 			hn = child.addChild(nn, search) |  | ||||||
| 
 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	n.children[child.typ] = append(n.children[child.typ], child) |  | ||||||
| 	n.children[child.typ].Sort() |  | ||||||
| 	return hn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) replaceChild(label, tail byte, child *node) { |  | ||||||
| 	for i := 0; i < len(n.children[child.typ]); i++ { |  | ||||||
| 		if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail { |  | ||||||
| 			n.children[child.typ][i] = child |  | ||||||
| 			n.children[child.typ][i].label = label |  | ||||||
| 			n.children[child.typ][i].tail = tail |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	panic("chi: replacing missing child") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { |  | ||||||
| 	nds := n.children[ntyp] |  | ||||||
| 	for i := 0; i < len(nds); i++ { |  | ||||||
| 		if nds[i].label == label && nds[i].tail == tail { |  | ||||||
| 			if ntyp == ntRegexp && nds[i].prefix != prefix { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			return nds[i] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) { |  | ||||||
| 	// Set the handler for the method type on the node |  | ||||||
| 	if n.endpoints == nil { |  | ||||||
| 		n.endpoints = make(endpoints, 0) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	paramKeys := patParamKeys(pattern) |  | ||||||
| 
 |  | ||||||
| 	if method&mSTUB == mSTUB { |  | ||||||
| 		n.endpoints.Value(mSTUB).handler = handler |  | ||||||
| 	} |  | ||||||
| 	if method&mALL == mALL { |  | ||||||
| 		h := n.endpoints.Value(mALL) |  | ||||||
| 		h.handler = handler |  | ||||||
| 		h.pattern = pattern |  | ||||||
| 		h.paramKeys = paramKeys |  | ||||||
| 		for _, m := range methodMap { |  | ||||||
| 			h := n.endpoints.Value(m) |  | ||||||
| 			h.handler = handler |  | ||||||
| 			h.pattern = pattern |  | ||||||
| 			h.paramKeys = paramKeys |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		h := n.endpoints.Value(method) |  | ||||||
| 		h.handler = handler |  | ||||||
| 		h.pattern = pattern |  | ||||||
| 		h.paramKeys = paramKeys |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) { |  | ||||||
| 	// Reset the context routing pattern and params |  | ||||||
| 	rctx.routePattern = "" |  | ||||||
| 	rctx.routeParams.Keys = rctx.routeParams.Keys[:0] |  | ||||||
| 	rctx.routeParams.Values = rctx.routeParams.Values[:0] |  | ||||||
| 
 |  | ||||||
| 	// Find the routing handlers for the path |  | ||||||
| 	rn := n.findRoute(rctx, method, path) |  | ||||||
| 	if rn == nil { |  | ||||||
| 		return nil, nil, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Record the routing params in the request lifecycle |  | ||||||
| 	rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...) |  | ||||||
| 	rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...) |  | ||||||
| 
 |  | ||||||
| 	// Record the routing pattern in the request lifecycle |  | ||||||
| 	if rn.endpoints[method].pattern != "" { |  | ||||||
| 		rctx.routePattern = rn.endpoints[method].pattern |  | ||||||
| 		rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return rn, rn.endpoints, rn.endpoints[method].handler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Recursive edge traversal by checking all nodeTyp groups along the way. |  | ||||||
| // It's like searching through a multi-dimensional radix trie. |  | ||||||
| func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { |  | ||||||
| 	nn := n |  | ||||||
| 	search := path |  | ||||||
| 
 |  | ||||||
| 	for t, nds := range nn.children { |  | ||||||
| 		ntyp := nodeTyp(t) |  | ||||||
| 		if len(nds) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var xn *node |  | ||||||
| 		xsearch := search |  | ||||||
| 
 |  | ||||||
| 		var label byte |  | ||||||
| 		if search != "" { |  | ||||||
| 			label = search[0] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		switch ntyp { |  | ||||||
| 		case ntStatic: |  | ||||||
| 			xn = nds.findEdge(label) |  | ||||||
| 			if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			xsearch = xsearch[len(xn.prefix):] |  | ||||||
| 
 |  | ||||||
| 		case ntParam, ntRegexp: |  | ||||||
| 			// short-circuit and return no matching route for empty param values |  | ||||||
| 			if xsearch == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// serially loop through each node grouped by the tail delimiter |  | ||||||
| 			for idx := 0; idx < len(nds); idx++ { |  | ||||||
| 				xn = nds[idx] |  | ||||||
| 
 |  | ||||||
| 				// label for param nodes is the delimiter byte |  | ||||||
| 				p := strings.IndexByte(xsearch, xn.tail) |  | ||||||
| 
 |  | ||||||
| 				if p < 0 { |  | ||||||
| 					if xn.tail == '/' { |  | ||||||
| 						p = len(xsearch) |  | ||||||
| 					} else { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if ntyp == ntRegexp && xn.rex != nil { |  | ||||||
| 					if xn.rex.Match([]byte(xsearch[:p])) == false { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} else if strings.IndexByte(xsearch[:p], '/') != -1 { |  | ||||||
| 					// avoid a match across path segments |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) |  | ||||||
| 				xsearch = xsearch[p:] |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		default: |  | ||||||
| 			// catch-all nodes |  | ||||||
| 			rctx.routeParams.Values = append(rctx.routeParams.Values, search) |  | ||||||
| 			xn = nds[0] |  | ||||||
| 			xsearch = "" |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if xn == nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// did we find it yet? |  | ||||||
| 		if len(xsearch) == 0 { |  | ||||||
| 			if xn.isLeaf() { |  | ||||||
| 				h, _ := xn.endpoints[method] |  | ||||||
| 				if h != nil && h.handler != nil { |  | ||||||
| 					rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) |  | ||||||
| 					return xn |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// flag that the routing context found a route, but not a corresponding |  | ||||||
| 				// supported method |  | ||||||
| 				rctx.methodNotAllowed = true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// recursively find the next node.. |  | ||||||
| 		fin := xn.findRoute(rctx, method, xsearch) |  | ||||||
| 		if fin != nil { |  | ||||||
| 			return fin |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Did not find final handler, let's remove the param here if it was set |  | ||||||
| 		if xn.typ > ntStatic { |  | ||||||
| 			if len(rctx.routeParams.Values) > 0 { |  | ||||||
| 				rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) findEdge(ntyp nodeTyp, label byte) *node { |  | ||||||
| 	nds := n.children[ntyp] |  | ||||||
| 	num := len(nds) |  | ||||||
| 	idx := 0 |  | ||||||
| 
 |  | ||||||
| 	switch ntyp { |  | ||||||
| 	case ntStatic, ntParam, ntRegexp: |  | ||||||
| 		i, j := 0, num-1 |  | ||||||
| 		for i <= j { |  | ||||||
| 			idx = i + (j-i)/2 |  | ||||||
| 			if label > nds[idx].label { |  | ||||||
| 				i = idx + 1 |  | ||||||
| 			} else if label < nds[idx].label { |  | ||||||
| 				j = idx - 1 |  | ||||||
| 			} else { |  | ||||||
| 				i = num // breaks cond |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if nds[idx].label != label { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		return nds[idx] |  | ||||||
| 
 |  | ||||||
| 	default: // catch all |  | ||||||
| 		return nds[idx] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) isEmpty() bool { |  | ||||||
| 	for _, nds := range n.children { |  | ||||||
| 		if len(nds) > 0 { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) isLeaf() bool { |  | ||||||
| 	return n.endpoints != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) findPattern(pattern string) bool { |  | ||||||
| 	nn := n |  | ||||||
| 	for _, nds := range nn.children { |  | ||||||
| 		if len(nds) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		n = nn.findEdge(nds[0].typ, pattern[0]) |  | ||||||
| 		if n == nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var idx int |  | ||||||
| 		var xpattern string |  | ||||||
| 
 |  | ||||||
| 		switch n.typ { |  | ||||||
| 		case ntStatic: |  | ||||||
| 			idx = longestPrefix(pattern, n.prefix) |  | ||||||
| 			if idx < len(n.prefix) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		case ntParam, ntRegexp: |  | ||||||
| 			idx = strings.IndexByte(pattern, '}') + 1 |  | ||||||
| 
 |  | ||||||
| 		case ntCatchAll: |  | ||||||
| 			idx = longestPrefix(pattern, "*") |  | ||||||
| 
 |  | ||||||
| 		default: |  | ||||||
| 			panic("chi: unknown node type") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		xpattern = pattern[idx:] |  | ||||||
| 		if len(xpattern) == 0 { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return n.findPattern(xpattern) |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) routes() []Route { |  | ||||||
| 	rts := []Route{} |  | ||||||
| 
 |  | ||||||
| 	n.walk(func(eps endpoints, subroutes Routes) bool { |  | ||||||
| 		if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Group methodHandlers by unique patterns |  | ||||||
| 		pats := make(map[string]endpoints, 0) |  | ||||||
| 
 |  | ||||||
| 		for mt, h := range eps { |  | ||||||
| 			if h.pattern == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			p, ok := pats[h.pattern] |  | ||||||
| 			if !ok { |  | ||||||
| 				p = endpoints{} |  | ||||||
| 				pats[h.pattern] = p |  | ||||||
| 			} |  | ||||||
| 			p[mt] = h |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for p, mh := range pats { |  | ||||||
| 			hs := make(map[string]http.Handler, 0) |  | ||||||
| 			if mh[mALL] != nil && mh[mALL].handler != nil { |  | ||||||
| 				hs["*"] = mh[mALL].handler |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			for mt, h := range mh { |  | ||||||
| 				if h.handler == nil { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				m := methodTypString(mt) |  | ||||||
| 				if m == "" { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				hs[m] = h.handler |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			rt := Route{p, hs, subroutes} |  | ||||||
| 			rts = append(rts, rt) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return false |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	return rts |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool { |  | ||||||
| 	// Visit the leaf values if any |  | ||||||
| 	if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Recurse on the children |  | ||||||
| 	for _, ns := range n.children { |  | ||||||
| 		for _, cn := range ns { |  | ||||||
| 			if cn.walk(fn) { |  | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // patNextSegment returns the next segment details from a pattern: |  | ||||||
| // node type, param key, regexp string, param tail byte, param starting index, param ending index |  | ||||||
| func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { |  | ||||||
| 	ps := strings.Index(pattern, "{") |  | ||||||
| 	ws := strings.Index(pattern, "*") |  | ||||||
| 
 |  | ||||||
| 	if ps < 0 && ws < 0 { |  | ||||||
| 		return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Sanity check |  | ||||||
| 	if ps >= 0 && ws >= 0 && ws < ps { |  | ||||||
| 		panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var tail byte = '/' // Default endpoint tail to / byte |  | ||||||
| 
 |  | ||||||
| 	if ps >= 0 { |  | ||||||
| 		// Param/Regexp pattern is next |  | ||||||
| 		nt := ntParam |  | ||||||
| 
 |  | ||||||
| 		// Read to closing } taking into account opens and closes in curl count (cc) |  | ||||||
| 		cc := 0 |  | ||||||
| 		pe := ps |  | ||||||
| 		for i, c := range pattern[ps:] { |  | ||||||
| 			if c == '{' { |  | ||||||
| 				cc++ |  | ||||||
| 			} else if c == '}' { |  | ||||||
| 				cc-- |  | ||||||
| 				if cc == 0 { |  | ||||||
| 					pe = ps + i |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if pe == ps { |  | ||||||
| 			panic("chi: route param closing delimiter '}' is missing") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		key := pattern[ps+1 : pe] |  | ||||||
| 		pe++ // set end to next position |  | ||||||
| 
 |  | ||||||
| 		if pe < len(pattern) { |  | ||||||
| 			tail = pattern[pe] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var rexpat string |  | ||||||
| 		if idx := strings.Index(key, ":"); idx >= 0 { |  | ||||||
| 			nt = ntRegexp |  | ||||||
| 			rexpat = key[idx+1:] |  | ||||||
| 			key = key[:idx] |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if len(rexpat) > 0 { |  | ||||||
| 			if rexpat[0] != '^' { |  | ||||||
| 				rexpat = "^" + rexpat |  | ||||||
| 			} |  | ||||||
| 			if rexpat[len(rexpat)-1] != '$' { |  | ||||||
| 				rexpat = rexpat + "$" |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nt, key, rexpat, tail, ps, pe |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wildcard pattern as finale |  | ||||||
| 	if ws < len(pattern)-1 { |  | ||||||
| 		panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead") |  | ||||||
| 	} |  | ||||||
| 	return ntCatchAll, "*", "", 0, ws, len(pattern) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func patParamKeys(pattern string) []string { |  | ||||||
| 	pat := pattern |  | ||||||
| 	paramKeys := []string{} |  | ||||||
| 	for { |  | ||||||
| 		ptyp, paramKey, _, _, _, e := patNextSegment(pat) |  | ||||||
| 		if ptyp == ntStatic { |  | ||||||
| 			return paramKeys |  | ||||||
| 		} |  | ||||||
| 		for i := 0; i < len(paramKeys); i++ { |  | ||||||
| 			if paramKeys[i] == paramKey { |  | ||||||
| 				panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		paramKeys = append(paramKeys, paramKey) |  | ||||||
| 		pat = pat[e:] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // longestPrefix finds the length of the shared prefix |  | ||||||
| // of two strings |  | ||||||
| func longestPrefix(k1, k2 string) int { |  | ||||||
| 	max := len(k1) |  | ||||||
| 	if l := len(k2); l < max { |  | ||||||
| 		max = l |  | ||||||
| 	} |  | ||||||
| 	var i int |  | ||||||
| 	for i = 0; i < max; i++ { |  | ||||||
| 		if k1[i] != k2[i] { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return i |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func methodTypString(method methodTyp) string { |  | ||||||
| 	for s, t := range methodMap { |  | ||||||
| 		if method == t { |  | ||||||
| 			return s |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type nodes []*node |  | ||||||
| 
 |  | ||||||
| // Sort the list of nodes by label |  | ||||||
| func (ns nodes) Sort()              { sort.Sort(ns); ns.tailSort() } |  | ||||||
| func (ns nodes) Len() int           { return len(ns) } |  | ||||||
| func (ns nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] } |  | ||||||
| func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label } |  | ||||||
| 
 |  | ||||||
| // tailSort pushes nodes with '/' as the tail to the end of the list for param nodes. |  | ||||||
| // The list order determines the traversal order. |  | ||||||
| func (ns nodes) tailSort() { |  | ||||||
| 	for i := len(ns) - 1; i >= 0; i-- { |  | ||||||
| 		if ns[i].typ > ntStatic && ns[i].tail == '/' { |  | ||||||
| 			ns.Swap(i, len(ns)-1) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ns nodes) findEdge(label byte) *node { |  | ||||||
| 	num := len(ns) |  | ||||||
| 	idx := 0 |  | ||||||
| 	i, j := 0, num-1 |  | ||||||
| 	for i <= j { |  | ||||||
| 		idx = i + (j-i)/2 |  | ||||||
| 		if label > ns[idx].label { |  | ||||||
| 			i = idx + 1 |  | ||||||
| 		} else if label < ns[idx].label { |  | ||||||
| 			j = idx - 1 |  | ||||||
| 		} else { |  | ||||||
| 			i = num // breaks cond |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if ns[idx].label != label { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return ns[idx] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Route describes the details of a routing handler. |  | ||||||
| type Route struct { |  | ||||||
| 	Pattern   string |  | ||||||
| 	Handlers  map[string]http.Handler |  | ||||||
| 	SubRoutes Routes |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WalkFunc is the type of the function called for each method and route visited by Walk. |  | ||||||
| type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error |  | ||||||
| 
 |  | ||||||
| // Walk walks any router tree that implements Routes interface. |  | ||||||
| func Walk(r Routes, walkFn WalkFunc) error { |  | ||||||
| 	return walk(r, walkFn, "") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error { |  | ||||||
| 	for _, route := range r.Routes() { |  | ||||||
| 		mws := make([]func(http.Handler) http.Handler, len(parentMw)) |  | ||||||
| 		copy(mws, parentMw) |  | ||||||
| 		mws = append(mws, r.Middlewares()...) |  | ||||||
| 
 |  | ||||||
| 		if route.SubRoutes != nil { |  | ||||||
| 			if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for method, handler := range route.Handlers { |  | ||||||
| 			if method == "*" { |  | ||||||
| 				// Ignore a "catchAll" method, since we pass down all the specific methods for each route. |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			fullRoute := parentRoute + route.Pattern |  | ||||||
| 
 |  | ||||||
| 			if chain, ok := handler.(*ChainHandler); ok { |  | ||||||
| 				if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				if err := walkFn(method, fullRoute, handler, mws...); err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										15
									
								
								vendor/github.com/gofrs/uuid/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/gofrs/uuid/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,15 +0,0 @@ | ||||||
| # Binaries for programs and plugins |  | ||||||
| *.exe |  | ||||||
| *.exe~ |  | ||||||
| *.dll |  | ||||||
| *.so |  | ||||||
| *.dylib |  | ||||||
| 
 |  | ||||||
| # Test binary, build with `go test -c` |  | ||||||
| *.test |  | ||||||
| 
 |  | ||||||
| # Output of the go coverage tool, specifically when used with LiteIDE |  | ||||||
| *.out |  | ||||||
| 
 |  | ||||||
| # binary bundle generated by go-fuzz |  | ||||||
| uuid-fuzz.zip |  | ||||||
							
								
								
									
										23
									
								
								vendor/github.com/gofrs/uuid/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/gofrs/uuid/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,23 +0,0 @@ | ||||||
| language: go |  | ||||||
| sudo: false |  | ||||||
| go: |  | ||||||
|   - 1.7.x |  | ||||||
|   - 1.8.x |  | ||||||
|   - 1.9.x |  | ||||||
|   - 1.10.x |  | ||||||
|   - 1.11.x |  | ||||||
|   - tip |  | ||||||
| matrix: |  | ||||||
|   allow_failures: |  | ||||||
|     - go: tip |  | ||||||
|   fast_finish: true |  | ||||||
| env: |  | ||||||
|   - GO111MODULE=on |  | ||||||
| before_install: |  | ||||||
|   - go get golang.org/x/tools/cmd/cover |  | ||||||
| script: |  | ||||||
|   - go test ./... -race -coverprofile=coverage.txt -covermode=atomic |  | ||||||
| after_success: |  | ||||||
|   - bash <(curl -s https://codecov.io/bash) |  | ||||||
| notifications: |  | ||||||
|   email: false |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/gofrs/uuid/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/gofrs/uuid/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,20 +0,0 @@ | ||||||
| Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| a copy of this software and associated documentation files (the |  | ||||||
| "Software"), to deal in the Software without restriction, including |  | ||||||
| without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be |  | ||||||
| included in all copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
							
								
								
									
										109
									
								
								vendor/github.com/gofrs/uuid/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/gofrs/uuid/README.md
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,109 +0,0 @@ | ||||||
| # UUID |  | ||||||
| 
 |  | ||||||
| [](https://github.com/gofrs/uuid/blob/master/LICENSE) |  | ||||||
| [](https://travis-ci.org/gofrs/uuid) |  | ||||||
| [](http://godoc.org/github.com/gofrs/uuid) |  | ||||||
| [](https://codecov.io/gh/gofrs/uuid/) |  | ||||||
| [](https://goreportcard.com/report/github.com/gofrs/uuid) |  | ||||||
| 
 |  | ||||||
| Package uuid provides a pure Go implementation of Universally Unique Identifiers |  | ||||||
| (UUID) variant as defined in RFC-4122. This package supports both the creation |  | ||||||
| and parsing of UUIDs in different formats. |  | ||||||
| 
 |  | ||||||
| This package supports the following UUID versions: |  | ||||||
| * Version 1, based on timestamp and MAC address (RFC-4122) |  | ||||||
| * Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) |  | ||||||
| * Version 3, based on MD5 hashing of a named value (RFC-4122) |  | ||||||
| * Version 4, based on random numbers (RFC-4122) |  | ||||||
| * Version 5, based on SHA-1 hashing of a named value (RFC-4122) |  | ||||||
| 
 |  | ||||||
| ## Project History |  | ||||||
| 
 |  | ||||||
| This project was originally forked from the |  | ||||||
| [github.com/satori/go.uuid](https://github.com/satori/go.uuid) repository after |  | ||||||
| it appeared to be no longer maintained, while exhibiting [critical |  | ||||||
| flaws](https://github.com/satori/go.uuid/issues/73). We have decided to take |  | ||||||
| over this project to ensure it receives regular maintenance for the benefit of |  | ||||||
| the larger Go community. |  | ||||||
| 
 |  | ||||||
| We'd like to thank Maxim Bublis for his hard work on the original iteration of |  | ||||||
| the package. |  | ||||||
| 
 |  | ||||||
| ## License |  | ||||||
| 
 |  | ||||||
| This source code of this package is released under the MIT License. Please see |  | ||||||
| the [LICENSE](https://github.com/gofrs/uuid/blob/master/LICENSE) for the full |  | ||||||
| content of the license. |  | ||||||
| 
 |  | ||||||
| ## Recommended Package Version |  | ||||||
| 
 |  | ||||||
| We recommend using v2.0.0+ of this package, as versions prior to 2.0.0 were |  | ||||||
| created before our fork of the original package and have some known |  | ||||||
| deficiencies. |  | ||||||
| 
 |  | ||||||
| ## Installation |  | ||||||
| 
 |  | ||||||
| It is recommended to use a package manager like `dep` that understands tagged |  | ||||||
| releases of a package, as well as semantic versioning. |  | ||||||
| 
 |  | ||||||
| If you are unable to make use of a dependency manager with your project, you can |  | ||||||
| use the `go get` command to download it directly: |  | ||||||
| 
 |  | ||||||
| ```Shell |  | ||||||
| $ go get github.com/gofrs/uuid |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Requirements |  | ||||||
| 
 |  | ||||||
| Due to subtests not being supported in older versions of Go, this package is |  | ||||||
| only regularly tested against Go 1.7+. This package may work perfectly fine with |  | ||||||
| Go 1.2+, but support for these older versions is not actively maintained. |  | ||||||
| 
 |  | ||||||
| ## Go 1.11 Modules |  | ||||||
| 
 |  | ||||||
| As of v3.2.0, this repository no longer adopts Go modules, and v3.2.0 no longer has a `go.mod` file.  As a result, v3.2.0 also drops support for the `github.com/gofrs/uuid/v3` import path. Only module-based consumers are impacted.  With the v3.2.0 release, _all_ gofrs/uuid consumers should use the `github.com/gofrs/uuid` import path. |  | ||||||
| 
 |  | ||||||
| An existing module-based consumer will continue to be able to build using the `github.com/gofrs/uuid/v3` import path using any valid consumer `go.mod` that worked prior to the publishing of v3.2.0, but any module-based consumer should start using the `github.com/gofrs/uuid` import path when possible and _must_ use the `github.com/gofrs/uuid` import path prior to upgrading to v3.2.0. |  | ||||||
| 
 |  | ||||||
| Please refer to [Issue #61](https://github.com/gofrs/uuid/issues/61) and [Issue #66](https://github.com/gofrs/uuid/issues/66) for more details. |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| Here is a quick overview of how to use this package. For more detailed |  | ||||||
| documentation, please see the [GoDoc Page](http://godoc.org/github.com/gofrs/uuid). |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"log" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gofrs/uuid" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Create a Version 4 UUID, panicking on error. |  | ||||||
| // Use this form to initialize package-level variables. |  | ||||||
| var u1 = uuid.Must(uuid.NewV4()) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	// Create a Version 4 UUID. |  | ||||||
| 	u2, err := uuid.NewV4() |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatalf("failed to generate UUID: %v", err) |  | ||||||
| 	} |  | ||||||
| 	log.Printf("generated Version 4 UUID %v", u2) |  | ||||||
| 
 |  | ||||||
| 	// Parse a UUID from a string. |  | ||||||
| 	s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" |  | ||||||
| 	u3, err := uuid.FromString(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatalf("failed to parse UUID %q: %v", s, err) |  | ||||||
| 	} |  | ||||||
| 	log.Printf("successfully parsed UUID %v", u3) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## References |  | ||||||
| 
 |  | ||||||
| * [RFC-4122](https://tools.ietf.org/html/rfc4122) |  | ||||||
| * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) |  | ||||||
							
								
								
									
										212
									
								
								vendor/github.com/gofrs/uuid/codec.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										212
									
								
								vendor/github.com/gofrs/uuid/codec.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,212 +0,0 @@ | ||||||
| // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| // the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"fmt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // FromBytes returns a UUID generated from the raw byte slice input. |  | ||||||
| // It will return an error if the slice isn't 16 bytes long. |  | ||||||
| func FromBytes(input []byte) (UUID, error) { |  | ||||||
| 	u := UUID{} |  | ||||||
| 	err := u.UnmarshalBinary(input) |  | ||||||
| 	return u, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FromBytesOrNil returns a UUID generated from the raw byte slice input. |  | ||||||
| // Same behavior as FromBytes(), but returns uuid.Nil instead of an error. |  | ||||||
| func FromBytesOrNil(input []byte) UUID { |  | ||||||
| 	uuid, err := FromBytes(input) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil |  | ||||||
| 	} |  | ||||||
| 	return uuid |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FromString returns a UUID parsed from the input string. |  | ||||||
| // Input is expected in a form accepted by UnmarshalText. |  | ||||||
| func FromString(input string) (UUID, error) { |  | ||||||
| 	u := UUID{} |  | ||||||
| 	err := u.UnmarshalText([]byte(input)) |  | ||||||
| 	return u, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // FromStringOrNil returns a UUID parsed from the input string. |  | ||||||
| // Same behavior as FromString(), but returns uuid.Nil instead of an error. |  | ||||||
| func FromStringOrNil(input string) UUID { |  | ||||||
| 	uuid, err := FromString(input) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil |  | ||||||
| 	} |  | ||||||
| 	return uuid |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalText implements the encoding.TextMarshaler interface. |  | ||||||
| // The encoding is the same as returned by the String() method. |  | ||||||
| func (u UUID) MarshalText() ([]byte, error) { |  | ||||||
| 	return []byte(u.String()), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // UnmarshalText implements the encoding.TextUnmarshaler interface. |  | ||||||
| // Following formats are supported: |  | ||||||
| // |  | ||||||
| //   "6ba7b810-9dad-11d1-80b4-00c04fd430c8", |  | ||||||
| //   "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", |  | ||||||
| //   "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" |  | ||||||
| //   "6ba7b8109dad11d180b400c04fd430c8" |  | ||||||
| //   "{6ba7b8109dad11d180b400c04fd430c8}", |  | ||||||
| //   "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" |  | ||||||
| // |  | ||||||
| // ABNF for supported UUID text representation follows: |  | ||||||
| // |  | ||||||
| //   URN := 'urn' |  | ||||||
| //   UUID-NID := 'uuid' |  | ||||||
| // |  | ||||||
| //   hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | |  | ||||||
| //             'a' | 'b' | 'c' | 'd' | 'e' | 'f' | |  | ||||||
| //             'A' | 'B' | 'C' | 'D' | 'E' | 'F' |  | ||||||
| // |  | ||||||
| //   hexoct := hexdig hexdig |  | ||||||
| //   2hexoct := hexoct hexoct |  | ||||||
| //   4hexoct := 2hexoct 2hexoct |  | ||||||
| //   6hexoct := 4hexoct 2hexoct |  | ||||||
| //   12hexoct := 6hexoct 6hexoct |  | ||||||
| // |  | ||||||
| //   hashlike := 12hexoct |  | ||||||
| //   canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct |  | ||||||
| // |  | ||||||
| //   plain := canonical | hashlike |  | ||||||
| //   uuid := canonical | hashlike | braced | urn |  | ||||||
| // |  | ||||||
| //   braced := '{' plain '}' | '{' hashlike  '}' |  | ||||||
| //   urn := URN ':' UUID-NID ':' plain |  | ||||||
| // |  | ||||||
| func (u *UUID) UnmarshalText(text []byte) error { |  | ||||||
| 	switch len(text) { |  | ||||||
| 	case 32: |  | ||||||
| 		return u.decodeHashLike(text) |  | ||||||
| 	case 34, 38: |  | ||||||
| 		return u.decodeBraced(text) |  | ||||||
| 	case 36: |  | ||||||
| 		return u.decodeCanonical(text) |  | ||||||
| 	case 41, 45: |  | ||||||
| 		return u.decodeURN(text) |  | ||||||
| 	default: |  | ||||||
| 		return fmt.Errorf("uuid: incorrect UUID length: %s", text) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // decodeCanonical decodes UUID strings that are formatted as defined in RFC-4122 (section 3): |  | ||||||
| // "6ba7b810-9dad-11d1-80b4-00c04fd430c8". |  | ||||||
| func (u *UUID) decodeCanonical(t []byte) error { |  | ||||||
| 	if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { |  | ||||||
| 		return fmt.Errorf("uuid: incorrect UUID format %s", t) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	src := t |  | ||||||
| 	dst := u[:] |  | ||||||
| 
 |  | ||||||
| 	for i, byteGroup := range byteGroups { |  | ||||||
| 		if i > 0 { |  | ||||||
| 			src = src[1:] // skip dash |  | ||||||
| 		} |  | ||||||
| 		_, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		src = src[byteGroup:] |  | ||||||
| 		dst = dst[byteGroup/2:] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // decodeHashLike decodes UUID strings that are using the following format: |  | ||||||
| //  "6ba7b8109dad11d180b400c04fd430c8". |  | ||||||
| func (u *UUID) decodeHashLike(t []byte) error { |  | ||||||
| 	src := t[:] |  | ||||||
| 	dst := u[:] |  | ||||||
| 
 |  | ||||||
| 	_, err := hex.Decode(dst, src) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // decodeBraced decodes UUID strings that are using the following formats: |  | ||||||
| //  "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" |  | ||||||
| //  "{6ba7b8109dad11d180b400c04fd430c8}". |  | ||||||
| func (u *UUID) decodeBraced(t []byte) error { |  | ||||||
| 	l := len(t) |  | ||||||
| 
 |  | ||||||
| 	if t[0] != '{' || t[l-1] != '}' { |  | ||||||
| 		return fmt.Errorf("uuid: incorrect UUID format %s", t) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return u.decodePlain(t[1 : l-1]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // decodeURN decodes UUID strings that are using the following formats: |  | ||||||
| //  "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" |  | ||||||
| //  "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". |  | ||||||
| func (u *UUID) decodeURN(t []byte) error { |  | ||||||
| 	total := len(t) |  | ||||||
| 
 |  | ||||||
| 	urnUUIDPrefix := t[:9] |  | ||||||
| 
 |  | ||||||
| 	if !bytes.Equal(urnUUIDPrefix, urnPrefix) { |  | ||||||
| 		return fmt.Errorf("uuid: incorrect UUID format: %s", t) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return u.decodePlain(t[9:total]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // decodePlain decodes UUID strings that are using the following formats: |  | ||||||
| //  "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format |  | ||||||
| //  "6ba7b8109dad11d180b400c04fd430c8". |  | ||||||
| func (u *UUID) decodePlain(t []byte) error { |  | ||||||
| 	switch len(t) { |  | ||||||
| 	case 32: |  | ||||||
| 		return u.decodeHashLike(t) |  | ||||||
| 	case 36: |  | ||||||
| 		return u.decodeCanonical(t) |  | ||||||
| 	default: |  | ||||||
| 		return fmt.Errorf("uuid: incorrect UUID length: %s", t) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalBinary implements the encoding.BinaryMarshaler interface. |  | ||||||
| func (u UUID) MarshalBinary() ([]byte, error) { |  | ||||||
| 	return u.Bytes(), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. |  | ||||||
| // It will return an error if the slice isn't 16 bytes long. |  | ||||||
| func (u *UUID) UnmarshalBinary(data []byte) error { |  | ||||||
| 	if len(data) != Size { |  | ||||||
| 		return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) |  | ||||||
| 	} |  | ||||||
| 	copy(u[:], data) |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										47
									
								
								vendor/github.com/gofrs/uuid/fuzz.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/gofrs/uuid/fuzz.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,47 +0,0 @@ | ||||||
| // Copyright (c) 2018 Andrei Tudor Călin <mail@acln.ro> |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| // the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
| // +build gofuzz |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| // Fuzz implements a simple fuzz test for FromString / UnmarshalText. |  | ||||||
| // |  | ||||||
| // To run: |  | ||||||
| // |  | ||||||
| //     $ go get github.com/dvyukov/go-fuzz/... |  | ||||||
| //     $ cd $GOPATH/src/github.com/gofrs/uuid |  | ||||||
| //     $ go-fuzz-build github.com/gofrs/uuid |  | ||||||
| //     $ go-fuzz -bin=uuid-fuzz.zip -workdir=./testdata |  | ||||||
| // |  | ||||||
| // If you make significant changes to FromString / UnmarshalText and add |  | ||||||
| // new cases to fromStringTests (in codec_test.go), please run |  | ||||||
| // |  | ||||||
| //    $ go test -seed_fuzz_corpus |  | ||||||
| // |  | ||||||
| // to seed the corpus with the new interesting inputs, then run the fuzzer. |  | ||||||
| func Fuzz(data []byte) int { |  | ||||||
| 	_, err := FromString(string(data)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0 |  | ||||||
| 	} |  | ||||||
| 	return 1 |  | ||||||
| } |  | ||||||
							
								
								
									
										299
									
								
								vendor/github.com/gofrs/uuid/generator.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										299
									
								
								vendor/github.com/gofrs/uuid/generator.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,299 +0,0 @@ | ||||||
| // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| // the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/md5" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/sha1" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"fmt" |  | ||||||
| 	"hash" |  | ||||||
| 	"io" |  | ||||||
| 	"net" |  | ||||||
| 	"os" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Difference in 100-nanosecond intervals between |  | ||||||
| // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). |  | ||||||
| const epochStart = 122192928000000000 |  | ||||||
| 
 |  | ||||||
| type epochFunc func() time.Time |  | ||||||
| 
 |  | ||||||
| // HWAddrFunc is the function type used to provide hardware (MAC) addresses. |  | ||||||
| type HWAddrFunc func() (net.HardwareAddr, error) |  | ||||||
| 
 |  | ||||||
| // DefaultGenerator is the default UUID Generator used by this package. |  | ||||||
| var DefaultGenerator Generator = NewGen() |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	posixUID = uint32(os.Getuid()) |  | ||||||
| 	posixGID = uint32(os.Getgid()) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // NewV1 returns a UUID based on the current timestamp and MAC address. |  | ||||||
| func NewV1() (UUID, error) { |  | ||||||
| 	return DefaultGenerator.NewV1() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV2 returns a DCE Security UUID based on the POSIX UID/GID. |  | ||||||
| func NewV2(domain byte) (UUID, error) { |  | ||||||
| 	return DefaultGenerator.NewV2(domain) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. |  | ||||||
| func NewV3(ns UUID, name string) UUID { |  | ||||||
| 	return DefaultGenerator.NewV3(ns, name) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV4 returns a randomly generated UUID. |  | ||||||
| func NewV4() (UUID, error) { |  | ||||||
| 	return DefaultGenerator.NewV4() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. |  | ||||||
| func NewV5(ns UUID, name string) UUID { |  | ||||||
| 	return DefaultGenerator.NewV5(ns, name) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Generator provides an interface for generating UUIDs. |  | ||||||
| type Generator interface { |  | ||||||
| 	NewV1() (UUID, error) |  | ||||||
| 	NewV2(domain byte) (UUID, error) |  | ||||||
| 	NewV3(ns UUID, name string) UUID |  | ||||||
| 	NewV4() (UUID, error) |  | ||||||
| 	NewV5(ns UUID, name string) UUID |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Gen is a reference UUID generator based on the specifications laid out in |  | ||||||
| // RFC-4122 and DCE 1.1: Authentication and Security Services. This type |  | ||||||
| // satisfies the Generator interface as defined in this package. |  | ||||||
| // |  | ||||||
| // For consumers who are generating V1 UUIDs, but don't want to expose the MAC |  | ||||||
| // address of the node generating the UUIDs, the NewGenWithHWAF() function has been |  | ||||||
| // provided as a convenience. See the function's documentation for more info. |  | ||||||
| // |  | ||||||
| // The authors of this package do not feel that the majority of users will need |  | ||||||
| // to obfuscate their MAC address, and so we recommend using NewGen() to create |  | ||||||
| // a new generator. |  | ||||||
| type Gen struct { |  | ||||||
| 	clockSequenceOnce sync.Once |  | ||||||
| 	hardwareAddrOnce  sync.Once |  | ||||||
| 	storageMutex      sync.Mutex |  | ||||||
| 
 |  | ||||||
| 	rand io.Reader |  | ||||||
| 
 |  | ||||||
| 	epochFunc     epochFunc |  | ||||||
| 	hwAddrFunc    HWAddrFunc |  | ||||||
| 	lastTime      uint64 |  | ||||||
| 	clockSequence uint16 |  | ||||||
| 	hardwareAddr  [6]byte |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // interface check -- build will fail if *Gen doesn't satisfy Generator |  | ||||||
| var _ Generator = (*Gen)(nil) |  | ||||||
| 
 |  | ||||||
| // NewGen returns a new instance of Gen with some default values set. Most |  | ||||||
| // people should use this. |  | ||||||
| func NewGen() *Gen { |  | ||||||
| 	return NewGenWithHWAF(defaultHWAddrFunc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewGenWithHWAF builds a new UUID generator with the HWAddrFunc provided. Most |  | ||||||
| // consumers should use NewGen() instead. |  | ||||||
| // |  | ||||||
| // This is used so that consumers can generate their own MAC addresses, for use |  | ||||||
| // in the generated UUIDs, if there is some concern about exposing the physical |  | ||||||
| // address of the machine generating the UUID. |  | ||||||
| // |  | ||||||
| // The Gen generator will only invoke the HWAddrFunc once, and cache that MAC |  | ||||||
| // address for all the future UUIDs generated by it. If you'd like to switch the |  | ||||||
| // MAC address being used, you'll need to create a new generator using this |  | ||||||
| // function. |  | ||||||
| func NewGenWithHWAF(hwaf HWAddrFunc) *Gen { |  | ||||||
| 	return &Gen{ |  | ||||||
| 		epochFunc:  time.Now, |  | ||||||
| 		hwAddrFunc: hwaf, |  | ||||||
| 		rand:       rand.Reader, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV1 returns a UUID based on the current timestamp and MAC address. |  | ||||||
| func (g *Gen) NewV1() (UUID, error) { |  | ||||||
| 	u := UUID{} |  | ||||||
| 
 |  | ||||||
| 	timeNow, clockSeq, err := g.getClockSequence() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil, err |  | ||||||
| 	} |  | ||||||
| 	binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) |  | ||||||
| 	binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) |  | ||||||
| 	binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) |  | ||||||
| 	binary.BigEndian.PutUint16(u[8:], clockSeq) |  | ||||||
| 
 |  | ||||||
| 	hardwareAddr, err := g.getHardwareAddr() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil, err |  | ||||||
| 	} |  | ||||||
| 	copy(u[10:], hardwareAddr) |  | ||||||
| 
 |  | ||||||
| 	u.SetVersion(V1) |  | ||||||
| 	u.SetVariant(VariantRFC4122) |  | ||||||
| 
 |  | ||||||
| 	return u, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV2 returns a DCE Security UUID based on the POSIX UID/GID. |  | ||||||
| func (g *Gen) NewV2(domain byte) (UUID, error) { |  | ||||||
| 	u, err := g.NewV1() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return Nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch domain { |  | ||||||
| 	case DomainPerson: |  | ||||||
| 		binary.BigEndian.PutUint32(u[:], posixUID) |  | ||||||
| 	case DomainGroup: |  | ||||||
| 		binary.BigEndian.PutUint32(u[:], posixGID) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	u[9] = domain |  | ||||||
| 
 |  | ||||||
| 	u.SetVersion(V2) |  | ||||||
| 	u.SetVariant(VariantRFC4122) |  | ||||||
| 
 |  | ||||||
| 	return u, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. |  | ||||||
| func (g *Gen) NewV3(ns UUID, name string) UUID { |  | ||||||
| 	u := newFromHash(md5.New(), ns, name) |  | ||||||
| 	u.SetVersion(V3) |  | ||||||
| 	u.SetVariant(VariantRFC4122) |  | ||||||
| 
 |  | ||||||
| 	return u |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV4 returns a randomly generated UUID. |  | ||||||
| func (g *Gen) NewV4() (UUID, error) { |  | ||||||
| 	u := UUID{} |  | ||||||
| 	if _, err := io.ReadFull(g.rand, u[:]); err != nil { |  | ||||||
| 		return Nil, err |  | ||||||
| 	} |  | ||||||
| 	u.SetVersion(V4) |  | ||||||
| 	u.SetVariant(VariantRFC4122) |  | ||||||
| 
 |  | ||||||
| 	return u, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. |  | ||||||
| func (g *Gen) NewV5(ns UUID, name string) UUID { |  | ||||||
| 	u := newFromHash(sha1.New(), ns, name) |  | ||||||
| 	u.SetVersion(V5) |  | ||||||
| 	u.SetVariant(VariantRFC4122) |  | ||||||
| 
 |  | ||||||
| 	return u |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the epoch and clock sequence. |  | ||||||
| func (g *Gen) getClockSequence() (uint64, uint16, error) { |  | ||||||
| 	var err error |  | ||||||
| 	g.clockSequenceOnce.Do(func() { |  | ||||||
| 		buf := make([]byte, 2) |  | ||||||
| 		if _, err = io.ReadFull(g.rand, buf); err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		g.clockSequence = binary.BigEndian.Uint16(buf) |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, 0, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	g.storageMutex.Lock() |  | ||||||
| 	defer g.storageMutex.Unlock() |  | ||||||
| 
 |  | ||||||
| 	timeNow := g.getEpoch() |  | ||||||
| 	// Clock didn't change since last UUID generation. |  | ||||||
| 	// Should increase clock sequence. |  | ||||||
| 	if timeNow <= g.lastTime { |  | ||||||
| 		g.clockSequence++ |  | ||||||
| 	} |  | ||||||
| 	g.lastTime = timeNow |  | ||||||
| 
 |  | ||||||
| 	return timeNow, g.clockSequence, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the hardware address. |  | ||||||
| func (g *Gen) getHardwareAddr() ([]byte, error) { |  | ||||||
| 	var err error |  | ||||||
| 	g.hardwareAddrOnce.Do(func() { |  | ||||||
| 		var hwAddr net.HardwareAddr |  | ||||||
| 		if hwAddr, err = g.hwAddrFunc(); err == nil { |  | ||||||
| 			copy(g.hardwareAddr[:], hwAddr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Initialize hardwareAddr randomly in case |  | ||||||
| 		// of real network interfaces absence. |  | ||||||
| 		if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		// Set multicast bit as recommended by RFC-4122 |  | ||||||
| 		g.hardwareAddr[0] |= 0x01 |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return []byte{}, err |  | ||||||
| 	} |  | ||||||
| 	return g.hardwareAddr[:], nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the difference between UUID epoch (October 15, 1582) |  | ||||||
| // and current time in 100-nanosecond intervals. |  | ||||||
| func (g *Gen) getEpoch() uint64 { |  | ||||||
| 	return epochStart + uint64(g.epochFunc().UnixNano()/100) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the UUID based on the hashing of the namespace UUID and name. |  | ||||||
| func newFromHash(h hash.Hash, ns UUID, name string) UUID { |  | ||||||
| 	u := UUID{} |  | ||||||
| 	h.Write(ns[:]) |  | ||||||
| 	h.Write([]byte(name)) |  | ||||||
| 	copy(u[:], h.Sum(nil)) |  | ||||||
| 
 |  | ||||||
| 	return u |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Returns the hardware address. |  | ||||||
| func defaultHWAddrFunc() (net.HardwareAddr, error) { |  | ||||||
| 	ifaces, err := net.Interfaces() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return []byte{}, err |  | ||||||
| 	} |  | ||||||
| 	for _, iface := range ifaces { |  | ||||||
| 		if len(iface.HardwareAddr) >= 6 { |  | ||||||
| 			return iface.HardwareAddr, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return []byte{}, fmt.Errorf("uuid: no HW address found") |  | ||||||
| } |  | ||||||
							
								
								
									
										109
									
								
								vendor/github.com/gofrs/uuid/sql.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/gofrs/uuid/sql.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,109 +0,0 @@ | ||||||
| // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| // the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"database/sql/driver" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Value implements the driver.Valuer interface. |  | ||||||
| func (u UUID) Value() (driver.Value, error) { |  | ||||||
| 	return u.String(), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Scan implements the sql.Scanner interface. |  | ||||||
| // A 16-byte slice will be handled by UnmarshalBinary, while |  | ||||||
| // a longer byte slice or a string will be handled by UnmarshalText. |  | ||||||
| func (u *UUID) Scan(src interface{}) error { |  | ||||||
| 	switch src := src.(type) { |  | ||||||
| 	case UUID: // support gorm convert from UUID to NullUUID |  | ||||||
| 		*u = src |  | ||||||
| 		return nil |  | ||||||
| 
 |  | ||||||
| 	case []byte: |  | ||||||
| 		if len(src) == Size { |  | ||||||
| 			return u.UnmarshalBinary(src) |  | ||||||
| 		} |  | ||||||
| 		return u.UnmarshalText(src) |  | ||||||
| 
 |  | ||||||
| 	case string: |  | ||||||
| 		return u.UnmarshalText([]byte(src)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fmt.Errorf("uuid: cannot convert %T to UUID", src) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NullUUID can be used with the standard sql package to represent a |  | ||||||
| // UUID value that can be NULL in the database. |  | ||||||
| type NullUUID struct { |  | ||||||
| 	UUID  UUID |  | ||||||
| 	Valid bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Value implements the driver.Valuer interface. |  | ||||||
| func (u NullUUID) Value() (driver.Value, error) { |  | ||||||
| 	if !u.Valid { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| 	// Delegate to UUID Value function |  | ||||||
| 	return u.UUID.Value() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Scan implements the sql.Scanner interface. |  | ||||||
| func (u *NullUUID) Scan(src interface{}) error { |  | ||||||
| 	if src == nil { |  | ||||||
| 		u.UUID, u.Valid = Nil, false |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Delegate to UUID Scan function |  | ||||||
| 	u.Valid = true |  | ||||||
| 	return u.UUID.Scan(src) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // MarshalJSON marshals the NullUUID as null or the nested UUID |  | ||||||
| func (u NullUUID) MarshalJSON() ([]byte, error) { |  | ||||||
| 	if !u.Valid { |  | ||||||
| 		return json.Marshal(nil) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return json.Marshal(u.UUID) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // UnmarshalJSON unmarshals a NullUUID |  | ||||||
| func (u *NullUUID) UnmarshalJSON(b []byte) error { |  | ||||||
| 	if bytes.Equal(b, []byte("null")) { |  | ||||||
| 		u.UUID, u.Valid = Nil, false |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := json.Unmarshal(b, &u.UUID); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	u.Valid = true |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										189
									
								
								vendor/github.com/gofrs/uuid/uuid.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										189
									
								
								vendor/github.com/gofrs/uuid/uuid.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,189 +0,0 @@ | ||||||
| // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| // the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
| 
 |  | ||||||
| // Package uuid provides implementations of the Universally Unique Identifier (UUID), as specified in RFC-4122 and DCE 1.1. |  | ||||||
| // |  | ||||||
| // RFC-4122[1] provides the specification for versions 1, 3, 4, and 5. |  | ||||||
| // |  | ||||||
| // DCE 1.1[2] provides the specification for version 2. |  | ||||||
| // |  | ||||||
| // [1] https://tools.ietf.org/html/rfc4122 |  | ||||||
| // [2] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 |  | ||||||
| package uuid |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"fmt" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Size of a UUID in bytes. |  | ||||||
| const Size = 16 |  | ||||||
| 
 |  | ||||||
| // UUID is an array type to represent the value of a UUID, as defined in RFC-4122. |  | ||||||
| type UUID [Size]byte |  | ||||||
| 
 |  | ||||||
| // UUID versions. |  | ||||||
| const ( |  | ||||||
| 	_  byte = iota |  | ||||||
| 	V1      // Version 1 (date-time and MAC address) |  | ||||||
| 	V2      // Version 2 (date-time and MAC address, DCE security version) |  | ||||||
| 	V3      // Version 3 (namespace name-based) |  | ||||||
| 	V4      // Version 4 (random) |  | ||||||
| 	V5      // Version 5 (namespace name-based) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // UUID layout variants. |  | ||||||
| const ( |  | ||||||
| 	VariantNCS byte = iota |  | ||||||
| 	VariantRFC4122 |  | ||||||
| 	VariantMicrosoft |  | ||||||
| 	VariantFuture |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // UUID DCE domains. |  | ||||||
| const ( |  | ||||||
| 	DomainPerson = iota |  | ||||||
| 	DomainGroup |  | ||||||
| 	DomainOrg |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Timestamp is the count of 100-nanosecond intervals since 00:00:00.00, |  | ||||||
| // 15 October 1582 within a V1 UUID. This type has no meaning for V2-V5 |  | ||||||
| // UUIDs since they don't have an embedded timestamp. |  | ||||||
| type Timestamp uint64 |  | ||||||
| 
 |  | ||||||
| const _100nsPerSecond = 10000000 |  | ||||||
| 
 |  | ||||||
| // Time returns the UTC time.Time representation of a Timestamp |  | ||||||
| func (t Timestamp) Time() (time.Time, error) { |  | ||||||
| 	secs := uint64(t) / _100nsPerSecond |  | ||||||
| 	nsecs := 100 * (uint64(t) % _100nsPerSecond) |  | ||||||
| 	return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TimestampFromV1 returns the Timestamp embedded within a V1 UUID. |  | ||||||
| // Returns an error if the UUID is any version other than 1. |  | ||||||
| func TimestampFromV1(u UUID) (Timestamp, error) { |  | ||||||
| 	if u.Version() != 1 { |  | ||||||
| 		err := fmt.Errorf("uuid: %s is version %d, not version 1", u, u.Version()) |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
| 	low := binary.BigEndian.Uint32(u[0:4]) |  | ||||||
| 	mid := binary.BigEndian.Uint16(u[4:6]) |  | ||||||
| 	hi := binary.BigEndian.Uint16(u[6:8]) & 0xfff |  | ||||||
| 	return Timestamp(uint64(low) + (uint64(mid) << 32) + (uint64(hi) << 48)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String parse helpers. |  | ||||||
| var ( |  | ||||||
| 	urnPrefix  = []byte("urn:uuid:") |  | ||||||
| 	byteGroups = []int{8, 4, 4, 4, 12} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Nil is the nil UUID, as specified in RFC-4122, that has all 128 bits set to |  | ||||||
| // zero. |  | ||||||
| var Nil = UUID{} |  | ||||||
| 
 |  | ||||||
| // Predefined namespace UUIDs. |  | ||||||
| var ( |  | ||||||
| 	NamespaceDNS  = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NamespaceURL  = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NamespaceOID  = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| 	NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Version returns the algorithm version used to generate the UUID. |  | ||||||
| func (u UUID) Version() byte { |  | ||||||
| 	return u[6] >> 4 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Variant returns the UUID layout variant. |  | ||||||
| func (u UUID) Variant() byte { |  | ||||||
| 	switch { |  | ||||||
| 	case (u[8] >> 7) == 0x00: |  | ||||||
| 		return VariantNCS |  | ||||||
| 	case (u[8] >> 6) == 0x02: |  | ||||||
| 		return VariantRFC4122 |  | ||||||
| 	case (u[8] >> 5) == 0x06: |  | ||||||
| 		return VariantMicrosoft |  | ||||||
| 	case (u[8] >> 5) == 0x07: |  | ||||||
| 		fallthrough |  | ||||||
| 	default: |  | ||||||
| 		return VariantFuture |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Bytes returns a byte slice representation of the UUID. |  | ||||||
| func (u UUID) Bytes() []byte { |  | ||||||
| 	return u[:] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // String returns a canonical RFC-4122 string representation of the UUID: |  | ||||||
| // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. |  | ||||||
| func (u UUID) String() string { |  | ||||||
| 	buf := make([]byte, 36) |  | ||||||
| 
 |  | ||||||
| 	hex.Encode(buf[0:8], u[0:4]) |  | ||||||
| 	buf[8] = '-' |  | ||||||
| 	hex.Encode(buf[9:13], u[4:6]) |  | ||||||
| 	buf[13] = '-' |  | ||||||
| 	hex.Encode(buf[14:18], u[6:8]) |  | ||||||
| 	buf[18] = '-' |  | ||||||
| 	hex.Encode(buf[19:23], u[8:10]) |  | ||||||
| 	buf[23] = '-' |  | ||||||
| 	hex.Encode(buf[24:], u[10:]) |  | ||||||
| 
 |  | ||||||
| 	return string(buf) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetVersion sets the version bits. |  | ||||||
| func (u *UUID) SetVersion(v byte) { |  | ||||||
| 	u[6] = (u[6] & 0x0f) | (v << 4) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetVariant sets the variant bits. |  | ||||||
| func (u *UUID) SetVariant(v byte) { |  | ||||||
| 	switch v { |  | ||||||
| 	case VariantNCS: |  | ||||||
| 		u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) |  | ||||||
| 	case VariantRFC4122: |  | ||||||
| 		u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) |  | ||||||
| 	case VariantMicrosoft: |  | ||||||
| 		u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) |  | ||||||
| 	case VariantFuture: |  | ||||||
| 		fallthrough |  | ||||||
| 	default: |  | ||||||
| 		u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Must is a helper that wraps a call to a function returning (UUID, error) |  | ||||||
| // and panics if the error is non-nil. It is intended for use in variable |  | ||||||
| // initializations such as |  | ||||||
| //  var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")) |  | ||||||
| func Must(u UUID, err error) UUID { |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	return u |  | ||||||
| } |  | ||||||
							
								
								
									
										8
									
								
								vendor/github.com/gorilla/mux/AUTHORS
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/gorilla/mux/AUTHORS
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,8 +0,0 @@ | ||||||
| # This is the official list of gorilla/mux authors for copyright purposes. |  | ||||||
| # |  | ||||||
| # Please keep the list sorted. |  | ||||||
| 
 |  | ||||||
| Google LLC (https://opensource.google.com/) |  | ||||||
| Kamil Kisielk <kamil@kamilkisiel.net> |  | ||||||
| Matt Silverlock <matt@eatsleeprepeat.net> |  | ||||||
| Rodrigo Moraes (https://github.com/moraes) |  | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue