Compare commits
108 commits
Author | SHA1 | Date | |
---|---|---|---|
Augusto Dwenger J. | 627dea49c5 | ||
777ff2376d | |||
41981a3d8a | |||
47789eec68 | |||
4c880193ce | |||
86aeecfac2 | |||
Augusto Dwenger J. | 284d332424 | ||
Augusto Dwenger J. | 2e58b052f9 | ||
e8af97872c | |||
d7590729f1 | |||
f7063651c2 | |||
42d1e077c9 | |||
1258ac759f | |||
a60a9c4811 | |||
b6200a88c0 | |||
1618440f4f | |||
f676d25787 | |||
e24ff056f7 | |||
833443dfaf | |||
b6f8563d18 | |||
20ed231c53 | |||
b4470cf4bd | |||
83c202fde3 | |||
65f98b227a | |||
74545dd8bc | |||
400b503587 | |||
3846857b38 | |||
aa3eabc9e2 | |||
Augusto Dwenger J. | 6eb81b7579 | ||
Augusto Dwenger J. | ecc6de50a1 | ||
Augusto Dwenger J. | e58ae4bd33 | ||
bd4e9250c0 | |||
00940603ca | |||
621dc6c22f | |||
5367b9ea94 | |||
8e275e27be | |||
56e184748f | |||
b40bf8101f | |||
70e364c57b | |||
98b09cb27f | |||
aad95ae813 | |||
2bd7ec5cd0 | |||
85b2bd5dc4 | |||
fdec6aa9ac | |||
eaa82b8605 | |||
f4bdbeee73 | |||
905412e91b | |||
9318d19383 | |||
d62e765ae7 | |||
097ff92f17 | |||
fe28075b35 | |||
3339651d88 | |||
c8d67edf2c | |||
41ba422675 | |||
f9f3142543 | |||
9442e00997 | |||
1bbc103672 | |||
a16f689e99 | |||
6d5aae4dde | |||
9f9d518689 | |||
23441ea0c2 | |||
d22065f7e0 | |||
8e3ddf6f3c | |||
0fdd7d5906 | |||
e0ced3f803 | |||
261a04d83f | |||
d0fd2cc458 | |||
7056ab85f8 | |||
84d280a9a4 | |||
21ef8634cf | |||
392f251bc2 | |||
727a1cdbfa | |||
5333393394 | |||
6556afc08a | |||
c012da6e2e | |||
44fb4d2d09 | |||
64299ce9f9 | |||
4f6226d83d | |||
afe8cb7692 | |||
a80617c85f | |||
fac40bac1c | |||
cdc5b318a0 | |||
b0403b60d9 | |||
d7b886751b | |||
22555ed424 | |||
1c60ccc233 | |||
3175093adf | |||
72a9609acb | |||
b2923eaa4a | |||
c3218a122f | |||
65bb5c6302 | |||
0cacad260e | |||
40f02fba65 | |||
e5bb87aaa1 | |||
80b84ad6b9 | |||
395537a5d2 | |||
dc03b85283 | |||
56aec4c702 | |||
951128c735 | |||
8e4187fe19 | |||
403a2ea84b | |||
7f4444b8a3 | |||
bd7666b3e6 | |||
876b8cffc4 | |||
6bfa914a51 | |||
24ef16e80e | |||
cc6b6987f8 | |||
1b217ca440 |
47
.drone.yml
Normal file
|
@ -0,0 +1,47 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: tests
|
||||
|
||||
steps:
|
||||
- name: unit-test
|
||||
image: golang
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test -coverprofile=coverage.out -covermode=count ./...
|
||||
- go tool cover -func=coverage.out | grep total
|
||||
|
||||
- name: race-test
|
||||
image: golang
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test -race ./...
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
temp: {}
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-build
|
||||
|
||||
steps:
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.hhhammer.de
|
||||
username: ci
|
||||
password:
|
||||
from_secret: DOCKER_REGISTRY_KEY
|
||||
repo: registry.hhhammer.de/gohttpserver
|
||||
dockerfile: docker/Dockerfile
|
||||
auto_tag: true
|
||||
pull_image: true
|
||||
|
||||
depends_on:
|
||||
- tests
|
||||
|
5
.gitignore
vendored
|
@ -23,8 +23,11 @@ _testmain.go
|
|||
*.test
|
||||
*.prof
|
||||
|
||||
dist/
|
||||
|
||||
gohttpserver
|
||||
bindata_assetfs.go
|
||||
assets_vfsdata.go
|
||||
*.un~
|
||||
*.swp
|
||||
|
||||
dist/
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
project_name: gohttpserver
|
||||
release:
|
||||
github:
|
||||
owner: codeskyblue
|
||||
name: gohttpserver
|
||||
brew:
|
||||
install: bin.install "gohttpserver"
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
goarm:
|
||||
- "6"
|
||||
main: .
|
||||
ldflags: -s -w -X main.VERSION={{.Version}}
|
||||
flags: -tags bindata
|
||||
binary: gohttpserver
|
||||
archive:
|
||||
format: tar.gz
|
||||
name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
|
||||
.Arm }}{{ end }}'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
- license*
|
||||
- LICENSE*
|
||||
- readme*
|
||||
- README*
|
||||
- changelog*
|
||||
- CHANGELOG*
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
|
14
.travis.yml
|
@ -1,14 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
env:
|
||||
global:
|
||||
- GO15VENDOREXPERIMENT=1
|
||||
script:
|
||||
- go test -v
|
||||
# .travis.yml
|
||||
after_success:
|
||||
- go get github.com/jteeuwen/go-bindata/...
|
||||
- go get github.com/elazarl/go-bindata-assetfs/...
|
||||
- test -n "$TRAVIS_TAG" && go-bindata-assetfs -tags bindata res/...
|
||||
- test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
|
94
Godeps/Godeps.json
generated
|
@ -1,94 +0,0 @@
|
|||
{
|
||||
"ImportPath": "github.com/codeskyblue/gohttpserver",
|
||||
"GoVersion": "go1.7",
|
||||
"GodepVersion": "v74",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/DHowett/go-plist",
|
||||
"Rev": "fd61394c0abc85ba629ff8471fa35d4309cd6609"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/kingpin",
|
||||
"Comment": "v2.2.0",
|
||||
"Rev": "92b2733684bc5f1971162349ce098fe0d070e2a9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/template",
|
||||
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/template/parse",
|
||||
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/units",
|
||||
"Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codeskyblue/dockerignore",
|
||||
"Rev": "de82dee623d9207f906d327172149cba50427a88"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codeskyblue/openid-go",
|
||||
"Rev": "0d30842b2fb4704849cd07f6fba862a7a9f4b403"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-yaml/yaml",
|
||||
"Rev": "e4d366fc3c7938e2958e662b4258c7a89e1f0e3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/goji/httpauth",
|
||||
"Rev": "2da839ab0f4df05a6db5eb277995589dadbd4fb9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Comment": "v1.1-8-g0b6087c",
|
||||
"Rev": "0b6087c0035ecc2ecddef2451329bca7f36192ab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/handlers",
|
||||
"Comment": "v1.1-14-g3a5767c",
|
||||
"Rev": "3a5767ca75ece5f7f1440b1d16975247f8d8b221"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/mux",
|
||||
"Comment": "v1.1-27-g757bef9",
|
||||
"Rev": "757bef944d0f21880861c2dd9c871ca543023cba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/securecookie",
|
||||
"Comment": "v1.1-5-gfa5329f",
|
||||
"Rev": "fa5329f913702981df43dcb2a380bac429c810b5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/sessions",
|
||||
"Comment": "v1.1",
|
||||
"Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mash/go-accesslog",
|
||||
"Rev": "9ba8e13f36087d6cb83d9a9f17f9e8da137d5ee9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pkg/errors",
|
||||
"Comment": "v0.8.0-1-g839d9e9",
|
||||
"Rev": "839d9e913e063e28dfd0e6c7b7512793e0a48be9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/shogo82148/androidbinary",
|
||||
"Rev": "2fb750de4ec6a45853fcc5f85694eab47e1007f9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/shogo82148/androidbinary/apk",
|
||||
"Rev": "2fb750de4ec6a45853fcc5f85694eab47e1007f9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html",
|
||||
"Rev": "a625e3953219464fdd5611bb48bf87c927717295"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html/atom",
|
||||
"Rev": "a625e3953219464fdd5611bb48bf87c927717295"
|
||||
}
|
||||
]
|
||||
}
|
5
Godeps/Readme
generated
|
@ -1,5 +0,0 @@
|
|||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
3
LICENSE
|
@ -1,6 +1,7 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 shengxiang
|
||||
Copyright (c) 2020 Augusto Dwenger J.
|
||||
Copyright (c) 2018 shengxiang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
157
README.md
|
@ -1,5 +1,5 @@
|
|||
# gohttpserver
|
||||
[![Build Status](https://travis-ci.org/codeskyblue/gohttpserver.svg?branch=master)](https://travis-ci.org/codeskyblue/gohttpserver)
|
||||
This is a fork from [codeskyblue/gohttpserver](https://github.com/codeskyblue/gohttpserver/) without google-analytics.
|
||||
|
||||
- Goal: Make the best HTTP File Server.
|
||||
- Features: Human-friendly UI, file uploading support, direct QR-code generation for Apple & Android install package.
|
||||
|
@ -9,10 +9,9 @@
|
|||
- 目标: 做最好的HTTP文件服务器
|
||||
- 功能: 人性化的UI体验,文件的上传支持,安卓和苹果安装包的二维码直接生成。
|
||||
|
||||
**Binaries** can be downloaded from [this repo releases](https://github.com/codeskyblue/gohttpserver/releases/)
|
||||
|
||||
## Notes
|
||||
If using go1.5, ensure you set GO15VENDOREXPERIMENT=1
|
||||
## Requirements
|
||||
Tested with go-1.16
|
||||
|
||||
## Screenshots
|
||||
![screen](testdata/filetypes/gohttpserver.gif)
|
||||
|
@ -23,7 +22,7 @@ If using go1.5, ensure you set GO15VENDOREXPERIMENT=1
|
|||
1. [x] All assets package to Standalone binary
|
||||
1. [x] Different file type different icon
|
||||
1. [x] Support show or hide hidden files
|
||||
1. [x] Upload support (for security reason, you need enabled it by option `--upload`)
|
||||
1. [x] Upload support (auth by token or session)
|
||||
1. [x] README.md preview
|
||||
1. [x] HTTP Basic Auth
|
||||
1. [x] Partial reload pages when directory change
|
||||
|
@ -52,6 +51,9 @@ If using go1.5, ensure you set GO15VENDOREXPERIMENT=1
|
|||
1. [x] Support setting from conf file
|
||||
1. [x] Quick copy download link
|
||||
1. [x] Show folder size
|
||||
1. [x] Create folder
|
||||
1. [x] Skip delete confirm when alt pressed
|
||||
1. [x] Support unzip zip file when upload(with form: unzip=true)
|
||||
|
||||
## Installation
|
||||
```
|
||||
|
@ -59,12 +61,43 @@ go get -v github.com/codeskyblue/gohttpserver
|
|||
cd $GOPATH/src/github.com/codeskyblue/gohttpserver
|
||||
go build && ./gohttpserver
|
||||
```
|
||||
|
||||
## Usage
|
||||
Listen on port 8000 of all interfaces, and enable file uploading.
|
||||
|
||||
```
|
||||
./gohttpserver -r ./ --addr :8000 --upload
|
||||
./gohttpserver -r ./ --port 8000 --upload
|
||||
```
|
||||
|
||||
Use command `gohttpserver --help` to see more usage.
|
||||
|
||||
## Docker Usage
|
||||
share current directory
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver registry.hhhammer.de/gohttpserver
|
||||
```
|
||||
|
||||
Share current directory with http basic auth
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
|
||||
registry.hhhammer.de/gohttpserver \
|
||||
--auth-type http --auth-http username:password
|
||||
```
|
||||
|
||||
Share current directory with openid auth. (Works only in netease company.)
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
|
||||
registry.hhhammer.de/gohttpserver \
|
||||
--auth-type openid
|
||||
```
|
||||
|
||||
To build image yourself, please change the PWD to the root of this repo.
|
||||
|
||||
```bash
|
||||
cd gohttpserver/
|
||||
docker build -t registry.hhhhammer.de/gohttpserver -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
## Authentication options
|
||||
|
@ -80,7 +113,35 @@ Listen on port 8000 of all interfaces, and enable file uploading.
|
|||
$ gohttpserver --auth-type openid --auth-openid https://login.example-hostname.com/openid/
|
||||
```
|
||||
|
||||
The openid returns url using "http" instead of "https", but I am not planing to fix this currently.
|
||||
- Use oauth2-proxy with
|
||||
|
||||
```sh
|
||||
$ gohttpserver --auth-type oauth2-proxy
|
||||
```
|
||||
You can configure to let a http reverse proxy handling authentication.
|
||||
When using oauth2-proxy, the backend will use identification info from request headers `X-Auth-Request-Email` as userId and `X-Auth-Request-Fullname` as user's display name.
|
||||
Please config your oauth2 reverse proxy yourself.
|
||||
More about [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy).
|
||||
|
||||
All required headers list as following.
|
||||
|
||||
|header|value|
|
||||
|---|---|
|
||||
|X-Auth-Request-Email| userId |
|
||||
|X-Auth-Request-Fullname| user's display name(urlencoded) |
|
||||
|X-Auth-Request-User| user's nickname (mostly email prefix) |
|
||||
|
||||
- Enable upload
|
||||
|
||||
```sh
|
||||
$ gohttpserver --upload
|
||||
```
|
||||
|
||||
- Enable delete and Create folder
|
||||
|
||||
```sh
|
||||
$ gohttpserver --delete
|
||||
```
|
||||
|
||||
## Advanced usage
|
||||
Add access rule by creating a `.ghs.yml` file under a sub-directory. An example:
|
||||
|
@ -93,10 +154,13 @@ users:
|
|||
- email: "codeskyblue@codeskyblue.com"
|
||||
delete: true
|
||||
upload: true
|
||||
token: 4567gf8asydhf293r23r
|
||||
```
|
||||
|
||||
In this case, if openid auth is enabled and user "codeskyblue@codeskyblue.com" has logged in, he/she can delete/upload files under the directory where the `.ghs.yml` file exits.
|
||||
|
||||
`token` is used for upload. see [upload with curl](#upload-with-curl)
|
||||
|
||||
For example, in the following directory hierarchy, users can delete/uploade files in directory `foo`, but he/she cannot do this in directory `bar`.
|
||||
|
||||
```
|
||||
|
@ -130,21 +194,65 @@ This is used for server on which https is enabled. default use <https://plistpro
|
|||
Test if proxy works:
|
||||
|
||||
```sh
|
||||
$ http POST https://proxyhost.com/plist < app.plist
|
||||
$ http POST https://someproxyhost.com/plist < app.plist
|
||||
{
|
||||
"key": "18f99211"
|
||||
}
|
||||
$ http GET https://proxyhost.com/plist/18f99211
|
||||
$ http GET https://someproxyhost.com/plist/18f99211
|
||||
# show the app.plist content
|
||||
```
|
||||
|
||||
If your ghs running behide nginx server and have https configed. plistproxy will be disabled automaticly.
|
||||
|
||||
### Upload with CURL
|
||||
For example, upload a file named `foo.txt` to directory `somedir`
|
||||
|
||||
```sh
|
||||
$ curl -F file=@foo.txt localhost:8000/somedir
|
||||
{"destination":"somedir/foo.txt","success":true}
|
||||
# upload with token
|
||||
$ curl -F file=@foo.txt -F token=12312jlkjafs localhost:8000/somedir
|
||||
{"destination":"somedir/foo.txt","success":true}
|
||||
|
||||
# upload and change filename
|
||||
$ curl -F file=@foo.txt -F filename=hi.txt localhost:8000/somedir
|
||||
{"destination":"somedir/hi.txt","success":true}
|
||||
```
|
||||
|
||||
Upload zip file and unzip it (zip file will be delete when finished unzip)
|
||||
|
||||
```
|
||||
$ curl -F file=@pkg.zip -F unzip=true localhost:8000/somedir
|
||||
{"success": true}
|
||||
```
|
||||
|
||||
Note: `\/:*<>|` are not allowed in filenames.
|
||||
|
||||
### Deploy with nginx
|
||||
Recommended configuration, assume your gohttpserver listening on `127.0.0.1:8200`
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain-name.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8200; # here need to change
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
client_max_body_size 0; # disable upload limit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
gohttpserver should started with `--xheaders` argument when behide nginx.
|
||||
|
||||
Refs: <http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>
|
||||
|
||||
## FAQ
|
||||
- [How to generate self signed certificate with openssl](http://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl)
|
||||
|
||||
|
@ -155,21 +263,23 @@ The search query follows common format rules just like Google. Keywords are sepe
|
|||
1. `hello -world` means must contains `hello` but not contains `world`
|
||||
|
||||
## Developer Guide
|
||||
Depdencies are managed by godep
|
||||
Depdencies are managed by [govendor](https://github.com/kardianos/govendor)
|
||||
|
||||
```sh
|
||||
go get -v github.com/tools/godep
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
```
|
||||
1. Build develop version. **assets** directory must exists
|
||||
|
||||
Theme are all defined in [res/themes](res/themes) directory. Now only two themes are available, "black" and "green".
|
||||
```sh
|
||||
go build
|
||||
./gohttpserver
|
||||
```
|
||||
2. Build single binary release
|
||||
|
||||
```sh
|
||||
go generate .
|
||||
go build -tags vfs
|
||||
```
|
||||
|
||||
Theme are defined in [assets/themes](assets/themes) directory. Now only two themes are available, "black" and "green".
|
||||
|
||||
## How to build single binary release
|
||||
```sh
|
||||
go-bindata-assetfs -tags bindata res/...
|
||||
go build -tags bindata
|
||||
```
|
||||
|
||||
## Reference Web sites
|
||||
|
||||
|
@ -185,7 +295,8 @@ go build -tags bindata
|
|||
|
||||
**Go Libraries**
|
||||
|
||||
* <https://github.com/elazarl/go-bindata-assetfs>
|
||||
* [vfsgen](https://github.com/shurcooL/vfsgen)
|
||||
* [go-bindata-assetfs](https://github.com/elazarl/go-bindata-assetfs) Not using now
|
||||
* <http://www.gorillatoolkit.org/pkg/handlers>
|
||||
|
||||
## History
|
||||
|
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 382 KiB |
Before Width: | Height: | Size: 698 B After Width: | Height: | Size: 698 B |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
@ -4,15 +4,16 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>gohttp server</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/themes/[[.Theme]].css">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<title>[[.Title]]</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/-/assets/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/themes/[[.Theme]].css">
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
|
@ -31,31 +32,46 @@
|
|||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-2">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="hidden-xs">
|
||||
<a href="javascript:void(0)" v-on:click='genQrcode(location.pathname)'>
|
||||
<a href="javascript:void(0)" v-on:click='genQrcode()'>
|
||||
View in Phone
|
||||
<span class="glyphicon glyphicon-qrcode"></span>
|
||||
</a>
|
||||
</li>
|
||||
[[if eq .AuthType "openid"]]
|
||||
<li class="hidden-xs">
|
||||
<a href="/-/logout" v-if="user.email">
|
||||
<template v-if="!user.email">
|
||||
<a href="/-/login" class="btn btn-sm btn-default navbar-btn">
|
||||
Sign in <span class="glyphicon glyphicon-user"></span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a href="/-/logout" class="btn btn-sm btn-default navbar-btn">
|
||||
<span v-text="user.name"></span>
|
||||
<span class="glyphicon glyphicon-indent-left"></span>
|
||||
<i class="fa fa-sign-out"></i>
|
||||
</a>
|
||||
<a href="/-/login" v-else>
|
||||
Login
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
[[end]]
|
||||
[[if eq .AuthType "oauth2-proxy"]]
|
||||
<template v-if="!user.email">
|
||||
<a href="#" class="btn btn-sm btn-default navbar-btn">
|
||||
Guest <span class="glyphicon glyphicon-user"></span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a href="/-/logout" class="btn btn-sm btn-default navbar-btn">
|
||||
<span v-text="user.name"></span>
|
||||
<i class="fa fa-sign-out"></i>
|
||||
</a>
|
||||
</template>
|
||||
[[end]]
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search text" v-bind:value="search" autofocus>
|
||||
<input type="text" name="search" class="form-control" placeholder="Search text" v-bind:value="search"
|
||||
autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
<button class="btn btn-default" type="submit">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -83,17 +99,22 @@
|
|||
<tr>
|
||||
<td colspan=4>
|
||||
<!-- <button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Back <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button> -->
|
||||
<button class="btn btn-xs btn-default" onclick="history.back()">
|
||||
Back <i class="fa fa-arrow-left"></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Hidden <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-if="auth.upload" data-toggle="modal" data-target="#upload-modal">
|
||||
Upload <i class="fa fa-upload"></i>
|
||||
</button>
|
||||
Back <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button> -->
|
||||
<div>
|
||||
<button class="btn btn-xs btn-default" onclick="history.back()">
|
||||
Back <i class="fa fa-arrow-left"></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Hidden <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-show="auth.upload" data-toggle="modal" data-target="#upload-modal">
|
||||
Upload <i class="fa fa-upload"></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-show="auth.delete" @click="makeDirectory">
|
||||
New Folder <i class="fa fa-folder"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -108,26 +129,37 @@
|
|||
<tbody>
|
||||
<tr v-for="f in computedFiles">
|
||||
<td>
|
||||
<a v-on:click='clickFileOrDir(f, $event)' href="/{{f.path + (f.type == 'dir' ? '' : '')}}">
|
||||
<a v-on:click='clickFileOrDir(f, $event)' href="{{getEncodePath(f)}}">
|
||||
<!-- ?raw=false -->
|
||||
<i style="padding-right: 0.5em" class="fa" v-bind:class='genFileClass(f)'></i> {{f.name}}
|
||||
</a>
|
||||
<!-- for search -->
|
||||
<button v-show="f.type == 'file' && f.name.indexOf('/') >= 0" class="btn btn-default btn-xs" @click="changeParentDirectory(f.path)">
|
||||
<i class="fa fa-folder-open-o"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td><span v-if="f.type == 'dir'">~</span> {{f.size | formatBytes}}</td>
|
||||
<td class="hidden-xs">{{formatTime(f.mtime)}}</td>
|
||||
<td style="text-align: left">
|
||||
<template v-if="f.type == 'dir'">
|
||||
<a class="btn btn-default btn-xs" href="/-/zip/{{f.path}}">
|
||||
<a class="btn btn-default btn-xs" href="{{getEncodePath(f)}}/?op=archive">
|
||||
<span class="hidden-xs">Archive</span> Zip
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
<button class="btn btn-default btn-xs" v-on:click="showInfo(f)">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs" v-if="auth.delete" v-on:click="deletePathConfirm(f, $event)">
|
||||
<span style="color:#CC3300" class="glyphicon glyphicon-trash"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-if="f.type == 'file'">
|
||||
<a class="btn btn-default btn-xs hidden-xs" href="/{{f.path}}?download=true">
|
||||
<a class="btn btn-default btn-xs hidden-xs" href="{{genDownloadURL(f)}}">
|
||||
<span class="hidden-xs">Download</span>
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
<button class="btn btn-default btn-xs bstooltip" data-trigger="manual" data-title="Copied!" data-clipboard-text="{{genDownloadURL(f)}}">
|
||||
<button class="btn btn-default btn-xs bstooltip" data-trigger="manual" data-title="Copied!"
|
||||
data-clipboard-text="{{genDownloadURL(f)}}">
|
||||
<i class="fa fa-copy"></i>
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs" v-on:click="showInfo(f)">
|
||||
|
@ -224,27 +256,34 @@
|
|||
</div>
|
||||
<div class="col-md-12">
|
||||
<div id="footer" class="pull-right" style="margin: 2em 1em">
|
||||
<a href="https://github.com/codeskyblue/gohttpserver">gohttpserver (ver:{{version}})</a>, written by <a href="https://github.com/codeskyblue">codeskyblue</a>. Copyright 2016-2017. go1.7
|
||||
<a href="https://github.com/hamburghammer/gohttpserver">gohttpserver (ver:{{version}})</a>, fork from <a href="https://github.com/codeskyblue/gohttpserver">codeskyblue/gohttpserver</a>.
|
||||
Lizensed under <a href="https://github.com/hamburghammer/gohttpserver/blob/master/LICENSE">MIT</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/-/res/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="/-/res/js/jquery.qrcode.js"></script>
|
||||
<script src="/-/res/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="/-/res/js/qrcode.js"></script>
|
||||
<script src="/-/res/js/vue-1.0.min.js"></script>
|
||||
<script src="/-/res/js/showdown-1.6.4.min.js"></script>
|
||||
<script src="/-/res/js/moment.min.js"></script>
|
||||
<script src="/-/res/js/dropzone.js"></script>
|
||||
<script src="/-/res/js/underscore-min.js"></script>
|
||||
<script src="/-/res/js/clipboard-1.5.12.min.js"></script>
|
||||
<script src="/-/res/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src="/-/res/js/index.js"></script>
|
||||
[[if .GoogleTrackerId ]]
|
||||
<script src="/-/assets/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="/-/assets/js/jquery.qrcode.js"></script>
|
||||
<script src="/-/assets/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="/-/assets/js/qrcode.js"></script>
|
||||
<script src="/-/assets/js/vue-1.0.min.js"></script>
|
||||
<script src="/-/assets/js/showdown-1.6.4.min.js"></script>
|
||||
<script src="/-/assets/js/moment.min.js"></script>
|
||||
<script src="/-/assets/js/dropzone.js"></script>
|
||||
<script src="/-/assets/js/underscore-min.js"></script>
|
||||
<script src="/-/assets/js/clipboard-1.5.12.min.js"></script>
|
||||
<script src="/-/assets/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src='/-/assets/[["js/index.js" | urlhash ]]'></script>
|
||||
<!-- <script src="/-/assets/js/index.js"></script> -->
|
||||
<!--Sync status bar color with border-color on mobile platforms.-->
|
||||
<script>
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
var META = document.getElementsByTagName("meta");
|
||||
META[2]["content"]=$('.navbar').css('border-color');
|
||||
</script>
|
||||
[[if .GoogleTrackerID ]]
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function() {
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
|
@ -254,10 +293,9 @@
|
|||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '[[.GoogleTrackerId]]', 'auto');
|
||||
ga('create', '[[.GoogleTrackerID]]', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
[[ end ]]
|
||||
</script> [[ end ]]
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
|
@ -1,71 +1,71 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>[[.Name]] install</title>
|
||||
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<script type="text/javascript" src="/-/res/js/ua-parser.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function showById(name) {
|
||||
document.getElementById(name).style.display = 'block';
|
||||
}
|
||||
|
||||
function checkBrowerAndDownload() {
|
||||
var parser = new UAParser();
|
||||
var os_info = parser.getOS();
|
||||
console.log(os_info)
|
||||
|
||||
var plistLink = "[[.PlistLink]]";
|
||||
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||
document.getElementById('itms-link').href = ipaInstallLink;
|
||||
|
||||
// wechat is support AppStore link now.
|
||||
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else if (os_info.name == 'Android') {
|
||||
showById("android");
|
||||
return;
|
||||
} else if (os_info.name == 'iOS') {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else {
|
||||
showById('browser');
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
#wechat {
|
||||
position: relative;
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 777px;
|
||||
}
|
||||
</style>
|
||||
<div id="wechat" style="display: none">
|
||||
<img style='width: 100%;position: relative;' src='/-/res/imgs/wx.png' />
|
||||
</div>
|
||||
<div id="browser" style="display: none">
|
||||
This is IPA install page, you should open this link with your iPhone.
|
||||
</div>
|
||||
<div id="safari" style="display: none">
|
||||
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||
</div>
|
||||
<div id="android" style="display: none">
|
||||
This is IPA install page, not for android.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
checkBrowerAndDownload();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>[[.Name]] install</title>
|
||||
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||
<link rel="shortcut icon" type="image/png" href="/-/assets/favicon.png" />
|
||||
<script type="text/javascript" src="/-/assets/js/ua-parser.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function showById(name) {
|
||||
document.getElementById(name).style.display = 'block';
|
||||
}
|
||||
|
||||
function checkBrowerAndDownload() {
|
||||
var parser = new UAParser();
|
||||
var os_info = parser.getOS();
|
||||
console.log(os_info)
|
||||
|
||||
var plistLink = "[[.PlistLink]]";
|
||||
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||
document.getElementById('itms-link').href = ipaInstallLink;
|
||||
|
||||
// wechat is support AppStore link now.
|
||||
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else if (os_info.name == 'Android') {
|
||||
showById("android");
|
||||
return;
|
||||
} else if (os_info.name == 'iOS') {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else {
|
||||
showById('browser');
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
#wechat {
|
||||
position: relative;
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 777px;
|
||||
}
|
||||
</style>
|
||||
<div id="wechat" style="display: none">
|
||||
<img style='width: 100%;position: relative;' src='/-/assets/imgs/wx.png' />
|
||||
</div>
|
||||
<div id="browser" style="display: none">
|
||||
This is IPA install page, you should open this link with your iPhone.
|
||||
</div>
|
||||
<div id="safari" style="display: none">
|
||||
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||
</div>
|
||||
<div id="android" style="display: none">
|
||||
This is IPA install page, not for android.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
checkBrowerAndDownload();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -21,6 +21,22 @@ function getQueryString(name) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function checkPathNameLegal(name) {
|
||||
var reg = new RegExp("[\\/:*<>|]");
|
||||
var r = name.match(reg)
|
||||
return r == null;
|
||||
}
|
||||
|
||||
function showErrorMessage(jqXHR) {
|
||||
let errMsg = jqXHR.getResponseHeader("x-auth-authentication-message")
|
||||
if (errMsg == null) {
|
||||
errMsg = jqXHR.responseText
|
||||
}
|
||||
alert(String(jqXHR.status).concat(":", errMsg));
|
||||
console.error(errMsg)
|
||||
}
|
||||
|
||||
|
||||
var vm = new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
|
@ -51,11 +67,11 @@ var vm = new Vue({
|
|||
myDropzone: null,
|
||||
},
|
||||
computed: {
|
||||
computedFiles: function() {
|
||||
computedFiles: function () {
|
||||
var that = this;
|
||||
that.preview.filename = null;
|
||||
|
||||
var files = this.files.filter(function(f) {
|
||||
var files = this.files.filter(function (f) {
|
||||
if (f.name == 'README.md') {
|
||||
that.preview.filename = f.name;
|
||||
}
|
||||
|
@ -71,7 +87,7 @@ var vm = new Vue({
|
|||
$.ajax({
|
||||
url: pathJoin([location.pathname, 'README.md']),
|
||||
method: 'GET',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
var converter = new showdown.Converter({
|
||||
tables: true,
|
||||
omitExtraWLInCodeBlocks: true,
|
||||
|
@ -88,7 +104,7 @@ var vm = new Vue({
|
|||
var html = converter.makeHtml(res);
|
||||
that.preview.contentHTML = html;
|
||||
},
|
||||
error: function(err) {
|
||||
error: function (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
|
@ -97,12 +113,12 @@ var vm = new Vue({
|
|||
return files;
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
created: function () {
|
||||
$.ajax({
|
||||
url: "/-/user",
|
||||
method: "get",
|
||||
dataType: "json",
|
||||
success: function(ret) {
|
||||
success: function (ret) {
|
||||
if (ret) {
|
||||
this.user.email = ret.email;
|
||||
this.user.name = ret.name;
|
||||
|
@ -111,13 +127,13 @@ var vm = new Vue({
|
|||
})
|
||||
this.myDropzone = new Dropzone("#upload-form", {
|
||||
paramName: "file",
|
||||
maxFilesize: 1024,
|
||||
maxFilesize: 10240,
|
||||
addRemoveLinks: true,
|
||||
init: function() {
|
||||
this.on("uploadprogress", function(file, progress) {
|
||||
init: function () {
|
||||
this.on("uploadprogress", function (file, progress) {
|
||||
// console.log("File progress", progress);
|
||||
});
|
||||
this.on("complete", function(file) {
|
||||
this.on("complete", function (file) {
|
||||
console.log("reload file list")
|
||||
loadFileList()
|
||||
})
|
||||
|
@ -125,45 +141,62 @@ var vm = new Vue({
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
formatTime: function(timestamp) {
|
||||
getEncodePath: function (f) {
|
||||
return pathJoin([location.pathname, encodeURIComponent(f.name)]);
|
||||
},
|
||||
formatTime: function (timestamp) {
|
||||
var m = moment(timestamp);
|
||||
if (this.mtimeTypeFromNow) {
|
||||
return m.fromNow();
|
||||
}
|
||||
return m.format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
toggleHidden: function() {
|
||||
toggleHidden: function () {
|
||||
this.showHidden = !this.showHidden;
|
||||
},
|
||||
removeAllUploads: function() {
|
||||
removeAllUploads: function () {
|
||||
this.myDropzone.removeAllFiles();
|
||||
},
|
||||
genInstallURL: function(name) {
|
||||
var urlPath;
|
||||
if (getExtention(name) == "ipa") {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, "/-/ipa/link", location.pathname, name]);
|
||||
} else {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, location.pathname, name]);
|
||||
}
|
||||
return encodeURI(urlPath);
|
||||
parentDirectory: function (path) {
|
||||
return path.replace('\\', '/').split('/').slice(0, -1).join('/')
|
||||
},
|
||||
genQrcode: function(name, title) {
|
||||
var installURL = this.genInstallURL(name);
|
||||
$("#qrcode-title").html(title || name);
|
||||
$("#qrcode-link").attr("href", installURL);
|
||||
changeParentDirectory: function (path) {
|
||||
var parentDir = this.parentDirectory(path);
|
||||
loadFileOrDir(parentDir);
|
||||
},
|
||||
genInstallURL: function (name, noEncode) {
|
||||
var parts = [location.host];
|
||||
var pathname = decodeURI(location.pathname);
|
||||
if (!name) {
|
||||
parts.push(pathname);
|
||||
} else if (getExtention(name) == "ipa") {
|
||||
parts.push("/-/ipa/link", pathname, encodeURIComponent(name));
|
||||
} else {
|
||||
parts.push(pathname, name);
|
||||
}
|
||||
var urlPath = location.protocol + "//" + pathJoin(parts);
|
||||
return noEncode ? urlPath : encodeURI(urlPath);
|
||||
},
|
||||
genQrcode: function (name, title) {
|
||||
var urlPath = this.genInstallURL(name, true);
|
||||
$("#qrcode-title").html(title || name || location.pathname);
|
||||
$("#qrcode-link").attr("href", urlPath);
|
||||
$('#qrcodeCanvas').empty().qrcode({
|
||||
text: installURL
|
||||
text: encodeURI(urlPath),
|
||||
});
|
||||
$("#qrcodeRight a").attr("href", encodeURI(location.origin + pathJoin([location.pathname, name])));
|
||||
|
||||
$("#qrcodeRight a").attr("href", urlPath);
|
||||
$("#qrcode-modal").modal("show");
|
||||
},
|
||||
genDownloadURL: function(f) {
|
||||
return location.origin + "/" + f.path;
|
||||
genDownloadURL: function (f) {
|
||||
var search = location.search;
|
||||
var sep = search == "" ? "?" : "&"
|
||||
return location.origin + this.getEncodePath(f) + location.search + sep + "download=true";
|
||||
},
|
||||
shouldHaveQrcode: function(name) {
|
||||
shouldHaveQrcode: function (name) {
|
||||
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
|
||||
},
|
||||
genFileClass: function(f) {
|
||||
genFileClass: function (f) {
|
||||
if (f.type == "dir") {
|
||||
if (f.name == '.git') {
|
||||
return 'fa-git-square';
|
||||
|
@ -203,48 +236,82 @@ var vm = new Vue({
|
|||
}
|
||||
return "fa-file-text-o"
|
||||
},
|
||||
clickFileOrDir: function(f, e) {
|
||||
clickFileOrDir: function (f, e) {
|
||||
var reqPath = pathJoin([location.pathname, encodeURIComponent(f.name)]);
|
||||
// TODO: fix here tomorrow
|
||||
if (f.type == "file") {
|
||||
return true;
|
||||
window.location.href = reqPath;
|
||||
return;
|
||||
}
|
||||
var reqPath = pathJoin([location.pathname, f.name]);
|
||||
loadFileOrDir(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
changePath: function(reqPath, e) {
|
||||
changePath: function (reqPath, e) {
|
||||
loadFileOrDir(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
showInfo: function(f) {
|
||||
showInfo: function (f) {
|
||||
console.log(f);
|
||||
$.ajax({
|
||||
url: pathJoin(["/-/info", location.pathname, f.name]),
|
||||
url: pathJoin(["/", location.pathname, encodeURIComponent(f.name)]),
|
||||
data: {
|
||||
op: "info",
|
||||
},
|
||||
method: "GET",
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
$("#file-info-title").text(f.name);
|
||||
$("#file-info-content").text(JSON.stringify(res, null, 4));
|
||||
$("#file-info-modal").modal("show");
|
||||
// console.log(JSON.stringify(res, null, 4));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(jqXHR)
|
||||
}
|
||||
})
|
||||
},
|
||||
deletePathConfirm: function(f, e) {
|
||||
// confirm
|
||||
e.preventDefault();
|
||||
makeDirectory: function () {
|
||||
var name = window.prompt("current path: " + location.pathname + "\nplease enter the new directory name", "")
|
||||
console.log(name)
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
if(!checkPathNameLegal(name)) {
|
||||
alert("Name should not contains any of \\/:*<>|")
|
||||
return
|
||||
}
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, f.name]),
|
||||
method: 'DELETE',
|
||||
success: function(res) {
|
||||
url: pathJoin(["/", location.pathname, "/", encodeURIComponent(name)]),
|
||||
method: "POST",
|
||||
success: function (res) {
|
||||
console.log(res)
|
||||
loadFileList()
|
||||
},
|
||||
error: function(err) {
|
||||
alert(err.responseText);
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(jqXHR)
|
||||
}
|
||||
})
|
||||
},
|
||||
deletePathConfirm: function (f, e) {
|
||||
e.preventDefault();
|
||||
if (!e.altKey) { // skip confirm when alt pressed
|
||||
if (!window.confirm("Delete " + location.pathname + "/" + f.name + " ?")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, encodeURIComponent(f.name)]),
|
||||
method: 'DELETE',
|
||||
success: function (res) {
|
||||
loadFileList()
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(jqXHR)
|
||||
}
|
||||
});
|
||||
},
|
||||
updateBreadcrumb: function() {
|
||||
var pathname = decodeURI(location.pathname || "/");
|
||||
updateBreadcrumb: function (pathname) {
|
||||
var pathname = decodeURI(pathname || location.pathname || "/");
|
||||
pathname = pathname.split('?')[0]
|
||||
var parts = pathname.split('/');
|
||||
this.breadcrumb = [];
|
||||
if (pathname == "/") {
|
||||
|
@ -264,82 +331,92 @@ var vm = new Vue({
|
|||
}
|
||||
return this.breadcrumb;
|
||||
},
|
||||
loadPreviewFile: function(filepath, e) {
|
||||
loadPreviewFile: function (filepath, e) {
|
||||
if (e) {
|
||||
e.preventDefault() // may be need a switch
|
||||
}
|
||||
var that = this;
|
||||
$.getJSON(pathJoin(['/-/info', location.pathname]))
|
||||
.then(function(res) {
|
||||
console.log(res);
|
||||
that.preview.filename = res.name;
|
||||
that.preview.filesize = res.size;
|
||||
return $.ajax({
|
||||
url: '/' + res.path,
|
||||
dataType: 'text',
|
||||
.then(function (res) {
|
||||
console.log(res);
|
||||
that.preview.filename = res.name;
|
||||
that.preview.filesize = res.size;
|
||||
return $.ajax({
|
||||
url: '/' + res.path,
|
||||
dataType: 'text',
|
||||
});
|
||||
})
|
||||
.then(function (res) {
|
||||
console.log(res)
|
||||
that.preview.contentHTML = '<pre>' + res + '</pre>';
|
||||
console.log("Finally")
|
||||
})
|
||||
.done(function (res) {
|
||||
console.log("done", res)
|
||||
});
|
||||
})
|
||||
.then(function(res) {
|
||||
console.log(res)
|
||||
that.preview.contentHTML = '<pre>' + res + '</pre>';
|
||||
console.log("Finally")
|
||||
})
|
||||
.done(function(res) {
|
||||
console.log("done", res)
|
||||
});
|
||||
},
|
||||
loadAll: function() {
|
||||
loadAll: function () {
|
||||
// TODO: move loadFileList here
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
window.onpopstate = function(event) {
|
||||
var pathname = decodeURI(location.pathname)
|
||||
window.onpopstate = function (event) {
|
||||
if (location.search.match(/\?search=/)) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
loadFileList()
|
||||
}
|
||||
|
||||
function loadFileOrDir(reqPath) {
|
||||
window.history.pushState({}, "", reqPath);
|
||||
loadFileList(reqPath)
|
||||
let requestUri = reqPath + location.search
|
||||
var retObj = loadFileList(requestUri)
|
||||
if (retObj !== null) {
|
||||
retObj.done(function () {
|
||||
window.history.pushState({}, "", requestUri);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function loadFileList(pathname) {
|
||||
var pathname = pathname || location.pathname;
|
||||
// console.log("load filelist:", pathname)
|
||||
var pathname = pathname || location.pathname + location.search;
|
||||
var retObj = null
|
||||
if (getQueryString("raw") !== "false") { // not a file preview
|
||||
$.ajax({
|
||||
url: pathJoin(["/-/json", pathname]),
|
||||
var sep = pathname.indexOf("?") === -1 ? "?" : "&"
|
||||
retObj = $.ajax({
|
||||
url: pathname + sep + "json=true",
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(res) {
|
||||
res.files = _.sortBy(res.files, function(f) {
|
||||
success: function (res) {
|
||||
res.files = _.sortBy(res.files, function (f) {
|
||||
var weight = f.type == 'dir' ? 1000 : 1;
|
||||
return -weight * f.mtime;
|
||||
})
|
||||
|
||||
vm.files = res.files;
|
||||
vm.auth = res.auth;
|
||||
vm.updateBreadcrumb(pathname);
|
||||
},
|
||||
error: function(err) {
|
||||
console.error(err)
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(jqXHR)
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
vm.updateBreadcrumb();
|
||||
vm.previewMode = getQueryString("raw") == "false";
|
||||
if (vm.previewMode) {
|
||||
vm.loadPreviewFile();
|
||||
}
|
||||
return retObj
|
||||
}
|
||||
|
||||
Vue.filter('fromNow', function(value) {
|
||||
Vue.filter('fromNow', function (value) {
|
||||
return moment(value).fromNow();
|
||||
})
|
||||
|
||||
Vue.filter('formatBytes', function(value) {
|
||||
Vue.filter('formatBytes', function (value) {
|
||||
var bytes = parseFloat(value);
|
||||
if (bytes < 0) return "-";
|
||||
else if (bytes < 1024) return bytes + " B";
|
||||
|
@ -348,7 +425,7 @@ Vue.filter('formatBytes', function(value) {
|
|||
else return (bytes / 1073741824).toFixed(1) + " GB";
|
||||
})
|
||||
|
||||
$(function() {
|
||||
$(function () {
|
||||
$.scrollUp({
|
||||
scrollText: '', // text are defined in css
|
||||
});
|
||||
|
@ -357,21 +434,21 @@ $(function() {
|
|||
loadFileList(location.pathname + location.search)
|
||||
|
||||
// update version
|
||||
$.getJSON("/-/sysinfo", function(res) {
|
||||
$.getJSON("/-/sysinfo", function (res) {
|
||||
vm.version = res.version;
|
||||
})
|
||||
|
||||
var clipboard = new Clipboard('.btn');
|
||||
clipboard.on('success', function(e) {
|
||||
clipboard.on('success', function (e) {
|
||||
console.info('Action:', e.action);
|
||||
console.info('Text:', e.text);
|
||||
console.info('Trigger:', e.trigger);
|
||||
$(e.trigger)
|
||||
.tooltip('show')
|
||||
.mouseleave(function() {
|
||||
$(this).tooltip('hide');
|
||||
})
|
||||
.tooltip('show')
|
||||
.mouseleave(function () {
|
||||
$(this).tooltip('hide');
|
||||
})
|
||||
|
||||
e.clearSelection();
|
||||
});
|
||||
});
|
||||
});
|
31
assets/themes/cyan.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
body {}
|
||||
|
||||
td>a:hover {
|
||||
color: #4cc0cf;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
td>a {
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #00BCD4;
|
||||
border-color: #0097A7;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.navbar-default ul.navbar-nav>li>a {
|
||||
color: white;
|
||||
}
|
9
assets_dev.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build !vfs
|
||||
//go:generate go run assets_generate.go
|
||||
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Assets contains project assets.
|
||||
var Assets http.FileSystem = http.Dir("assets")
|
23
assets_generate.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var fs http.FileSystem = http.Dir("assets")
|
||||
|
||||
err := vfsgen.Generate(fs, vfsgen.Options{
|
||||
PackageName: "main",
|
||||
BuildTags: "vfs",
|
||||
VariableName: "Assets",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
4
build.sh
|
@ -23,12 +23,12 @@ fi
|
|||
build() {
|
||||
echo "$1 $2 ..."
|
||||
GOOS=$1 GOARCH=$2 go build \
|
||||
-tags bindata \
|
||||
-tags vfs \
|
||||
-ldflags "$LDFLAGS" \
|
||||
-o dist/gohttpserver-${3:-""}
|
||||
}
|
||||
|
||||
go-bindata-assetfs -tags bindata res/...
|
||||
go generate .
|
||||
|
||||
build linux arm linux-arm
|
||||
build darwin amd64 mac-amd64
|
||||
|
|
14
docker/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
|||
FROM docker.io/golang:1.16 AS build
|
||||
WORKDIR /app/gohttpserver
|
||||
ADD . /app/gohttpserver
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-X main.VERSION=docker' -o gohttpserver
|
||||
|
||||
FROM docker.io/alpine:latest
|
||||
RUN mkdir -p /app/public
|
||||
VOLUME /app/public
|
||||
WORKDIR /app
|
||||
ADD assets ./assets
|
||||
COPY --from=build /app/gohttpserver/gohttpserver .
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT [ "/app/gohttpserver", "--root=/app/public" ]
|
||||
CMD []
|
15
docker/Dockerfile.armhf
Normal file
|
@ -0,0 +1,15 @@
|
|||
FROM golang:1.16
|
||||
WORKDIR /appsrc/gohttpserver
|
||||
ADD . /appsrc/gohttpserver
|
||||
RUN GOOS=linux GOARCH=arm go build -ldflags '-X main.VERSION=docker' -o gohttpserver .
|
||||
|
||||
FROM multiarch/debian-debootstrap:armhf-stretch
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /app/public
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
VOLUME /app/public
|
||||
ADD assets ./assets
|
||||
COPY --from=0 /appsrc/gohttpserver/gohttpserver .
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT [ "/app/gohttpserver", "--root=/app/public" ]
|
||||
CMD []
|
18
docker/push_images
Normal file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# article: https://lantian.pub/article/modify-computer/build-arm-docker-image-on-x86-docker-hub-travis-automatic-build.lantian
|
||||
|
||||
set -ex
|
||||
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
|
||||
IMAGE_NAME="gohttpserver"
|
||||
|
||||
# arm linux for respberry
|
||||
docker build -t $DOCKER_USERNAME/$IMAGE_NAME:armhf -f docker/Dockerfile.armhf .
|
||||
|
||||
# x86 linux
|
||||
docker build -t $DOCKER_USERNAME/$IMAGE_NAME:latest -f docker/Dockerfile .
|
||||
|
||||
docker push $DOCKER_USERNAME/$IMAGE_NAME
|
28
docker/push_manifest
Normal file
|
@ -0,0 +1,28 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
|
||||
# push manifest
|
||||
if [[ ! -d $HOME/.docker ]]
|
||||
then
|
||||
mkdir $HOME/.docker
|
||||
fi
|
||||
|
||||
set -ex
|
||||
|
||||
if test $(uname) = "Linux"
|
||||
then
|
||||
sed -i '/experimental/d' $HOME/.docker/config.json
|
||||
sed -i '1a"experimental": "enabled",' $HOME/.docker/config.json
|
||||
fi
|
||||
|
||||
docker manifest create codeskyblue/gohttpserver \
|
||||
codeskyblue/gohttpserver:latest \
|
||||
codeskyblue/gohttpserver:armhf
|
||||
docker manifest annotate codeskyblue/gohttpserver \
|
||||
codeskyblue/gohttpserver:latest --os linux --arch amd64
|
||||
docker manifest annotate codeskyblue/gohttpserver \
|
||||
codeskyblue/gohttpserver:armhf --os linux --arch arm --variant v7
|
||||
docker manifest push codeskyblue/gohttpserver
|
||||
|
||||
# check again
|
||||
docker run mplatform/mquery codeskyblue/gohttpserver
|
27
go.mod
Normal file
|
@ -0,0 +1,27 @@
|
|||
module github.com/hamburghammer/gohttpserver
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
|
||||
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9
|
||||
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371
|
||||
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4
|
||||
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d
|
||||
github.com/gorilla/handlers v1.4.0
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/sessions v1.1.3
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/text v0.3.3
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
|
||||
)
|
91
go.sum
Normal file
|
@ -0,0 +1,91 @@
|
|||
github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9 h1:c9axcChJwkLuSl9AvwTHi8jiBa6+VX4gGgERhABgv2E=
|
||||
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9/go.mod h1:XNZkUhPf+qgRnhY/ecS3B73ODJ2NXCzDMJHXM069IMg=
|
||||
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371 h1:dEBIvaVFaP2Uc9QA6J41qWxE5NfEnDWEBk+kWv5nK5k=
|
||||
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371/go.mod h1:sgXnVxxZ1u72GAzc9s1SzpuPMxBDKfTg6F2PvDrPSJU=
|
||||
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4 h1:66lzN78lwccK+BPztRgBiWCYzhlerQEVOh2oeBksu5I=
|
||||
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4/go.mod h1:K/hSCtAHvnE9aM+LsYgVmgzPNFuWFdx6i9t6/3jNrZQ=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636 h1:ESUdS2eb8LyDQfboYyFBwAL+rqYhnTZ15ntw8BLsd9g=
|
||||
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636/go.mod h1:v6KRhgoO1QKamoeuZ7yHqZIP8p6j9k41Tb0jCyOEmr4=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
|
||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5 h1:bXRaUWl3Afe3F9YR5NU1U3UB5zjCHlu4im5p3J/LUYk=
|
||||
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5/go.mod h1:05AjXWPWLdTIl9+REKhSmTeoJ6Wz5e9ir0Q0NRxCIKo=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca h1:3fECS8atRjByijiI8yYiuwLwQ2ZxXobW7ua/8GRB3pI=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58=
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
|
@ -3,6 +3,9 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -13,6 +16,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
@ -22,6 +26,8 @@ import (
|
|||
"github.com/shogo82148/androidbinary/apk"
|
||||
)
|
||||
|
||||
const YAMLCONF = ".ghs.yml"
|
||||
|
||||
type ApkInfo struct {
|
||||
PackageName string `json:"packageName"`
|
||||
MainActivity string `json:"mainActivity"`
|
||||
|
@ -37,17 +43,17 @@ type IndexFileItem struct {
|
|||
}
|
||||
|
||||
type HTTPStaticServer struct {
|
||||
Root string
|
||||
Upload bool
|
||||
Delete bool
|
||||
Title string
|
||||
Theme string
|
||||
PlistProxy string
|
||||
GoogleTrackerId string
|
||||
AuthType string
|
||||
Root string
|
||||
Upload bool
|
||||
Delete bool
|
||||
Title string
|
||||
Theme string
|
||||
PlistProxy string
|
||||
AuthType string
|
||||
|
||||
indexes []IndexFileItem
|
||||
m *mux.Router
|
||||
bufPool sync.Pool // use sync.Pool caching buf to reduce gc ratio
|
||||
}
|
||||
|
||||
func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
||||
|
@ -64,6 +70,9 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
|||
Root: root,
|
||||
Theme: "black",
|
||||
m: m,
|
||||
bufPool: sync.Pool{
|
||||
New: func() interface{} { return make([]byte, 32*1024) },
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
@ -78,19 +87,12 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
|||
}
|
||||
}()
|
||||
|
||||
m.HandleFunc("/-/status", s.hStatus)
|
||||
m.HandleFunc("/-/zip/{path:.*}", s.hZip)
|
||||
m.HandleFunc("/-/unzip/{zip_path:.*}/-/{path:.*}", s.hUnzip)
|
||||
m.HandleFunc("/-/json/{path:.*}", s.hJSONList)
|
||||
// routers for Apple *.ipa
|
||||
m.HandleFunc("/-/ipa/plist/{path:.*}", s.hPlist)
|
||||
m.HandleFunc("/-/ipa/link/{path:.*}", s.hIpaLink)
|
||||
|
||||
// TODO: /ipa/info
|
||||
m.HandleFunc("/-/info/{path:.*}", s.hInfo)
|
||||
|
||||
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD")
|
||||
m.HandleFunc("/{path:.*}", s.hUpload).Methods("POST")
|
||||
m.HandleFunc("/{path:.*}", s.hUploadOrMkdir).Methods("POST")
|
||||
m.HandleFunc("/{path:.*}", s.hDelete).Methods("DELETE")
|
||||
return s
|
||||
}
|
||||
|
@ -102,13 +104,35 @@ func (s *HTTPStaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
if r.FormValue("json") == "true" {
|
||||
s.hJSONList(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("op") == "info" {
|
||||
s.hInfo(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("op") == "archive" {
|
||||
s.hZip(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("GET", path, relPath)
|
||||
if r.FormValue("raw") == "false" || isDir(relPath) {
|
||||
if r.Method == "HEAD" {
|
||||
return
|
||||
}
|
||||
tmpl.ExecuteTemplate(w, "index", s)
|
||||
renderHTML(w, "index.html", s)
|
||||
} else {
|
||||
if filepath.Base(path) == YAMLCONF {
|
||||
auth := s.readAccessConf(path)
|
||||
if !auth.Delete {
|
||||
http.Error(w, "Security warning, not allowed to read", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.FormValue("download") == "true" {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filepath.Base(path)))
|
||||
}
|
||||
|
@ -116,22 +140,20 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hStatus(w http.ResponseWriter, r *http.Request) {
|
||||
data, _ := json.MarshalIndent(s, "", " ")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
// only can delete file now
|
||||
path := mux.Vars(req)["path"]
|
||||
func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := filepath.Dir(mux.Vars(req)["path"])
|
||||
auth := s.readAccessConf(path)
|
||||
log.Printf("%#v", auth)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Delete forbidden", http.StatusForbidden)
|
||||
http.Error(w, "Mkdir forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
err := os.Remove(filepath.Join(s.Root, path))
|
||||
|
||||
name := filepath.Base(mux.Vars(req)["path"])
|
||||
if err := checkFilename(name); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
err := os.Mkdir(filepath.Join(s.Root, path, name), 0755)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
|
@ -139,7 +161,30 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
|||
w.Write([]byte("Success"))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
path = filepath.Clean(path) // for safe reason, prevent path contain ..
|
||||
auth := s.readAccessConf(path)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Delete forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: path safe check
|
||||
err := os.RemoveAll(filepath.Join(s.Root, path))
|
||||
if err != nil {
|
||||
pathErr, ok := err.(*os.PathError)
|
||||
if ok {
|
||||
http.Error(w, pathErr.Op+" "+path+": "+pathErr.Err.Error(), 500)
|
||||
} else {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.Write([]byte("Success"))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
dirpath := filepath.Join(s.Root, path)
|
||||
|
||||
|
@ -151,6 +196,24 @@ func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
file, header, err := req.FormFile("file")
|
||||
|
||||
if _, err := os.Stat(dirpath); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dirpath, os.ModePerm); err != nil {
|
||||
log.Println("Create directory:", err)
|
||||
http.Error(w, "Directory create "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if file == nil { // only mkdir
|
||||
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"destination": dirpath,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println("Parse form file:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
@ -160,20 +223,65 @@ func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
|
|||
file.Close()
|
||||
req.MultipartForm.RemoveAll() // Seen from go source code, req.MultipartForm not nil after call FormFile(..)
|
||||
}()
|
||||
dstPath := filepath.Join(dirpath, header.Filename)
|
||||
|
||||
filename := req.FormValue("filename")
|
||||
if filename == "" {
|
||||
filename = header.Filename
|
||||
}
|
||||
if err := checkFilename(filename); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dirpath, filename)
|
||||
|
||||
// Large file (>32MB) will store in tmp directory
|
||||
// The quickest operation is call os.Move instead of os.Copy
|
||||
// Note: it seems not working well, os.Rename might be failed
|
||||
|
||||
var copyErr error
|
||||
// if osFile, ok := file.(*os.File); ok && fileExists(osFile.Name()) {
|
||||
// tmpUploadPath := osFile.Name()
|
||||
// osFile.Close() // Windows can not rename opened file
|
||||
// log.Printf("Move %s -> %s", tmpUploadPath, dstPath)
|
||||
// copyErr = os.Rename(tmpUploadPath, dstPath)
|
||||
// } else {
|
||||
dst, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
log.Println("Create file:", err)
|
||||
http.Error(w, "File create "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
|
||||
// Note: very large size file might cause poor performance
|
||||
// _, copyErr = io.Copy(dst, file)
|
||||
buf := s.bufPool.Get().([]byte)
|
||||
defer s.bufPool.Put(buf)
|
||||
_, copyErr = io.CopyBuffer(dst, file, buf)
|
||||
dst.Close()
|
||||
// }
|
||||
if copyErr != nil {
|
||||
log.Println("Handle upload file:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||
|
||||
if req.FormValue("unzip") == "true" {
|
||||
err = unzipFile(dstPath, dirpath)
|
||||
os.Remove(dstPath)
|
||||
message := "success"
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": err == nil,
|
||||
"description": message,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"destination": dstPath,
|
||||
|
@ -201,7 +309,7 @@ func parseApkInfo(path string) (ai *ApkInfo) {
|
|||
return
|
||||
}
|
||||
ai = &ApkInfo{}
|
||||
ai.MainActivity, _ = apkf.MainAcitivty()
|
||||
ai.MainActivity, _ = apkf.MainActivity()
|
||||
ai.PackageName = apkf.PackageName()
|
||||
ai.Version.Code = apkf.Manifest().VersionCode
|
||||
ai.Version.Name = apkf.Manifest().VersionName
|
||||
|
@ -211,10 +319,7 @@ func parseApkInfo(path string) (ai *ApkInfo) {
|
|||
func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
if !isFile(relPath) {
|
||||
http.Error(w, "Not a file", 403)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := os.Stat(relPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
@ -233,6 +338,8 @@ func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
|
|||
case ".apk":
|
||||
fji.Type = "apk"
|
||||
fji.Extra = parseApkInfo(relPath)
|
||||
case "":
|
||||
fji.Type = "dir"
|
||||
default:
|
||||
fji.Type = "text"
|
||||
}
|
||||
|
@ -260,13 +367,9 @@ func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func genURLStr(r *http.Request, path string) *url.URL {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
func combineURL(r *http.Request, path string) *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Scheme: r.URL.Scheme,
|
||||
Host: r.Host,
|
||||
Path: path,
|
||||
}
|
||||
|
@ -305,9 +408,11 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
plistUrl := genURLStr(r, "/-/ipa/plist/"+path).String()
|
||||
if r.TLS == nil {
|
||||
// send plist to plistproxy and get a https link
|
||||
var plistUrl string
|
||||
|
||||
if r.URL.Scheme == "https" {
|
||||
plistUrl = combineURL(r, "/-/ipa/plist/"+path).String()
|
||||
} else if s.PlistProxy != "" {
|
||||
httpPlistLink := "http://" + r.Host + "/-/ipa/plist/" + path
|
||||
url, err := s.genPlistLink(httpPlistLink)
|
||||
if err != nil {
|
||||
|
@ -315,17 +420,17 @@ func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
plistUrl = url
|
||||
//plistUrl = strings.TrimSuffix(s.PlistProxy, "/") + "/" + r.Host + "/-/ipa/plist/" + path
|
||||
} else {
|
||||
http.Error(w, "500: Server should be https:// or provide valid plistproxy", 500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
tmpl.ExecuteTemplate(w, "ipa-install", map[string]string{
|
||||
log.Println("PlistURL:", plistUrl)
|
||||
renderHTML(w, "ipa-install.html", map[string]string{
|
||||
"Name": filepath.Base(path),
|
||||
"PlistLink": plistUrl,
|
||||
})
|
||||
// w.Write([]byte(fmt.Sprintf(
|
||||
// `<a href='itms-services://?action=download-manifest&url=%s'>Click this link to install</a>`,
|
||||
// plistUrl)))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) genPlistLink(httpPlistLink string) (plistUrl string, err error) {
|
||||
|
@ -379,6 +484,7 @@ type UserControl struct {
|
|||
// Access bool
|
||||
Upload bool
|
||||
Delete bool
|
||||
Token string
|
||||
}
|
||||
|
||||
type AccessConf struct {
|
||||
|
@ -426,7 +532,20 @@ func (c *AccessConf) canDelete(r *http.Request) bool {
|
|||
return c.Delete
|
||||
}
|
||||
|
||||
func (c *AccessConf) canUploadByToken(token string) bool {
|
||||
for _, rule := range c.Users {
|
||||
if rule.Token == token {
|
||||
return rule.Upload
|
||||
}
|
||||
}
|
||||
return c.Upload
|
||||
}
|
||||
|
||||
func (c *AccessConf) canUpload(r *http.Request) bool {
|
||||
token := r.FormValue("token")
|
||||
if token != "" {
|
||||
return c.canUploadByToken(token)
|
||||
}
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
return c.Upload
|
||||
|
@ -436,6 +555,7 @@ func (c *AccessConf) canUpload(r *http.Request) bool {
|
|||
return c.Upload
|
||||
}
|
||||
userInfo := val.(*UserInfo)
|
||||
|
||||
for _, rule := range c.Users {
|
||||
if rule.Email == userInfo.Email {
|
||||
return rule.Upload
|
||||
|
@ -598,7 +718,7 @@ func (s *HTTPStaticServer) readAccessConf(requestPath string) (ac AccessConf) {
|
|||
if isFile(relPath) {
|
||||
relPath = filepath.Dir(relPath)
|
||||
}
|
||||
cfgFile := filepath.Join(relPath, ".ghs.yml")
|
||||
cfgFile := filepath.Join(relPath, YAMLCONF)
|
||||
data, err := ioutil.ReadFile(cfgFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
@ -640,3 +760,68 @@ func isDir(path string) bool {
|
|||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsDir()
|
||||
}
|
||||
|
||||
func assetsContent(name string) string {
|
||||
fd, err := Assets.Open(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// TODO: I need to read more abouthtml/template
|
||||
var (
|
||||
funcMap template.FuncMap
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcMap = template.FuncMap{
|
||||
"title": strings.Title,
|
||||
"urlhash": func(path string) string {
|
||||
httpFile, err := Assets.Open(path)
|
||||
if err != nil {
|
||||
return path + "#no-such-file"
|
||||
}
|
||||
info, err := httpFile.Stat()
|
||||
if err != nil {
|
||||
return path + "#stat-error"
|
||||
}
|
||||
return fmt.Sprintf("%s?t=%d", path, info.ModTime().Unix())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_tmpls = make(map[string]*template.Template)
|
||||
)
|
||||
|
||||
func executeTemplate(w http.ResponseWriter, name string, v interface{}) {
|
||||
if t, ok := _tmpls[name]; ok {
|
||||
t.Execute(w, v)
|
||||
return
|
||||
}
|
||||
t := template.Must(template.New(name).Funcs(funcMap).Delims("[[", "]]").Parse(assetsContent(name)))
|
||||
_tmpls[name] = t
|
||||
t.Execute(w, v)
|
||||
}
|
||||
|
||||
func renderHTML(w http.ResponseWriter, name string, v interface{}) {
|
||||
if _, ok := Assets.(http.Dir); ok {
|
||||
log.Println("Hot load", name)
|
||||
t := template.Must(template.New(name).Funcs(funcMap).Delims("[[", "]]").Parse(assetsContent(name)))
|
||||
t.Execute(w, v)
|
||||
} else {
|
||||
executeTemplate(w, name, v)
|
||||
}
|
||||
}
|
||||
|
||||
func checkFilename(name string) error {
|
||||
if strings.ContainsAny(name, "\\/:*<>|") {
|
||||
return errors.New("Name should not contains \\/:*<>|")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
3
ipa.go
|
@ -10,7 +10,8 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
goplist "github.com/DHowett/go-plist"
|
||||
goplist "github.com/fork2fix/go-plist"
|
||||
//goplist "github.com/DHowett/go-plist"
|
||||
)
|
||||
|
||||
func parseIpaIcon(path string) (data []byte, err error) {
|
||||
|
|
76
main.go
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -15,46 +16,49 @@ import (
|
|||
"text/template"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
accesslog "github.com/codeskyblue/go-accesslog"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/goji/httpauth"
|
||||
"github.com/gorilla/handlers"
|
||||
accesslog "github.com/mash/go-accesslog"
|
||||
_ "github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
type Configure struct {
|
||||
Conf *os.File `yaml:"-"`
|
||||
Addr string `yaml:"addr"`
|
||||
Root string `yaml:"root"`
|
||||
HTTPAuth string `yaml:"httpauth"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Cors bool `yaml:"cors"`
|
||||
Theme string `yaml:"theme"`
|
||||
XHeaders bool `yaml:"xheaders"`
|
||||
Upload bool `yaml:"upload"`
|
||||
Delete bool `yaml:"delete"`
|
||||
PlistProxy string `yaml:"plistproxy"`
|
||||
Title string `yaml:"title"`
|
||||
Debug bool `yaml:"debug"`
|
||||
GoogleTrackerId string `yaml:"google-tracker-id"`
|
||||
Auth struct {
|
||||
Type string `yaml:"type"`
|
||||
Conf *os.File `yaml:"-"`
|
||||
Addr string `yaml:"addr"`
|
||||
Port int `yaml:"port"`
|
||||
Root string `yaml:"root"`
|
||||
HTTPAuth string `yaml:"httpauth"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Cors bool `yaml:"cors"`
|
||||
Theme string `yaml:"theme"`
|
||||
XHeaders bool `yaml:"xheaders"`
|
||||
Upload bool `yaml:"upload"`
|
||||
Delete bool `yaml:"delete"`
|
||||
PlistProxy string `yaml:"plistproxy"`
|
||||
Title string `yaml:"title"`
|
||||
Debug bool `yaml:"debug"`
|
||||
Auth struct {
|
||||
Type string `yaml:"type"` // openid|http|github
|
||||
OpenID string `yaml:"openid"`
|
||||
HTTP string `yaml:"http"`
|
||||
ID string `yaml:"id"` // for oauth2
|
||||
Secret string `yaml:"secret"` // for oauth2
|
||||
} `yaml:"auth"`
|
||||
}
|
||||
|
||||
type logger struct{}
|
||||
type httpLogger struct{}
|
||||
|
||||
func (l logger) Log(record accesslog.LogRecord) {
|
||||
func (l httpLogger) Log(record accesslog.LogRecord) {
|
||||
log.Printf("%s - %s %d %s", record.Ip, record.Method, record.Status, record.Uri)
|
||||
}
|
||||
|
||||
var (
|
||||
defaultPlistProxy = "https://plistproxy.herokuapp.com/plist"
|
||||
defaultOpenID = "https://some-hostname.com/openid/"
|
||||
defaultOpenID = "https://login.netease.com/openid"
|
||||
gcfg = Configure{}
|
||||
l = logger{}
|
||||
logger = httpLogger{}
|
||||
|
||||
VERSION = "unknown"
|
||||
BUILDTIME = "unknown time"
|
||||
|
@ -85,18 +89,19 @@ func versionMessage() string {
|
|||
func parseFlags() error {
|
||||
// initial default conf
|
||||
gcfg.Root = "./"
|
||||
gcfg.Addr = ":8000"
|
||||
gcfg.Port = 8000
|
||||
gcfg.Addr = ""
|
||||
gcfg.Theme = "black"
|
||||
gcfg.PlistProxy = defaultPlistProxy
|
||||
gcfg.Auth.OpenID = defaultOpenID
|
||||
gcfg.GoogleTrackerId = "UA-81205425-2"
|
||||
gcfg.Title = "Go HTTP File Server"
|
||||
|
||||
kingpin.HelpFlag.Short('h')
|
||||
kingpin.Version(versionMessage())
|
||||
kingpin.Flag("conf", "config file path, yaml format").FileVar(&gcfg.Conf)
|
||||
kingpin.Flag("root", "root directory, default ./").Short('r').StringVar(&gcfg.Root)
|
||||
kingpin.Flag("addr", "listen address, default :8000").Short('a').StringVar(&gcfg.Addr)
|
||||
kingpin.Flag("port", "listen port, default 8000").IntVar(&gcfg.Port)
|
||||
kingpin.Flag("addr", "listen address, eg 127.0.0.1:8000").Short('a').StringVar(&gcfg.Addr)
|
||||
kingpin.Flag("cert", "tls cert.pem path").StringVar(&gcfg.Cert)
|
||||
kingpin.Flag("key", "tls key.pem path").StringVar(&gcfg.Key)
|
||||
kingpin.Flag("auth-type", "Auth type <http|openid>").StringVar(&gcfg.Auth.Type)
|
||||
|
@ -110,7 +115,6 @@ func parseFlags() error {
|
|||
kingpin.Flag("debug", "enable debug mode").BoolVar(&gcfg.Debug)
|
||||
kingpin.Flag("plistproxy", "plist proxy when server is not https").Short('p').StringVar(&gcfg.PlistProxy)
|
||||
kingpin.Flag("title", "server title").StringVar(&gcfg.Title)
|
||||
kingpin.Flag("google-tracker-id", "set to empty to disable it").StringVar(&gcfg.GoogleTrackerId)
|
||||
|
||||
kingpin.Parse() // first parse conf
|
||||
|
||||
|
@ -140,7 +144,6 @@ func main() {
|
|||
ss := NewHTTPStaticServer(gcfg.Root)
|
||||
ss.Theme = gcfg.Theme
|
||||
ss.Title = gcfg.Title
|
||||
ss.GoogleTrackerId = gcfg.GoogleTrackerId
|
||||
ss.Upload = gcfg.Upload
|
||||
ss.Delete = gcfg.Delete
|
||||
ss.AuthType = gcfg.Auth.Type
|
||||
|
@ -153,10 +156,13 @@ func main() {
|
|||
u.Scheme = "https"
|
||||
ss.PlistProxy = u.String()
|
||||
}
|
||||
if ss.PlistProxy != "" {
|
||||
log.Printf("plistproxy: %s", strconv.Quote(ss.PlistProxy))
|
||||
}
|
||||
|
||||
var hdlr http.Handler = ss
|
||||
|
||||
hdlr = accesslog.NewLoggingHandler(hdlr, l)
|
||||
hdlr = accesslog.NewLoggingHandler(hdlr, logger)
|
||||
|
||||
// HTTP Basic Authentication
|
||||
userpass := strings.SplitN(gcfg.Auth.HTTP, ":", 2)
|
||||
|
@ -167,8 +173,13 @@ func main() {
|
|||
hdlr = httpauth.SimpleBasicAuth(user, pass)(hdlr)
|
||||
}
|
||||
case "openid":
|
||||
handleOpenID(false) // FIXME(ssx): set secure default to false
|
||||
handleOpenID(gcfg.Auth.OpenID, false) // FIXME(ssx): set secure default to false
|
||||
// case "github":
|
||||
// handleOAuth2ID(gcfg.Auth.Type, gcfg.Auth.ID, gcfg.Auth.Secret) // FIXME(ssx): set secure default to false
|
||||
case "oauth2-proxy":
|
||||
handleOauth2()
|
||||
}
|
||||
|
||||
// CORS
|
||||
if gcfg.Cors {
|
||||
hdlr = handlers.CORS()(hdlr)
|
||||
|
@ -178,6 +189,7 @@ func main() {
|
|||
}
|
||||
|
||||
http.Handle("/", hdlr)
|
||||
http.Handle("/-/assets/", http.StripPrefix("/-/assets/", http.FileServer(Assets)))
|
||||
http.HandleFunc("/-/sysinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
|
@ -186,10 +198,14 @@ func main() {
|
|||
w.Write(data)
|
||||
})
|
||||
|
||||
if gcfg.Addr == "" {
|
||||
gcfg.Addr = fmt.Sprintf(":%d", gcfg.Port)
|
||||
}
|
||||
if !strings.Contains(gcfg.Addr, ":") {
|
||||
gcfg.Addr = ":" + gcfg.Addr
|
||||
}
|
||||
log.Printf("listening on %s\n", strconv.Quote(gcfg.Addr))
|
||||
_, port, _ := net.SplitHostPort(gcfg.Addr)
|
||||
log.Printf("listening on %s, local address http://%s:%s\n", strconv.Quote(gcfg.Addr), getLocalIP(), port)
|
||||
|
||||
var err error
|
||||
if gcfg.Key != "" && gcfg.Cert != "" {
|
||||
|
|
27
oauth2-proxy.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func handleOauth2() {
|
||||
http.HandleFunc("/-/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
fullNameMap, _ := url.ParseQuery(r.Header.Get("X-Auth-Request-Fullname"))
|
||||
var fullName string
|
||||
for k := range fullNameMap {
|
||||
fullName = k
|
||||
break
|
||||
}
|
||||
user := &UserInfo{
|
||||
Email: r.Header.Get("X-Auth-Request-Email"),
|
||||
Name: fullName,
|
||||
NickName: r.Header.Get("X-Auth-Request-User"),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
data, _ := json.Marshal(user)
|
||||
w.Write(data)
|
||||
})
|
||||
}
|
|
@ -33,7 +33,7 @@ func init() {
|
|||
gob.Register(&M{})
|
||||
}
|
||||
|
||||
func handleOpenID(secure bool) {
|
||||
func handleOpenID(loginUrl string, secure bool) {
|
||||
http.HandleFunc("/-/login", func(w http.ResponseWriter, r *http.Request) {
|
||||
nextUrl := r.FormValue("next")
|
||||
referer := r.Referer()
|
||||
|
@ -41,10 +41,11 @@ func handleOpenID(secure bool) {
|
|||
nextUrl = referer
|
||||
}
|
||||
scheme := "http"
|
||||
if secure {
|
||||
scheme = "https"
|
||||
if r.URL.Scheme != "" {
|
||||
scheme = r.URL.Scheme
|
||||
}
|
||||
if url, err := openid.RedirectURL("https://login.netease.com/openid",
|
||||
log.Println("Scheme:", scheme)
|
||||
if url, err := openid.RedirectURL(loginUrl,
|
||||
scheme+"://"+r.Host+"/-/openidcallback?next="+nextUrl, ""); err == nil {
|
||||
http.Redirect(w, r, url, 303)
|
||||
} else {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// +build bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(assetFS())))
|
||||
|
||||
for name, path := range templates {
|
||||
data, err := Asset(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ParseTemplate(name, string(data))
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// +build !bindata
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//selfDir := filepath.Dir(os.Args[0])
|
||||
//resDir := filepath.Join(selfDir, "./res")
|
||||
resDir := "./res"
|
||||
http.Handle("/-/res/", http.StripPrefix("/-/res/", http.FileServer(http.Dir(resDir))))
|
||||
|
||||
for name, path := range templates {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ParseTemplate(name, string(content))
|
||||
}
|
||||
}
|
5
testdata/uploadable/.ghs.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
upload: true
|
||||
upload: false
|
||||
users:
|
||||
- email: "user@example.com"
|
||||
upload: true
|
||||
delete: true
|
||||
delete: true
|
||||
token: 123456
|
27
utils.go
|
@ -1,7 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -53,3 +55,28 @@ func SublimeContains(s, substr string) bool {
|
|||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// getLocalIP returns the non loopback local IP of the host
|
||||
func getLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback the display it
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
|
58
vendor/github.com/DHowett/go-plist/LICENSE
generated
vendored
|
@ -1,58 +0,0 @@
|
|||
Copyright (c) 2013, Dustin L. Howett. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Parts of this package were made available under the license covering
|
||||
the Go language and all attended core libraries. That license follows.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
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.
|
19
vendor/github.com/DHowett/go-plist/README.md
generated
vendored
|
@ -1,19 +0,0 @@
|
|||
# plist - A pure Go property list transcoder
|
||||
## INSTALL
|
||||
$ go get howett.net/plist
|
||||
|
||||
## FEATURES
|
||||
* Supports encoding/decoding property lists (Apple XML, Apple Binary, OpenStep and GNUStep) from/to arbitrary Go types
|
||||
|
||||
## USE
|
||||
```go
|
||||
package main
|
||||
import (
|
||||
"howett.net/plist"
|
||||
"os"
|
||||
)
|
||||
func main() {
|
||||
encoder := plist.NewEncoder(os.Stdout)
|
||||
encoder.Encode(map[string]string{"hello": "world"})
|
||||
}
|
||||
```
|
601
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
|
@ -1,601 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
type bplistTrailer struct {
|
||||
Unused [5]uint8
|
||||
SortVersion uint8
|
||||
OffsetIntSize uint8
|
||||
ObjectRefSize uint8
|
||||
NumObjects uint64
|
||||
TopObject uint64
|
||||
OffsetTableOffset uint64
|
||||
}
|
||||
|
||||
const (
|
||||
bpTagNull uint8 = 0x00
|
||||
bpTagBoolFalse = 0x08
|
||||
bpTagBoolTrue = 0x09
|
||||
bpTagInteger = 0x10
|
||||
bpTagReal = 0x20
|
||||
bpTagDate = 0x30
|
||||
bpTagData = 0x40
|
||||
bpTagASCIIString = 0x50
|
||||
bpTagUTF16String = 0x60
|
||||
bpTagUID = 0x80
|
||||
bpTagArray = 0xA0
|
||||
bpTagDictionary = 0xD0
|
||||
)
|
||||
|
||||
type bplistGenerator struct {
|
||||
writer *countedWriter
|
||||
uniqmap map[interface{}]uint64
|
||||
objmap map[*plistValue]uint64
|
||||
objtable []*plistValue
|
||||
nobjects uint64
|
||||
trailer bplistTrailer
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) flattenPlistValue(pval *plistValue) {
|
||||
switch pval.kind {
|
||||
case String, Integer, Real:
|
||||
if _, ok := p.uniqmap[pval.value]; ok {
|
||||
return
|
||||
}
|
||||
p.uniqmap[pval.value] = p.nobjects
|
||||
case Date:
|
||||
k := pval.value.(time.Time).UnixNano()
|
||||
if _, ok := p.uniqmap[k]; ok {
|
||||
return
|
||||
}
|
||||
p.uniqmap[k] = p.nobjects
|
||||
case Data:
|
||||
// Data are uniqued by their checksums.
|
||||
// The wonderful difference between uint64 (which we use for numbers)
|
||||
// and uint32 makes this possible.
|
||||
// Todo: Look at calculating this only once and storing it somewhere;
|
||||
// crc32 is fairly quick, however.
|
||||
uniqkey := crc32.ChecksumIEEE(pval.value.([]byte))
|
||||
if _, ok := p.uniqmap[uniqkey]; ok {
|
||||
return
|
||||
}
|
||||
p.uniqmap[uniqkey] = p.nobjects
|
||||
}
|
||||
|
||||
p.objtable = append(p.objtable, pval)
|
||||
p.objmap[pval] = p.nobjects
|
||||
p.nobjects++
|
||||
|
||||
switch pval.kind {
|
||||
case Dictionary:
|
||||
dict := pval.value.(*dictionary)
|
||||
dict.populateArrays()
|
||||
for _, k := range dict.keys {
|
||||
p.flattenPlistValue(&plistValue{String, k})
|
||||
}
|
||||
for _, v := range dict.values {
|
||||
p.flattenPlistValue(v)
|
||||
}
|
||||
case Array:
|
||||
subvalues := pval.value.([]*plistValue)
|
||||
for _, v := range subvalues {
|
||||
p.flattenPlistValue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) indexForPlistValue(pval *plistValue) (uint64, bool) {
|
||||
var v uint64
|
||||
var ok bool
|
||||
switch pval.kind {
|
||||
case String, Integer, Real:
|
||||
v, ok = p.uniqmap[pval.value]
|
||||
case Date:
|
||||
v, ok = p.uniqmap[pval.value.(time.Time).UnixNano()]
|
||||
case Data:
|
||||
v, ok = p.uniqmap[crc32.ChecksumIEEE(pval.value.([]byte))]
|
||||
default:
|
||||
v, ok = p.objmap[pval]
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) generateDocument(rootpval *plistValue) {
|
||||
p.objtable = make([]*plistValue, 0, 15)
|
||||
p.uniqmap = make(map[interface{}]uint64)
|
||||
p.objmap = make(map[*plistValue]uint64)
|
||||
p.flattenPlistValue(rootpval)
|
||||
|
||||
p.trailer.NumObjects = uint64(len(p.objtable))
|
||||
p.trailer.ObjectRefSize = uint8(minimumSizeForInt(p.trailer.NumObjects))
|
||||
|
||||
p.writer.Write([]byte("bplist00"))
|
||||
|
||||
offtable := make([]uint64, p.trailer.NumObjects)
|
||||
for i, pval := range p.objtable {
|
||||
offtable[i] = uint64(p.writer.BytesWritten())
|
||||
p.writePlistValue(pval)
|
||||
}
|
||||
|
||||
p.trailer.OffsetIntSize = uint8(minimumSizeForInt(uint64(p.writer.BytesWritten())))
|
||||
p.trailer.TopObject = p.objmap[rootpval]
|
||||
p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten())
|
||||
|
||||
for _, offset := range offtable {
|
||||
p.writeSizedInt(offset, int(p.trailer.OffsetIntSize))
|
||||
}
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, p.trailer)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writePlistValue(pval *plistValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch pval.kind {
|
||||
case Dictionary:
|
||||
p.writeDictionaryTag(pval.value.(*dictionary))
|
||||
case Array:
|
||||
p.writeArrayTag(pval.value.([]*plistValue))
|
||||
case String:
|
||||
p.writeStringTag(pval.value.(string))
|
||||
case Integer:
|
||||
p.writeIntTag(pval.value.(signedInt).value)
|
||||
case Real:
|
||||
p.writeRealTag(pval.value.(sizedFloat).value, pval.value.(sizedFloat).bits)
|
||||
case Boolean:
|
||||
p.writeBoolTag(pval.value.(bool))
|
||||
case Data:
|
||||
p.writeDataTag(pval.value.([]byte))
|
||||
case Date:
|
||||
p.writeDateTag(pval.value.(time.Time))
|
||||
}
|
||||
}
|
||||
|
||||
func minimumSizeForInt(n uint64) int {
|
||||
switch {
|
||||
case n <= uint64(0xff):
|
||||
return 1
|
||||
case n <= uint64(0xffff):
|
||||
return 2
|
||||
case n <= uint64(0xffffffff):
|
||||
return 4
|
||||
default:
|
||||
return 8
|
||||
}
|
||||
panic(errors.New("illegal integer size"))
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) {
|
||||
var val interface{}
|
||||
switch nbytes {
|
||||
case 1:
|
||||
val = uint8(n)
|
||||
case 2:
|
||||
val = uint16(n)
|
||||
case 4:
|
||||
val = uint32(n)
|
||||
case 8:
|
||||
val = n
|
||||
default:
|
||||
panic(errors.New("illegal integer size"))
|
||||
}
|
||||
binary.Write(p.writer, binary.BigEndian, val)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeBoolTag(v bool) {
|
||||
tag := uint8(bpTagBoolFalse)
|
||||
if v {
|
||||
tag = bpTagBoolTrue
|
||||
}
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeIntTag(n uint64) {
|
||||
var tag uint8
|
||||
var val interface{}
|
||||
switch {
|
||||
case n <= uint64(0xff):
|
||||
val = uint8(n)
|
||||
tag = bpTagInteger | 0x0
|
||||
case n <= uint64(0xffff):
|
||||
val = uint16(n)
|
||||
tag = bpTagInteger | 0x1
|
||||
case n <= uint64(0xffffffff):
|
||||
val = uint32(n)
|
||||
tag = bpTagInteger | 0x2
|
||||
default:
|
||||
val = n
|
||||
tag = bpTagInteger | 0x3
|
||||
}
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
binary.Write(p.writer, binary.BigEndian, val)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeRealTag(n float64, bits int) {
|
||||
var tag uint8 = bpTagReal | 0x3
|
||||
var val interface{} = n
|
||||
if bits == 32 {
|
||||
val = float32(n)
|
||||
tag = bpTagReal | 0x2
|
||||
}
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
binary.Write(p.writer, binary.BigEndian, val)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeDateTag(t time.Time) {
|
||||
tag := uint8(bpTagDate) | 0x3
|
||||
val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second)
|
||||
val -= 978307200 // Adjust to Apple Epoch
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
binary.Write(p.writer, binary.BigEndian, val)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) {
|
||||
marker := tag
|
||||
if count >= 0xF {
|
||||
marker |= 0xF
|
||||
} else {
|
||||
marker |= uint8(count)
|
||||
}
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, marker)
|
||||
|
||||
if count >= 0xF {
|
||||
p.writeIntTag(count)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeDataTag(data []byte) {
|
||||
p.writeCountedTag(bpTagData, uint64(len(data)))
|
||||
binary.Write(p.writer, binary.BigEndian, data)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeStringTag(str string) {
|
||||
for _, r := range str {
|
||||
if r > 0xFF {
|
||||
utf16Runes := utf16.Encode([]rune(str))
|
||||
p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes)))
|
||||
binary.Write(p.writer, binary.BigEndian, utf16Runes)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p.writeCountedTag(bpTagASCIIString, uint64(len(str)))
|
||||
binary.Write(p.writer, binary.BigEndian, []byte(str))
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeDictionaryTag(dict *dictionary) {
|
||||
p.writeCountedTag(bpTagDictionary, uint64(dict.count))
|
||||
vals := make([]uint64, dict.count*2)
|
||||
cnt := dict.count
|
||||
for i, k := range dict.keys {
|
||||
keyIdx, ok := p.uniqmap[k]
|
||||
if !ok {
|
||||
panic(errors.New("failed to find key " + k + " in object map during serialization"))
|
||||
}
|
||||
vals[i] = keyIdx
|
||||
}
|
||||
for i, v := range dict.values {
|
||||
objIdx, ok := p.indexForPlistValue(v)
|
||||
if !ok {
|
||||
panic(errors.New("failed to find value in object map during serialization"))
|
||||
}
|
||||
vals[i+cnt] = objIdx
|
||||
}
|
||||
|
||||
for _, v := range vals {
|
||||
p.writeSizedInt(v, int(p.trailer.ObjectRefSize))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeArrayTag(arr []*plistValue) {
|
||||
p.writeCountedTag(bpTagArray, uint64(len(arr)))
|
||||
for _, v := range arr {
|
||||
objIdx, ok := p.indexForPlistValue(v)
|
||||
if !ok {
|
||||
panic(errors.New("failed to find value in object map during serialization"))
|
||||
}
|
||||
|
||||
p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) Indent(i string) {
|
||||
// There's nothing to indent.
|
||||
}
|
||||
|
||||
func newBplistGenerator(w io.Writer) *bplistGenerator {
|
||||
return &bplistGenerator{
|
||||
writer: &countedWriter{Writer: mustWriter{w}},
|
||||
}
|
||||
}
|
||||
|
||||
type bplistParser struct {
|
||||
reader io.ReadSeeker
|
||||
version int
|
||||
buf []byte
|
||||
objrefs map[uint64]*plistValue
|
||||
offtable []uint64
|
||||
trailer bplistTrailer
|
||||
trailerOffset int64
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
if _, ok := r.(invalidPlistError); ok {
|
||||
parseError = r.(error)
|
||||
} else {
|
||||
// Wrap all non-invalid-plist errors.
|
||||
parseError = plistParseError{"binary", r.(error)}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
magic := make([]byte, 6)
|
||||
ver := make([]byte, 2)
|
||||
p.reader.Seek(0, 0)
|
||||
p.reader.Read(magic)
|
||||
if !bytes.Equal(magic, []byte("bplist")) {
|
||||
panic(invalidPlistError{"binary", errors.New("mismatched magic")})
|
||||
}
|
||||
|
||||
_, err := p.reader.Read(ver)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.version = int(mustParseInt(string(ver), 10, 0))
|
||||
|
||||
if p.version > 1 {
|
||||
panic(fmt.Errorf("unexpected version %d", p.version))
|
||||
}
|
||||
|
||||
p.objrefs = make(map[uint64]*plistValue)
|
||||
p.trailerOffset, err = p.reader.Seek(-32, 2)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = binary.Read(p.reader, binary.BigEndian, &p.trailer)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if p.trailer.NumObjects > uint64(math.Pow(2, 8*float64(p.trailer.ObjectRefSize))) {
|
||||
panic(fmt.Errorf("binary property list contains more objects (%v) than its object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize))
|
||||
}
|
||||
|
||||
if p.trailer.TopObject >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("top object index %v is out of range (only %v objects exist)", p.trailer.TopObject, p.trailer.NumObjects))
|
||||
}
|
||||
p.offtable = make([]uint64, p.trailer.NumObjects)
|
||||
|
||||
// SEEK_SET
|
||||
_, err = p.reader.Seek(int64(p.trailer.OffsetTableOffset), 0)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := uint64(0); i < p.trailer.NumObjects; i++ {
|
||||
off := p.readSizedInt(int(p.trailer.OffsetIntSize))
|
||||
if off >= uint64(p.trailerOffset) {
|
||||
panic(fmt.Errorf("object %v starts beyond end of plist trailer (%v vs %v)", i, off, p.trailerOffset))
|
||||
}
|
||||
p.offtable[i] = off
|
||||
}
|
||||
|
||||
for _, off := range p.offtable {
|
||||
p.valueAtOffset(off)
|
||||
}
|
||||
|
||||
pval = p.valueAtOffset(p.offtable[p.trailer.TopObject])
|
||||
return
|
||||
}
|
||||
|
||||
func (p *bplistParser) readSizedInt(nbytes int) uint64 {
|
||||
switch nbytes {
|
||||
case 1:
|
||||
var val uint8
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return uint64(val)
|
||||
case 2:
|
||||
var val uint16
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return uint64(val)
|
||||
case 4:
|
||||
var val uint32
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return uint64(val)
|
||||
case 8:
|
||||
var val uint64
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return uint64(val)
|
||||
case 16:
|
||||
var high, low uint64
|
||||
binary.Read(p.reader, binary.BigEndian, &high)
|
||||
binary.Read(p.reader, binary.BigEndian, &low)
|
||||
// TODO: int128 support (!)
|
||||
return uint64(low)
|
||||
}
|
||||
panic(errors.New("illegal integer size"))
|
||||
}
|
||||
|
||||
func (p *bplistParser) countForTag(tag uint8) uint64 {
|
||||
cnt := uint64(tag & 0x0F)
|
||||
if cnt == 0xF {
|
||||
var intTag uint8
|
||||
binary.Read(p.reader, binary.BigEndian, &intTag)
|
||||
cnt = p.readSizedInt(1 << (intTag & 0xF))
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func (p *bplistParser) valueAtOffset(off uint64) *plistValue {
|
||||
if pval, ok := p.objrefs[off]; ok {
|
||||
return pval
|
||||
}
|
||||
pval := p.parseTagAtOffset(int64(off))
|
||||
p.objrefs[off] = pval
|
||||
return pval
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseTagAtOffset(off int64) *plistValue {
|
||||
var tag uint8
|
||||
_, err := p.reader.Seek(off, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = binary.Read(p.reader, binary.BigEndian, &tag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch tag & 0xF0 {
|
||||
case bpTagNull:
|
||||
switch tag & 0x0F {
|
||||
case bpTagBoolTrue, bpTagBoolFalse:
|
||||
return &plistValue{Boolean, tag == bpTagBoolTrue}
|
||||
}
|
||||
return nil
|
||||
case bpTagInteger:
|
||||
val := p.readSizedInt(1 << (tag & 0xF))
|
||||
return &plistValue{Integer, signedInt{val, false}}
|
||||
case bpTagReal:
|
||||
nbytes := 1 << (tag & 0x0F)
|
||||
switch nbytes {
|
||||
case 4:
|
||||
var val float32
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return &plistValue{Real, sizedFloat{float64(val), 32}}
|
||||
case 8:
|
||||
var val float64
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
return &plistValue{Real, sizedFloat{float64(val), 64}}
|
||||
}
|
||||
panic(errors.New("illegal float size"))
|
||||
case bpTagDate:
|
||||
var val float64
|
||||
binary.Read(p.reader, binary.BigEndian, &val)
|
||||
|
||||
// Apple Epoch is 20110101000000Z
|
||||
// Adjust for UNIX Time
|
||||
val += 978307200
|
||||
|
||||
sec, fsec := math.Modf(val)
|
||||
time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC)
|
||||
return &plistValue{Date, time}
|
||||
case bpTagData:
|
||||
cnt := p.countForTag(tag)
|
||||
if int64(cnt) > p.trailerOffset-int64(off) {
|
||||
panic(fmt.Errorf("data at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off)))
|
||||
}
|
||||
|
||||
bytes := make([]byte, cnt)
|
||||
binary.Read(p.reader, binary.BigEndian, bytes)
|
||||
return &plistValue{Data, bytes}
|
||||
case bpTagASCIIString, bpTagUTF16String:
|
||||
cnt := p.countForTag(tag)
|
||||
if int64(cnt) > p.trailerOffset-int64(off) {
|
||||
panic(fmt.Errorf("string at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off)))
|
||||
}
|
||||
|
||||
if tag&0xF0 == bpTagASCIIString {
|
||||
bytes := make([]byte, cnt)
|
||||
binary.Read(p.reader, binary.BigEndian, bytes)
|
||||
return &plistValue{String, string(bytes)}
|
||||
} else {
|
||||
bytes := make([]uint16, cnt)
|
||||
binary.Read(p.reader, binary.BigEndian, bytes)
|
||||
runes := utf16.Decode(bytes)
|
||||
return &plistValue{String, string(runes)}
|
||||
}
|
||||
case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes)
|
||||
val := p.readSizedInt(int(tag&0xF) + 1)
|
||||
return &plistValue{Integer, signedInt{val, false}}
|
||||
case bpTagDictionary:
|
||||
cnt := p.countForTag(tag)
|
||||
|
||||
subvalues := make(map[string]*plistValue)
|
||||
indices := make([]uint64, cnt*2)
|
||||
for i := uint64(0); i < cnt*2; i++ {
|
||||
idx := p.readSizedInt(int(p.trailer.ObjectRefSize))
|
||||
|
||||
if idx >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("dictionary contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects))
|
||||
}
|
||||
|
||||
indices[i] = idx
|
||||
}
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
keyOffset := p.offtable[indices[i]]
|
||||
valueOffset := p.offtable[indices[i+cnt]]
|
||||
if keyOffset == uint64(off) {
|
||||
panic(fmt.Errorf("dictionary contains self-referential key %x (index %d)", off, i))
|
||||
}
|
||||
if valueOffset == uint64(off) {
|
||||
panic(fmt.Errorf("dictionary contains self-referential value %x (index %d)", off, i))
|
||||
}
|
||||
|
||||
kval := p.valueAtOffset(keyOffset)
|
||||
if kval == nil || kval.kind != String {
|
||||
panic(fmt.Errorf("dictionary contains non-string key at index %d", i))
|
||||
}
|
||||
|
||||
key, ok := kval.value.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("string-type plist value contains non-string at index %d", i))
|
||||
}
|
||||
subvalues[key] = p.valueAtOffset(valueOffset)
|
||||
}
|
||||
|
||||
return &plistValue{Dictionary, &dictionary{m: subvalues}}
|
||||
case bpTagArray:
|
||||
cnt := p.countForTag(tag)
|
||||
|
||||
arr := make([]*plistValue, cnt)
|
||||
indices := make([]uint64, cnt)
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
idx := p.readSizedInt(int(p.trailer.ObjectRefSize))
|
||||
|
||||
if idx >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("array contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects))
|
||||
}
|
||||
|
||||
indices[i] = idx
|
||||
}
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
valueOffset := p.offtable[indices[i]]
|
||||
if valueOffset == uint64(off) {
|
||||
panic(fmt.Errorf("array contains self-referential value %x (index %d)", off, i))
|
||||
}
|
||||
arr[i] = p.valueAtOffset(valueOffset)
|
||||
}
|
||||
|
||||
return &plistValue{Array, arr}
|
||||
}
|
||||
panic(fmt.Errorf("unexpected atom 0x%2.02x at offset %d", tag, off))
|
||||
}
|
||||
|
||||
func newBplistParser(r io.ReadSeeker) *bplistParser {
|
||||
return &bplistParser{reader: r}
|
||||
}
|
118
vendor/github.com/DHowett/go-plist/decode.go
generated
vendored
|
@ -1,118 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type parser interface {
|
||||
parseDocument() (*plistValue, error)
|
||||
}
|
||||
|
||||
// A Decoder reads a property list from an input stream.
|
||||
type Decoder struct {
|
||||
// the format of the most-recently-decoded property list
|
||||
Format int
|
||||
|
||||
reader io.ReadSeeker
|
||||
lax bool
|
||||
}
|
||||
|
||||
// Decode works like Unmarshal, except it reads the decoder stream to find property list elements.
|
||||
//
|
||||
// After Decoding, the Decoder's Format field will be set to one of the plist format constants.
|
||||
func (p *Decoder) Decode(v interface{}) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
err = r.(error)
|
||||
}
|
||||
}()
|
||||
|
||||
header := make([]byte, 6)
|
||||
p.reader.Read(header)
|
||||
p.reader.Seek(0, 0)
|
||||
|
||||
var parser parser
|
||||
var pval *plistValue
|
||||
if bytes.Equal(header, []byte("bplist")) {
|
||||
parser = newBplistParser(p.reader)
|
||||
pval, err = parser.parseDocument()
|
||||
if err != nil {
|
||||
// Had a bplist header, but still got an error: we have to die here.
|
||||
return err
|
||||
}
|
||||
p.Format = BinaryFormat
|
||||
} else {
|
||||
parser = newXMLPlistParser(p.reader)
|
||||
pval, err = parser.parseDocument()
|
||||
if _, ok := err.(invalidPlistError); ok {
|
||||
// Rewind: the XML parser might have exhausted the file.
|
||||
p.reader.Seek(0, 0)
|
||||
// We don't use parser here because we want the textPlistParser type
|
||||
tp := newTextPlistParser(p.reader)
|
||||
pval, err = tp.parseDocument()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Format = tp.format
|
||||
if p.Format == OpenStepFormat {
|
||||
// OpenStep property lists can only store strings,
|
||||
// so we have to turn on lax mode here for the unmarshal step later.
|
||||
p.lax = true
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Format = XMLFormat
|
||||
}
|
||||
}
|
||||
|
||||
p.unmarshal(pval, reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
|
||||
// NewDecoder returns a Decoder that reads property list elements from a stream reader, r.
|
||||
// NewDecoder requires a Seekable stream for the purposes of file type detection.
|
||||
func NewDecoder(r io.ReadSeeker) *Decoder {
|
||||
return &Decoder{Format: InvalidFormat, reader: r, lax: false}
|
||||
}
|
||||
|
||||
// Unmarshal parses a property list document and stores the result in the value pointed to by v.
|
||||
//
|
||||
// Unmarshal uses the inverse of the type encodings that Marshal uses, allocating heap-borne types as necessary.
|
||||
//
|
||||
// When given a nil pointer, Unmarshal allocates a new value for it to point to.
|
||||
//
|
||||
// To decode property list values into an interface value, Unmarshal decodes the property list into the concrete value contained
|
||||
// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value:
|
||||
//
|
||||
// string, bool, uint64, float64
|
||||
// []byte, for plist data
|
||||
// []interface{}, for plist arrays
|
||||
// map[string]interface{}, for plist dictionaries
|
||||
//
|
||||
// If a property list value is not appropriate for a given value type, Unmarshal aborts immediately and returns an error.
|
||||
//
|
||||
// As Go does not support 128-bit types, and we don't want to pretend we're giving the user integer types (as opposed to
|
||||
// secretly passing them structs), Unmarshal will drop the high 64 bits of any 128-bit integers encoded in binary property lists.
|
||||
// (This is important because CoreFoundation serializes some large 64-bit values as 128-bit values with an empty high half.)
|
||||
//
|
||||
// When Unmarshal encounters an OpenStep property list, it will enter a relaxed parsing mode: OpenStep property lists can only store
|
||||
// plain old data as strings, so we will attempt to recover integer, floating-point, boolean and date values wherever they are necessary.
|
||||
// (for example, if Unmarshal attempts to unmarshal an OpenStep property list into a time.Time, it will try to parse the string it
|
||||
// receives as a time.)
|
||||
//
|
||||
// Unmarshal returns the detected property list format and an error, if any.
|
||||
func Unmarshal(data []byte, v interface{}) (format int, err error) {
|
||||
r := bytes.NewReader(data)
|
||||
dec := NewDecoder(r)
|
||||
err = dec.Decode(v)
|
||||
format = dec.Format
|
||||
return
|
||||
}
|
5
vendor/github.com/DHowett/go-plist/doc.go
generated
vendored
|
@ -1,5 +0,0 @@
|
|||
// Package plist implements encoding and decoding of Apple's "property list" format.
|
||||
// Property lists come in three sorts: plain text (GNUStep and OpenStep), XML and binary.
|
||||
// plist supports all of them.
|
||||
// The mapping between property list and Go objects is described in the documentation for the Marshal and Unmarshal functions.
|
||||
package plist
|
126
vendor/github.com/DHowett/go-plist/encode.go
generated
vendored
|
@ -1,126 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type generator interface {
|
||||
generateDocument(*plistValue)
|
||||
Indent(string)
|
||||
}
|
||||
|
||||
// An Encoder writes a property list to an output stream.
|
||||
type Encoder struct {
|
||||
writer io.Writer
|
||||
format int
|
||||
|
||||
indent string
|
||||
}
|
||||
|
||||
// Encode writes the property list encoding of v to the stream.
|
||||
func (p *Encoder) Encode(v interface{}) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
err = r.(error)
|
||||
}
|
||||
}()
|
||||
|
||||
pval := p.marshal(reflect.ValueOf(v))
|
||||
if pval == nil {
|
||||
panic(errors.New("plist: no root element to encode"))
|
||||
}
|
||||
|
||||
var g generator
|
||||
switch p.format {
|
||||
case XMLFormat:
|
||||
g = newXMLPlistGenerator(p.writer)
|
||||
case BinaryFormat, AutomaticFormat:
|
||||
g = newBplistGenerator(p.writer)
|
||||
case OpenStepFormat, GNUStepFormat:
|
||||
g = newTextPlistGenerator(p.writer, p.format)
|
||||
}
|
||||
g.Indent(p.indent)
|
||||
g.generateDocument(pval)
|
||||
return
|
||||
}
|
||||
|
||||
// Indent turns on pretty-printing for the XML and Text property list formats.
|
||||
// Each element begins on a new line and is preceded by one or more copies of indent according to its nesting depth.
|
||||
func (p *Encoder) Indent(indent string) {
|
||||
p.indent = indent
|
||||
}
|
||||
|
||||
// NewEncoder returns an Encoder that writes an XML property list to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return NewEncoderForFormat(w, XMLFormat)
|
||||
}
|
||||
|
||||
// NewEncoderForFormat returns an Encoder that writes a property list to w in the specified format.
|
||||
// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat).
|
||||
func NewEncoderForFormat(w io.Writer, format int) *Encoder {
|
||||
return &Encoder{
|
||||
writer: w,
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBinaryEncoder returns an Encoder that writes a binary property list to w.
|
||||
func NewBinaryEncoder(w io.Writer) *Encoder {
|
||||
return NewEncoderForFormat(w, BinaryFormat)
|
||||
}
|
||||
|
||||
// Marshal returns the property list encoding of v in the specified format.
|
||||
//
|
||||
// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat).
|
||||
//
|
||||
// Marshal traverses the value v recursively.
|
||||
// Any nil values encountered, other than the root, will be silently discarded as
|
||||
// the property list format bears no representation for nil values.
|
||||
//
|
||||
// Strings, integers of varying size, floats and booleans are encoded unchanged.
|
||||
// Strings bearing non-ASCII runes will be encoded differently depending upon the property list format:
|
||||
// UTF-8 for XML property lists and UTF-16 for binary property lists.
|
||||
//
|
||||
// Slice and Array values are encoded as property list arrays, except for
|
||||
// []byte values, which are encoded as data.
|
||||
//
|
||||
// Map values encode as dictionaries. The map's key type must be string; there is no provision for encoding non-string dictionary keys.
|
||||
//
|
||||
// Struct values are encoded as dictionaries, with only exported fields being serialized. Struct field encoding may be influenced with the use of tags.
|
||||
// The tag format is:
|
||||
//
|
||||
// `plist:"<key>[,flags...]"`
|
||||
//
|
||||
// The following flags are supported:
|
||||
//
|
||||
// omitempty Only include the field if it is not set to the zero value for its type.
|
||||
//
|
||||
// If the key is "-", the field is ignored.
|
||||
//
|
||||
// Anonymous struct fields are encoded as if their exported fields were exposed via the outer struct.
|
||||
//
|
||||
// Pointer values encode as the value pointed to.
|
||||
//
|
||||
// Channel, complex and function values cannot be encoded. Any attempt to do so causes Marshal to return an error.
|
||||
func Marshal(v interface{}, format int) ([]byte, error) {
|
||||
return MarshalIndent(v, format, "")
|
||||
}
|
||||
|
||||
// MarshalIndent works like Marshal, but each property list element
|
||||
// begins on a new line and is preceded by one or more copies of indent according to its nesting depth.
|
||||
func MarshalIndent(v interface{}, format int, indent string) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
enc := NewEncoderForFormat(buf, format)
|
||||
enc.Indent(indent)
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
17
vendor/github.com/DHowett/go-plist/fuzz.go
generated
vendored
|
@ -1,17 +0,0 @@
|
|||
// +build gofuzz
|
||||
|
||||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
buf := bytes.NewReader(data)
|
||||
|
||||
var obj interface{}
|
||||
if err := NewDecoder(buf).Decode(&obj); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
154
vendor/github.com/DHowett/go-plist/marshal.go
generated
vendored
|
@ -1,154 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
)
|
||||
|
||||
func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) *plistValue {
|
||||
s, err := marshalable.MarshalText()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &plistValue{String, string(s)}
|
||||
}
|
||||
|
||||
func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) *plistValue {
|
||||
tinfo, _ := getTypeInfo(typ)
|
||||
|
||||
dict := &dictionary{
|
||||
m: make(map[string]*plistValue, len(tinfo.fields)),
|
||||
}
|
||||
for _, finfo := range tinfo.fields {
|
||||
value := finfo.value(val)
|
||||
if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) {
|
||||
continue
|
||||
}
|
||||
dict.m[finfo.name] = p.marshal(value)
|
||||
}
|
||||
|
||||
return &plistValue{Dictionary, dict}
|
||||
}
|
||||
|
||||
func (p *Encoder) marshalTime(val reflect.Value) *plistValue {
|
||||
time := val.Interface().(time.Time)
|
||||
return &plistValue{Date, time}
|
||||
}
|
||||
|
||||
func (p *Encoder) marshal(val reflect.Value) *plistValue {
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// time.Time implements TextMarshaler, but we need to store it in RFC3339
|
||||
if val.Type() == timeType {
|
||||
return p.marshalTime(val)
|
||||
}
|
||||
if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) {
|
||||
ival := val.Elem()
|
||||
if ival.IsValid() && ival.Type() == timeType {
|
||||
return p.marshalTime(ival)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for text marshaler.
|
||||
if val.CanInterface() && val.Type().Implements(textMarshalerType) {
|
||||
return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler))
|
||||
}
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
|
||||
return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler))
|
||||
}
|
||||
}
|
||||
|
||||
// Descend into pointers or interfaces
|
||||
if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
// We got this far and still may have an invalid anything or nil ptr/interface
|
||||
if !val.IsValid() || ((val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface) && val.IsNil()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
if val.Kind() == reflect.Struct {
|
||||
return p.marshalStruct(typ, val)
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
return &plistValue{String, val.String()}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return &plistValue{Integer, signedInt{uint64(val.Int()), true}}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return &plistValue{Integer, signedInt{uint64(val.Uint()), false}}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return &plistValue{Real, sizedFloat{val.Float(), val.Type().Bits()}}
|
||||
case reflect.Bool:
|
||||
return &plistValue{Boolean, val.Bool()}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
bytes := []byte(nil)
|
||||
if val.CanAddr() {
|
||||
bytes = val.Bytes()
|
||||
} else {
|
||||
bytes = make([]byte, val.Len())
|
||||
reflect.Copy(reflect.ValueOf(bytes), val)
|
||||
}
|
||||
return &plistValue{Data, bytes}
|
||||
} else {
|
||||
subvalues := make([]*plistValue, val.Len())
|
||||
for idx, length := 0, val.Len(); idx < length; idx++ {
|
||||
if subpval := p.marshal(val.Index(idx)); subpval != nil {
|
||||
subvalues[idx] = subpval
|
||||
}
|
||||
}
|
||||
return &plistValue{Array, subvalues}
|
||||
}
|
||||
case reflect.Map:
|
||||
if typ.Key().Kind() != reflect.String {
|
||||
panic(&unknownTypeError{typ})
|
||||
}
|
||||
|
||||
l := val.Len()
|
||||
dict := &dictionary{
|
||||
m: make(map[string]*plistValue, l),
|
||||
}
|
||||
for _, keyv := range val.MapKeys() {
|
||||
if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil {
|
||||
dict.m[keyv.String()] = subpval
|
||||
}
|
||||
}
|
||||
return &plistValue{Dictionary, dict}
|
||||
default:
|
||||
panic(&unknownTypeError{typ})
|
||||
}
|
||||
return nil
|
||||
}
|
50
vendor/github.com/DHowett/go-plist/must.go
generated
vendored
|
@ -1,50 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type mustWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w mustWriter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func mustParseInt(str string, base, bits int) int64 {
|
||||
i, err := strconv.ParseInt(str, base, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func mustParseUint(str string, base, bits int) uint64 {
|
||||
i, err := strconv.ParseUint(str, base, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func mustParseFloat(str string, bits int) float64 {
|
||||
i, err := strconv.ParseFloat(str, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func mustParseBool(str string) bool {
|
||||
i, err := strconv.ParseBool(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
141
vendor/github.com/DHowett/go-plist/plist.go
generated
vendored
|
@ -1,141 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Property list format constants
|
||||
const (
|
||||
// Used by Decoder to represent an invalid property list.
|
||||
InvalidFormat int = 0
|
||||
|
||||
// Used to indicate total abandon with regards to Encoder's output format.
|
||||
AutomaticFormat = 0
|
||||
|
||||
XMLFormat = 1
|
||||
BinaryFormat = 2
|
||||
OpenStepFormat = 3
|
||||
GNUStepFormat = 4
|
||||
)
|
||||
|
||||
var FormatNames = map[int]string{
|
||||
InvalidFormat: "unknown/invalid",
|
||||
XMLFormat: "XML",
|
||||
BinaryFormat: "Binary",
|
||||
OpenStepFormat: "OpenStep",
|
||||
GNUStepFormat: "GNUStep",
|
||||
}
|
||||
|
||||
type plistKind uint
|
||||
|
||||
const (
|
||||
Invalid plistKind = iota
|
||||
Dictionary
|
||||
Array
|
||||
String
|
||||
Integer
|
||||
Real
|
||||
Boolean
|
||||
Data
|
||||
Date
|
||||
)
|
||||
|
||||
var plistKindNames map[plistKind]string = map[plistKind]string{
|
||||
Invalid: "invalid",
|
||||
Dictionary: "dictionary",
|
||||
Array: "array",
|
||||
String: "string",
|
||||
Integer: "integer",
|
||||
Real: "real",
|
||||
Boolean: "boolean",
|
||||
Data: "data",
|
||||
Date: "date",
|
||||
}
|
||||
|
||||
type plistValue struct {
|
||||
kind plistKind
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type signedInt struct {
|
||||
value uint64
|
||||
signed bool
|
||||
}
|
||||
|
||||
type sizedFloat struct {
|
||||
value float64
|
||||
bits int
|
||||
}
|
||||
|
||||
type dictionary struct {
|
||||
count int
|
||||
m map[string]*plistValue
|
||||
keys sort.StringSlice
|
||||
values []*plistValue
|
||||
}
|
||||
|
||||
func (d *dictionary) Len() int {
|
||||
return d.count
|
||||
}
|
||||
|
||||
func (d *dictionary) Less(i, j int) bool {
|
||||
return d.keys.Less(i, j)
|
||||
}
|
||||
|
||||
func (d *dictionary) Swap(i, j int) {
|
||||
d.keys.Swap(i, j)
|
||||
d.values[i], d.values[j] = d.values[j], d.values[i]
|
||||
}
|
||||
|
||||
func (d *dictionary) populateArrays() {
|
||||
if d.count > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l := len(d.m)
|
||||
d.count = l
|
||||
d.keys = make([]string, l)
|
||||
d.values = make([]*plistValue, l)
|
||||
i := 0
|
||||
for k, v := range d.m {
|
||||
d.keys[i] = k
|
||||
d.values[i] = v
|
||||
i++
|
||||
}
|
||||
sort.Sort(d)
|
||||
}
|
||||
|
||||
type unknownTypeError struct {
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
func (u *unknownTypeError) Error() string {
|
||||
return "plist: can't marshal value of type " + u.typ.String()
|
||||
}
|
||||
|
||||
type invalidPlistError struct {
|
||||
format string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e invalidPlistError) Error() string {
|
||||
s := "plist: invalid " + e.format + " property list"
|
||||
if e.err != nil {
|
||||
s += ": " + e.err.Error()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type plistParseError struct {
|
||||
format string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e plistParseError) Error() string {
|
||||
s := "plist: error parsing " + e.format + " property list"
|
||||
if e.err != nil {
|
||||
s += ": " + e.err.Error()
|
||||
}
|
||||
return s
|
||||
}
|
569
vendor/github.com/DHowett/go-plist/text.go
generated
vendored
|
@ -1,569 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type textPlistGenerator struct {
|
||||
writer io.Writer
|
||||
format int
|
||||
|
||||
quotableTable *[4]uint64
|
||||
|
||||
indent string
|
||||
depth int
|
||||
|
||||
dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte
|
||||
}
|
||||
|
||||
var (
|
||||
textPlistTimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
padding = "0000"
|
||||
)
|
||||
|
||||
func (p *textPlistGenerator) generateDocument(pval *plistValue) {
|
||||
p.writePlistValue(pval)
|
||||
}
|
||||
|
||||
func (p *textPlistGenerator) plistQuotedString(str string) string {
|
||||
if str == "" {
|
||||
return `""`
|
||||
}
|
||||
s := ""
|
||||
quot := false
|
||||
for _, r := range str {
|
||||
if r > 0xFF {
|
||||
quot = true
|
||||
s += `\U`
|
||||
us := strconv.FormatInt(int64(r), 16)
|
||||
s += padding[len(us):]
|
||||
s += us
|
||||
} else if r > 0x7F {
|
||||
quot = true
|
||||
s += `\`
|
||||
us := strconv.FormatInt(int64(r), 8)
|
||||
s += padding[1+len(us):]
|
||||
s += us
|
||||
} else {
|
||||
c := uint8(r)
|
||||
if (*p.quotableTable)[c/64]&(1<<(c%64)) > 0 {
|
||||
quot = true
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\a':
|
||||
s += `\a`
|
||||
case '\b':
|
||||
s += `\b`
|
||||
case '\v':
|
||||
s += `\v`
|
||||
case '\f':
|
||||
s += `\f`
|
||||
case '\\':
|
||||
s += `\\`
|
||||
case '"':
|
||||
s += `\"`
|
||||
case '\t', '\r', '\n':
|
||||
fallthrough
|
||||
default:
|
||||
s += string(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
if quot {
|
||||
s = `"` + s + `"`
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *textPlistGenerator) deltaIndent(depthDelta int) {
|
||||
if depthDelta < 0 {
|
||||
p.depth--
|
||||
} else if depthDelta > 0 {
|
||||
p.depth++
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistGenerator) writeIndent() {
|
||||
if len(p.indent) == 0 {
|
||||
return
|
||||
}
|
||||
if len(p.indent) > 0 {
|
||||
p.writer.Write([]byte("\n"))
|
||||
for i := 0; i < p.depth; i++ {
|
||||
io.WriteString(p.writer, p.indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistGenerator) writePlistValue(pval *plistValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch pval.kind {
|
||||
case Dictionary:
|
||||
p.writer.Write([]byte(`{`))
|
||||
p.deltaIndent(1)
|
||||
dict := pval.value.(*dictionary)
|
||||
dict.populateArrays()
|
||||
for i, k := range dict.keys {
|
||||
p.writeIndent()
|
||||
io.WriteString(p.writer, p.plistQuotedString(k))
|
||||
p.writer.Write(p.dictKvDelimiter)
|
||||
p.writePlistValue(dict.values[i])
|
||||
p.writer.Write(p.dictEntryDelimiter)
|
||||
}
|
||||
p.deltaIndent(-1)
|
||||
p.writeIndent()
|
||||
p.writer.Write([]byte(`}`))
|
||||
case Array:
|
||||
p.writer.Write([]byte(`(`))
|
||||
p.deltaIndent(1)
|
||||
values := pval.value.([]*plistValue)
|
||||
for _, v := range values {
|
||||
p.writeIndent()
|
||||
p.writePlistValue(v)
|
||||
p.writer.Write(p.arrayDelimiter)
|
||||
}
|
||||
p.deltaIndent(-1)
|
||||
p.writeIndent()
|
||||
p.writer.Write([]byte(`)`))
|
||||
case String:
|
||||
io.WriteString(p.writer, p.plistQuotedString(pval.value.(string)))
|
||||
case Integer:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*I`))
|
||||
}
|
||||
if pval.value.(signedInt).signed {
|
||||
io.WriteString(p.writer, strconv.FormatInt(int64(pval.value.(signedInt).value), 10))
|
||||
} else {
|
||||
io.WriteString(p.writer, strconv.FormatUint(pval.value.(signedInt).value, 10))
|
||||
}
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`>`))
|
||||
}
|
||||
case Real:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*R`))
|
||||
}
|
||||
io.WriteString(p.writer, strconv.FormatFloat(pval.value.(sizedFloat).value, 'g', -1, 64))
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`>`))
|
||||
}
|
||||
case Boolean:
|
||||
b := pval.value.(bool)
|
||||
if p.format == GNUStepFormat {
|
||||
if b {
|
||||
p.writer.Write([]byte(`<*BY>`))
|
||||
} else {
|
||||
p.writer.Write([]byte(`<*BN>`))
|
||||
}
|
||||
} else {
|
||||
if b {
|
||||
p.writer.Write([]byte(`1`))
|
||||
} else {
|
||||
p.writer.Write([]byte(`0`))
|
||||
}
|
||||
}
|
||||
case Data:
|
||||
b := pval.value.([]byte)
|
||||
var hexencoded [9]byte
|
||||
var l int
|
||||
var asc = 9
|
||||
hexencoded[8] = ' '
|
||||
|
||||
p.writer.Write([]byte(`<`))
|
||||
for i := 0; i < len(b); i += 4 {
|
||||
l = i + 4
|
||||
if l >= len(b) {
|
||||
l = len(b)
|
||||
// We no longer need the space - or the rest of the buffer.
|
||||
// (we used >= above to get this part without another conditional :P)
|
||||
asc = (l - i) * 2
|
||||
}
|
||||
// Fill the buffer (only up to 8 characters, to preserve the space we implicitly include
|
||||
// at the end of every encode)
|
||||
hex.Encode(hexencoded[:8], b[i:l])
|
||||
io.WriteString(p.writer, string(hexencoded[:asc]))
|
||||
}
|
||||
p.writer.Write([]byte(`>`))
|
||||
case Date:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*D`))
|
||||
io.WriteString(p.writer, pval.value.(time.Time).In(time.UTC).Format(textPlistTimeLayout))
|
||||
p.writer.Write([]byte(`>`))
|
||||
} else {
|
||||
io.WriteString(p.writer, p.plistQuotedString(pval.value.(time.Time).In(time.UTC).Format(textPlistTimeLayout)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistGenerator) Indent(i string) {
|
||||
p.indent = i
|
||||
if i == "" {
|
||||
p.dictKvDelimiter = []byte(`=`)
|
||||
} else {
|
||||
// For pretty-printing
|
||||
p.dictKvDelimiter = []byte(` = `)
|
||||
}
|
||||
}
|
||||
|
||||
func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator {
|
||||
table := &osQuotable
|
||||
if format == GNUStepFormat {
|
||||
table = &gsQuotable
|
||||
}
|
||||
return &textPlistGenerator{
|
||||
writer: mustWriter{w},
|
||||
format: format,
|
||||
quotableTable: table,
|
||||
dictKvDelimiter: []byte(`=`),
|
||||
arrayDelimiter: []byte(`,`),
|
||||
dictEntryDelimiter: []byte(`;`),
|
||||
}
|
||||
}
|
||||
|
||||
type byteReader interface {
|
||||
io.Reader
|
||||
io.ByteScanner
|
||||
Peek(n int) ([]byte, error)
|
||||
ReadBytes(delim byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type textPlistParser struct {
|
||||
reader byteReader
|
||||
whitespaceReplacer *strings.Replacer
|
||||
format int
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
if _, ok := r.(invalidPlistError); ok {
|
||||
parseError = r.(error)
|
||||
} else {
|
||||
// Wrap all non-invalid-plist errors.
|
||||
parseError = plistParseError{"text", r.(error)}
|
||||
}
|
||||
}
|
||||
}()
|
||||
pval = p.parsePlistValue()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *textPlistParser) chugWhitespace() {
|
||||
ws:
|
||||
for {
|
||||
c, err := p.reader.ReadByte()
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
if whitespace[c/64]&(1<<(c%64)) == 0 {
|
||||
if c == '/' && err != io.EOF {
|
||||
// A / at the end of the file is not the begining of a comment.
|
||||
cs, err := p.reader.Peek(1)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
c = cs[0]
|
||||
switch c {
|
||||
case '/':
|
||||
for {
|
||||
c, err = p.reader.ReadByte()
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
// TODO: UTF-8
|
||||
if c == '\n' || c == '\r' {
|
||||
break
|
||||
}
|
||||
}
|
||||
case '*':
|
||||
// Peek returned a value here, so it is safe to read.
|
||||
_, _ = p.reader.ReadByte()
|
||||
star := false
|
||||
for {
|
||||
c, err = p.reader.ReadByte()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if c == '*' {
|
||||
star = true
|
||||
} else if c == '/' && star {
|
||||
break
|
||||
} else {
|
||||
star = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
p.reader.UnreadByte() // Not the beginning of a // or /* comment
|
||||
break ws
|
||||
}
|
||||
continue
|
||||
}
|
||||
p.reader.UnreadByte()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseQuotedString() *plistValue {
|
||||
escaping := false
|
||||
s := ""
|
||||
for {
|
||||
byt, err := p.reader.ReadByte()
|
||||
// EOF here is an error: we're inside a quoted string!
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c := rune(byt)
|
||||
if !escaping {
|
||||
if c == '"' {
|
||||
break
|
||||
} else if c == '\\' {
|
||||
escaping = true
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
escaping = false
|
||||
// Everything that is not listed here passes through unharmed.
|
||||
switch c {
|
||||
case 'a':
|
||||
c = '\a'
|
||||
case 'b':
|
||||
c = '\b'
|
||||
case 'v':
|
||||
c = '\v'
|
||||
case 'f':
|
||||
c = '\f'
|
||||
case 't':
|
||||
c = '\t'
|
||||
case 'r':
|
||||
c = '\r'
|
||||
case 'n':
|
||||
c = '\n'
|
||||
case 'x', 'u', 'U': // hex and unicode
|
||||
l := 4
|
||||
if c == 'x' {
|
||||
l = 2
|
||||
}
|
||||
hex := make([]byte, l)
|
||||
p.reader.Read(hex)
|
||||
newc := mustParseInt(string(hex), 16, 16)
|
||||
c = rune(newc)
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7': // octal!
|
||||
oct := make([]byte, 3)
|
||||
oct[0] = uint8(c)
|
||||
p.reader.Read(oct[1:])
|
||||
newc := mustParseInt(string(oct), 8, 16)
|
||||
c = rune(newc)
|
||||
}
|
||||
}
|
||||
s += string(c)
|
||||
}
|
||||
return &plistValue{String, s}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseUnquotedString() *plistValue {
|
||||
s := ""
|
||||
for {
|
||||
c, err := p.reader.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
// if we encounter a character that must be quoted, we're done.
|
||||
// the GNUStep quote table is more lax here, so we use it instead of the OpenStep one.
|
||||
if gsQuotable[c/64]&(1<<(c%64)) > 0 {
|
||||
p.reader.UnreadByte()
|
||||
break
|
||||
}
|
||||
s += string(c)
|
||||
}
|
||||
return &plistValue{String, s}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseDictionary() *plistValue {
|
||||
var keypv *plistValue
|
||||
subval := make(map[string]*plistValue)
|
||||
for {
|
||||
p.chugWhitespace()
|
||||
|
||||
c, err := p.reader.ReadByte()
|
||||
// EOF here is an error: we're inside a dictionary!
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if c == '}' {
|
||||
break
|
||||
} else if c == '"' {
|
||||
keypv = p.parseQuotedString()
|
||||
} else {
|
||||
p.reader.UnreadByte() // Whoops, ate part of the string
|
||||
keypv = p.parseUnquotedString()
|
||||
}
|
||||
if keypv == nil {
|
||||
// TODO better error
|
||||
panic(errors.New("missing dictionary key"))
|
||||
}
|
||||
|
||||
p.chugWhitespace()
|
||||
c, err = p.reader.ReadByte()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if c != '=' {
|
||||
panic(errors.New("missing = in dictionary"))
|
||||
}
|
||||
|
||||
// whitespace is guzzled within
|
||||
val := p.parsePlistValue()
|
||||
|
||||
p.chugWhitespace()
|
||||
c, err = p.reader.ReadByte()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if c != ';' {
|
||||
panic(errors.New("missing ; in dictionary"))
|
||||
}
|
||||
|
||||
subval[keypv.value.(string)] = val
|
||||
}
|
||||
return &plistValue{Dictionary, &dictionary{m: subval}}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseArray() *plistValue {
|
||||
subval := make([]*plistValue, 0, 10)
|
||||
for {
|
||||
c, err := p.reader.ReadByte()
|
||||
// EOF here is an error: we're inside an array!
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if c == ')' {
|
||||
break
|
||||
} else if c == ',' {
|
||||
continue
|
||||
}
|
||||
|
||||
p.reader.UnreadByte()
|
||||
pval := p.parsePlistValue()
|
||||
if pval.kind == String && pval.value.(string) == "" {
|
||||
continue
|
||||
}
|
||||
subval = append(subval, pval)
|
||||
}
|
||||
return &plistValue{Array, subval}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseGNUStepValue(v []byte) *plistValue {
|
||||
if len(v) < 3 {
|
||||
panic(errors.New("invalid GNUStep extended value"))
|
||||
}
|
||||
typ := v[1]
|
||||
v = v[2:]
|
||||
switch typ {
|
||||
case 'I':
|
||||
if v[0] == '-' {
|
||||
n := mustParseInt(string(v), 10, 64)
|
||||
return &plistValue{Integer, signedInt{uint64(n), true}}
|
||||
} else {
|
||||
n := mustParseUint(string(v), 10, 64)
|
||||
return &plistValue{Integer, signedInt{n, false}}
|
||||
}
|
||||
case 'R':
|
||||
n := mustParseFloat(string(v), 64)
|
||||
return &plistValue{Real, sizedFloat{n, 64}}
|
||||
case 'B':
|
||||
b := v[0] == 'Y'
|
||||
return &plistValue{Boolean, b}
|
||||
case 'D':
|
||||
t, err := time.Parse(textPlistTimeLayout, string(v))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &plistValue{Date, t.In(time.UTC)}
|
||||
}
|
||||
panic(errors.New("invalid GNUStep type " + string(typ)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parsePlistValue() *plistValue {
|
||||
for {
|
||||
p.chugWhitespace()
|
||||
|
||||
c, err := p.reader.ReadByte()
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
switch c {
|
||||
case '<':
|
||||
bytes, err := p.reader.ReadBytes('>')
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bytes = bytes[:len(bytes)-1]
|
||||
|
||||
if len(bytes) == 0 {
|
||||
panic(errors.New("invalid empty angle-bracketed element"))
|
||||
}
|
||||
|
||||
if bytes[0] == '*' {
|
||||
p.format = GNUStepFormat
|
||||
return p.parseGNUStepValue(bytes)
|
||||
} else {
|
||||
s := p.whitespaceReplacer.Replace(string(bytes))
|
||||
data, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &plistValue{Data, data}
|
||||
}
|
||||
case '"':
|
||||
return p.parseQuotedString()
|
||||
case '{':
|
||||
return p.parseDictionary()
|
||||
case '(':
|
||||
return p.parseArray()
|
||||
default:
|
||||
p.reader.UnreadByte() // Place back in buffer for parseUnquotedString
|
||||
return p.parseUnquotedString()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTextPlistParser(r io.Reader) *textPlistParser {
|
||||
var reader byteReader
|
||||
if rd, ok := r.(byteReader); ok {
|
||||
reader = rd
|
||||
} else {
|
||||
reader = bufio.NewReader(r)
|
||||
}
|
||||
return &textPlistParser{
|
||||
reader: reader,
|
||||
whitespaceReplacer: strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""),
|
||||
format: OpenStepFormat,
|
||||
}
|
||||
}
|
26
vendor/github.com/DHowett/go-plist/text_tables.go
generated
vendored
|
@ -1,26 +0,0 @@
|
|||
package plist
|
||||
|
||||
// Bitmap of characters that must be inside a quoted string
|
||||
// when written to an old-style property list
|
||||
// Low bits represent lower characters, and each uint64 represents 64 characters.
|
||||
var gsQuotable = [4]uint64{
|
||||
0x78001385ffffffff,
|
||||
0xa800000138000000,
|
||||
0xffffffffffffffff,
|
||||
0xffffffffffffffff,
|
||||
}
|
||||
|
||||
// 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it.
|
||||
var osQuotable = [4]uint64{
|
||||
0xf4007f6fffffffff,
|
||||
0xf8000001f8000001,
|
||||
0xffffffffffffffff,
|
||||
0xffffffffffffffff,
|
||||
}
|
||||
|
||||
var whitespace = [4]uint64{
|
||||
0x0000000100003f00,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
}
|
170
vendor/github.com/DHowett/go-plist/typeinfo.go
generated
vendored
|
@ -1,170 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// typeInfo holds details for the plist representation of a type.
|
||||
type typeInfo struct {
|
||||
fields []fieldInfo
|
||||
}
|
||||
|
||||
// fieldInfo holds details for the plist representation of a single field.
|
||||
type fieldInfo struct {
|
||||
idx []int
|
||||
name string
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
var tinfoMap = make(map[reflect.Type]*typeInfo)
|
||||
var tinfoLock sync.RWMutex
|
||||
|
||||
// getTypeInfo returns the typeInfo structure with details necessary
|
||||
// for marshalling and unmarshalling typ.
|
||||
func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
||||
tinfoLock.RLock()
|
||||
tinfo, ok := tinfoMap[typ]
|
||||
tinfoLock.RUnlock()
|
||||
if ok {
|
||||
return tinfo, nil
|
||||
}
|
||||
tinfo = &typeInfo{}
|
||||
if typ.Kind() == reflect.Struct {
|
||||
n := typ.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.PkgPath != "" || f.Tag.Get("plist") == "-" {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
// For embedded structs, embed its fields.
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Struct {
|
||||
inner, err := getTypeInfo(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, finfo := range inner.fields {
|
||||
finfo.idx = append([]int{i}, finfo.idx...)
|
||||
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add the field if it doesn't conflict with other fields.
|
||||
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
tinfoLock.Lock()
|
||||
tinfoMap[typ] = tinfo
|
||||
tinfoLock.Unlock()
|
||||
return tinfo, nil
|
||||
}
|
||||
|
||||
// structFieldInfo builds and returns a fieldInfo for f.
|
||||
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
|
||||
finfo := &fieldInfo{idx: f.Index}
|
||||
|
||||
// Split the tag from the xml namespace if necessary.
|
||||
tag := f.Tag.Get("plist")
|
||||
|
||||
// Parse flags.
|
||||
tokens := strings.Split(tag, ",")
|
||||
tag = tokens[0]
|
||||
if len(tokens) > 1 {
|
||||
tag = tokens[0]
|
||||
for _, flag := range tokens[1:] {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
finfo.omitEmpty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
// If the name part of the tag is completely empty,
|
||||
// use the field name
|
||||
finfo.name = f.Name
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
finfo.name = tag
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
// addFieldInfo adds finfo to tinfo.fields if there are no
|
||||
// conflicts, or if conflicts arise from previous fields that were
|
||||
// obtained from deeper embedded structures than finfo. In the latter
|
||||
// case, the conflicting entries are dropped.
|
||||
// A conflict occurs when the path (parent + name) to a field is
|
||||
// itself a prefix of another path, or when two paths match exactly.
|
||||
// It is okay for field paths to share a common, shorter prefix.
|
||||
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
|
||||
var conflicts []int
|
||||
// First, figure all conflicts. Most working code will have none.
|
||||
for i := range tinfo.fields {
|
||||
oldf := &tinfo.fields[i]
|
||||
if newf.name == oldf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Without conflicts, add the new field and return.
|
||||
if conflicts == nil {
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If any conflict is shallower, ignore the new field.
|
||||
// This matches the Go field resolution on embedding.
|
||||
for _, i := range conflicts {
|
||||
if len(tinfo.fields[i].idx) < len(newf.idx) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the new field is shallower, and thus takes precedence,
|
||||
// so drop the conflicting fields from tinfo and append the new one.
|
||||
for c := len(conflicts) - 1; c >= 0; c-- {
|
||||
i := conflicts[c]
|
||||
copy(tinfo.fields[i:], tinfo.fields[i+1:])
|
||||
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
|
||||
}
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// value returns v's field value corresponding to finfo.
|
||||
// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
|
||||
// and dereferences pointers as necessary.
|
||||
func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
|
||||
for i, x := range finfo.idx {
|
||||
if i > 0 {
|
||||
t := v.Type()
|
||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
v = v.Field(x)
|
||||
}
|
||||
return v
|
||||
}
|
276
vendor/github.com/DHowett/go-plist/unmarshal.go
generated
vendored
|
@ -1,276 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type incompatibleDecodeTypeError struct {
|
||||
typ reflect.Type
|
||||
pKind plistKind
|
||||
}
|
||||
|
||||
func (u *incompatibleDecodeTypeError) Error() string {
|
||||
return fmt.Sprintf("plist: type mismatch: tried to decode %v into value of type %v", plistKindNames[u.pKind], u.typ)
|
||||
}
|
||||
|
||||
var (
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
func isEmptyInterface(v reflect.Value) bool {
|
||||
return v.Kind() == reflect.Interface && v.NumMethod() == 0
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalTextInterface(pval *plistValue, unmarshalable encoding.TextUnmarshaler) {
|
||||
err := unmarshalable.UnmarshalText([]byte(pval.value.(string)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalTime(pval *plistValue, val reflect.Value) {
|
||||
val.Set(reflect.ValueOf(pval.value.(time.Time)))
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) {
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
i := mustParseInt(s, 10, 64)
|
||||
val.SetInt(i)
|
||||
return
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
i := mustParseUint(s, 10, 64)
|
||||
val.SetUint(i)
|
||||
return
|
||||
case reflect.Float32, reflect.Float64:
|
||||
f := mustParseFloat(s, 64)
|
||||
val.SetFloat(f)
|
||||
return
|
||||
case reflect.Bool:
|
||||
b := mustParseBool(s)
|
||||
val.SetBool(b)
|
||||
return
|
||||
case reflect.Struct:
|
||||
if val.Type() == timeType {
|
||||
t, err := time.Parse(textPlistTimeLayout, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
val.Set(reflect.ValueOf(t.In(time.UTC)))
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
panic(&incompatibleDecodeTypeError{val.Type(), String})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshal(pval *plistValue, val reflect.Value) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
if isEmptyInterface(val) {
|
||||
v := p.valueInterface(pval)
|
||||
val.Set(reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
|
||||
incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.kind}
|
||||
|
||||
// time.Time implements TextMarshaler, but we need to parse it as RFC3339
|
||||
if pval.kind == Date {
|
||||
if val.Type() == timeType {
|
||||
p.unmarshalTime(pval, val)
|
||||
return
|
||||
}
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
|
||||
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) && val.Type() != timeType {
|
||||
p.unmarshalTextInterface(pval, val.Interface().(encoding.TextUnmarshaler))
|
||||
return
|
||||
}
|
||||
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) && val.Type() != timeType {
|
||||
p.unmarshalTextInterface(pval, pv.Interface().(encoding.TextUnmarshaler))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
switch pval.kind {
|
||||
case String:
|
||||
if val.Kind() == reflect.String {
|
||||
val.SetString(pval.value.(string))
|
||||
return
|
||||
}
|
||||
if p.lax {
|
||||
p.unmarshalLaxString(pval.value.(string), val)
|
||||
return
|
||||
}
|
||||
|
||||
panic(incompatibleTypeError)
|
||||
case Integer:
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val.SetInt(int64(pval.value.(signedInt).value))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
val.SetUint(pval.value.(signedInt).value)
|
||||
default:
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case Real:
|
||||
if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 {
|
||||
val.SetFloat(pval.value.(sizedFloat).value)
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case Boolean:
|
||||
if val.Kind() == reflect.Bool {
|
||||
val.SetBool(pval.value.(bool))
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case Data:
|
||||
if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 {
|
||||
val.SetBytes(pval.value.([]byte))
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case Array:
|
||||
p.unmarshalArray(pval, val)
|
||||
case Dictionary:
|
||||
p.unmarshalDictionary(pval, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalArray(pval *plistValue, val reflect.Value) {
|
||||
subvalues := pval.value.([]*plistValue)
|
||||
|
||||
var n int
|
||||
if val.Kind() == reflect.Slice {
|
||||
// Slice of element values.
|
||||
// Grow slice.
|
||||
cnt := len(subvalues) + val.Len()
|
||||
if cnt >= val.Cap() {
|
||||
ncap := 2 * cnt
|
||||
if ncap < 4 {
|
||||
ncap = 4
|
||||
}
|
||||
new := reflect.MakeSlice(val.Type(), val.Len(), ncap)
|
||||
reflect.Copy(new, val)
|
||||
val.Set(new)
|
||||
}
|
||||
n = val.Len()
|
||||
val.SetLen(cnt)
|
||||
} else if val.Kind() == reflect.Array {
|
||||
if len(subvalues) > val.Cap() {
|
||||
panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(subvalues), val.Cap()))
|
||||
}
|
||||
} else {
|
||||
panic(&incompatibleDecodeTypeError{val.Type(), pval.kind})
|
||||
}
|
||||
|
||||
// Recur to read element into slice.
|
||||
for _, sval := range subvalues {
|
||||
p.unmarshal(sval, val.Index(n))
|
||||
n++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalDictionary(pval *plistValue, val reflect.Value) {
|
||||
typ := val.Type()
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
tinfo, err := getTypeInfo(typ)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
subvalues := pval.value.(*dictionary).m
|
||||
for _, finfo := range tinfo.fields {
|
||||
p.unmarshal(subvalues[finfo.name], finfo.value(val))
|
||||
}
|
||||
case reflect.Map:
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.MakeMap(typ))
|
||||
}
|
||||
|
||||
subvalues := pval.value.(*dictionary).m
|
||||
for k, sval := range subvalues {
|
||||
keyv := reflect.ValueOf(k).Convert(typ.Key())
|
||||
mapElem := val.MapIndex(keyv)
|
||||
if !mapElem.IsValid() {
|
||||
mapElem = reflect.New(typ.Elem()).Elem()
|
||||
}
|
||||
|
||||
p.unmarshal(sval, mapElem)
|
||||
val.SetMapIndex(keyv, mapElem)
|
||||
}
|
||||
default:
|
||||
panic(&incompatibleDecodeTypeError{typ, pval.kind})
|
||||
}
|
||||
}
|
||||
|
||||
/* *Interface is modelled after encoding/json */
|
||||
func (p *Decoder) valueInterface(pval *plistValue) interface{} {
|
||||
switch pval.kind {
|
||||
case String:
|
||||
return pval.value.(string)
|
||||
case Integer:
|
||||
if pval.value.(signedInt).signed {
|
||||
return int64(pval.value.(signedInt).value)
|
||||
}
|
||||
return pval.value.(signedInt).value
|
||||
case Real:
|
||||
bits := pval.value.(sizedFloat).bits
|
||||
switch bits {
|
||||
case 32:
|
||||
return float32(pval.value.(sizedFloat).value)
|
||||
case 64:
|
||||
return pval.value.(sizedFloat).value
|
||||
}
|
||||
case Boolean:
|
||||
return pval.value.(bool)
|
||||
case Array:
|
||||
return p.arrayInterface(pval.value.([]*plistValue))
|
||||
case Dictionary:
|
||||
return p.dictionaryInterface(pval.value.(*dictionary))
|
||||
case Data:
|
||||
return pval.value.([]byte)
|
||||
case Date:
|
||||
return pval.value.(time.Time)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Decoder) arrayInterface(subvalues []*plistValue) []interface{} {
|
||||
out := make([]interface{}, len(subvalues))
|
||||
for i, subv := range subvalues {
|
||||
out[i] = p.valueInterface(subv)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Decoder) dictionaryInterface(dict *dictionary) map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
for k, subv := range dict.m {
|
||||
out[k] = p.valueInterface(subv)
|
||||
}
|
||||
return out
|
||||
}
|
25
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
|
@ -1,25 +0,0 @@
|
|||
package plist
|
||||
|
||||
import "io"
|
||||
|
||||
type countedWriter struct {
|
||||
io.Writer
|
||||
nbytes int
|
||||
}
|
||||
|
||||
func (w *countedWriter) Write(p []byte) (int, error) {
|
||||
n, err := w.Writer.Write(p)
|
||||
w.nbytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *countedWriter) BytesWritten() int {
|
||||
return w.nbytes
|
||||
}
|
||||
|
||||
func unsignedGetBase(s string) (string, int) {
|
||||
if len(s) > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
|
||||
return s[2:], 16
|
||||
}
|
||||
return s, 10
|
||||
}
|
320
vendor/github.com/DHowett/go-plist/xml.go
generated
vendored
|
@ -1,320 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
`
|
||||
|
||||
type xmlPlistGenerator struct {
|
||||
writer io.Writer
|
||||
xmlEncoder *xml.Encoder
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) generateDocument(pval *plistValue) {
|
||||
io.WriteString(p.writer, xml.Header)
|
||||
io.WriteString(p.writer, xmlDOCTYPE)
|
||||
|
||||
plistStartElement := xml.StartElement{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "plist",
|
||||
},
|
||||
Attr: []xml.Attr{{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "version"},
|
||||
Value: "1.0"},
|
||||
},
|
||||
}
|
||||
|
||||
p.xmlEncoder.EncodeToken(plistStartElement)
|
||||
|
||||
p.writePlistValue(pval)
|
||||
|
||||
p.xmlEncoder.EncodeToken(plistStartElement.End())
|
||||
p.xmlEncoder.Flush()
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) writePlistValue(pval *plistValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer p.xmlEncoder.Flush()
|
||||
|
||||
key := ""
|
||||
encodedValue := pval.value
|
||||
switch pval.kind {
|
||||
case Dictionary:
|
||||
startElement := xml.StartElement{Name: xml.Name{Local: "dict"}}
|
||||
p.xmlEncoder.EncodeToken(startElement)
|
||||
dict := encodedValue.(*dictionary)
|
||||
dict.populateArrays()
|
||||
for i, k := range dict.keys {
|
||||
p.xmlEncoder.EncodeElement(k, xml.StartElement{Name: xml.Name{Local: "key"}})
|
||||
p.writePlistValue(dict.values[i])
|
||||
}
|
||||
p.xmlEncoder.EncodeToken(startElement.End())
|
||||
case Array:
|
||||
startElement := xml.StartElement{Name: xml.Name{Local: "array"}}
|
||||
p.xmlEncoder.EncodeToken(startElement)
|
||||
values := encodedValue.([]*plistValue)
|
||||
for _, v := range values {
|
||||
p.writePlistValue(v)
|
||||
}
|
||||
p.xmlEncoder.EncodeToken(startElement.End())
|
||||
case String:
|
||||
key = "string"
|
||||
case Integer:
|
||||
key = "integer"
|
||||
if pval.value.(signedInt).signed {
|
||||
encodedValue = int64(pval.value.(signedInt).value)
|
||||
} else {
|
||||
encodedValue = pval.value.(signedInt).value
|
||||
}
|
||||
case Real:
|
||||
key = "real"
|
||||
encodedValue = pval.value.(sizedFloat).value
|
||||
switch {
|
||||
case math.IsInf(pval.value.(sizedFloat).value, 1):
|
||||
encodedValue = "inf"
|
||||
case math.IsInf(pval.value.(sizedFloat).value, -1):
|
||||
encodedValue = "-inf"
|
||||
case math.IsNaN(pval.value.(sizedFloat).value):
|
||||
encodedValue = "nan"
|
||||
}
|
||||
case Boolean:
|
||||
key = "false"
|
||||
b := pval.value.(bool)
|
||||
if b {
|
||||
key = "true"
|
||||
}
|
||||
encodedValue = ""
|
||||
case Data:
|
||||
key = "data"
|
||||
encodedValue = xml.CharData(base64.StdEncoding.EncodeToString(pval.value.([]byte)))
|
||||
case Date:
|
||||
key = "date"
|
||||
encodedValue = pval.value.(time.Time).In(time.UTC).Format(time.RFC3339)
|
||||
}
|
||||
if key != "" {
|
||||
err := p.xmlEncoder.EncodeElement(encodedValue, xml.StartElement{Name: xml.Name{Local: key}})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) Indent(i string) {
|
||||
p.xmlEncoder.Indent("", i)
|
||||
}
|
||||
|
||||
func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
|
||||
mw := mustWriter{w}
|
||||
return &xmlPlistGenerator{mw, xml.NewEncoder(mw)}
|
||||
}
|
||||
|
||||
type xmlPlistParser struct {
|
||||
reader io.Reader
|
||||
xmlDecoder *xml.Decoder
|
||||
whitespaceReplacer *strings.Replacer
|
||||
ntags int
|
||||
}
|
||||
|
||||
func (p *xmlPlistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
if _, ok := r.(invalidPlistError); ok {
|
||||
parseError = r.(error)
|
||||
} else {
|
||||
// Wrap all non-invalid-plist errors.
|
||||
parseError = plistParseError{"XML", r.(error)}
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if token, err := p.xmlDecoder.Token(); err == nil {
|
||||
if element, ok := token.(xml.StartElement); ok {
|
||||
pval = p.parseXMLElement(element)
|
||||
if p.ntags == 0 {
|
||||
panic(invalidPlistError{"XML", errors.New("no elements encountered")})
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// The first XML parse turned out to be invalid:
|
||||
// we do not have an XML property list.
|
||||
panic(invalidPlistError{"XML", err})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue {
|
||||
var charData xml.CharData
|
||||
switch element.Name.Local {
|
||||
case "plist":
|
||||
p.ntags++
|
||||
for {
|
||||
token, err := p.xmlDecoder.Token()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.EndElement); ok && el.Name.Local == "plist" {
|
||||
break
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.StartElement); ok {
|
||||
return p.parseXMLElement(el)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case "string":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &plistValue{String, string(charData)}
|
||||
case "integer":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s := string(charData)
|
||||
if len(s) == 0 {
|
||||
panic(errors.New("invalid empty <integer/>"))
|
||||
}
|
||||
|
||||
if s[0] == '-' {
|
||||
s, base := unsignedGetBase(s[1:])
|
||||
n := mustParseInt("-"+s, base, 64)
|
||||
return &plistValue{Integer, signedInt{uint64(n), true}}
|
||||
} else {
|
||||
s, base := unsignedGetBase(s)
|
||||
n := mustParseUint(s, base, 64)
|
||||
return &plistValue{Integer, signedInt{n, false}}
|
||||
}
|
||||
case "real":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
n := mustParseFloat(string(charData), 64)
|
||||
return &plistValue{Real, sizedFloat{n, 64}}
|
||||
case "true", "false":
|
||||
p.ntags++
|
||||
p.xmlDecoder.Skip()
|
||||
|
||||
b := element.Name.Local == "true"
|
||||
return &plistValue{Boolean, b}
|
||||
case "date":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
t, err := time.ParseInLocation(time.RFC3339, string(charData), time.UTC)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &plistValue{Date, t}
|
||||
case "data":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
str := p.whitespaceReplacer.Replace(string(charData))
|
||||
|
||||
l := base64.StdEncoding.DecodedLen(len(str))
|
||||
bytes := make([]uint8, l)
|
||||
l, err = base64.StdEncoding.Decode(bytes, []byte(str))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &plistValue{Data, bytes[:l]}
|
||||
case "dict":
|
||||
p.ntags++
|
||||
var key *string
|
||||
var subvalues map[string]*plistValue = make(map[string]*plistValue)
|
||||
for {
|
||||
token, err := p.xmlDecoder.Token()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.EndElement); ok && el.Name.Local == "dict" {
|
||||
if key != nil {
|
||||
panic(errors.New("missing value in dictionary"))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.StartElement); ok {
|
||||
if el.Name.Local == "key" {
|
||||
var k string
|
||||
p.xmlDecoder.DecodeElement(&k, &el)
|
||||
key = &k
|
||||
} else {
|
||||
if key == nil {
|
||||
panic(errors.New("missing key in dictionary"))
|
||||
}
|
||||
subvalues[*key] = p.parseXMLElement(el)
|
||||
key = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return &plistValue{Dictionary, &dictionary{m: subvalues}}
|
||||
case "array":
|
||||
p.ntags++
|
||||
var subvalues []*plistValue = make([]*plistValue, 0, 10)
|
||||
for {
|
||||
token, err := p.xmlDecoder.Token()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.EndElement); ok && el.Name.Local == "array" {
|
||||
break
|
||||
}
|
||||
|
||||
if el, ok := token.(xml.StartElement); ok {
|
||||
subvalues = append(subvalues, p.parseXMLElement(el))
|
||||
}
|
||||
}
|
||||
return &plistValue{Array, subvalues}
|
||||
}
|
||||
err := fmt.Errorf("encountered unknown element %s", element.Name.Local)
|
||||
if p.ntags == 0 {
|
||||
// If out first XML tag is invalid, it might be an openstep data element, ala <abab> or <0101>
|
||||
panic(invalidPlistError{"XML", err})
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func newXMLPlistParser(r io.Reader) *xmlPlistParser {
|
||||
return &xmlPlistParser{r, xml.NewDecoder(r), strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), 0}
|
||||
}
|
4
vendor/github.com/alecthomas/kingpin/.travis.yml
generated
vendored
|
@ -1,4 +0,0 @@
|
|||
sudo: false
|
||||
language: go
|
||||
install: go get -t -v ./...
|
||||
go: 1.2
|
19
vendor/github.com/alecthomas/kingpin/COPYING
generated
vendored
|
@ -1,19 +0,0 @@
|
|||
Copyright (C) 2014 Alec Thomas
|
||||
|
||||
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.
|
671
vendor/github.com/alecthomas/kingpin/README.md
generated
vendored
|
@ -1,671 +0,0 @@
|
|||
# Kingpin - A Go (golang) command line and flag parser [![](https://godoc.org/github.com/alecthomas/kingpin?status.svg)](http://godoc.org/github.com/alecthomas/kingpin) [![Build Status](https://travis-ci.org/alecthomas/kingpin.png)](https://travis-ci.org/alecthomas/kingpin)
|
||||
|
||||
<!-- MarkdownTOC -->
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [User-visible changes between v1 and v2](#user-visible-changes-between-v1-and-v2)
|
||||
- [Flags can be used at any point after their definition.](#flags-can-be-used-at-any-point-after-their-definition)
|
||||
- [Short flags can be combined with their parameters](#short-flags-can-be-combined-with-their-parameters)
|
||||
- [API changes between v1 and v2](#api-changes-between-v1-and-v2)
|
||||
- [Versions](#versions)
|
||||
- [V2 is the current stable version](#v2-is-the-current-stable-version)
|
||||
- [V1 is the OLD stable version](#v1-is-the-old-stable-version)
|
||||
- [Change History](#change-history)
|
||||
- [Examples](#examples)
|
||||
- [Simple Example](#simple-example)
|
||||
- [Complex Example](#complex-example)
|
||||
- [Reference Documentation](#reference-documentation)
|
||||
- [Displaying errors and usage information](#displaying-errors-and-usage-information)
|
||||
- [Sub-commands](#sub-commands)
|
||||
- [Custom Parsers](#custom-parsers)
|
||||
- [Repeatable flags](#repeatable-flags)
|
||||
- [Boolean Values](#boolean-values)
|
||||
- [Default Values](#default-values)
|
||||
- [Place-holders in Help](#place-holders-in-help)
|
||||
- [Consuming all remaining arguments](#consuming-all-remaining-arguments)
|
||||
- [Bash/ZSH Shell Completion](#bashzsh-shell-completion)
|
||||
- [Supporting -h for help](#supporting--h-for-help)
|
||||
- [Custom help](#custom-help)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
## Overview
|
||||
|
||||
Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface),
|
||||
type-safe command-line parser. It supports flags, nested commands, and
|
||||
positional arguments.
|
||||
|
||||
Install it with:
|
||||
|
||||
$ go get gopkg.in/alecthomas/kingpin.v2
|
||||
|
||||
It looks like this:
|
||||
|
||||
```go
|
||||
var (
|
||||
verbose = kingpin.Flag("verbose", "Verbose mode.").Short('v').Bool()
|
||||
name = kingpin.Arg("name", "Name of user.").Required().String()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Parse()
|
||||
fmt.Printf("%v, %s\n", *verbose, *name)
|
||||
}
|
||||
```
|
||||
|
||||
More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available.
|
||||
|
||||
Second to parsing, providing the user with useful help is probably the most
|
||||
important thing a command-line parser does. Kingpin tries to provide detailed
|
||||
contextual help if `--help` is encountered at any point in the command line
|
||||
(excluding after `--`).
|
||||
|
||||
## Features
|
||||
|
||||
- Help output that isn't as ugly as sin.
|
||||
- Fully [customisable help](#custom-help), via Go templates.
|
||||
- Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`)
|
||||
- Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`).
|
||||
- Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`).
|
||||
- Support for required flags and required positional arguments (`kingpin.Flag("f", "").Required().Int()`).
|
||||
- Support for arbitrarily nested default commands (`command.Default()`).
|
||||
- Callbacks per command, flag and argument (`kingpin.Command("c", "").Action(myAction)`).
|
||||
- POSIX-style short flag combining (`-a -b` -> `-ab`).
|
||||
- Short-flag+parameter combining (`-a parm` -> `-aparm`).
|
||||
- Read command-line from files (`@<file>`).
|
||||
- Automatically generate man pages (`--help-man`).
|
||||
|
||||
## User-visible changes between v1 and v2
|
||||
|
||||
### Flags can be used at any point after their definition.
|
||||
|
||||
Flags can be specified at any point after their definition, not just
|
||||
*immediately after their associated command*. From the chat example below, the
|
||||
following used to be required:
|
||||
|
||||
```
|
||||
$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
But the following will now work:
|
||||
|
||||
```
|
||||
$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
### Short flags can be combined with their parameters
|
||||
|
||||
Previously, if a short flag was used, any argument to that flag would have to
|
||||
be separated by a space. That is no longer the case.
|
||||
|
||||
## API changes between v1 and v2
|
||||
|
||||
- `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@<file>`.
|
||||
- Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating.
|
||||
- `Dispatch()` renamed to `Action()`.
|
||||
- Added `ParseContext()` for parsing a command line into its intermediate context form without executing.
|
||||
- Added `Terminate()` function to override the termination function.
|
||||
- Added `UsageForContextWithTemplate()` for printing usage via a custom template.
|
||||
- Added `UsageTemplate()` for overriding the default template to use. Two templates are included:
|
||||
1. `DefaultUsageTemplate` - default template.
|
||||
2. `CompactUsageTemplate` - compact command template for larger applications.
|
||||
|
||||
## Versions
|
||||
|
||||
Kingpin uses [gopkg.in](https://gopkg.in/alecthomas/kingpin) for versioning.
|
||||
|
||||
The current stable version is [gopkg.in/alecthomas/kingpin.v2](https://gopkg.in/alecthomas/kingpin.v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode.
|
||||
|
||||
### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/alecthomas/kingpin.v2
|
||||
```
|
||||
|
||||
### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/alecthomas/kingpin.v1
|
||||
```
|
||||
|
||||
## Change History
|
||||
|
||||
- *2015-09-19* -- Stable v2.1.0 release.
|
||||
- Added `command.Default()` to specify a default command to use if no other
|
||||
command matches. This allows for convenient user shortcuts.
|
||||
- Exposed `HelpFlag` and `VersionFlag` for further customisation.
|
||||
- `Action()` and `PreAction()` added and both now support an arbitrary
|
||||
number of callbacks.
|
||||
- `kingpin.SeparateOptionalFlagsUsageTemplate`.
|
||||
- `--help-long` and `--help-man` (hidden by default) flags.
|
||||
- Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`.
|
||||
- Added flags for all simple builtin types (int8, uint16, etc.) and slice variants.
|
||||
- Use `app.Writer(os.Writer)` to specify the default writer for all output functions.
|
||||
- Dropped `os.Writer` prefix from all printf-like functions.
|
||||
|
||||
- *2015-05-22* -- Stable v2.0.0 release.
|
||||
- Initial stable release of v2.0.0.
|
||||
- Fully supports interspersed flags, commands and arguments.
|
||||
- Flags can be present at any point after their logical definition.
|
||||
- Application.Parse() terminates if commands are present and a command is not parsed.
|
||||
- Dispatch() -> Action().
|
||||
- Actions are dispatched after all values are populated.
|
||||
- Override termination function (defaults to os.Exit).
|
||||
- Override output stream (defaults to os.Stderr).
|
||||
- Templatised usage help, with default and compact templates.
|
||||
- Make error/usage functions more consistent.
|
||||
- Support argument expansion from files by default (with @<file>).
|
||||
- Fully public data model is available via .Model().
|
||||
- Parser has been completely refactored.
|
||||
- Parsing and execution has been split into distinct stages.
|
||||
- Use `go generate` to generate repeated flags.
|
||||
- Support combined short-flag+argument: -fARG.
|
||||
|
||||
- *2015-01-23* -- Stable v1.3.4 release.
|
||||
- Support "--" for separating flags from positional arguments.
|
||||
- Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument.
|
||||
- Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added.
|
||||
- A bunch of improvements to help usage and formatting.
|
||||
- Support arbitrarily nested sub-commands.
|
||||
|
||||
- *2014-07-08* -- Stable v1.2.0 release.
|
||||
- Pass any value through to `Strings()` when final argument.
|
||||
Allows for values that look like flags to be processed.
|
||||
- Allow `--help` to be used with commands.
|
||||
- Support `Hidden()` flags.
|
||||
- Parser for [units.Base2Bytes](https://github.com/alecthomas/units)
|
||||
type. Allows for flags like `--ram=512MB` or `--ram=1GB`.
|
||||
- Add an `Enum()` value, allowing only one of a set of values
|
||||
to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`.
|
||||
|
||||
- *2014-06-27* -- Stable v1.1.0 release.
|
||||
- Bug fixes.
|
||||
- Always return an error (rather than panicing) when misconfigured.
|
||||
- `OpenFile(flag, perm)` value type added, for finer control over opening files.
|
||||
- Significantly improved usage formatting.
|
||||
|
||||
- *2014-06-19* -- Stable v1.0.0 release.
|
||||
- Support [cumulative positional](#consuming-all-remaining-arguments) arguments.
|
||||
- Return error rather than panic when there are fatal errors not caught by
|
||||
the type system. eg. when a default value is invalid.
|
||||
- Use gokpg.in.
|
||||
|
||||
- *2014-06-10* -- Place-holder streamlining.
|
||||
- Renamed `MetaVar` to `PlaceHolder`.
|
||||
- Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help)
|
||||
to determine what to display.
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Example
|
||||
|
||||
Kingpin can be used for simple flag+arg applications like so:
|
||||
|
||||
```
|
||||
$ ping --help
|
||||
usage: ping [<flags>] <ip> [<count>]
|
||||
|
||||
Flags:
|
||||
--debug Enable debug mode.
|
||||
--help Show help.
|
||||
-t, --timeout=5s Timeout waiting for ping.
|
||||
|
||||
Args:
|
||||
<ip> IP address to ping.
|
||||
[<count>] Number of packets to send
|
||||
$ ping 1.2.3.4 5
|
||||
Would ping: 1.2.3.4 with timeout 5s and count 0
|
||||
```
|
||||
|
||||
From the following source:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = kingpin.Flag("debug", "Enable debug mode.").Bool()
|
||||
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration()
|
||||
ip = kingpin.Arg("ip", "IP address to ping.").Required().IP()
|
||||
count = kingpin.Arg("count", "Number of packets to send").Int()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Version("0.0.1")
|
||||
kingpin.Parse()
|
||||
fmt.Printf("Would ping: %s with timeout %s and count %d", *ip, *timeout, *count)
|
||||
}
|
||||
```
|
||||
|
||||
### Complex Example
|
||||
|
||||
Kingpin can also produce complex command-line applications with global flags,
|
||||
subcommands, and per-subcommand flags, like this:
|
||||
|
||||
```
|
||||
$ chat --help
|
||||
usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
|
||||
A command-line chat application.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
--debug Enable debug mode.
|
||||
--server=127.0.0.1 Server address.
|
||||
|
||||
Commands:
|
||||
help [<command>]
|
||||
Show help for a command.
|
||||
|
||||
register <nick> <name>
|
||||
Register a new user.
|
||||
|
||||
post [<flags>] <channel> [<text>]
|
||||
Post a message to a channel.
|
||||
|
||||
$ chat help post
|
||||
usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
|
||||
Post a message to a channel.
|
||||
|
||||
Flags:
|
||||
--image=IMAGE Image to post.
|
||||
|
||||
Args:
|
||||
<channel> Channel to post to.
|
||||
[<text>] Text to post.
|
||||
|
||||
$ chat post --image=~/Downloads/owls.jpg pics
|
||||
...
|
||||
```
|
||||
|
||||
From this code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
app = kingpin.New("chat", "A command-line chat application.")
|
||||
debug = app.Flag("debug", "Enable debug mode.").Bool()
|
||||
serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP()
|
||||
|
||||
register = app.Command("register", "Register a new user.")
|
||||
registerNick = register.Arg("nick", "Nickname for user.").Required().String()
|
||||
registerName = register.Arg("name", "Name of user.").Required().String()
|
||||
|
||||
post = app.Command("post", "Post a message to a channel.")
|
||||
postImage = post.Flag("image", "Image to post.").File()
|
||||
postChannel = post.Arg("channel", "Channel to post to.").Required().String()
|
||||
postText = post.Arg("text", "Text to post.").Strings()
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
// Register user
|
||||
case register.FullCommand():
|
||||
println(*registerNick)
|
||||
|
||||
// Post message
|
||||
case post.FullCommand():
|
||||
if *postImage != nil {
|
||||
}
|
||||
text := strings.Join(*postText, " ")
|
||||
println("Post:", text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
### Displaying errors and usage information
|
||||
|
||||
Kingpin exports a set of functions to provide consistent errors and usage
|
||||
information to the user.
|
||||
|
||||
Error messages look something like this:
|
||||
|
||||
<app>: error: <message>
|
||||
|
||||
The functions on `Application` are:
|
||||
|
||||
Function | Purpose
|
||||
---------|--------------
|
||||
`Errorf(format, args)` | Display a printf formatted error to the user.
|
||||
`Fatalf(format, args)` | As with Errorf, but also call the termination handler.
|
||||
`FatalUsage(format, args)` | As with Fatalf, but also print contextual usage information.
|
||||
`FatalUsageContext(context, format, args)` | As with Fatalf, but also print contextual usage information from a `ParseContext`.
|
||||
`FatalIfError(err, format, args)` | Conditionally print an error prefixed with format+args, then call the termination handler
|
||||
|
||||
There are equivalent global functions in the kingpin namespace for the default
|
||||
`kingpin.CommandLine` instance.
|
||||
|
||||
### Sub-commands
|
||||
|
||||
Kingpin supports nested sub-commands, with separate flag and positional
|
||||
arguments per sub-command. Note that positional arguments may only occur after
|
||||
sub-commands.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
var (
|
||||
deleteCommand = kingpin.Command("delete", "Delete an object.")
|
||||
deleteUserCommand = deleteCommand.Command("user", "Delete a user.")
|
||||
deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.")
|
||||
deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.")
|
||||
deletePostCommand = deleteCommand.Command("post", "Delete a post.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.Parse() {
|
||||
case "delete user":
|
||||
case "delete post":
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Parsers
|
||||
|
||||
Kingpin supports both flag and positional argument parsers for converting to
|
||||
Go types. For example, some included parsers are `Int()`, `Float()`,
|
||||
`Duration()` and `ExistingFile()`.
|
||||
|
||||
Parsers conform to Go's [`flag.Value`](http://godoc.org/flag#Value)
|
||||
interface, so any existing implementations will work.
|
||||
|
||||
For example, a parser for accumulating HTTP header values might look like this:
|
||||
|
||||
```go
|
||||
type HTTPHeaderValue http.Header
|
||||
|
||||
func (h *HTTPHeaderValue) Set(value string) error {
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected HEADER:VALUE got '%s'", value)
|
||||
}
|
||||
(*http.Header)(h).Add(parts[0], parts[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTPHeaderValue) String() string {
|
||||
return ""
|
||||
}
|
||||
```
|
||||
|
||||
As a convenience, I would recommend something like this:
|
||||
|
||||
```go
|
||||
func HTTPHeader(s Settings) (target *http.Header) {
|
||||
target = new(http.Header)
|
||||
s.SetValue((*HTTPHeaderValue)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
You would use it like so:
|
||||
|
||||
```go
|
||||
headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H'))
|
||||
```
|
||||
|
||||
### Repeatable flags
|
||||
|
||||
Depending on the `Value` they hold, some flags may be repeated. The
|
||||
`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()`
|
||||
multiple times or if an error should be raised if several values are passed.
|
||||
|
||||
The built-in `Value`s returning slices and maps, as well as `Counter` are
|
||||
examples of `Value`s that make a flag repeatable.
|
||||
|
||||
### Boolean values
|
||||
|
||||
Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement:
|
||||
`--<name>` and `--no-<name>`.
|
||||
|
||||
### Default Values
|
||||
|
||||
The default value is the zero value for a type. This can be overridden with
|
||||
the `Default(value...)` function on flags and arguments. This function accepts
|
||||
one or several strings, which are parsed by the value itself, so they *must*
|
||||
be compliant with the format expected.
|
||||
|
||||
### Place-holders in Help
|
||||
|
||||
The place-holder value for a flag is the value used in the help to describe
|
||||
the value of a non-boolean flag.
|
||||
|
||||
The value provided to PlaceHolder() is used if provided, then the value
|
||||
provided by Default() if provided, then finally the capitalised flag name is
|
||||
used.
|
||||
|
||||
Here are some examples of flags with various permutations:
|
||||
|
||||
--name=NAME // Flag(...).String()
|
||||
--name="Harry" // Flag(...).Default("Harry").String()
|
||||
--name=FULL-NAME // flag(...).PlaceHolder("FULL-NAME").Default("Harry").String()
|
||||
|
||||
### Consuming all remaining arguments
|
||||
|
||||
A common command-line idiom is to use all remaining arguments for some
|
||||
purpose. eg. The following command accepts an arbitrary number of
|
||||
IP addresses as positional arguments:
|
||||
|
||||
./cmd ping 10.1.1.1 192.168.1.1
|
||||
|
||||
Such arguments are similar to [repeatable flags](#repeatable-flags), but for
|
||||
arguments. Therefore they use the same `IsCumulative() bool` function on the
|
||||
underlying `Value`, so the built-in `Value`s for which the `Set()` function
|
||||
can be called several times will consume multiple arguments.
|
||||
|
||||
To implement the above example with a custom `Value`, we might do something
|
||||
like this:
|
||||
|
||||
```go
|
||||
type ipList []net.IP
|
||||
|
||||
func (i *ipList) Set(value string) error {
|
||||
if ip := net.ParseIP(value); ip == nil {
|
||||
return fmt.Errorf("'%s' is not an IP address", value)
|
||||
} else {
|
||||
*i = append(*i, ip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipList) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *ipList) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IPList(s Settings) (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
s.SetValue((*ipList)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
And use it like so:
|
||||
|
||||
```go
|
||||
ips := IPList(kingpin.Arg("ips", "IP addresses to ping."))
|
||||
```
|
||||
|
||||
### Bash/ZSH Shell Completion
|
||||
|
||||
By default, all flags and commands/subcommands generate completions
|
||||
internally.
|
||||
|
||||
Out of the box, CLI tools using kingpin should be able to take advantage
|
||||
of completion hinting for flags and commands. By specifying
|
||||
`--completion-bash` as the first argument, your CLI tool will show
|
||||
possible subcommands. By ending your argv with `--`, hints for flags
|
||||
will be shown.
|
||||
|
||||
To allow your end users to take advantage you must package a
|
||||
`/etc/bash_completion.d` script with your distribution (or the equivalent
|
||||
for your target platform/shell). An alternative is to instruct your end
|
||||
user to source a script from their `bash_profile` (or equivalent).
|
||||
|
||||
Fortunately Kingpin makes it easy to generate or source a script for use
|
||||
with end users shells. `./yourtool --completion-script-bash` and
|
||||
`./yourtool --completion-script-zsh` will generate these scripts for you.
|
||||
|
||||
**Installation by Package**
|
||||
|
||||
For the best user experience, you should bundle your pre-created
|
||||
completion script with your CLI tool and install it inside
|
||||
`/etc/bash_completion.d` (or equivalent). A good suggestion is to add
|
||||
this as an automated step to your build pipeline, in the implementation
|
||||
is improved for bug fixed.
|
||||
|
||||
**Installation by `bash_profile`**
|
||||
|
||||
Alternatively, instruct your users to add an additional statement to
|
||||
their `bash_profile` (or equivalent):
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-bash)"
|
||||
```
|
||||
|
||||
Or for ZSH
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-zsh)"
|
||||
```
|
||||
|
||||
#### Additional API
|
||||
To provide more flexibility, a completion option API has been
|
||||
exposed for flags to allow user defined completion options, to extend
|
||||
completions further than just EnumVar/Enum.
|
||||
|
||||
|
||||
**Provide Static Options**
|
||||
|
||||
When using an `Enum` or `EnumVar`, users are limited to only the options
|
||||
given. Maybe we wish to hint possible options to the user, but also
|
||||
allow them to provide their own custom option. `HintOptions` gives
|
||||
this functionality to flags.
|
||||
|
||||
```
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("port", "Provide a port to connect to").
|
||||
Required().
|
||||
HintOptions("80", "443", "8080").
|
||||
IntVar(&c.port)
|
||||
```
|
||||
|
||||
**Provide Dynamic Options**
|
||||
Consider the case that you needed to read a local database or a file to
|
||||
provide suggestions. You can dynamically generate the options
|
||||
|
||||
```
|
||||
func listHosts(args []string) []string {
|
||||
// Provide a dynamic list of hosts from a hosts file or otherwise
|
||||
// for bash completion. In this example we simply return static slice.
|
||||
|
||||
// You could use this functionality to reach into a hosts file to provide
|
||||
// completion for a list of known hosts.
|
||||
return []string{"sshhost.example", "webhost.example", "ftphost.example"}
|
||||
}
|
||||
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("flag-1", "").HintAction(listHosts).String()
|
||||
```
|
||||
|
||||
**EnumVar/Enum**
|
||||
When using `Enum` or `EnumVar`, any provided options will be automatically
|
||||
used for bash autocompletion. However, if you wish to provide a subset or
|
||||
different options, you can use `HintOptions` or `HintAction` which will override
|
||||
the default completion options for `Enum`/`EnumVar`.
|
||||
|
||||
|
||||
**Examples**
|
||||
You can see an in depth example of the completion API within
|
||||
`examples/completion/main.go`
|
||||
|
||||
|
||||
### Supporting -h for help
|
||||
|
||||
`kingpin.CommandLine.HelpFlag.Short('h')`
|
||||
|
||||
### Custom help
|
||||
|
||||
Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)).
|
||||
|
||||
You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/gopkg.in/alecthomas/kingpin.v2#Application.UsageTemplate) function.
|
||||
|
||||
There are four included templates: `kingpin.DefaultUsageTemplate` is the default,
|
||||
`kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures,
|
||||
`kingpin.SeparateOptionalFlagsUsageTemplate` looks like the default template, but splits required
|
||||
and optional command flags into separate lists, and `kingpin.ManPageTemplate` is used to generate man pages.
|
||||
|
||||
See the above templates for examples of usage, and the the function [UsageForContextWithTemplate()](https://github.com/alecthomas/kingpin/blob/master/usage.go#L198) method for details on the context.
|
||||
|
||||
#### Default help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
Show help.
|
||||
|
||||
get url <url>
|
||||
Retrieve a URL.
|
||||
|
||||
get file <file>
|
||||
Retrieve a file.
|
||||
|
||||
post [<flags>] <url>
|
||||
POST a resource.
|
||||
```
|
||||
|
||||
#### Compact help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
get [<flags>]
|
||||
url <url>
|
||||
file <file>
|
||||
post [<flags>] <url>
|
||||
```
|
42
vendor/github.com/alecthomas/kingpin/actions.go
generated
vendored
|
@ -1,42 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
// Action callback executed at various stages after all values are populated.
|
||||
// The application, commands, arguments and flags all have corresponding
|
||||
// actions.
|
||||
type Action func(*ParseContext) error
|
||||
|
||||
type actionMixin struct {
|
||||
actions []Action
|
||||
preActions []Action
|
||||
}
|
||||
|
||||
type actionApplier interface {
|
||||
applyActions(*ParseContext) error
|
||||
applyPreActions(*ParseContext) error
|
||||
}
|
||||
|
||||
func (a *actionMixin) addAction(action Action) {
|
||||
a.actions = append(a.actions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) addPreAction(action Action) {
|
||||
a.preActions = append(a.preActions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyActions(context *ParseContext) error {
|
||||
for _, action := range a.actions {
|
||||
if err := action(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyPreActions(context *ParseContext) error {
|
||||
for _, preAction := range a.preActions {
|
||||
if err := preAction(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
662
vendor/github.com/alecthomas/kingpin/app.go
generated
vendored
|
@ -1,662 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCommandNotSpecified = fmt.Errorf("command not specified")
|
||||
)
|
||||
|
||||
var (
|
||||
envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z_]+`)
|
||||
)
|
||||
|
||||
type ApplicationValidator func(*Application) error
|
||||
|
||||
// An Application contains the definitions of flags, arguments and commands
|
||||
// for an application.
|
||||
type Application struct {
|
||||
cmdMixin
|
||||
initialized bool
|
||||
|
||||
Name string
|
||||
Help string
|
||||
|
||||
author string
|
||||
version string
|
||||
writer io.Writer // Destination for usage and errors.
|
||||
usageTemplate string
|
||||
validator ApplicationValidator
|
||||
terminate func(status int) // See Terminate()
|
||||
noInterspersed bool // can flags be interspersed with args (or must they come first)
|
||||
defaultEnvars bool
|
||||
completion bool
|
||||
|
||||
// Help flag. Exposed for user customisation.
|
||||
HelpFlag *FlagClause
|
||||
// Help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand *CmdClause
|
||||
// Version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag *FlagClause
|
||||
}
|
||||
|
||||
// New creates a new Kingpin application instance.
|
||||
func New(name, help string) *Application {
|
||||
a := &Application{
|
||||
Name: name,
|
||||
Help: help,
|
||||
writer: os.Stderr,
|
||||
usageTemplate: DefaultUsageTemplate,
|
||||
terminate: os.Exit,
|
||||
}
|
||||
a.flagGroup = newFlagGroup()
|
||||
a.argGroup = newArgGroup()
|
||||
a.cmdGroup = newCmdGroup(a)
|
||||
a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
|
||||
a.HelpFlag.Bool()
|
||||
a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool()
|
||||
a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool()
|
||||
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion)
|
||||
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool()
|
||||
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) generateLongHelp(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, LongHelpTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateManPage(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ManPageTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateZSHCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultEnvars configures all flags (that do not already have an associated
|
||||
// envar) to use a default environment variable in the form "<app>_<flag>".
|
||||
//
|
||||
// For example, if the application is named "foo" and a flag is named "bar-
|
||||
// waz" the environment variable: "FOO_BAR_WAZ".
|
||||
func (a *Application) DefaultEnvars() *Application {
|
||||
a.defaultEnvars = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Terminate specifies the termination handler. Defaults to os.Exit(status).
|
||||
// If nil is passed, a no-op function will be used.
|
||||
func (a *Application) Terminate(terminate func(int)) *Application {
|
||||
if terminate == nil {
|
||||
terminate = func(int) {}
|
||||
}
|
||||
a.terminate = terminate
|
||||
return a
|
||||
}
|
||||
|
||||
// Specify the writer to use for usage and errors. Defaults to os.Stderr.
|
||||
func (a *Application) Writer(w io.Writer) *Application {
|
||||
a.writer = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageTemplate specifies the text template to use when displaying usage
|
||||
// information. The default is UsageTemplate.
|
||||
func (a *Application) UsageTemplate(template string) *Application {
|
||||
a.usageTemplate = template
|
||||
return a
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (a *Application) Validate(validator ApplicationValidator) *Application {
|
||||
a.validator = validator
|
||||
return a
|
||||
}
|
||||
|
||||
// ParseContext parses the given command line and returns the fully populated
|
||||
// ParseContext.
|
||||
func (a *Application) ParseContext(args []string) (*ParseContext, error) {
|
||||
return a.parseContext(false, args)
|
||||
}
|
||||
|
||||
func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseContext, error) {
|
||||
if err := a.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context := tokenize(args, ignoreDefault)
|
||||
err := parse(context, a)
|
||||
return context, err
|
||||
}
|
||||
|
||||
// Parse parses command-line arguments. It returns the selected command and an
|
||||
// error. The selected command will be a space separated subcommand, if
|
||||
// subcommands have been configured.
|
||||
//
|
||||
// This will populate all flag and argument values, call all callbacks, and so
|
||||
// on.
|
||||
func (a *Application) Parse(args []string) (command string, err error) {
|
||||
|
||||
context, parseErr := a.ParseContext(args)
|
||||
selected := []string{}
|
||||
var setValuesErr error
|
||||
|
||||
if context == nil {
|
||||
// Since we do not throw error immediately, there could be a case
|
||||
// where a context returns nil. Protect against that.
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
if err = a.setDefaults(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
selected, setValuesErr = a.setValues(context)
|
||||
|
||||
if err = a.applyPreActions(context, !a.completion); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if a.completion {
|
||||
a.generateBashCompletion(context)
|
||||
a.terminate(0)
|
||||
} else {
|
||||
if parseErr != nil {
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
a.maybeHelp(context)
|
||||
if !context.EOL() {
|
||||
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||
}
|
||||
|
||||
if setValuesErr != nil {
|
||||
return "", setValuesErr
|
||||
}
|
||||
|
||||
command, err = a.execute(context, selected)
|
||||
if err == ErrCommandNotSpecified {
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) writeUsage(context *ParseContext, err error) {
|
||||
if err != nil {
|
||||
a.Errorf("%s", err)
|
||||
}
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
func (a *Application) maybeHelp(context *ParseContext) {
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag {
|
||||
// Re-parse the command-line ignoring defaults, so that help works correctly.
|
||||
context, _ = a.parseContext(true, context.rawArgs)
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version adds a --version flag for displaying the application version.
|
||||
func (a *Application) Version(version string) *Application {
|
||||
a.version = version
|
||||
a.VersionFlag = a.Flag("version", "Show application version.").PreAction(func(*ParseContext) error {
|
||||
fmt.Fprintln(a.writer, version)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.VersionFlag.Bool()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) Author(author string) *Application {
|
||||
a.author = author
|
||||
return a
|
||||
}
|
||||
|
||||
// Action callback to call when all values are populated and parsing is
|
||||
// complete, but before any command, flag or argument actions.
|
||||
//
|
||||
// All Action() callbacks are called in the order they are encountered on the
|
||||
// command line.
|
||||
func (a *Application) Action(action Action) *Application {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Action called after parsing completes but before validation and execution.
|
||||
func (a *Application) PreAction(action Action) *Application {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Command adds a new top-level command.
|
||||
func (a *Application) Command(name, help string) *CmdClause {
|
||||
return a.addCommand(name, help)
|
||||
}
|
||||
|
||||
// Interspersed control if flags can be interspersed with positional arguments
|
||||
//
|
||||
// true (the default) means that they can, false means that all the flags must appear before the first positional arguments.
|
||||
func (a *Application) Interspersed(interspersed bool) *Application {
|
||||
a.noInterspersed = !interspersed
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) defaultEnvarPrefix() string {
|
||||
if a.defaultEnvars {
|
||||
return a.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Application) init() error {
|
||||
if a.initialized {
|
||||
return nil
|
||||
}
|
||||
if a.cmdGroup.have() && a.argGroup.have() {
|
||||
return fmt.Errorf("can't mix top-level Arg()s with Command()s")
|
||||
}
|
||||
|
||||
// If we have subcommands, add a help command at the top-level.
|
||||
if a.cmdGroup.have() {
|
||||
var command []string
|
||||
a.HelpCommand = a.Command("help", "Show help.").PreAction(func(context *ParseContext) error {
|
||||
a.Usage(command)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.HelpCommand.Arg("command", "Show help on command.").StringsVar(&command)
|
||||
// Make help first command.
|
||||
l := len(a.commandOrder)
|
||||
a.commandOrder = append(a.commandOrder[l-1:l], a.commandOrder[:l-1]...)
|
||||
}
|
||||
|
||||
if err := a.flagGroup.init(a.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range a.commands {
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flagGroups := []*flagGroup{a.flagGroup}
|
||||
for _, cmd := range a.commandOrder {
|
||||
if err := checkDuplicateFlags(cmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
a.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recursively check commands for duplicate flags.
|
||||
func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error {
|
||||
// Check for duplicates.
|
||||
for _, flags := range flagGroups {
|
||||
for _, flag := range current.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := flags.short[string(flag.shorthand)]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
}
|
||||
if _, ok := flags.long[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
flagGroups = append(flagGroups, current.flagGroup)
|
||||
// Check subcommands.
|
||||
for _, subcmd := range current.commandOrder {
|
||||
if err := checkDuplicateFlags(subcmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) execute(context *ParseContext, selected []string) (string, error) {
|
||||
var err error
|
||||
|
||||
if err = a.validateRequired(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyValidators(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyActions(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
command := strings.Join(selected, " ")
|
||||
if command == "" && a.cmdGroup.have() {
|
||||
return "", ErrCommandNotSpecified
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) setDefaults(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
if err := flag.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if err := arg.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) validateRequired(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
// Check required flags were provided.
|
||||
if flag.needsValue() {
|
||||
return fmt.Errorf("required flag --%s not provided", flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if arg.needsValue() {
|
||||
return fmt.Errorf("required argument '%s' not provided", arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) setValues(context *ParseContext) (selected []string, err error) {
|
||||
// Set all arg and flag values.
|
||||
var (
|
||||
lastCmd *CmdClause
|
||||
flagSet = map[string]struct{}{}
|
||||
)
|
||||
for _, element := range context.Elements {
|
||||
switch clause := element.Clause.(type) {
|
||||
case *FlagClause:
|
||||
if _, ok := flagSet[clause.name]; ok {
|
||||
if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name)
|
||||
}
|
||||
}
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
flagSet[clause.name] = struct{}{}
|
||||
|
||||
case *ArgClause:
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case *CmdClause:
|
||||
if clause.validator != nil {
|
||||
if err = clause.validator(clause); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
selected = append(selected, clause.name)
|
||||
lastCmd = clause
|
||||
}
|
||||
}
|
||||
|
||||
if lastCmd != nil && len(lastCmd.commands) > 0 {
|
||||
return nil, fmt.Errorf("must select a subcommand of '%s'", lastCmd.FullCommand())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Application) applyValidators(context *ParseContext) (err error) {
|
||||
// Call command validation functions.
|
||||
for _, element := range context.Elements {
|
||||
if cmd, ok := element.Clause.(*CmdClause); ok && cmd.validator != nil {
|
||||
if err = cmd.validator(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.validator != nil {
|
||||
err = a.validator(a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error {
|
||||
if err := a.actionMixin.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
if dispatch {
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) applyActions(context *ParseContext) error {
|
||||
if err := a.actionMixin.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Errorf prints an error message to w in the format "<appname>: error: <message>".
|
||||
func (a *Application) Errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(a.writer, a.Name+": error: "+format+"\n", args...)
|
||||
}
|
||||
|
||||
// Fatalf writes a formatted error to w then terminates with exit status 1.
|
||||
func (a *Application) Fatalf(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func (a *Application) FatalUsage(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
a.Usage([]string{})
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to w, then usage
|
||||
// information for the given ParseContext, before exiting.
|
||||
func (a *Application) FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given formatted string, if any.
|
||||
func (a *Application) FatalIfError(err error, format string, args ...interface{}) {
|
||||
if err != nil {
|
||||
prefix := ""
|
||||
if format != "" {
|
||||
prefix = fmt.Sprintf(format, args...) + ": "
|
||||
}
|
||||
a.Errorf(prefix+"%s", err)
|
||||
a.terminate(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) completionOptions(context *ParseContext) []string {
|
||||
args := context.rawArgs
|
||||
|
||||
var (
|
||||
currArg string
|
||||
prevArg string
|
||||
target cmdMixin
|
||||
)
|
||||
|
||||
numArgs := len(args)
|
||||
if numArgs > 1 {
|
||||
args = args[1:]
|
||||
currArg = args[len(args)-1]
|
||||
}
|
||||
if numArgs > 2 {
|
||||
prevArg = args[len(args)-2]
|
||||
}
|
||||
|
||||
target = a.cmdMixin
|
||||
if context.SelectedCommand != nil {
|
||||
// A subcommand was in use. We will use it as the target
|
||||
target = context.SelectedCommand.cmdMixin
|
||||
}
|
||||
|
||||
if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") {
|
||||
// Perform completion for A flag. The last/current argument started with "-"
|
||||
var (
|
||||
flagName string // The name of a flag if given (could be half complete)
|
||||
flagValue string // The value assigned to a flag (if given) (could be half complete)
|
||||
)
|
||||
|
||||
if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag value
|
||||
// Wont Match: ./myApp --flag --
|
||||
flagName = prevArg[2:] // Strip the "--"
|
||||
flagValue = currArg
|
||||
} else if strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag --
|
||||
// Matches: ./myApp --flag somevalue --
|
||||
// Matches: ./myApp --
|
||||
flagName = currArg[2:] // Strip the "--"
|
||||
}
|
||||
|
||||
options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue)
|
||||
if valueMatched {
|
||||
// Value Matched. Show cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
// Add top level flags if we're not at the top level and no match was found.
|
||||
if context.SelectedCommand != nil && flagMatched == false {
|
||||
topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue)
|
||||
if topValueMatched {
|
||||
// Value Matched. Back to cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
if topFlagMatched {
|
||||
// Top level had a flag which matched the input. Return it's options.
|
||||
options = topOptions
|
||||
} else {
|
||||
// Add top level flags
|
||||
options = append(options, topOptions...)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Perform completion for sub commands and arguments.
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletion(context *ParseContext) {
|
||||
options := a.completionOptions(context)
|
||||
fmt.Printf("%s", strings.Join(options, "\n"))
|
||||
}
|
||||
|
||||
func envarTransform(name string) string {
|
||||
return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_"))
|
||||
}
|
185
vendor/github.com/alecthomas/kingpin/args.go
generated
vendored
|
@ -1,185 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type argGroup struct {
|
||||
args []*ArgClause
|
||||
}
|
||||
|
||||
func newArgGroup() *argGroup {
|
||||
return &argGroup{}
|
||||
}
|
||||
|
||||
func (a *argGroup) have() bool {
|
||||
return len(a.args) > 0
|
||||
}
|
||||
|
||||
// GetArg gets an argument definition.
|
||||
//
|
||||
// This allows existing arguments to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (a *argGroup) GetArg(name string) *ArgClause {
|
||||
for _, arg := range a.args {
|
||||
if arg.name == name {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argGroup) Arg(name, help string) *ArgClause {
|
||||
arg := newArg(name, help)
|
||||
a.args = append(a.args, arg)
|
||||
return arg
|
||||
}
|
||||
|
||||
func (a *argGroup) init() error {
|
||||
required := 0
|
||||
seen := map[string]struct{}{}
|
||||
previousArgMustBeLast := false
|
||||
for i, arg := range a.args {
|
||||
if previousArgMustBeLast {
|
||||
return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name)
|
||||
}
|
||||
if arg.consumesRemainder() {
|
||||
previousArgMustBeLast = true
|
||||
}
|
||||
if _, ok := seen[arg.name]; ok {
|
||||
return fmt.Errorf("duplicate argument '%s'", arg.name)
|
||||
}
|
||||
seen[arg.name] = struct{}{}
|
||||
if arg.required && required != i {
|
||||
return fmt.Errorf("required arguments found after non-required")
|
||||
}
|
||||
if arg.required {
|
||||
required++
|
||||
}
|
||||
if err := arg.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArgClause struct {
|
||||
actionMixin
|
||||
parserMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
help string
|
||||
defaultValues []string
|
||||
required bool
|
||||
}
|
||||
|
||||
func newArg(name, help string) *ArgClause {
|
||||
a := &ArgClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) setDefault() error {
|
||||
if a.HasEnvarValue() {
|
||||
if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return a.value.Set(a.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range a.GetSplitEnvarValue() {
|
||||
if err := a.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(a.defaultValues) > 0 {
|
||||
for _, defaultValue := range a.defaultValues {
|
||||
if err := a.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ArgClause) needsValue() bool {
|
||||
haveDefault := len(a.defaultValues) > 0
|
||||
return a.required && !(haveDefault || a.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (a *ArgClause) consumesRemainder() bool {
|
||||
if r, ok := a.value.(remainderArg); ok {
|
||||
return r.IsCumulative()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Required arguments must be input by the user. They can not have a Default() value provided.
|
||||
func (a *ArgClause) Required() *ArgClause {
|
||||
a.required = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Default values for this argument. They *must* be parseable by the value of the argument.
|
||||
func (a *ArgClause) Default(values ...string) *ArgClause {
|
||||
a.defaultValues = values
|
||||
return a
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (a *ArgClause) Envar(name string) *ArgClause {
|
||||
a.envar = name
|
||||
a.noEnvar = false
|
||||
return a
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (a *ArgClause) NoEnvar() *ArgClause {
|
||||
a.envar = ""
|
||||
a.noEnvar = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) Action(action Action) *ArgClause {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) PreAction(action Action) *ArgClause {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the arg to provide completions
|
||||
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) init() error {
|
||||
if a.required && len(a.defaultValues) > 0 {
|
||||
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
|
||||
}
|
||||
if a.value == nil {
|
||||
return fmt.Errorf("no parser defined for arg '%s'", a.name)
|
||||
}
|
||||
return nil
|
||||
}
|
274
vendor/github.com/alecthomas/kingpin/cmd.go
generated
vendored
|
@ -1,274 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type cmdMixin struct {
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
actionMixin
|
||||
}
|
||||
|
||||
// CmdCompletion returns completion options for arguments, if that's where
|
||||
// parsing left off, or commands if there aren't any unsatisfied args.
|
||||
func (c *cmdMixin) CmdCompletion(context *ParseContext) []string {
|
||||
var options []string
|
||||
|
||||
// Count args already satisfied - we won't complete those, and add any
|
||||
// default commands' alternatives, since they weren't listed explicitly
|
||||
// and the user may want to explicitly list something else.
|
||||
argsSatisfied := 0
|
||||
for _, el := range context.Elements {
|
||||
switch clause := el.Clause.(type) {
|
||||
case *ArgClause:
|
||||
if el.Value != nil && *el.Value != "" {
|
||||
argsSatisfied++
|
||||
}
|
||||
case *CmdClause:
|
||||
options = append(options, clause.completionAlts...)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if argsSatisfied < len(c.argGroup.args) {
|
||||
// Since not all args have been satisfied, show options for the current one
|
||||
options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...)
|
||||
} else {
|
||||
// If all args are satisfied, then go back to completing commands
|
||||
for _, cmd := range c.cmdGroup.commandOrder {
|
||||
if !cmd.hidden {
|
||||
options = append(options, cmd.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) {
|
||||
// Check if flagName matches a known flag.
|
||||
// If it does, show the options for the flag
|
||||
// Otherwise, show all flags
|
||||
|
||||
options := []string{}
|
||||
|
||||
for _, flag := range c.flagGroup.flagOrder {
|
||||
// Loop through each flag and determine if a match exists
|
||||
if flag.name == flagName {
|
||||
// User typed entire flag. Need to look for flag options.
|
||||
options = flag.resolveCompletions()
|
||||
if len(options) == 0 {
|
||||
// No Options to Choose From, Assume Match.
|
||||
return options, true, true
|
||||
}
|
||||
|
||||
// Loop options to find if the user specified value matches
|
||||
isPrefix := false
|
||||
matched := false
|
||||
|
||||
for _, opt := range options {
|
||||
if flagValue == opt {
|
||||
matched = true
|
||||
} else if strings.HasPrefix(opt, flagValue) {
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
|
||||
// Matched Flag Directly
|
||||
// Flag Value Not Prefixed, and Matched Directly
|
||||
return options, true, !isPrefix && matched
|
||||
}
|
||||
|
||||
if !flag.hidden {
|
||||
options = append(options, "--"+flag.name)
|
||||
}
|
||||
}
|
||||
// No Flag directly matched.
|
||||
return options, false, false
|
||||
|
||||
}
|
||||
|
||||
type cmdGroup struct {
|
||||
app *Application
|
||||
parent *CmdClause
|
||||
commands map[string]*CmdClause
|
||||
commandOrder []*CmdClause
|
||||
}
|
||||
|
||||
func (c *cmdGroup) defaultSubcommand() *CmdClause {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) cmdNames() []string {
|
||||
names := make([]string, 0, len(c.commandOrder))
|
||||
for _, cmd := range c.commandOrder {
|
||||
names = append(names, cmd.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GetArg gets a command definition.
|
||||
//
|
||||
// This allows existing commands to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (c *cmdGroup) GetCommand(name string) *CmdClause {
|
||||
return c.commands[name]
|
||||
}
|
||||
|
||||
func newCmdGroup(app *Application) *cmdGroup {
|
||||
return &cmdGroup{
|
||||
app: app,
|
||||
commands: make(map[string]*CmdClause),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) flattenedCommands() (out []*CmdClause) {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if len(cmd.commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.flattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cmdGroup) addCommand(name, help string) *CmdClause {
|
||||
cmd := newCommand(c.app, name, help)
|
||||
c.commands[name] = cmd
|
||||
c.commandOrder = append(c.commandOrder, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdGroup) init() error {
|
||||
seen := map[string]bool{}
|
||||
if c.defaultSubcommand() != nil && !c.have() {
|
||||
return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name)
|
||||
}
|
||||
defaults := []string{}
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
defaults = append(defaults, cmd.name)
|
||||
}
|
||||
if seen[cmd.name] {
|
||||
return fmt.Errorf("duplicate command %q", cmd.name)
|
||||
}
|
||||
seen[cmd.name] = true
|
||||
for _, alias := range cmd.aliases {
|
||||
if seen[alias] {
|
||||
return fmt.Errorf("alias duplicates existing command %q", alias)
|
||||
}
|
||||
c.commands[alias] = cmd
|
||||
}
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(defaults) > 1 {
|
||||
return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) have() bool {
|
||||
return len(c.commands) > 0
|
||||
}
|
||||
|
||||
type CmdClauseValidator func(*CmdClause) error
|
||||
|
||||
// A CmdClause is a single top-level command. It encapsulates a set of flags
|
||||
// and either subcommands or positional arguments.
|
||||
type CmdClause struct {
|
||||
cmdMixin
|
||||
app *Application
|
||||
name string
|
||||
aliases []string
|
||||
help string
|
||||
isDefault bool
|
||||
validator CmdClauseValidator
|
||||
hidden bool
|
||||
completionAlts []string
|
||||
}
|
||||
|
||||
func newCommand(app *Application, name, help string) *CmdClause {
|
||||
c := &CmdClause{
|
||||
app: app,
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
c.flagGroup = newFlagGroup()
|
||||
c.argGroup = newArgGroup()
|
||||
c.cmdGroup = newCmdGroup(app)
|
||||
return c
|
||||
}
|
||||
|
||||
// Add an Alias for this command.
|
||||
func (c *CmdClause) Alias(name string) *CmdClause {
|
||||
c.aliases = append(c.aliases, name)
|
||||
return c
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause {
|
||||
c.validator = validator
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) FullCommand() string {
|
||||
out := []string{c.name}
|
||||
for p := c.parent; p != nil; p = p.parent {
|
||||
out = append([]string{p.name}, out...)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// Command adds a new sub-command.
|
||||
func (c *CmdClause) Command(name, help string) *CmdClause {
|
||||
cmd := c.addCommand(name, help)
|
||||
cmd.parent = c
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Default makes this command the default if commands don't match.
|
||||
func (c *CmdClause) Default() *CmdClause {
|
||||
c.isDefault = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) Action(action Action) *CmdClause {
|
||||
c.addAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) PreAction(action Action) *CmdClause {
|
||||
c.addPreAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) init() error {
|
||||
if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.argGroup.have() && c.cmdGroup.have() {
|
||||
return fmt.Errorf("can't mix Arg()s with Command()s")
|
||||
}
|
||||
if err := c.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CmdClause) Hidden() *CmdClause {
|
||||
c.hidden = true
|
||||
return c
|
||||
}
|
33
vendor/github.com/alecthomas/kingpin/completions.go
generated
vendored
|
@ -1,33 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
// HintAction is a function type who is expected to return a slice of possible
|
||||
// command line arguments.
|
||||
type HintAction func() []string
|
||||
type completionsMixin struct {
|
||||
hintActions []HintAction
|
||||
builtinHintActions []HintAction
|
||||
}
|
||||
|
||||
func (a *completionsMixin) addHintAction(action HintAction) {
|
||||
a.hintActions = append(a.hintActions, action)
|
||||
}
|
||||
|
||||
// Allow adding of HintActions which are added internally, ie, EnumVar
|
||||
func (a *completionsMixin) addHintActionBuiltin(action HintAction) {
|
||||
a.builtinHintActions = append(a.builtinHintActions, action)
|
||||
}
|
||||
|
||||
func (a *completionsMixin) resolveCompletions() []string {
|
||||
var hints []string
|
||||
|
||||
options := a.builtinHintActions
|
||||
if len(a.hintActions) > 0 {
|
||||
// User specified their own hintActions. Use those instead.
|
||||
options = a.hintActions
|
||||
}
|
||||
|
||||
for _, hintAction := range options {
|
||||
hints = append(hints, hintAction()...)
|
||||
}
|
||||
return hints
|
||||
}
|
68
vendor/github.com/alecthomas/kingpin/doc.go
generated
vendored
|
@ -1,68 +0,0 @@
|
|||
// Package kingpin provides command line interfaces like this:
|
||||
//
|
||||
// $ chat
|
||||
// usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
//
|
||||
// Flags:
|
||||
// --debug enable debug mode
|
||||
// --help Show help.
|
||||
// --server=127.0.0.1 server address
|
||||
//
|
||||
// Commands:
|
||||
// help <command>
|
||||
// Show help for a command.
|
||||
//
|
||||
// post [<flags>] <channel>
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// register <nick> <name>
|
||||
// Register a new user.
|
||||
//
|
||||
// $ chat help post
|
||||
// usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
//
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// Flags:
|
||||
// --image=IMAGE image to post
|
||||
//
|
||||
// Args:
|
||||
// <channel> channel to post to
|
||||
// [<text>] text to post
|
||||
// $ chat post --image=~/Downloads/owls.jpg pics
|
||||
//
|
||||
// From code like this:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import "gopkg.in/alecthomas/kingpin.v1"
|
||||
//
|
||||
// var (
|
||||
// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool()
|
||||
// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP()
|
||||
//
|
||||
// register = kingpin.Command("register", "Register a new user.")
|
||||
// registerNick = register.Arg("nick", "nickname for user").Required().String()
|
||||
// registerName = register.Arg("name", "name of user").Required().String()
|
||||
//
|
||||
// post = kingpin.Command("post", "Post a message to a channel.")
|
||||
// postImage = post.Flag("image", "image to post").ExistingFile()
|
||||
// postChannel = post.Arg("channel", "channel to post to").Required().String()
|
||||
// postText = post.Arg("text", "text to post").String()
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// switch kingpin.Parse() {
|
||||
// // Register user
|
||||
// case "register":
|
||||
// println(*registerNick)
|
||||
//
|
||||
// // Post message
|
||||
// case "post":
|
||||
// if *postImage != nil {
|
||||
// }
|
||||
// if *postText != "" {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
package kingpin
|
45
vendor/github.com/alecthomas/kingpin/envar.go
generated
vendored
|
@ -1,45 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envVarValuesSeparator = "\r?\n"
|
||||
envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$")
|
||||
envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator)
|
||||
)
|
||||
|
||||
type envarMixin struct {
|
||||
envar string
|
||||
noEnvar bool
|
||||
}
|
||||
|
||||
func (e *envarMixin) HasEnvarValue() bool {
|
||||
return e.GetEnvarValue() != ""
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetEnvarValue() string {
|
||||
if e.noEnvar || e.envar == "" {
|
||||
return ""
|
||||
}
|
||||
return os.Getenv(e.envar)
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetSplitEnvarValue() []string {
|
||||
values := make([]string, 0)
|
||||
|
||||
envarValue := e.GetEnvarValue()
|
||||
if envarValue == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
// Split by new line to extract multiple values, if any.
|
||||
trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "")
|
||||
for _, value := range envVarValuesSplitter.Split(trimmed, -1) {
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|