mirror of
https://github.com/hamburghammer/gohttpserver.git
synced 2024-05-17 10:04:35 +02:00
Compare commits
85 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 |
47
.drone.yml
Normal file
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
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -27,3 +27,7 @@ _testmain.go
|
|||
gohttpserver
|
||||
bindata_assetfs.go
|
||||
assets_vfsdata.go
|
||||
*.un~
|
||||
*.swp
|
||||
|
||||
dist/
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
project_name: gohttpserver
|
||||
release:
|
||||
github:
|
||||
owner: codeskyblue
|
||||
name: gohttpserver
|
||||
brew:
|
||||
github:
|
||||
owner: codeskyblue
|
||||
name: homebrew-tap
|
||||
homepage: https://github.com/codeskyblue/gohttpserver
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
goarm:
|
||||
- "6"
|
||||
main: .
|
||||
ldflags: -s -w -X main.VERSION={{.Version}}
|
||||
flags: -tags vfs
|
||||
binary: gohttpserver
|
||||
hooks:
|
||||
pre: go generate .
|
||||
archive:
|
||||
format: zip
|
||||
name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
|
||||
.Arm }}{{ end }}'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
- license*
|
||||
- LICENSE*
|
||||
- readme*
|
||||
- README*
|
||||
- changelog*
|
||||
- CHANGELOG*
|
||||
- .ghs.yml
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
|
12
.travis.yml
12
.travis.yml
|
@ -1,12 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- "1.10"
|
||||
script:
|
||||
- go test -v
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: curl -sL https://git.io/goreleaser | bash
|
||||
on:
|
||||
tags: true
|
||||
condition: $TRAVIS_OS_NAME = linux
|
16
Dockerfile
16
Dockerfile
|
@ -1,16 +0,0 @@
|
|||
FROM golang:1.10
|
||||
WORKDIR /go/src/github.com/codeskyblue/gohttpserver
|
||||
ADD . /go/src/github.com/codeskyblue/gohttpserver/
|
||||
RUN go get -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o gohttpserver .
|
||||
|
||||
FROM debian: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 /go/src/github.com/codeskyblue/gohttpserver/gohttpserver .
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT [ "/app/gohttpserver" ]
|
||||
CMD ["--root=/app/public"]
|
1
LICENSE
1
LICENSE
|
@ -1,5 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Augusto Dwenger J.
|
||||
Copyright (c) 2018 shengxiang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
|
57
README.md
57
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/)
|
||||
|
||||
## Requirements
|
||||
Tested with go-1.10, go-1.11
|
||||
Tested with go-1.16
|
||||
|
||||
## Screenshots
|
||||
![screen](testdata/filetypes/gohttpserver.gif)
|
||||
|
@ -54,6 +53,7 @@ Tested with go-1.10, go-1.11
|
|||
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
|
||||
```
|
||||
|
@ -61,9 +61,6 @@ go get -v github.com/codeskyblue/gohttpserver
|
|||
cd $GOPATH/src/github.com/codeskyblue/gohttpserver
|
||||
go build && ./gohttpserver
|
||||
```
|
||||
|
||||
Or download binaries from [github releases](https://github.com/codeskyblue/gohttpserver/releases)
|
||||
|
||||
## Usage
|
||||
Listen on port 8000 of all interfaces, and enable file uploading.
|
||||
|
||||
|
@ -77,14 +74,14 @@ Use command `gohttpserver --help` to see more usage.
|
|||
share current directory
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver codeskyblue/gohttpserver
|
||||
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 \
|
||||
codeskyblue/gohttpserver --root /app/public \
|
||||
registry.hhhammer.de/gohttpserver \
|
||||
--auth-type http --auth-http username:password
|
||||
```
|
||||
|
||||
|
@ -92,10 +89,17 @@ 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 \
|
||||
codeskyblue/gohttpserver --root /app/public \
|
||||
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
|
||||
- Enable basic http authentication
|
||||
|
||||
|
@ -109,6 +113,24 @@ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
|
|||
$ gohttpserver --auth-type openid --auth-openid https://login.example-hostname.com/openid/
|
||||
```
|
||||
|
||||
- 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
|
||||
|
@ -180,6 +202,8 @@ $ 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`
|
||||
|
||||
|
@ -189,8 +213,21 @@ $ curl -F file=@foo.txt localhost:8000/somedir
|
|||
# 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`
|
||||
|
||||
|
@ -212,6 +249,8 @@ server {
|
|||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
<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>
|
||||
<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">
|
||||
|
@ -49,13 +50,26 @@
|
|||
</a>
|
||||
</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>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">
|
||||
<button class="btn btn-default" type="submit">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
|
@ -115,25 +129,32 @@
|
|||
<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>
|
||||
|
@ -235,8 +256,8 @@
|
|||
</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-2018. go1.10
|
||||
<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>
|
||||
|
@ -253,6 +274,11 @@
|
|||
<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>
|
||||
var META = document.getElementsByTagName("meta");
|
||||
META[2]["content"]=$('.navbar').css('border-color');
|
||||
</script>
|
||||
[[if .GoogleTrackerID ]]
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
|
@ -272,4 +298,4 @@
|
|||
</script> [[ end ]]
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</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: {
|
||||
|
@ -125,6 +141,9 @@ var vm = new Vue({
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
getEncodePath: function (f) {
|
||||
return pathJoin([location.pathname, encodeURIComponent(f.name)]);
|
||||
},
|
||||
formatTime: function (timestamp) {
|
||||
var m = moment(timestamp);
|
||||
if (this.mtimeTypeFromNow) {
|
||||
|
@ -138,14 +157,22 @@ var vm = new Vue({
|
|||
removeAllUploads: function () {
|
||||
this.myDropzone.removeAllFiles();
|
||||
},
|
||||
parentDirectory: function (path) {
|
||||
return path.replace('\\', '/').split('/').slice(0, -1).join('/')
|
||||
},
|
||||
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(location.pathname);
|
||||
parts.push(pathname);
|
||||
} else if (getExtention(name) == "ipa") {
|
||||
parts.push("/-/ipa/link", location.pathname, name);
|
||||
parts.push("/-/ipa/link", pathname, encodeURIComponent(name));
|
||||
} else {
|
||||
parts.push(location.pathname, name);
|
||||
parts.push(pathname, name);
|
||||
}
|
||||
var urlPath = location.protocol + "//" + pathJoin(parts);
|
||||
return noEncode ? urlPath : encodeURI(urlPath);
|
||||
|
@ -155,14 +182,16 @@ var vm = new Vue({
|
|||
$("#qrcode-title").html(title || name || location.pathname);
|
||||
$("#qrcode-link").attr("href", urlPath);
|
||||
$('#qrcodeCanvas').empty().qrcode({
|
||||
text: urlPath
|
||||
text: encodeURI(urlPath),
|
||||
});
|
||||
|
||||
$("#qrcodeRight a").attr("href", encodeURI(urlPath));
|
||||
$("#qrcodeRight a").attr("href", urlPath);
|
||||
$("#qrcode-modal").modal("show");
|
||||
},
|
||||
genDownloadURL: function (f) {
|
||||
return location.origin + "/" + f.path;
|
||||
var search = location.search;
|
||||
var sep = search == "" ? "?" : "&"
|
||||
return location.origin + this.getEncodePath(f) + location.search + sep + "download=true";
|
||||
},
|
||||
shouldHaveQrcode: function (name) {
|
||||
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
|
||||
|
@ -208,11 +237,12 @@ var vm = new Vue({
|
|||
return "fa-file-text-o"
|
||||
},
|
||||
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()
|
||||
},
|
||||
|
@ -223,57 +253,65 @@ var vm = new Vue({
|
|||
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) {
|
||||
$("#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)
|
||||
}
|
||||
})
|
||||
},
|
||||
makeDirectory: function () {
|
||||
var name = window.prompt("Directory name?")
|
||||
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(["/-/mkdir", location.pathname]),
|
||||
data: {
|
||||
name: name,
|
||||
},
|
||||
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 " + f.name + " ?")) {
|
||||
if (!window.confirm("Delete " + location.pathname + "/" + f.name + " ?")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, f.name]),
|
||||
url: pathJoin([location.pathname, encodeURIComponent(f.name)]),
|
||||
method: 'DELETE',
|
||||
success: function (res) {
|
||||
loadFileList()
|
||||
},
|
||||
error: function (err) {
|
||||
alert(err.responseText);
|
||||
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 == "/") {
|
||||
|
@ -299,23 +337,23 @@ var vm = new Vue({
|
|||
}
|
||||
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 () {
|
||||
// TODO: move loadFileList here
|
||||
|
@ -324,21 +362,31 @@ var vm = new Vue({
|
|||
})
|
||||
|
||||
window.onpopstate = function (event) {
|
||||
var pathname = decodeURI(location.pathname)
|
||||
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) {
|
||||
|
@ -346,22 +394,22 @@ function loadFileList(pathname) {
|
|||
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) {
|
||||
|
@ -396,10 +444,10 @@ $(function () {
|
|||
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
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;
|
||||
}
|
4
build.sh
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
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
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
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
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
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
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,7 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
@ -41,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 {
|
||||
|
@ -68,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() {
|
||||
|
@ -82,20 +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("/-/mkdir/{path:.*}", s.hMkdir)
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -107,6 +104,21 @@ 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" {
|
||||
|
@ -128,22 +140,17 @@ 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) hMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
path := filepath.Dir(mux.Vars(req)["path"])
|
||||
auth := s.readAccessConf(path)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Mkdir forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
name := req.FormValue("name")
|
||||
if strings.ContainsAny(name, "\\/:*<>|") {
|
||||
http.Error(w, "Name should not contains \\/:*<>|", http.StatusForbidden)
|
||||
|
||||
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)
|
||||
|
@ -155,23 +162,29 @@ func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
// only can delete file now
|
||||
path := mux.Vars(req)["path"]
|
||||
path = filepath.Clean(path) // for safe reason, prevent path contain ..
|
||||
auth := s.readAccessConf(path)
|
||||
log.Printf("%#v", auth)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Delete forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
err := os.Remove(filepath.Join(s.Root, path))
|
||||
|
||||
// TODO: path safe check
|
||||
err := os.RemoveAll(filepath.Join(s.Root, path))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
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) hUpload(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
dirpath := filepath.Join(s.Root, path)
|
||||
|
||||
|
@ -183,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)
|
||||
|
@ -193,21 +224,64 @@ func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
|
|||
req.MultipartForm.RemoveAll() // Seen from go source code, req.MultipartForm not nil after call FormFile(..)
|
||||
}()
|
||||
|
||||
// FIXME(ssx): should I check header.Filename here?
|
||||
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,
|
||||
|
@ -245,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)
|
||||
|
@ -267,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"
|
||||
}
|
||||
|
@ -294,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,
|
||||
}
|
||||
|
@ -339,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 {
|
||||
|
@ -349,6 +420,9 @@ func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
plistUrl = url
|
||||
} else {
|
||||
http.Error(w, "500: Server should be https:// or provide valid plistproxy", 500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
@ -744,3 +818,10 @@ func renderHTML(w http.ResponseWriter, name string, v interface{}) {
|
|||
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
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) {
|
||||
|
|
41
main.go
41
main.go
|
@ -24,23 +24,22 @@ import (
|
|||
)
|
||||
|
||||
type Configure struct {
|
||||
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"`
|
||||
GoogleTrackerID string `yaml:"google-tracker-id"`
|
||||
Auth struct {
|
||||
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"`
|
||||
|
@ -95,7 +94,6 @@ func parseFlags() error {
|
|||
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')
|
||||
|
@ -117,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
|
||||
|
||||
|
@ -147,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
|
||||
|
@ -160,6 +156,9 @@ 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
|
||||
|
||||
|
@ -177,6 +176,8 @@ func main() {
|
|||
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
|
||||
|
|
27
oauth2-proxy.go
Normal file
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)
|
||||
})
|
||||
}
|
9
utils.go
9
utils.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -71,3 +72,11 @@ func getLocalIP() 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
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.
|
21
vendor/github.com/DHowett/go-plist/README.md
generated
vendored
21
vendor/github.com/DHowett/go-plist/README.md
generated
vendored
|
@ -1,21 +0,0 @@
|
|||
# plist - A pure Go property list transcoder [![coverage report](https://gitlab.howett.net/go/plist/badges/master/coverage.svg)](https://gitlab.howett.net/go/plist/commits/master)
|
||||
## 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"})
|
||||
}
|
||||
```
|
26
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
26
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
|
@ -1,26 +0,0 @@
|
|||
package plist
|
||||
|
||||
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
|
||||
)
|
303
vendor/github.com/DHowett/go-plist/bplist_generator.go
generated
vendored
303
vendor/github.com/DHowett/go-plist/bplist_generator.go
generated
vendored
|
@ -1,303 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
func bplistMinimumIntSize(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
|
||||
}
|
||||
}
|
||||
|
||||
func bplistValueShouldUnique(pval cfValue) bool {
|
||||
switch pval.(type) {
|
||||
case cfString, *cfNumber, *cfReal, cfDate, cfData:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type bplistGenerator struct {
|
||||
writer *countedWriter
|
||||
objmap map[interface{}]uint64 // maps pValue.hash()es to object locations
|
||||
objtable []cfValue
|
||||
trailer bplistTrailer
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) flattenPlistValue(pval cfValue) {
|
||||
key := pval.hash()
|
||||
if bplistValueShouldUnique(pval) {
|
||||
if _, ok := p.objmap[key]; ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p.objmap[key] = uint64(len(p.objtable))
|
||||
p.objtable = append(p.objtable, pval)
|
||||
|
||||
switch pval := pval.(type) {
|
||||
case *cfDictionary:
|
||||
pval.sort()
|
||||
for _, k := range pval.keys {
|
||||
p.flattenPlistValue(cfString(k))
|
||||
}
|
||||
for _, v := range pval.values {
|
||||
p.flattenPlistValue(v)
|
||||
}
|
||||
case *cfArray:
|
||||
for _, v := range pval.values {
|
||||
p.flattenPlistValue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) indexForPlistValue(pval cfValue) (uint64, bool) {
|
||||
v, ok := p.objmap[pval.hash()]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) generateDocument(root cfValue) {
|
||||
p.objtable = make([]cfValue, 0, 16)
|
||||
p.objmap = make(map[interface{}]uint64)
|
||||
p.flattenPlistValue(root)
|
||||
|
||||
p.trailer.NumObjects = uint64(len(p.objtable))
|
||||
p.trailer.ObjectRefSize = uint8(bplistMinimumIntSize(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(bplistMinimumIntSize(uint64(p.writer.BytesWritten())))
|
||||
p.trailer.TopObject = p.objmap[root.hash()]
|
||||
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 cfValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch pval := pval.(type) {
|
||||
case *cfDictionary:
|
||||
p.writeDictionaryTag(pval)
|
||||
case *cfArray:
|
||||
p.writeArrayTag(pval.values)
|
||||
case cfString:
|
||||
p.writeStringTag(string(pval))
|
||||
case *cfNumber:
|
||||
p.writeIntTag(pval.signed, pval.value)
|
||||
case *cfReal:
|
||||
if pval.wide {
|
||||
p.writeRealTag(pval.value, 64)
|
||||
} else {
|
||||
p.writeRealTag(pval.value, 32)
|
||||
}
|
||||
case cfBoolean:
|
||||
p.writeBoolTag(bool(pval))
|
||||
case cfData:
|
||||
p.writeDataTag([]byte(pval))
|
||||
case cfDate:
|
||||
p.writeDateTag(time.Time(pval))
|
||||
case cfUID:
|
||||
p.writeUIDTag(UID(pval))
|
||||
default:
|
||||
panic(fmt.Errorf("unknown plist type %t", pval))
|
||||
}
|
||||
}
|
||||
|
||||
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(signed bool, 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
|
||||
case n > uint64(0x7fffffffffffffff) && !signed:
|
||||
// 64-bit values are always *signed* in format 00.
|
||||
// Any unsigned value that doesn't intersect with the signed
|
||||
// range must be sign-extended and stored as a SInt128
|
||||
val = n
|
||||
tag = bpTagInteger | 0x4
|
||||
default:
|
||||
val = n
|
||||
tag = bpTagInteger | 0x3
|
||||
}
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
if tag&0xF == 0x4 {
|
||||
// SInt128; in the absence of true 128-bit integers in Go,
|
||||
// we'll just fake the top half. We only got here because
|
||||
// we had an unsigned 64-bit int that didn't fit,
|
||||
// so sign extend it with zeroes.
|
||||
binary.Write(p.writer, binary.BigEndian, uint64(0))
|
||||
}
|
||||
binary.Write(p.writer, binary.BigEndian, val)
|
||||
}
|
||||
|
||||
func (p *bplistGenerator) writeUIDTag(u UID) {
|
||||
nbytes := bplistMinimumIntSize(uint64(u))
|
||||
tag := uint8(bpTagUID | (nbytes - 1))
|
||||
|
||||
binary.Write(p.writer, binary.BigEndian, tag)
|
||||
p.writeSizedInt(uint64(u), nbytes)
|
||||
}
|
||||
|
||||
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(false, 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 > 0x7F {
|
||||
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 *cfDictionary) {
|
||||
// assumption: sorted already; flattenPlistValue did this.
|
||||
cnt := len(dict.keys)
|
||||
p.writeCountedTag(bpTagDictionary, uint64(cnt))
|
||||
vals := make([]uint64, cnt*2)
|
||||
for i, k := range dict.keys {
|
||||
// invariant: keys have already been "uniqued" (as PStrings)
|
||||
keyIdx, ok := p.objmap[cfString(k).hash()]
|
||||
if !ok {
|
||||
panic(errors.New("failed to find key " + k + " in object map during serialization"))
|
||||
}
|
||||
vals[i] = keyIdx
|
||||
}
|
||||
|
||||
for i, v := range dict.values {
|
||||
// invariant: values have already been "uniqued"
|
||||
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 []cfValue) {
|
||||
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}},
|
||||
}
|
||||
}
|
353
vendor/github.com/DHowett/go-plist/bplist_parser.go
generated
vendored
353
vendor/github.com/DHowett/go-plist/bplist_parser.go
generated
vendored
|
@ -1,353 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const (
|
||||
signedHighBits = 0xFFFFFFFFFFFFFFFF
|
||||
)
|
||||
|
||||
type offset uint64
|
||||
|
||||
type bplistParser struct {
|
||||
buffer []byte
|
||||
|
||||
reader io.ReadSeeker
|
||||
version int
|
||||
objects []cfValue // object ID to object
|
||||
trailer bplistTrailer
|
||||
trailerOffset uint64
|
||||
|
||||
containerStack []offset // slice of object offsets; manipulated during container deserialization
|
||||
}
|
||||
|
||||
func (p *bplistParser) validateDocumentTrailer() {
|
||||
if p.trailer.OffsetTableOffset >= p.trailerOffset {
|
||||
panic(fmt.Errorf("offset table beyond beginning of trailer (0x%x, trailer@0x%x)", p.trailer.OffsetTableOffset, p.trailerOffset))
|
||||
}
|
||||
|
||||
if p.trailer.OffsetTableOffset < 9 {
|
||||
panic(fmt.Errorf("offset table begins inside header (0x%x)", p.trailer.OffsetTableOffset))
|
||||
}
|
||||
|
||||
if p.trailerOffset > (p.trailer.NumObjects*uint64(p.trailer.OffsetIntSize))+p.trailer.OffsetTableOffset {
|
||||
panic(errors.New("garbage between offset table and trailer"))
|
||||
}
|
||||
|
||||
if p.trailer.OffsetTableOffset+(uint64(p.trailer.OffsetIntSize)*p.trailer.NumObjects) > p.trailerOffset {
|
||||
panic(errors.New("offset table isn't long enough to address every object"))
|
||||
}
|
||||
|
||||
maxObjectRef := uint64(1) << (8 * p.trailer.ObjectRefSize)
|
||||
if p.trailer.NumObjects > maxObjectRef {
|
||||
panic(fmt.Errorf("more objects (%v) than object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize))
|
||||
}
|
||||
|
||||
if p.trailer.OffsetIntSize < uint8(8) && (uint64(1)<<(8*p.trailer.OffsetIntSize)) <= p.trailer.OffsetTableOffset {
|
||||
panic(errors.New("offset size isn't big enough to address entire file"))
|
||||
}
|
||||
|
||||
if p.trailer.TopObject >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("top object #%d is out of range (only %d exist)", p.trailer.TopObject, p.trailer.NumObjects))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseDocument() (pval cfValue, parseError error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
parseError = plistParseError{"binary", r.(error)}
|
||||
}
|
||||
}()
|
||||
|
||||
p.buffer, _ = ioutil.ReadAll(p.reader)
|
||||
|
||||
l := len(p.buffer)
|
||||
if l < 40 {
|
||||
panic(errors.New("not enough data"))
|
||||
}
|
||||
|
||||
if !bytes.Equal(p.buffer[0:6], []byte{'b', 'p', 'l', 'i', 's', 't'}) {
|
||||
panic(errors.New("incomprehensible magic"))
|
||||
}
|
||||
|
||||
p.version = int(((p.buffer[6] - '0') * 10) + (p.buffer[7] - '0'))
|
||||
|
||||
if p.version > 1 {
|
||||
panic(fmt.Errorf("unexpected version %d", p.version))
|
||||
}
|
||||
|
||||
p.trailerOffset = uint64(l - 32)
|
||||
p.trailer = bplistTrailer{
|
||||
SortVersion: p.buffer[p.trailerOffset+5],
|
||||
OffsetIntSize: p.buffer[p.trailerOffset+6],
|
||||
ObjectRefSize: p.buffer[p.trailerOffset+7],
|
||||
NumObjects: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+8:]),
|
||||
TopObject: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+16:]),
|
||||
OffsetTableOffset: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+24:]),
|
||||
}
|
||||
|
||||
p.validateDocumentTrailer()
|
||||
|
||||
// INVARIANTS:
|
||||
// - Entire offset table is before trailer
|
||||
// - Offset table begins after header
|
||||
// - Offset table can address entire document
|
||||
// - Object IDs are big enough to support the number of objects in this plist
|
||||
// - Top object is in range
|
||||
|
||||
p.objects = make([]cfValue, p.trailer.NumObjects)
|
||||
|
||||
pval = p.objectAtIndex(p.trailer.TopObject)
|
||||
return
|
||||
}
|
||||
|
||||
// parseSizedInteger returns a 128-bit integer as low64, high64
|
||||
func (p *bplistParser) parseSizedInteger(off offset, nbytes int) (lo uint64, hi uint64, newOffset offset) {
|
||||
// Per comments in CoreFoundation, format version 00 requires that all
|
||||
// 1, 2 or 4-byte integers be interpreted as unsigned. 8-byte integers are
|
||||
// signed (always?) and therefore must be sign extended here.
|
||||
// negative 1, 2, or 4-byte integers are always emitted as 64-bit.
|
||||
switch nbytes {
|
||||
case 1:
|
||||
lo, hi = uint64(p.buffer[off]), 0
|
||||
case 2:
|
||||
lo, hi = uint64(binary.BigEndian.Uint16(p.buffer[off:])), 0
|
||||
case 4:
|
||||
lo, hi = uint64(binary.BigEndian.Uint32(p.buffer[off:])), 0
|
||||
case 8:
|
||||
lo = binary.BigEndian.Uint64(p.buffer[off:])
|
||||
if p.buffer[off]&0x80 != 0 {
|
||||
// sign extend if lo is signed
|
||||
hi = signedHighBits
|
||||
}
|
||||
case 16:
|
||||
lo, hi = binary.BigEndian.Uint64(p.buffer[off+8:]), binary.BigEndian.Uint64(p.buffer[off:])
|
||||
default:
|
||||
panic(errors.New("illegal integer size"))
|
||||
}
|
||||
newOffset = off + offset(nbytes)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseObjectRefAtOffset(off offset) (uint64, offset) {
|
||||
oid, _, next := p.parseSizedInteger(off, int(p.trailer.ObjectRefSize))
|
||||
return oid, next
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseOffsetAtOffset(off offset) (offset, offset) {
|
||||
parsedOffset, _, next := p.parseSizedInteger(off, int(p.trailer.OffsetIntSize))
|
||||
return offset(parsedOffset), next
|
||||
}
|
||||
|
||||
func (p *bplistParser) objectAtIndex(index uint64) cfValue {
|
||||
if index >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("invalid object#%d (max %d)", index, p.trailer.NumObjects))
|
||||
}
|
||||
|
||||
if pval := p.objects[index]; pval != nil {
|
||||
return pval
|
||||
}
|
||||
|
||||
off, _ := p.parseOffsetAtOffset(offset(p.trailer.OffsetTableOffset + (index * uint64(p.trailer.OffsetIntSize))))
|
||||
if off > offset(p.trailer.OffsetTableOffset-1) {
|
||||
panic(fmt.Errorf("object#%d starts beyond beginning of object table (0x%x, table@0x%x)", index, off, p.trailer.OffsetTableOffset))
|
||||
}
|
||||
|
||||
pval := p.parseTagAtOffset(off)
|
||||
p.objects[index] = pval
|
||||
return pval
|
||||
|
||||
}
|
||||
|
||||
func (p *bplistParser) pushNestedObject(off offset) {
|
||||
for _, v := range p.containerStack {
|
||||
if v == off {
|
||||
p.panicNestedObject(off)
|
||||
}
|
||||
}
|
||||
p.containerStack = append(p.containerStack, off)
|
||||
}
|
||||
|
||||
func (p *bplistParser) panicNestedObject(off offset) {
|
||||
ids := ""
|
||||
for _, v := range p.containerStack {
|
||||
ids += fmt.Sprintf("0x%x > ", v)
|
||||
}
|
||||
|
||||
// %s0x%d: ids above ends with " > "
|
||||
panic(fmt.Errorf("self-referential collection@0x%x (%s0x%x) cannot be deserialized", off, ids, off))
|
||||
}
|
||||
|
||||
func (p *bplistParser) popNestedObject() {
|
||||
p.containerStack = p.containerStack[:len(p.containerStack)-1]
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseTagAtOffset(off offset) cfValue {
|
||||
tag := p.buffer[off]
|
||||
|
||||
switch tag & 0xF0 {
|
||||
case bpTagNull:
|
||||
switch tag & 0x0F {
|
||||
case bpTagBoolTrue, bpTagBoolFalse:
|
||||
return cfBoolean(tag == bpTagBoolTrue)
|
||||
}
|
||||
case bpTagInteger:
|
||||
lo, hi, _ := p.parseIntegerAtOffset(off)
|
||||
return &cfNumber{
|
||||
signed: hi == signedHighBits, // a signed integer is stored as a 128-bit integer with the top 64 bits set
|
||||
value: lo,
|
||||
}
|
||||
case bpTagReal:
|
||||
nbytes := 1 << (tag & 0x0F)
|
||||
switch nbytes {
|
||||
case 4:
|
||||
bits := binary.BigEndian.Uint32(p.buffer[off+1:])
|
||||
return &cfReal{wide: false, value: float64(math.Float32frombits(bits))}
|
||||
case 8:
|
||||
bits := binary.BigEndian.Uint64(p.buffer[off+1:])
|
||||
return &cfReal{wide: true, value: math.Float64frombits(bits)}
|
||||
}
|
||||
panic(errors.New("illegal float size"))
|
||||
case bpTagDate:
|
||||
bits := binary.BigEndian.Uint64(p.buffer[off+1:])
|
||||
val := math.Float64frombits(bits)
|
||||
|
||||
// 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 cfDate(time)
|
||||
case bpTagData:
|
||||
data := p.parseDataAtOffset(off)
|
||||
return cfData(data)
|
||||
case bpTagASCIIString:
|
||||
str := p.parseASCIIStringAtOffset(off)
|
||||
return cfString(str)
|
||||
case bpTagUTF16String:
|
||||
str := p.parseUTF16StringAtOffset(off)
|
||||
return cfString(str)
|
||||
case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes)
|
||||
lo, _, _ := p.parseSizedInteger(off+1, int(tag&0xF)+1)
|
||||
return cfUID(lo)
|
||||
case bpTagDictionary:
|
||||
return p.parseDictionaryAtOffset(off)
|
||||
case bpTagArray:
|
||||
return p.parseArrayAtOffset(off)
|
||||
}
|
||||
panic(fmt.Errorf("unexpected atom 0x%2.02x at offset 0x%x", tag, off))
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseIntegerAtOffset(off offset) (uint64, uint64, offset) {
|
||||
tag := p.buffer[off]
|
||||
return p.parseSizedInteger(off+1, 1<<(tag&0xF))
|
||||
}
|
||||
|
||||
func (p *bplistParser) countForTagAtOffset(off offset) (uint64, offset) {
|
||||
tag := p.buffer[off]
|
||||
cnt := uint64(tag & 0x0F)
|
||||
if cnt == 0xF {
|
||||
cnt, _, off = p.parseIntegerAtOffset(off + 1)
|
||||
return cnt, off
|
||||
}
|
||||
return cnt, off + 1
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseDataAtOffset(off offset) []byte {
|
||||
len, start := p.countForTagAtOffset(off)
|
||||
if start+offset(len) > offset(p.trailer.OffsetTableOffset) {
|
||||
panic(fmt.Errorf("data@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start)))
|
||||
}
|
||||
return p.buffer[start : start+offset(len)]
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseASCIIStringAtOffset(off offset) string {
|
||||
len, start := p.countForTagAtOffset(off)
|
||||
if start+offset(len) > offset(p.trailer.OffsetTableOffset) {
|
||||
panic(fmt.Errorf("ascii string@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start)))
|
||||
}
|
||||
|
||||
return zeroCopy8BitString(p.buffer, int(start), int(len))
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseUTF16StringAtOffset(off offset) string {
|
||||
len, start := p.countForTagAtOffset(off)
|
||||
bytes := len * 2
|
||||
if start+offset(bytes) > offset(p.trailer.OffsetTableOffset) {
|
||||
panic(fmt.Errorf("utf16 string@0x%x too long (%v bytes, max is %v)", off, bytes, p.trailer.OffsetTableOffset-uint64(start)))
|
||||
}
|
||||
|
||||
u16s := make([]uint16, len)
|
||||
for i := offset(0); i < offset(len); i++ {
|
||||
u16s[i] = binary.BigEndian.Uint16(p.buffer[start+(i*2):])
|
||||
}
|
||||
runes := utf16.Decode(u16s)
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseObjectListAtOffset(off offset, count uint64) []cfValue {
|
||||
if off+offset(count*uint64(p.trailer.ObjectRefSize)) > offset(p.trailer.OffsetTableOffset) {
|
||||
panic(fmt.Errorf("list@0x%x length (%v) puts its end beyond the offset table at 0x%x", off, count, p.trailer.OffsetTableOffset))
|
||||
}
|
||||
objects := make([]cfValue, count)
|
||||
|
||||
next := off
|
||||
var oid uint64
|
||||
for i := uint64(0); i < count; i++ {
|
||||
oid, next = p.parseObjectRefAtOffset(next)
|
||||
objects[i] = p.objectAtIndex(oid)
|
||||
}
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseDictionaryAtOffset(off offset) *cfDictionary {
|
||||
p.pushNestedObject(off)
|
||||
defer p.popNestedObject()
|
||||
|
||||
// a dictionary is an object list of [key key key val val val]
|
||||
cnt, start := p.countForTagAtOffset(off)
|
||||
objects := p.parseObjectListAtOffset(start, cnt*2)
|
||||
|
||||
keys := make([]string, cnt)
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
if str, ok := objects[i].(cfString); ok {
|
||||
keys[i] = string(str)
|
||||
} else {
|
||||
panic(fmt.Errorf("dictionary@0x%x contains non-string key at index %d", off, i))
|
||||
}
|
||||
}
|
||||
|
||||
return &cfDictionary{
|
||||
keys: keys,
|
||||
values: objects[cnt:],
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseArrayAtOffset(off offset) *cfArray {
|
||||
p.pushNestedObject(off)
|
||||
defer p.popNestedObject()
|
||||
|
||||
// an array is just an object list
|
||||
cnt, start := p.countForTagAtOffset(off)
|
||||
return &cfArray{p.parseObjectListAtOffset(start, cnt)}
|
||||
}
|
||||
|
||||
func newBplistParser(r io.ReadSeeker) *bplistParser {
|
||||
return &bplistParser{reader: r}
|
||||
}
|
119
vendor/github.com/DHowett/go-plist/decode.go
generated
vendored
119
vendor/github.com/DHowett/go-plist/decode.go
generated
vendored
|
@ -1,119 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type parser interface {
|
||||
parseDocument() (cfValue, 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 cfValue
|
||||
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
|
||||
// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64)
|
||||
// []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
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
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(cfValue)
|
||||
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
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
|
||||
}
|
186
vendor/github.com/DHowett/go-plist/marshal.go
generated
vendored
186
vendor/github.com/DHowett/go-plist/marshal.go
generated
vendored
|
@ -1,186 +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 (
|
||||
plistMarshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
)
|
||||
|
||||
func implementsInterface(val reflect.Value, interfaceType reflect.Type) (interface{}, bool) {
|
||||
if val.CanInterface() && val.Type().Implements(interfaceType) {
|
||||
return val.Interface(), true
|
||||
}
|
||||
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(interfaceType) {
|
||||
return pv.Interface(), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *Encoder) marshalPlistInterface(marshalable Marshaler) cfValue {
|
||||
value, err := marshalable.MarshalPlist()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.marshal(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// marshalTextInterface marshals a TextMarshaler to a plist string.
|
||||
func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) cfValue {
|
||||
s, err := marshalable.MarshalText()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfString(s)
|
||||
}
|
||||
|
||||
// marshalStruct marshals a reflected struct value to a plist dictionary
|
||||
func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) cfValue {
|
||||
tinfo, _ := getTypeInfo(typ)
|
||||
|
||||
dict := &cfDictionary{
|
||||
keys: make([]string, 0, len(tinfo.fields)),
|
||||
values: make([]cfValue, 0, len(tinfo.fields)),
|
||||
}
|
||||
for _, finfo := range tinfo.fields {
|
||||
value := finfo.value(val)
|
||||
if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) {
|
||||
continue
|
||||
}
|
||||
dict.keys = append(dict.keys, finfo.name)
|
||||
dict.values = append(dict.values, p.marshal(value))
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
func (p *Encoder) marshalTime(val reflect.Value) cfValue {
|
||||
time := val.Interface().(time.Time)
|
||||
return cfDate(time)
|
||||
}
|
||||
|
||||
func (p *Encoder) marshal(val reflect.Value) cfValue {
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if receiver, can := implementsInterface(val, plistMarshalerType); can {
|
||||
return p.marshalPlistInterface(receiver.(Marshaler))
|
||||
}
|
||||
|
||||
// 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 receiver, can := implementsInterface(val, textMarshalerType); can {
|
||||
return p.marshalTextInterface(receiver.(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 typ == uidType {
|
||||
return cfUID(val.Uint())
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.Struct {
|
||||
return p.marshalStruct(typ, val)
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
return cfString(val.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return &cfNumber{signed: true, value: uint64(val.Int())}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return &cfNumber{signed: false, value: val.Uint()}
|
||||
case reflect.Float32:
|
||||
return &cfReal{wide: false, value: val.Float()}
|
||||
case reflect.Float64:
|
||||
return &cfReal{wide: true, value: val.Float()}
|
||||
case reflect.Bool:
|
||||
return cfBoolean(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 cfData(bytes)
|
||||
} else {
|
||||
values := make([]cfValue, val.Len())
|
||||
for i, length := 0, val.Len(); i < length; i++ {
|
||||
if subpval := p.marshal(val.Index(i)); subpval != nil {
|
||||
values[i] = subpval
|
||||
}
|
||||
}
|
||||
return &cfArray{values}
|
||||
}
|
||||
case reflect.Map:
|
||||
if typ.Key().Kind() != reflect.String {
|
||||
panic(&unknownTypeError{typ})
|
||||
}
|
||||
|
||||
l := val.Len()
|
||||
dict := &cfDictionary{
|
||||
keys: make([]string, 0, l),
|
||||
values: make([]cfValue, 0, l),
|
||||
}
|
||||
for _, keyv := range val.MapKeys() {
|
||||
if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil {
|
||||
dict.keys = append(dict.keys, keyv.String())
|
||||
dict.values = append(dict.values, subpval)
|
||||
}
|
||||
}
|
||||
return dict
|
||||
default:
|
||||
panic(&unknownTypeError{typ})
|
||||
}
|
||||
}
|
50
vendor/github.com/DHowett/go-plist/must.go
generated
vendored
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
|
||||
}
|
85
vendor/github.com/DHowett/go-plist/plist.go
generated
vendored
85
vendor/github.com/DHowett/go-plist/plist.go
generated
vendored
|
@ -1,85 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from
|
||||
// that of integers.
|
||||
//
|
||||
// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists.
|
||||
type UID uint64
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves into valid
|
||||
// property list objects. The returned value is marshaled in place of the original value
|
||||
// implementing Marshaler
|
||||
//
|
||||
// If an error is returned by MarshalPlist, marshaling stops and the error is returned.
|
||||
type Marshaler interface {
|
||||
MarshalPlist() (interface{}, error)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by types that can unmarshal themselves from
|
||||
// property list objects. The UnmarshalPlist method receives a function that may
|
||||
// be called to unmarshal the original property list value into a field or variable.
|
||||
//
|
||||
// It is safe to call the unmarshal function more than once.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalPlist(unmarshal func(interface{}) error) error
|
||||
}
|
139
vendor/github.com/DHowett/go-plist/plist_types.go
generated
vendored
139
vendor/github.com/DHowett/go-plist/plist_types.go
generated
vendored
|
@ -1,139 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cfValue interface {
|
||||
typeName() string
|
||||
hash() interface{}
|
||||
}
|
||||
|
||||
type cfDictionary struct {
|
||||
keys sort.StringSlice
|
||||
values []cfValue
|
||||
}
|
||||
|
||||
func (*cfDictionary) typeName() string {
|
||||
return "dictionary"
|
||||
}
|
||||
|
||||
func (p *cfDictionary) hash() interface{} {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *cfDictionary) Len() int {
|
||||
return len(p.keys)
|
||||
}
|
||||
|
||||
func (p *cfDictionary) Less(i, j int) bool {
|
||||
return p.keys.Less(i, j)
|
||||
}
|
||||
|
||||
func (p *cfDictionary) Swap(i, j int) {
|
||||
p.keys.Swap(i, j)
|
||||
p.values[i], p.values[j] = p.values[j], p.values[i]
|
||||
}
|
||||
|
||||
func (p *cfDictionary) sort() {
|
||||
sort.Sort(p)
|
||||
}
|
||||
|
||||
type cfArray struct {
|
||||
values []cfValue
|
||||
}
|
||||
|
||||
func (*cfArray) typeName() string {
|
||||
return "array"
|
||||
}
|
||||
|
||||
func (p *cfArray) hash() interface{} {
|
||||
return p
|
||||
}
|
||||
|
||||
type cfString string
|
||||
|
||||
func (cfString) typeName() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (p cfString) hash() interface{} {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
type cfNumber struct {
|
||||
signed bool
|
||||
value uint64
|
||||
}
|
||||
|
||||
func (*cfNumber) typeName() string {
|
||||
return "integer"
|
||||
}
|
||||
|
||||
func (p *cfNumber) hash() interface{} {
|
||||
if p.signed {
|
||||
return int64(p.value)
|
||||
}
|
||||
return p.value
|
||||
}
|
||||
|
||||
type cfReal struct {
|
||||
wide bool
|
||||
value float64
|
||||
}
|
||||
|
||||
func (cfReal) typeName() string {
|
||||
return "real"
|
||||
}
|
||||
|
||||
func (p *cfReal) hash() interface{} {
|
||||
if p.wide {
|
||||
return p.value
|
||||
}
|
||||
return float32(p.value)
|
||||
}
|
||||
|
||||
type cfBoolean bool
|
||||
|
||||
func (cfBoolean) typeName() string {
|
||||
return "boolean"
|
||||
}
|
||||
|
||||
func (p cfBoolean) hash() interface{} {
|
||||
return bool(p)
|
||||
}
|
||||
|
||||
type cfUID UID
|
||||
|
||||
func (cfUID) typeName() string {
|
||||
return "UID"
|
||||
}
|
||||
|
||||
func (p cfUID) hash() interface{} {
|
||||
return p
|
||||
}
|
||||
|
||||
type cfData []byte
|
||||
|
||||
func (cfData) typeName() string {
|
||||
return "data"
|
||||
}
|
||||
|
||||
func (p cfData) hash() interface{} {
|
||||
// Data are uniqued by their checksums.
|
||||
// Todo: Look at calculating this only once and storing it somewhere;
|
||||
// crc32 is fairly quick, however.
|
||||
return crc32.ChecksumIEEE([]byte(p))
|
||||
}
|
||||
|
||||
type cfDate time.Time
|
||||
|
||||
func (cfDate) typeName() string {
|
||||
return "date"
|
||||
}
|
||||
|
||||
func (p cfDate) hash() interface{} {
|
||||
return time.Time(p)
|
||||
}
|
226
vendor/github.com/DHowett/go-plist/text_generator.go
generated
vendored
226
vendor/github.com/DHowett/go-plist/text_generator.go
generated
vendored
|
@ -1,226 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type textPlistGenerator struct {
|
||||
writer io.Writer
|
||||
format int
|
||||
|
||||
quotableTable *characterSet
|
||||
|
||||
indent string
|
||||
depth int
|
||||
|
||||
dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte
|
||||
}
|
||||
|
||||
var (
|
||||
textPlistTimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
padding = "0000"
|
||||
)
|
||||
|
||||
func (p *textPlistGenerator) generateDocument(pval cfValue) {
|
||||
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.ContainsByte(c) {
|
||||
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 cfValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch pval := pval.(type) {
|
||||
case *cfDictionary:
|
||||
pval.sort()
|
||||
p.writer.Write([]byte(`{`))
|
||||
p.deltaIndent(1)
|
||||
for i, k := range pval.keys {
|
||||
p.writeIndent()
|
||||
io.WriteString(p.writer, p.plistQuotedString(k))
|
||||
p.writer.Write(p.dictKvDelimiter)
|
||||
p.writePlistValue(pval.values[i])
|
||||
p.writer.Write(p.dictEntryDelimiter)
|
||||
}
|
||||
p.deltaIndent(-1)
|
||||
p.writeIndent()
|
||||
p.writer.Write([]byte(`}`))
|
||||
case *cfArray:
|
||||
p.writer.Write([]byte(`(`))
|
||||
p.deltaIndent(1)
|
||||
for _, v := range pval.values {
|
||||
p.writeIndent()
|
||||
p.writePlistValue(v)
|
||||
p.writer.Write(p.arrayDelimiter)
|
||||
}
|
||||
p.deltaIndent(-1)
|
||||
p.writeIndent()
|
||||
p.writer.Write([]byte(`)`))
|
||||
case cfString:
|
||||
io.WriteString(p.writer, p.plistQuotedString(string(pval)))
|
||||
case *cfNumber:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*I`))
|
||||
}
|
||||
if pval.signed {
|
||||
io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10))
|
||||
} else {
|
||||
io.WriteString(p.writer, strconv.FormatUint(pval.value, 10))
|
||||
}
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`>`))
|
||||
}
|
||||
case *cfReal:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*R`))
|
||||
}
|
||||
// GNUstep does not differentiate between 32/64-bit floats.
|
||||
io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64))
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`>`))
|
||||
}
|
||||
case cfBoolean:
|
||||
if p.format == GNUStepFormat {
|
||||
if pval {
|
||||
p.writer.Write([]byte(`<*BY>`))
|
||||
} else {
|
||||
p.writer.Write([]byte(`<*BN>`))
|
||||
}
|
||||
} else {
|
||||
if pval {
|
||||
p.writer.Write([]byte(`1`))
|
||||
} else {
|
||||
p.writer.Write([]byte(`0`))
|
||||
}
|
||||
}
|
||||
case cfData:
|
||||
var hexencoded [9]byte
|
||||
var l int
|
||||
var asc = 9
|
||||
hexencoded[8] = ' '
|
||||
|
||||
p.writer.Write([]byte(`<`))
|
||||
b := []byte(pval)
|
||||
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 cfDate:
|
||||
if p.format == GNUStepFormat {
|
||||
p.writer.Write([]byte(`<*D`))
|
||||
io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))
|
||||
p.writer.Write([]byte(`>`))
|
||||
} else {
|
||||
io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).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(`;`),
|
||||
}
|
||||
}
|
515
vendor/github.com/DHowett/go-plist/text_parser.go
generated
vendored
515
vendor/github.com/DHowett/go-plist/text_parser.go
generated
vendored
|
@ -1,515 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type textPlistParser struct {
|
||||
reader io.Reader
|
||||
format int
|
||||
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
width int
|
||||
}
|
||||
|
||||
func convertU16(buffer []byte, bo binary.ByteOrder) (string, error) {
|
||||
if len(buffer)%2 != 0 {
|
||||
return "", errors.New("truncated utf16")
|
||||
}
|
||||
|
||||
tmp := make([]uint16, len(buffer)/2)
|
||||
for i := 0; i < len(buffer); i += 2 {
|
||||
tmp[i/2] = bo.Uint16(buffer[i : i+2])
|
||||
}
|
||||
return string(utf16.Decode(tmp)), nil
|
||||
}
|
||||
|
||||
func guessEncodingAndConvert(buffer []byte) (string, error) {
|
||||
if len(buffer) >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF {
|
||||
// UTF-8 BOM
|
||||
return zeroCopy8BitString(buffer, 3, len(buffer)-3), nil
|
||||
} else if len(buffer) >= 2 {
|
||||
// UTF-16 guesses
|
||||
|
||||
switch {
|
||||
// stream is big-endian (BOM is FE FF or head is 00 XX)
|
||||
case (buffer[0] == 0xFE && buffer[1] == 0xFF):
|
||||
return convertU16(buffer[2:], binary.BigEndian)
|
||||
case (buffer[0] == 0 && buffer[1] != 0):
|
||||
return convertU16(buffer, binary.BigEndian)
|
||||
|
||||
// stream is little-endian (BOM is FE FF or head is XX 00)
|
||||
case (buffer[0] == 0xFF && buffer[1] == 0xFE):
|
||||
return convertU16(buffer[2:], binary.LittleEndian)
|
||||
case (buffer[0] != 0 && buffer[1] == 0):
|
||||
return convertU16(buffer, binary.LittleEndian)
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: assume ASCII (not great!)
|
||||
return zeroCopy8BitString(buffer, 0, len(buffer)), nil
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseDocument() (pval cfValue, parseError error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
// Wrap all non-invalid-plist errors.
|
||||
parseError = plistParseError{"text", r.(error)}
|
||||
}
|
||||
}()
|
||||
|
||||
buffer, err := ioutil.ReadAll(p.reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.input, err = guessEncodingAndConvert(buffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
val := p.parsePlistValue()
|
||||
|
||||
p.skipWhitespaceAndComments()
|
||||
if p.peek() != eof {
|
||||
if _, ok := val.(cfString); !ok {
|
||||
p.error("garbage after end of document")
|
||||
}
|
||||
|
||||
p.start = 0
|
||||
p.pos = 0
|
||||
val = p.parseDictionary(true)
|
||||
}
|
||||
|
||||
pval = val
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const eof rune = -1
|
||||
|
||||
func (p *textPlistParser) error(e string, args ...interface{}) {
|
||||
line := strings.Count(p.input[:p.pos], "\n")
|
||||
char := p.pos - strings.LastIndex(p.input[:p.pos], "\n") - 1
|
||||
panic(fmt.Errorf("%s at line %d character %d", fmt.Sprintf(e, args...), line, char))
|
||||
}
|
||||
|
||||
func (p *textPlistParser) next() rune {
|
||||
if int(p.pos) >= len(p.input) {
|
||||
p.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(p.input[p.pos:])
|
||||
p.width = w
|
||||
p.pos += p.width
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *textPlistParser) backup() {
|
||||
p.pos -= p.width
|
||||
}
|
||||
|
||||
func (p *textPlistParser) peek() rune {
|
||||
r := p.next()
|
||||
p.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *textPlistParser) emit() string {
|
||||
s := p.input[p.start:p.pos]
|
||||
p.start = p.pos
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *textPlistParser) ignore() {
|
||||
p.start = p.pos
|
||||
}
|
||||
|
||||
func (p *textPlistParser) empty() bool {
|
||||
return p.start == p.pos
|
||||
}
|
||||
|
||||
func (p *textPlistParser) scanUntil(ch rune) {
|
||||
if x := strings.IndexRune(p.input[p.pos:], ch); x >= 0 {
|
||||
p.pos += x
|
||||
return
|
||||
}
|
||||
p.pos = len(p.input)
|
||||
}
|
||||
|
||||
func (p *textPlistParser) scanUntilAny(chs string) {
|
||||
if x := strings.IndexAny(p.input[p.pos:], chs); x >= 0 {
|
||||
p.pos += x
|
||||
return
|
||||
}
|
||||
p.pos = len(p.input)
|
||||
}
|
||||
|
||||
func (p *textPlistParser) scanCharactersInSet(ch *characterSet) {
|
||||
for ch.Contains(p.next()) {
|
||||
}
|
||||
p.backup()
|
||||
}
|
||||
|
||||
func (p *textPlistParser) scanCharactersNotInSet(ch *characterSet) {
|
||||
var r rune
|
||||
for {
|
||||
r = p.next()
|
||||
if r == eof || ch.Contains(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.backup()
|
||||
}
|
||||
|
||||
func (p *textPlistParser) skipWhitespaceAndComments() {
|
||||
for {
|
||||
p.scanCharactersInSet(&whitespace)
|
||||
if strings.HasPrefix(p.input[p.pos:], "//") {
|
||||
p.scanCharactersNotInSet(&newlineCharacterSet)
|
||||
} else if strings.HasPrefix(p.input[p.pos:], "/*") {
|
||||
if x := strings.Index(p.input[p.pos:], "*/"); x >= 0 {
|
||||
p.pos += x + 2 // skip the */ as well
|
||||
continue // consume more whitespace
|
||||
} else {
|
||||
p.error("unexpected eof in block comment")
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.ignore()
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseOctalDigits(max int) uint64 {
|
||||
var val uint64
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
r := p.next()
|
||||
|
||||
if r >= '0' && r <= '7' {
|
||||
val <<= 3
|
||||
val |= uint64((r - '0'))
|
||||
} else {
|
||||
p.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseHexDigits(max int) uint64 {
|
||||
var val uint64
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
r := p.next()
|
||||
|
||||
if r >= 'a' && r <= 'f' {
|
||||
val <<= 4
|
||||
val |= 10 + uint64((r - 'a'))
|
||||
} else if r >= 'A' && r <= 'F' {
|
||||
val <<= 4
|
||||
val |= 10 + uint64((r - 'A'))
|
||||
} else if r >= '0' && r <= '9' {
|
||||
val <<= 4
|
||||
val |= uint64((r - '0'))
|
||||
} else {
|
||||
p.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// the \ has already been consumed
|
||||
func (p *textPlistParser) parseEscape() string {
|
||||
var s string
|
||||
switch p.next() {
|
||||
case 'a':
|
||||
s = "\a"
|
||||
case 'b':
|
||||
s = "\b"
|
||||
case 'v':
|
||||
s = "\v"
|
||||
case 'f':
|
||||
s = "\f"
|
||||
case 't':
|
||||
s = "\t"
|
||||
case 'r':
|
||||
s = "\r"
|
||||
case 'n':
|
||||
s = "\n"
|
||||
case '\\':
|
||||
s = `\`
|
||||
case '"':
|
||||
s = `"`
|
||||
case 'x':
|
||||
s = string(rune(p.parseHexDigits(2)))
|
||||
case 'u', 'U':
|
||||
s = string(rune(p.parseHexDigits(4)))
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
p.backup() // we've already consumed one of the digits
|
||||
s = string(rune(p.parseOctalDigits(3)))
|
||||
default:
|
||||
p.backup() // everything else should be accepted
|
||||
}
|
||||
p.ignore() // skip the entire escape sequence
|
||||
return s
|
||||
}
|
||||
|
||||
// the " has already been consumed
|
||||
func (p *textPlistParser) parseQuotedString() cfString {
|
||||
p.ignore() // ignore the "
|
||||
|
||||
slowPath := false
|
||||
s := ""
|
||||
|
||||
for {
|
||||
p.scanUntilAny(`"\`)
|
||||
switch p.peek() {
|
||||
case eof:
|
||||
p.error("unexpected eof in quoted string")
|
||||
case '"':
|
||||
section := p.emit()
|
||||
p.pos++ // skip "
|
||||
if !slowPath {
|
||||
return cfString(section)
|
||||
} else {
|
||||
s += section
|
||||
return cfString(s)
|
||||
}
|
||||
case '\\':
|
||||
slowPath = true
|
||||
s += p.emit()
|
||||
p.next() // consume \
|
||||
s += p.parseEscape()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseUnquotedString() cfString {
|
||||
p.scanCharactersNotInSet(&gsQuotable)
|
||||
s := p.emit()
|
||||
if s == "" {
|
||||
p.error("invalid unquoted string (found an unquoted character that should be quoted?)")
|
||||
}
|
||||
|
||||
return cfString(s)
|
||||
}
|
||||
|
||||
// the { has already been consumed
|
||||
func (p *textPlistParser) parseDictionary(ignoreEof bool) *cfDictionary {
|
||||
//p.ignore() // ignore the {
|
||||
var keypv cfValue
|
||||
keys := make([]string, 0, 32)
|
||||
values := make([]cfValue, 0, 32)
|
||||
outer:
|
||||
for {
|
||||
p.skipWhitespaceAndComments()
|
||||
|
||||
switch p.next() {
|
||||
case eof:
|
||||
if !ignoreEof {
|
||||
p.error("unexpected eof in dictionary")
|
||||
}
|
||||
fallthrough
|
||||
case '}':
|
||||
break outer
|
||||
case '"':
|
||||
keypv = p.parseQuotedString()
|
||||
default:
|
||||
p.backup()
|
||||
keypv = p.parseUnquotedString()
|
||||
}
|
||||
|
||||
// INVARIANT: key can't be nil; parseQuoted and parseUnquoted
|
||||
// will panic out before they return nil.
|
||||
|
||||
p.skipWhitespaceAndComments()
|
||||
|
||||
var val cfValue
|
||||
n := p.next()
|
||||
if n == ';' {
|
||||
val = keypv
|
||||
} else if n == '=' {
|
||||
// whitespace is consumed within
|
||||
val = p.parsePlistValue()
|
||||
|
||||
p.skipWhitespaceAndComments()
|
||||
|
||||
if p.next() != ';' {
|
||||
p.error("missing ; in dictionary")
|
||||
}
|
||||
} else {
|
||||
p.error("missing = in dictionary")
|
||||
}
|
||||
|
||||
keys = append(keys, string(keypv.(cfString)))
|
||||
values = append(values, val)
|
||||
}
|
||||
|
||||
return &cfDictionary{keys: keys, values: values}
|
||||
}
|
||||
|
||||
// the ( has already been consumed
|
||||
func (p *textPlistParser) parseArray() *cfArray {
|
||||
//p.ignore() // ignore the (
|
||||
values := make([]cfValue, 0, 32)
|
||||
outer:
|
||||
for {
|
||||
p.skipWhitespaceAndComments()
|
||||
|
||||
switch p.next() {
|
||||
case eof:
|
||||
p.error("unexpected eof in array")
|
||||
case ')':
|
||||
break outer // done here
|
||||
case ',':
|
||||
continue // restart; ,) is valid and we don't want to blow it
|
||||
default:
|
||||
p.backup()
|
||||
}
|
||||
|
||||
pval := p.parsePlistValue() // whitespace is consumed within
|
||||
if str, ok := pval.(cfString); ok && string(str) == "" {
|
||||
// Empty strings in arrays are apparently skipped?
|
||||
// TODO: Figure out why this was implemented.
|
||||
continue
|
||||
}
|
||||
values = append(values, pval)
|
||||
}
|
||||
return &cfArray{values}
|
||||
}
|
||||
|
||||
// the <* have already been consumed
|
||||
func (p *textPlistParser) parseGNUStepValue() cfValue {
|
||||
typ := p.next()
|
||||
p.ignore()
|
||||
p.scanUntil('>')
|
||||
|
||||
if typ == eof || typ == '>' || p.empty() || p.peek() == eof {
|
||||
p.error("invalid GNUStep extended value")
|
||||
}
|
||||
|
||||
v := p.emit()
|
||||
p.next() // consume the >
|
||||
|
||||
switch typ {
|
||||
case 'I':
|
||||
if v[0] == '-' {
|
||||
n := mustParseInt(v, 10, 64)
|
||||
return &cfNumber{signed: true, value: uint64(n)}
|
||||
} else {
|
||||
n := mustParseUint(v, 10, 64)
|
||||
return &cfNumber{signed: false, value: n}
|
||||
}
|
||||
case 'R':
|
||||
n := mustParseFloat(v, 64)
|
||||
return &cfReal{wide: true, value: n} // TODO(DH) 32/64
|
||||
case 'B':
|
||||
b := v[0] == 'Y'
|
||||
return cfBoolean(b)
|
||||
case 'D':
|
||||
t, err := time.Parse(textPlistTimeLayout, v)
|
||||
if err != nil {
|
||||
p.error(err.Error())
|
||||
}
|
||||
|
||||
return cfDate(t.In(time.UTC))
|
||||
}
|
||||
p.error("invalid GNUStep type " + string(typ))
|
||||
return nil
|
||||
}
|
||||
|
||||
// The < has already been consumed
|
||||
func (p *textPlistParser) parseHexData() cfData {
|
||||
buf := make([]byte, 256)
|
||||
i := 0
|
||||
c := 0
|
||||
|
||||
for {
|
||||
r := p.next()
|
||||
switch r {
|
||||
case eof:
|
||||
p.error("unexpected eof in data")
|
||||
case '>':
|
||||
if c&1 == 1 {
|
||||
p.error("uneven number of hex digits in data")
|
||||
}
|
||||
p.ignore()
|
||||
return cfData(buf[:i])
|
||||
case ' ', '\t', '\n', '\r', '\u2028', '\u2029': // more lax than apple here: skip spaces
|
||||
continue
|
||||
}
|
||||
|
||||
buf[i] <<= 4
|
||||
if r >= 'a' && r <= 'f' {
|
||||
buf[i] |= 10 + byte((r - 'a'))
|
||||
} else if r >= 'A' && r <= 'F' {
|
||||
buf[i] |= 10 + byte((r - 'A'))
|
||||
} else if r >= '0' && r <= '9' {
|
||||
buf[i] |= byte((r - '0'))
|
||||
} else {
|
||||
p.error("unexpected hex digit `%c'", r)
|
||||
}
|
||||
|
||||
c++
|
||||
if c&1 == 0 {
|
||||
i++
|
||||
if i >= len(buf) {
|
||||
realloc := make([]byte, len(buf)*2)
|
||||
copy(realloc, buf)
|
||||
buf = realloc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parsePlistValue() cfValue {
|
||||
for {
|
||||
p.skipWhitespaceAndComments()
|
||||
|
||||
switch p.next() {
|
||||
case eof:
|
||||
return &cfDictionary{}
|
||||
case '<':
|
||||
if p.next() == '*' {
|
||||
p.format = GNUStepFormat
|
||||
return p.parseGNUStepValue()
|
||||
}
|
||||
|
||||
p.backup()
|
||||
return p.parseHexData()
|
||||
case '"':
|
||||
return p.parseQuotedString()
|
||||
case '{':
|
||||
return p.parseDictionary(false)
|
||||
case '(':
|
||||
return p.parseArray()
|
||||
default:
|
||||
p.backup()
|
||||
return p.parseUnquotedString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTextPlistParser(r io.Reader) *textPlistParser {
|
||||
return &textPlistParser{
|
||||
reader: r,
|
||||
format: OpenStepFormat,
|
||||
}
|
||||
}
|
43
vendor/github.com/DHowett/go-plist/text_tables.go
generated
vendored
43
vendor/github.com/DHowett/go-plist/text_tables.go
generated
vendored
|
@ -1,43 +0,0 @@
|
|||
package plist
|
||||
|
||||
type characterSet [4]uint64
|
||||
|
||||
func (s *characterSet) Contains(ch rune) bool {
|
||||
return ch >= 0 && ch <= 255 && s.ContainsByte(byte(ch))
|
||||
}
|
||||
|
||||
func (s *characterSet) ContainsByte(ch byte) bool {
|
||||
return (s[ch/64]&(1<<(ch%64)) > 0)
|
||||
}
|
||||
|
||||
// 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 = characterSet{
|
||||
0x78001385ffffffff,
|
||||
0xa800000138000000,
|
||||
0xffffffffffffffff,
|
||||
0xffffffffffffffff,
|
||||
}
|
||||
|
||||
// 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it.
|
||||
var osQuotable = characterSet{
|
||||
0xf4007f6fffffffff,
|
||||
0xf8000001f8000001,
|
||||
0xffffffffffffffff,
|
||||
0xffffffffffffffff,
|
||||
}
|
||||
|
||||
var whitespace = characterSet{
|
||||
0x0000000100003f00,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
}
|
||||
|
||||
var newlineCharacterSet = characterSet{
|
||||
0x0000000000002400,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
0x0000000000000000,
|
||||
}
|
170
vendor/github.com/DHowett/go-plist/typeinfo.go
generated
vendored
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
|
||||
}
|
317
vendor/github.com/DHowett/go-plist/unmarshal.go
generated
vendored
317
vendor/github.com/DHowett/go-plist/unmarshal.go
generated
vendored
|
@ -1,317 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type incompatibleDecodeTypeError struct {
|
||||
dest reflect.Type
|
||||
src string // type name (from cfValue)
|
||||
}
|
||||
|
||||
func (u *incompatibleDecodeTypeError) Error() string {
|
||||
return fmt.Sprintf("plist: type mismatch: tried to decode plist type `%v' into value of type `%v'", u.src, u.dest)
|
||||
}
|
||||
|
||||
var (
|
||||
plistUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
uidType = reflect.TypeOf(UID(0))
|
||||
)
|
||||
|
||||
func isEmptyInterface(v reflect.Value) bool {
|
||||
return v.Kind() == reflect.Interface && v.NumMethod() == 0
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalPlistInterface(pval cfValue, unmarshalable Unmarshaler) {
|
||||
err := unmarshalable.UnmarshalPlist(func(i interface{}) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
panic(r)
|
||||
}
|
||||
err = r.(error)
|
||||
}
|
||||
}()
|
||||
p.unmarshal(pval, reflect.ValueOf(i))
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalTextInterface(pval cfString, unmarshalable encoding.TextUnmarshaler) {
|
||||
err := unmarshalable.UnmarshalText([]byte(pval))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalTime(pval cfDate, val reflect.Value) {
|
||||
val.Set(reflect.ValueOf(time.Time(pval)))
|
||||
}
|
||||
|
||||
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 cfValue, 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.typeName()}
|
||||
|
||||
// time.Time implements TextMarshaler, but we need to parse it as RFC3339
|
||||
if date, ok := pval.(cfDate); ok {
|
||||
if val.Type() == timeType {
|
||||
p.unmarshalTime(date, val)
|
||||
return
|
||||
}
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
|
||||
if receiver, can := implementsInterface(val, plistUnmarshalerType); can {
|
||||
p.unmarshalPlistInterface(pval, receiver.(Unmarshaler))
|
||||
return
|
||||
}
|
||||
|
||||
if val.Type() != timeType {
|
||||
if receiver, can := implementsInterface(val, textUnmarshalerType); can {
|
||||
if str, ok := pval.(cfString); ok {
|
||||
p.unmarshalTextInterface(str, receiver.(encoding.TextUnmarshaler))
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
switch pval := pval.(type) {
|
||||
case cfString:
|
||||
if val.Kind() == reflect.String {
|
||||
val.SetString(string(pval))
|
||||
return
|
||||
}
|
||||
if p.lax {
|
||||
p.unmarshalLaxString(string(pval), val)
|
||||
return
|
||||
}
|
||||
|
||||
panic(incompatibleTypeError)
|
||||
case *cfNumber:
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val.SetInt(int64(pval.value))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
val.SetUint(pval.value)
|
||||
default:
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case *cfReal:
|
||||
if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 {
|
||||
// TODO: Consider warning on a downcast (storing a 64-bit value in a 32-bit reflect)
|
||||
val.SetFloat(pval.value)
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case cfBoolean:
|
||||
if val.Kind() == reflect.Bool {
|
||||
val.SetBool(bool(pval))
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case cfData:
|
||||
if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 {
|
||||
val.SetBytes([]byte(pval))
|
||||
} else {
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
case cfUID:
|
||||
if val.Type() == uidType {
|
||||
val.SetUint(uint64(pval))
|
||||
} else {
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val.SetInt(int64(pval))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
val.SetUint(uint64(pval))
|
||||
default:
|
||||
panic(incompatibleTypeError)
|
||||
}
|
||||
}
|
||||
case *cfArray:
|
||||
p.unmarshalArray(pval, val)
|
||||
case *cfDictionary:
|
||||
p.unmarshalDictionary(pval, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalArray(a *cfArray, val reflect.Value) {
|
||||
var n int
|
||||
if val.Kind() == reflect.Slice {
|
||||
// Slice of element values.
|
||||
// Grow slice.
|
||||
cnt := len(a.values) + 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(a.values) > val.Cap() {
|
||||
panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(a.values), val.Cap()))
|
||||
}
|
||||
} else {
|
||||
panic(&incompatibleDecodeTypeError{val.Type(), a.typeName()})
|
||||
}
|
||||
|
||||
// Recur to read element into slice.
|
||||
for _, sval := range a.values {
|
||||
p.unmarshal(sval, val.Index(n))
|
||||
n++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Decoder) unmarshalDictionary(dict *cfDictionary, val reflect.Value) {
|
||||
typ := val.Type()
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
tinfo, err := getTypeInfo(typ)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
entries := make(map[string]cfValue, len(dict.keys))
|
||||
for i, k := range dict.keys {
|
||||
sval := dict.values[i]
|
||||
entries[k] = sval
|
||||
}
|
||||
|
||||
for _, finfo := range tinfo.fields {
|
||||
p.unmarshal(entries[finfo.name], finfo.value(val))
|
||||
}
|
||||
case reflect.Map:
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.MakeMap(typ))
|
||||
}
|
||||
|
||||
for i, k := range dict.keys {
|
||||
sval := dict.values[i]
|
||||
|
||||
keyv := reflect.ValueOf(k).Convert(typ.Key())
|
||||
mapElem := reflect.New(typ.Elem()).Elem()
|
||||
|
||||
p.unmarshal(sval, mapElem)
|
||||
val.SetMapIndex(keyv, mapElem)
|
||||
}
|
||||
default:
|
||||
panic(&incompatibleDecodeTypeError{typ, dict.typeName()})
|
||||
}
|
||||
}
|
||||
|
||||
/* *Interface is modelled after encoding/json */
|
||||
func (p *Decoder) valueInterface(pval cfValue) interface{} {
|
||||
switch pval := pval.(type) {
|
||||
case cfString:
|
||||
return string(pval)
|
||||
case *cfNumber:
|
||||
if pval.signed {
|
||||
return int64(pval.value)
|
||||
}
|
||||
return pval.value
|
||||
case *cfReal:
|
||||
if pval.wide {
|
||||
return pval.value
|
||||
} else {
|
||||
return float32(pval.value)
|
||||
}
|
||||
case cfBoolean:
|
||||
return bool(pval)
|
||||
case *cfArray:
|
||||
return p.arrayInterface(pval)
|
||||
case *cfDictionary:
|
||||
return p.dictionaryInterface(pval)
|
||||
case cfData:
|
||||
return []byte(pval)
|
||||
case cfDate:
|
||||
return time.Time(pval)
|
||||
case cfUID:
|
||||
return UID(pval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Decoder) arrayInterface(a *cfArray) []interface{} {
|
||||
out := make([]interface{}, len(a.values))
|
||||
for i, subv := range a.values {
|
||||
out[i] = p.valueInterface(subv)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Decoder) dictionaryInterface(dict *cfDictionary) map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
for i, k := range dict.keys {
|
||||
subv := dict.values[i]
|
||||
out[k] = p.valueInterface(subv)
|
||||
}
|
||||
return out
|
||||
}
|
25
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
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
|
||||
}
|
185
vendor/github.com/DHowett/go-plist/xml_generator.go
generated
vendored
185
vendor/github.com/DHowett/go-plist/xml_generator.go
generated
vendored
|
@ -1,185 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
xmlHEADER string = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
|
||||
xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">` + "\n"
|
||||
xmlArrayTag = "array"
|
||||
xmlDataTag = "data"
|
||||
xmlDateTag = "date"
|
||||
xmlDictTag = "dict"
|
||||
xmlFalseTag = "false"
|
||||
xmlIntegerTag = "integer"
|
||||
xmlKeyTag = "key"
|
||||
xmlPlistTag = "plist"
|
||||
xmlRealTag = "real"
|
||||
xmlStringTag = "string"
|
||||
xmlTrueTag = "true"
|
||||
|
||||
// magic value used in the XML encoding of UIDs
|
||||
// (stored as a dictionary mapping CF$UID->integer)
|
||||
xmlCFUIDMagic = "CF$UID"
|
||||
)
|
||||
|
||||
func formatXMLFloat(f float64) string {
|
||||
switch {
|
||||
case math.IsInf(f, 1):
|
||||
return "inf"
|
||||
case math.IsInf(f, -1):
|
||||
return "-inf"
|
||||
case math.IsNaN(f):
|
||||
return "nan"
|
||||
}
|
||||
return strconv.FormatFloat(f, 'g', -1, 64)
|
||||
}
|
||||
|
||||
type xmlPlistGenerator struct {
|
||||
*bufio.Writer
|
||||
|
||||
indent string
|
||||
depth int
|
||||
putNewline bool
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) generateDocument(root cfValue) {
|
||||
p.WriteString(xmlHEADER)
|
||||
p.WriteString(xmlDOCTYPE)
|
||||
|
||||
p.openTag(`plist version="1.0"`)
|
||||
p.writePlistValue(root)
|
||||
p.closeTag(xmlPlistTag)
|
||||
p.Flush()
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) openTag(n string) {
|
||||
p.writeIndent(1)
|
||||
p.WriteByte('<')
|
||||
p.WriteString(n)
|
||||
p.WriteByte('>')
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) closeTag(n string) {
|
||||
p.writeIndent(-1)
|
||||
p.WriteString("</")
|
||||
p.WriteString(n)
|
||||
p.WriteByte('>')
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) element(n string, v string) {
|
||||
p.writeIndent(0)
|
||||
if len(v) == 0 {
|
||||
p.WriteByte('<')
|
||||
p.WriteString(n)
|
||||
p.WriteString("/>")
|
||||
} else {
|
||||
p.WriteByte('<')
|
||||
p.WriteString(n)
|
||||
p.WriteByte('>')
|
||||
|
||||
err := xml.EscapeText(p.Writer, []byte(v))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.WriteString("</")
|
||||
p.WriteString(n)
|
||||
p.WriteByte('>')
|
||||
}
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) {
|
||||
dict.sort()
|
||||
p.openTag(xmlDictTag)
|
||||
for i, k := range dict.keys {
|
||||
p.element(xmlKeyTag, k)
|
||||
p.writePlistValue(dict.values[i])
|
||||
}
|
||||
p.closeTag(xmlDictTag)
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) writeArray(a *cfArray) {
|
||||
p.openTag(xmlArrayTag)
|
||||
for _, v := range a.values {
|
||||
p.writePlistValue(v)
|
||||
}
|
||||
p.closeTag(xmlArrayTag)
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
|
||||
if pval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch pval := pval.(type) {
|
||||
case cfString:
|
||||
p.element(xmlStringTag, string(pval))
|
||||
case *cfNumber:
|
||||
if pval.signed {
|
||||
p.element(xmlIntegerTag, strconv.FormatInt(int64(pval.value), 10))
|
||||
} else {
|
||||
p.element(xmlIntegerTag, strconv.FormatUint(pval.value, 10))
|
||||
}
|
||||
case *cfReal:
|
||||
p.element(xmlRealTag, formatXMLFloat(pval.value))
|
||||
case cfBoolean:
|
||||
if bool(pval) {
|
||||
p.element(xmlTrueTag, "")
|
||||
} else {
|
||||
p.element(xmlFalseTag, "")
|
||||
}
|
||||
case cfData:
|
||||
p.element(xmlDataTag, base64.StdEncoding.EncodeToString([]byte(pval)))
|
||||
case cfDate:
|
||||
p.element(xmlDateTag, time.Time(pval).In(time.UTC).Format(time.RFC3339))
|
||||
case *cfDictionary:
|
||||
p.writeDictionary(pval)
|
||||
case *cfArray:
|
||||
p.writeArray(pval)
|
||||
case cfUID:
|
||||
p.openTag(xmlDictTag)
|
||||
p.element(xmlKeyTag, xmlCFUIDMagic)
|
||||
p.element(xmlIntegerTag, strconv.FormatUint(uint64(pval), 10))
|
||||
p.closeTag(xmlDictTag)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) writeIndent(delta int) {
|
||||
if len(p.indent) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if delta < 0 {
|
||||
p.depth--
|
||||
}
|
||||
|
||||
if p.putNewline {
|
||||
// from encoding/xml/marshal.go; it seems to be intended
|
||||
// to suppress the first newline.
|
||||
p.WriteByte('\n')
|
||||
} else {
|
||||
p.putNewline = true
|
||||
}
|
||||
for i := 0; i < p.depth; i++ {
|
||||
p.WriteString(p.indent)
|
||||
}
|
||||
if delta > 0 {
|
||||
p.depth++
|
||||
}
|
||||
}
|
||||
|
||||
func (p *xmlPlistGenerator) Indent(i string) {
|
||||
p.indent = i
|
||||
}
|
||||
|
||||
func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
|
||||
return &xmlPlistGenerator{Writer: bufio.NewWriter(w)}
|
||||
}
|
216
vendor/github.com/DHowett/go-plist/xml_parser.go
generated
vendored
216
vendor/github.com/DHowett/go-plist/xml_parser.go
generated
vendored
|
@ -1,216 +0,0 @@
|
|||
package plist
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type xmlPlistParser struct {
|
||||
reader io.Reader
|
||||
xmlDecoder *xml.Decoder
|
||||
whitespaceReplacer *strings.Replacer
|
||||
ntags int
|
||||
}
|
||||
|
||||
func (p *xmlPlistParser) parseDocument() (pval cfValue, 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) cfValue {
|
||||
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 cfString(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 &cfNumber{signed: true, value: uint64(n)}
|
||||
} else {
|
||||
s, base := unsignedGetBase(s)
|
||||
n := mustParseUint(s, base, 64)
|
||||
return &cfNumber{signed: false, value: n}
|
||||
}
|
||||
case "real":
|
||||
p.ntags++
|
||||
err := p.xmlDecoder.DecodeElement(&charData, &element)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
n := mustParseFloat(string(charData), 64)
|
||||
return &cfReal{wide: true, value: n}
|
||||
case "true", "false":
|
||||
p.ntags++
|
||||
p.xmlDecoder.Skip()
|
||||
|
||||
b := element.Name.Local == "true"
|
||||
return cfBoolean(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 cfDate(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 cfData(bytes[:l])
|
||||
case "dict":
|
||||
p.ntags++
|
||||
var key *string
|
||||
keys := make([]string, 0, 32)
|
||||
values := make([]cfValue, 0, 32)
|
||||
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"))
|
||||
}
|
||||
keys = append(keys, *key)
|
||||
values = append(values, p.parseXMLElement(el))
|
||||
key = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 {
|
||||
if integer, ok := values[0].(*cfNumber); ok {
|
||||
return cfUID(integer.value)
|
||||
}
|
||||
}
|
||||
|
||||
return &cfDictionary{keys: keys, values: values}
|
||||
case "array":
|
||||
p.ntags++
|
||||
values := make([]cfValue, 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 {
|
||||
values = append(values, p.parseXMLElement(el))
|
||||
}
|
||||
}
|
||||
return &cfArray{values}
|
||||
}
|
||||
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}
|
||||
}
|
20
vendor/github.com/DHowett/go-plist/zerocopy.go
generated
vendored
20
vendor/github.com/DHowett/go-plist/zerocopy.go
generated
vendored
|
@ -1,20 +0,0 @@
|
|||
// +build !appengine
|
||||
|
||||
package plist
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func zeroCopy8BitString(buf []byte, off int, len int) string {
|
||||
if len == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var s string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = uintptr(unsafe.Pointer(&buf[off]))
|
||||
hdr.Len = len
|
||||
return s
|
||||
}
|
7
vendor/github.com/DHowett/go-plist/zerocopy_appengine.go
generated
vendored
7
vendor/github.com/DHowett/go-plist/zerocopy_appengine.go
generated
vendored
|
@ -1,7 +0,0 @@
|
|||
// +build appengine
|
||||
|
||||
package plist
|
||||
|
||||
func zeroCopy8BitString(buf []byte, off int, len int) string {
|
||||
return string(buf[off : off+len])
|
||||
}
|
19
vendor/github.com/alecthomas/kingpin/COPYING
generated
vendored
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.
|
674
vendor/github.com/alecthomas/kingpin/README.md
generated
vendored
674
vendor/github.com/alecthomas/kingpin/README.md
generated
vendored
|
@ -1,674 +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.svg?branch=master)](https://travis-ci.org/alecthomas/kingpin) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby)
|
||||
|
||||
|
||||
|
||||
<!-- 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 5
|
||||
```
|
||||
|
||||
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\n", *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()` (see [parsers.go](./parsers.go) for a complete list of included parsers).
|
||||
|
||||
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 = &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() []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
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
|
||||
}
|
688
vendor/github.com/alecthomas/kingpin/app.go
generated
vendored
688
vendor/github.com/alecthomas/kingpin/app.go
generated
vendored
|
@ -1,688 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCommandNotSpecified = fmt.Errorf("command not specified")
|
||||
)
|
||||
|
||||
var (
|
||||
envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`)
|
||||
)
|
||||
|
||||
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
|
||||
errorWriter io.Writer // Destination for errors.
|
||||
usageWriter io.Writer // Destination for usage
|
||||
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,
|
||||
errorWriter: os.Stderr, // Left for backwards compatibility purposes.
|
||||
usageWriter: 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
|
||||
}
|
||||
|
||||
// Writer specifies the writer to use for usage and errors. Defaults to os.Stderr.
|
||||
// DEPRECATED: See ErrorWriter and UsageWriter.
|
||||
func (a *Application) Writer(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
a.usageWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// ErrorWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) ErrorWriter(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) UsageWriter(w io.Writer) *Application {
|
||||
a.usageWriter = 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)
|
||||
}
|
||||
if err != nil {
|
||||
a.terminate(1)
|
||||
} else {
|
||||
a.terminate(0)
|
||||
}
|
||||
}
|
||||
|
||||
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.usageWriter, version)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.VersionFlag.Bool()
|
||||
return a
|
||||
}
|
||||
|
||||
// Author sets the author output by some help templates.
|
||||
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 {
|
||||
if flag.name == "help" {
|
||||
return nil
|
||||
}
|
||||
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.errorWriter, 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...)
|
||||
// Force usage to go to error output.
|
||||
a.usageWriter = a.errorWriter
|
||||
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 {
|
||||
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, "_"))
|
||||
}
|
184
vendor/github.com/alecthomas/kingpin/args.go
generated
vendored
184
vendor/github.com/alecthomas/kingpin/args.go
generated
vendored
|
@ -1,184 +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())
|
||||
}
|
||||
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
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
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
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.v2"
|
||||
//
|
||||
// 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
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
|
||||
}
|
308
vendor/github.com/alecthomas/kingpin/flags.go
generated
vendored
308
vendor/github.com/alecthomas/kingpin/flags.go
generated
vendored
|
@ -1,308 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type flagGroup struct {
|
||||
short map[string]*FlagClause
|
||||
long map[string]*FlagClause
|
||||
flagOrder []*FlagClause
|
||||
}
|
||||
|
||||
func newFlagGroup() *flagGroup {
|
||||
return &flagGroup{
|
||||
short: map[string]*FlagClause{},
|
||||
long: map[string]*FlagClause{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetFlag gets a flag definition.
|
||||
//
|
||||
// This allows existing flags to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
||||
return f.long[name]
|
||||
}
|
||||
|
||||
// Flag defines a new flag with the given long name and help.
|
||||
func (f *flagGroup) Flag(name, help string) *FlagClause {
|
||||
flag := newFlag(name, help)
|
||||
f.long[name] = flag
|
||||
f.flagOrder = append(f.flagOrder, flag)
|
||||
return flag
|
||||
}
|
||||
|
||||
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||
if err := f.checkDuplicates(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, flag := range f.long {
|
||||
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
||||
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
||||
}
|
||||
if err := flag.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if flag.shorthand != 0 {
|
||||
f.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) checkDuplicates() error {
|
||||
seenShort := map[rune]bool{}
|
||||
seenLong := map[string]bool{}
|
||||
for _, flag := range f.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := seenShort[flag.shorthand]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
seenShort[flag.shorthand] = true
|
||||
}
|
||||
if _, ok := seenLong[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
seenLong[flag.name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
||||
var token *Token
|
||||
|
||||
loop:
|
||||
for {
|
||||
token = context.Peek()
|
||||
switch token.Type {
|
||||
case TokenEOL:
|
||||
break loop
|
||||
|
||||
case TokenLong, TokenShort:
|
||||
flagToken := token
|
||||
defaultValue := ""
|
||||
var flag *FlagClause
|
||||
var ok bool
|
||||
invert := false
|
||||
|
||||
name := token.Value
|
||||
if token.Type == TokenLong {
|
||||
flag, ok = f.long[name]
|
||||
if !ok {
|
||||
if strings.HasPrefix(name, "no-") {
|
||||
name = name[3:]
|
||||
invert = true
|
||||
}
|
||||
flag, ok = f.long[name]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
} else {
|
||||
flag, ok = f.short[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown short flag '%s'", flagToken)
|
||||
}
|
||||
}
|
||||
|
||||
context.Next()
|
||||
|
||||
fb, ok := flag.value.(boolFlag)
|
||||
if ok && fb.IsBoolFlag() {
|
||||
if invert {
|
||||
defaultValue = "false"
|
||||
} else {
|
||||
defaultValue = "true"
|
||||
}
|
||||
} else {
|
||||
if invert {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
token = context.Peek()
|
||||
if token.Type != TokenArg {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("expected argument for flag '%s'", flagToken)
|
||||
}
|
||||
context.Next()
|
||||
defaultValue = token.Value
|
||||
}
|
||||
|
||||
context.matchedFlag(flag, defaultValue)
|
||||
return flag, nil
|
||||
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FlagClause is a fluid interface used to build flags.
|
||||
type FlagClause struct {
|
||||
parserMixin
|
||||
actionMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
shorthand rune
|
||||
help string
|
||||
defaultValues []string
|
||||
placeholder string
|
||||
hidden bool
|
||||
}
|
||||
|
||||
func newFlag(name, help string) *FlagClause {
|
||||
f := &FlagClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) setDefault() error {
|
||||
if f.HasEnvarValue() {
|
||||
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return f.value.Set(f.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range f.GetSplitEnvarValue() {
|
||||
if err := f.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.defaultValues) > 0 {
|
||||
for _, defaultValue := range f.defaultValues {
|
||||
if err := f.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagClause) needsValue() bool {
|
||||
haveDefault := len(f.defaultValues) > 0
|
||||
return f.required && !(haveDefault || f.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (f *FlagClause) init() error {
|
||||
if f.required && len(f.defaultValues) > 0 {
|
||||
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
||||
}
|
||||
if f.value == nil {
|
||||
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
||||
}
|
||||
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
||||
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch to the given function after the flag is parsed and validated.
|
||||
func (f *FlagClause) Action(action Action) *FlagClause {
|
||||
f.addAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) PreAction(action Action) *FlagClause {
|
||||
f.addPreAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the flag to provide completions
|
||||
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
||||
a.parserMixin.EnumVar(target, options...)
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FlagClause) Enum(options ...string) (target *string) {
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
return a.parserMixin.Enum(options...)
|
||||
}
|
||||
|
||||
// Default values for this flag. They *must* be parseable by the value of the flag.
|
||||
func (f *FlagClause) Default(values ...string) *FlagClause {
|
||||
f.defaultValues = values
|
||||
return f
|
||||
}
|
||||
|
||||
// DEPRECATED: Use Envar(name) instead.
|
||||
func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
||||
return f.Envar(envar)
|
||||
}
|
||||
|
||||
// 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 (f *FlagClause) Envar(name string) *FlagClause {
|
||||
f.envar = name
|
||||
f.noEnvar = false
|
||||
return f
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (f *FlagClause) NoEnvar() *FlagClause {
|
||||
f.envar = ""
|
||||
f.noEnvar = true
|
||||
return f
|
||||
}
|
||||
|
||||
// PlaceHolder sets the place-holder string used for flag values in the help. The
|
||||
// default behaviour is to use the value provided by Default() if provided,
|
||||
// then fall back on the capitalized flag name.
|
||||
func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause {
|
||||
f.placeholder = placeholder
|
||||
return f
|
||||
}
|
||||
|
||||
// Hidden hides a flag from usage but still allows it to be used.
|
||||
func (f *FlagClause) Hidden() *FlagClause {
|
||||
f.hidden = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Required makes the flag required. You can not provide a Default() value to a Required() flag.
|
||||
func (f *FlagClause) Required() *FlagClause {
|
||||
f.required = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Short sets the short flag name.
|
||||
func (f *FlagClause) Short(name rune) *FlagClause {
|
||||
f.shorthand = name
|
||||
return f
|
||||
}
|
||||
|
||||
// Bool makes this flag a boolean flag.
|
||||
func (f *FlagClause) Bool() (target *bool) {
|
||||
target = new(bool)
|
||||
f.SetValue(newBoolValue(target))
|
||||
return
|
||||
}
|
96
vendor/github.com/alecthomas/kingpin/global.go
generated
vendored
96
vendor/github.com/alecthomas/kingpin/global.go
generated
vendored
|
@ -1,96 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
// CommandLine is the default Kingpin parser.
|
||||
CommandLine = New(filepath.Base(os.Args[0]), "")
|
||||
// Global help flag. Exposed for user customisation.
|
||||
HelpFlag = CommandLine.HelpFlag
|
||||
// Top-level help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand = CommandLine.HelpCommand
|
||||
// Global version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag = CommandLine.VersionFlag
|
||||
// Whether to file expansion with '@' is enabled.
|
||||
EnableFileExpansion = true
|
||||
)
|
||||
|
||||
// Command adds a new command to the default parser.
|
||||
func Command(name, help string) *CmdClause {
|
||||
return CommandLine.Command(name, help)
|
||||
}
|
||||
|
||||
// Flag adds a new flag to the default parser.
|
||||
func Flag(name, help string) *FlagClause {
|
||||
return CommandLine.Flag(name, help)
|
||||
}
|
||||
|
||||
// Arg adds a new argument to the top-level of the default parser.
|
||||
func Arg(name, help string) *ArgClause {
|
||||
return CommandLine.Arg(name, help)
|
||||
}
|
||||
|
||||
// Parse and return the selected command. Will call the termination handler if
|
||||
// an error is encountered.
|
||||
func Parse() string {
|
||||
selected := MustParse(CommandLine.Parse(os.Args[1:]))
|
||||
if selected == "" && CommandLine.cmdGroup.have() {
|
||||
Usage()
|
||||
CommandLine.terminate(0)
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
// Errorf prints an error message to stderr.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
CommandLine.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf prints an error message to stderr and exits.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
CommandLine.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given prefix.
|
||||
func FatalIfError(err error, format string, args ...interface{}) {
|
||||
CommandLine.FatalIfError(err, format, args...)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func FatalUsage(format string, args ...interface{}) {
|
||||
CommandLine.FatalUsage(format, args...)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to stderr, then
|
||||
// usage information for the given ParseContext, before exiting.
|
||||
func FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
CommandLine.FatalUsageContext(context, format, args...)
|
||||
}
|
||||
|
||||
// Usage prints usage to stderr.
|
||||
func Usage() {
|
||||
CommandLine.Usage(os.Args[1:])
|
||||
}
|
||||
|
||||
// Set global usage template to use (defaults to DefaultUsageTemplate).
|
||||
func UsageTemplate(template string) *Application {
|
||||
return CommandLine.UsageTemplate(template)
|
||||
}
|
||||
|
||||
// MustParse can be used with app.Parse(args) to exit with an error if parsing fails.
|
||||
func MustParse(command string, err error) string {
|
||||
if err != nil {
|
||||
Fatalf("%s, try --help", err)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// Version adds a flag for displaying the application version number.
|
||||
func Version(version string) *Application {
|
||||
return CommandLine.Version(version)
|
||||
}
|
9
vendor/github.com/alecthomas/kingpin/guesswidth.go
generated
vendored
9
vendor/github.com/alecthomas/kingpin/guesswidth.go
generated
vendored
|
@ -1,9 +0,0 @@
|
|||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import "io"
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
return 80
|
||||
}
|
38
vendor/github.com/alecthomas/kingpin/guesswidth_unix.go
generated
vendored
38
vendor/github.com/alecthomas/kingpin/guesswidth_unix.go
generated
vendored
|
@ -1,38 +0,0 @@
|
|||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
// check if COLUMNS env is set to comply with
|
||||
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
|
||||
colsStr := os.Getenv("COLUMNS")
|
||||
if colsStr != "" {
|
||||
if cols, err := strconv.Atoi(colsStr); err == nil {
|
||||
return cols
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := w.(*os.File); ok {
|
||||
fd := t.Fd()
|
||||
var dimensions [4]uint16
|
||||
|
||||
if _, _, err := syscall.Syscall6(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(syscall.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&dimensions)),
|
||||
0, 0, 0,
|
||||
); err == 0 {
|
||||
return int(dimensions[1])
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
241
vendor/github.com/alecthomas/kingpin/model.go
generated
vendored
241
vendor/github.com/alecthomas/kingpin/model.go
generated
vendored
|
@ -1,241 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Data model for Kingpin command-line structure.
|
||||
|
||||
var (
|
||||
ignoreInCount = map[string]bool{
|
||||
"help": true,
|
||||
"help-long": true,
|
||||
"help-man": true,
|
||||
"completion-bash": true,
|
||||
"completion-script-bash": true,
|
||||
"completion-script-zsh": true,
|
||||
}
|
||||
)
|
||||
|
||||
type FlagGroupModel struct {
|
||||
Flags []*FlagModel
|
||||
}
|
||||
|
||||
func (f *FlagGroupModel) FlagSummary() string {
|
||||
out := []string{}
|
||||
count := 0
|
||||
|
||||
for _, flag := range f.Flags {
|
||||
|
||||
if !ignoreInCount[flag.Name] {
|
||||
count++
|
||||
}
|
||||
|
||||
if flag.Required {
|
||||
if flag.IsBoolFlag() {
|
||||
out = append(out, fmt.Sprintf("--[no-]%s", flag.Name))
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder()))
|
||||
}
|
||||
}
|
||||
}
|
||||
if count != len(out) {
|
||||
out = append(out, "[<flags>]")
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type FlagModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Short rune
|
||||
Default []string
|
||||
Envar string
|
||||
PlaceHolder string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (f *FlagModel) String() string {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
func (f *FlagModel) IsBoolFlag() bool {
|
||||
if fl, ok := f.Value.(boolFlag); ok {
|
||||
return fl.IsBoolFlag()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FlagModel) FormatPlaceHolder() string {
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder
|
||||
}
|
||||
if len(f.Default) > 0 {
|
||||
ellipsis := ""
|
||||
if len(f.Default) > 1 {
|
||||
ellipsis = "..."
|
||||
}
|
||||
if _, ok := f.Value.(*stringValue); ok {
|
||||
return strconv.Quote(f.Default[0]) + ellipsis
|
||||
}
|
||||
return f.Default[0] + ellipsis
|
||||
}
|
||||
return strings.ToUpper(f.Name)
|
||||
}
|
||||
|
||||
type ArgGroupModel struct {
|
||||
Args []*ArgModel
|
||||
}
|
||||
|
||||
func (a *ArgGroupModel) ArgSummary() string {
|
||||
depth := 0
|
||||
out := []string{}
|
||||
for _, arg := range a.Args {
|
||||
h := "<" + arg.Name + ">"
|
||||
if !arg.Required {
|
||||
h = "[" + h
|
||||
depth++
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth)
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type ArgModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Default []string
|
||||
Envar string
|
||||
Required bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (a *ArgModel) String() string {
|
||||
return a.Value.String()
|
||||
}
|
||||
|
||||
type CmdGroupModel struct {
|
||||
Commands []*CmdModel
|
||||
}
|
||||
|
||||
func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
|
||||
for _, cmd := range c.Commands {
|
||||
if len(cmd.Commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.FlattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CmdModel struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Help string
|
||||
FullCommand string
|
||||
Depth int
|
||||
Hidden bool
|
||||
Default bool
|
||||
*FlagGroupModel
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
}
|
||||
|
||||
func (c *CmdModel) String() string {
|
||||
return c.FullCommand
|
||||
}
|
||||
|
||||
type ApplicationModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Version string
|
||||
Author string
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
*FlagGroupModel
|
||||
}
|
||||
|
||||
func (a *Application) Model() *ApplicationModel {
|
||||
return &ApplicationModel{
|
||||
Name: a.Name,
|
||||
Help: a.Help,
|
||||
Version: a.version,
|
||||
Author: a.author,
|
||||
FlagGroupModel: a.flagGroup.Model(),
|
||||
ArgGroupModel: a.argGroup.Model(),
|
||||
CmdGroupModel: a.cmdGroup.Model(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *argGroup) Model() *ArgGroupModel {
|
||||
m := &ArgGroupModel{}
|
||||
for _, arg := range a.args {
|
||||
m.Args = append(m.Args, arg.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (a *ArgClause) Model() *ArgModel {
|
||||
return &ArgModel{
|
||||
Name: a.name,
|
||||
Help: a.help,
|
||||
Default: a.defaultValues,
|
||||
Envar: a.envar,
|
||||
Required: a.required,
|
||||
Value: a.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *flagGroup) Model() *FlagGroupModel {
|
||||
m := &FlagGroupModel{}
|
||||
for _, fl := range f.flagOrder {
|
||||
m.Flags = append(m.Flags, fl.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *FlagClause) Model() *FlagModel {
|
||||
return &FlagModel{
|
||||
Name: f.name,
|
||||
Help: f.help,
|
||||
Short: rune(f.shorthand),
|
||||
Default: f.defaultValues,
|
||||
Envar: f.envar,
|
||||
PlaceHolder: f.placeholder,
|
||||
Required: f.required,
|
||||
Hidden: f.hidden,
|
||||
Value: f.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) Model() *CmdGroupModel {
|
||||
m := &CmdGroupModel{}
|
||||
for _, cm := range c.commandOrder {
|
||||
m.Commands = append(m.Commands, cm.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *CmdClause) Model() *CmdModel {
|
||||
depth := 0
|
||||
for i := c; i != nil; i = i.parent {
|
||||
depth++
|
||||
}
|
||||
return &CmdModel{
|
||||
Name: c.name,
|
||||
Aliases: c.aliases,
|
||||
Help: c.help,
|
||||
Depth: depth,
|
||||
Hidden: c.hidden,
|
||||
Default: c.isDefault,
|
||||
FullCommand: c.FullCommand(),
|
||||
FlagGroupModel: c.flagGroup.Model(),
|
||||
ArgGroupModel: c.argGroup.Model(),
|
||||
CmdGroupModel: c.cmdGroup.Model(),
|
||||
}
|
||||
}
|
396
vendor/github.com/alecthomas/kingpin/parser.go
generated
vendored
396
vendor/github.com/alecthomas/kingpin/parser.go
generated
vendored
|
@ -1,396 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type TokenType int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
TokenShort TokenType = iota
|
||||
TokenLong
|
||||
TokenArg
|
||||
TokenError
|
||||
TokenEOL
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case TokenShort:
|
||||
return "short flag"
|
||||
case TokenLong:
|
||||
return "long flag"
|
||||
case TokenArg:
|
||||
return "argument"
|
||||
case TokenError:
|
||||
return "error"
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
var (
|
||||
TokenEOLMarker = Token{-1, TokenEOL, ""}
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Index int
|
||||
Type TokenType
|
||||
Value string
|
||||
}
|
||||
|
||||
func (t *Token) Equal(o *Token) bool {
|
||||
return t.Index == o.Index
|
||||
}
|
||||
|
||||
func (t *Token) IsFlag() bool {
|
||||
return t.Type == TokenShort || t.Type == TokenLong
|
||||
}
|
||||
|
||||
func (t *Token) IsEOF() bool {
|
||||
return t.Type == TokenEOL
|
||||
}
|
||||
|
||||
func (t *Token) String() string {
|
||||
switch t.Type {
|
||||
case TokenShort:
|
||||
return "-" + t.Value
|
||||
case TokenLong:
|
||||
return "--" + t.Value
|
||||
case TokenArg:
|
||||
return t.Value
|
||||
case TokenError:
|
||||
return "error: " + t.Value
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
default:
|
||||
panic("unhandled type")
|
||||
}
|
||||
}
|
||||
|
||||
// A union of possible elements in a parse stack.
|
||||
type ParseElement struct {
|
||||
// Clause is either *CmdClause, *ArgClause or *FlagClause.
|
||||
Clause interface{}
|
||||
// Value is corresponding value for an ArgClause or FlagClause (if any).
|
||||
Value *string
|
||||
}
|
||||
|
||||
// ParseContext holds the current context of the parser. When passed to
|
||||
// Action() callbacks Elements will be fully populated with *FlagClause,
|
||||
// *ArgClause and *CmdClause values and their corresponding arguments (if
|
||||
// any).
|
||||
type ParseContext struct {
|
||||
SelectedCommand *CmdClause
|
||||
ignoreDefault bool
|
||||
argsOnly bool
|
||||
peek []*Token
|
||||
argi int // Index of current command-line arg we're processing.
|
||||
args []string
|
||||
rawArgs []string
|
||||
flags *flagGroup
|
||||
arguments *argGroup
|
||||
argumenti int // Cursor into arguments
|
||||
// Flags, arguments and commands encountered and collected during parse.
|
||||
Elements []*ParseElement
|
||||
}
|
||||
|
||||
func (p *ParseContext) nextArg() *ArgClause {
|
||||
if p.argumenti >= len(p.arguments.args) {
|
||||
return nil
|
||||
}
|
||||
arg := p.arguments.args[p.argumenti]
|
||||
if !arg.consumesRemainder() {
|
||||
p.argumenti++
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func (p *ParseContext) next() {
|
||||
p.argi++
|
||||
p.args = p.args[1:]
|
||||
}
|
||||
|
||||
// HasTrailingArgs returns true if there are unparsed command-line arguments.
|
||||
// This can occur if the parser can not match remaining arguments.
|
||||
func (p *ParseContext) HasTrailingArgs() bool {
|
||||
return len(p.args) > 0
|
||||
}
|
||||
|
||||
func tokenize(args []string, ignoreDefault bool) *ParseContext {
|
||||
return &ParseContext{
|
||||
ignoreDefault: ignoreDefault,
|
||||
args: args,
|
||||
rawArgs: args,
|
||||
flags: newFlagGroup(),
|
||||
arguments: newArgGroup(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeFlags(flags *flagGroup) {
|
||||
for _, flag := range flags.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
p.flags.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
p.flags.long[flag.name] = flag
|
||||
p.flags.flagOrder = append(p.flags.flagOrder, flag)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeArgs(args *argGroup) {
|
||||
for _, arg := range args.args {
|
||||
p.arguments.args = append(p.arguments.args, arg)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) EOL() bool {
|
||||
return p.Peek().Type == TokenEOL
|
||||
}
|
||||
|
||||
func (p *ParseContext) Error() bool {
|
||||
return p.Peek().Type == TokenError
|
||||
}
|
||||
|
||||
// Next token in the parse context.
|
||||
func (p *ParseContext) Next() *Token {
|
||||
if len(p.peek) > 0 {
|
||||
return p.pop()
|
||||
}
|
||||
|
||||
// End of tokens.
|
||||
if len(p.args) == 0 {
|
||||
return &Token{Index: p.argi, Type: TokenEOL}
|
||||
}
|
||||
|
||||
arg := p.args[0]
|
||||
p.next()
|
||||
|
||||
if p.argsOnly {
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
// All remaining args are passed directly.
|
||||
if arg == "--" {
|
||||
p.argsOnly = true
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
parts := strings.SplitN(arg[2:], "=", 2)
|
||||
token := &Token{p.argi, TokenLong, parts[0]}
|
||||
if len(parts) == 2 {
|
||||
p.Push(&Token{p.argi, TokenArg, parts[1]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
if len(arg) == 1 {
|
||||
return &Token{Index: p.argi, Type: TokenShort}
|
||||
}
|
||||
shortRune, size := utf8.DecodeRuneInString(arg[1:])
|
||||
short := string(shortRune)
|
||||
flag, ok := p.flags.short[short]
|
||||
// Not a known short flag, we'll just return it anyway.
|
||||
if !ok {
|
||||
} else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
// Bool short flag.
|
||||
} else {
|
||||
// Short flag with combined argument: -fARG
|
||||
token := &Token{p.argi, TokenShort, short}
|
||||
if len(arg) > size+1 {
|
||||
p.Push(&Token{p.argi, TokenArg, arg[size+1:]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if len(arg) > size+1 {
|
||||
p.args = append([]string{"-" + arg[size+1:]}, p.args...)
|
||||
}
|
||||
return &Token{p.argi, TokenShort, short}
|
||||
} else if EnableFileExpansion && strings.HasPrefix(arg, "@") {
|
||||
expanded, err := ExpandArgsFromFile(arg[1:])
|
||||
if err != nil {
|
||||
return &Token{p.argi, TokenError, err.Error()}
|
||||
}
|
||||
if len(p.args) == 0 {
|
||||
p.args = expanded
|
||||
} else {
|
||||
p.args = append(expanded, p.args...)
|
||||
}
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
func (p *ParseContext) Peek() *Token {
|
||||
if len(p.peek) == 0 {
|
||||
return p.Push(p.Next())
|
||||
}
|
||||
return p.peek[len(p.peek)-1]
|
||||
}
|
||||
|
||||
func (p *ParseContext) Push(token *Token) *Token {
|
||||
p.peek = append(p.peek, token)
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) pop() *Token {
|
||||
end := len(p.peek) - 1
|
||||
token := p.peek[end]
|
||||
p.peek = p.peek[0:end]
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) String() string {
|
||||
return p.SelectedCommand.FullCommand()
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedFlag(flag *FlagClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedArg(arg *ArgClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedCmd(cmd *CmdClause) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: cmd})
|
||||
p.mergeFlags(cmd.flagGroup)
|
||||
p.mergeArgs(cmd.argGroup)
|
||||
p.SelectedCommand = cmd
|
||||
}
|
||||
|
||||
// Expand arguments from a file. Lines starting with # will be treated as comments.
|
||||
func ExpandArgsFromFile(filename string) (out []string, err error) {
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("expected @ file to expand arguments from")
|
||||
}
|
||||
r, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open arguments file %q: %s", filename, err)
|
||||
}
|
||||
defer r.Close()
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read arguments from %q: %s", filename, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parse(context *ParseContext, app *Application) (err error) {
|
||||
context.mergeFlags(app.flagGroup)
|
||||
context.mergeArgs(app.argGroup)
|
||||
|
||||
cmds := app.cmdGroup
|
||||
ignoreDefault := context.ignoreDefault
|
||||
|
||||
loop:
|
||||
for !context.EOL() && !context.Error() {
|
||||
token := context.Peek()
|
||||
|
||||
switch token.Type {
|
||||
case TokenLong, TokenShort:
|
||||
if flag, err := context.flags.parse(context); err != nil {
|
||||
if !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
} else if flag == HelpFlag {
|
||||
ignoreDefault = true
|
||||
}
|
||||
|
||||
case TokenArg:
|
||||
if cmds.have() {
|
||||
selectedDefault := false
|
||||
cmd, ok := cmds.commands[token.String()]
|
||||
if !ok {
|
||||
if !ignoreDefault {
|
||||
if cmd = cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
selectedDefault = true
|
||||
}
|
||||
}
|
||||
if cmd == nil {
|
||||
return fmt.Errorf("expected command but got %q", token)
|
||||
}
|
||||
}
|
||||
if cmd == HelpCommand {
|
||||
ignoreDefault = true
|
||||
}
|
||||
cmd.completionAlts = nil
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
if !selectedDefault {
|
||||
context.Next()
|
||||
}
|
||||
} else if context.arguments.have() {
|
||||
if app.noInterspersed {
|
||||
// no more flags
|
||||
context.argsOnly = true
|
||||
}
|
||||
arg := context.nextArg()
|
||||
if arg == nil {
|
||||
break loop
|
||||
}
|
||||
context.matchedArg(arg, token.String())
|
||||
context.Next()
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
|
||||
case TokenEOL:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Move to innermost default command.
|
||||
for !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if context.Error() {
|
||||
return fmt.Errorf("%s", context.Peek().Value)
|
||||
}
|
||||
|
||||
if !context.EOL() {
|
||||
return fmt.Errorf("unexpected %s", context.Peek())
|
||||
}
|
||||
|
||||
// Set defaults for all remaining args.
|
||||
for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() {
|
||||
for _, defaultValue := range arg.defaultValues {
|
||||
if err := arg.value.Set(defaultValue); err != nil {
|
||||
return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
212
vendor/github.com/alecthomas/kingpin/parsers.go
generated
vendored
212
vendor/github.com/alecthomas/kingpin/parsers.go
generated
vendored
|
@ -1,212 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
type Settings interface {
|
||||
SetValue(value Value)
|
||||
}
|
||||
|
||||
type parserMixin struct {
|
||||
value Value
|
||||
required bool
|
||||
}
|
||||
|
||||
func (p *parserMixin) SetValue(value Value) {
|
||||
p.value = value
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMap() (target *map[string]string) {
|
||||
target = &(map[string]string{})
|
||||
p.StringMapVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) Duration() (target *time.Duration) {
|
||||
target = new(time.Duration)
|
||||
p.DurationVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Bytes parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) Bytes() (target *units.Base2Bytes) {
|
||||
target = new(units.Base2Bytes)
|
||||
p.BytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.IPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCP (host:port) address.
|
||||
func (p *parserMixin) TCP() (target **net.TCPAddr) {
|
||||
target = new(*net.TCPAddr)
|
||||
p.TCPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCPVar (host:port) address.
|
||||
func (p *parserMixin) TCPVar(target **net.TCPAddr) {
|
||||
p.SetValue(newTCPAddrValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFile() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory.
|
||||
func (p *parserMixin) ExistingFileOrDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileOrDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File returns an os.File against an existing file.
|
||||
func (p *parserMixin) File() (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.FileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File attempts to open a File with os.OpenFile(flag, perm).
|
||||
func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.OpenFileVar(target, flag, perm)
|
||||
return
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URL() (target **url.URL) {
|
||||
target = new(*url.URL)
|
||||
p.URLVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMapVar(target *map[string]string) {
|
||||
p.SetValue(newStringMapValue(target))
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) Float() (target *float64) {
|
||||
return p.Float64()
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) FloatVar(target *float64) {
|
||||
p.Float64Var(target)
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) DurationVar(target *time.Duration) {
|
||||
p.SetValue(newDurationValue(target))
|
||||
}
|
||||
|
||||
// BytesVar parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) BytesVar(target *units.Base2Bytes) {
|
||||
p.SetValue(newBytesValue(target))
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IPVar(target *net.IP) {
|
||||
p.SetValue(newIPValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFileVar(target *string) {
|
||||
p.SetValue(newExistingFileValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDirVar(target *string) {
|
||||
p.SetValue(newExistingDirValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingFileOrDirVar(target *string) {
|
||||
p.SetValue(newExistingFileOrDirValue(target))
|
||||
}
|
||||
|
||||
// FileVar opens an existing file.
|
||||
func (p *parserMixin) FileVar(target **os.File) {
|
||||
p.SetValue(newFileValue(target, os.O_RDONLY, 0))
|
||||
}
|
||||
|
||||
// OpenFileVar calls os.OpenFile(flag, perm)
|
||||
func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) {
|
||||
p.SetValue(newFileValue(target, flag, perm))
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URLVar(target **url.URL) {
|
||||
p.SetValue(newURLValue(target))
|
||||
}
|
||||
|
||||
// URLList provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLList() (target *[]*url.URL) {
|
||||
target = new([]*url.URL)
|
||||
p.URLListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// URLListVar provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLListVar(target *[]*url.URL) {
|
||||
p.SetValue(newURLListValue(target))
|
||||
}
|
||||
|
||||
// Enum allows a value from a set of options.
|
||||
func (p *parserMixin) Enum(options ...string) (target *string) {
|
||||
target = new(string)
|
||||
p.EnumVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumVar(target *string, options ...string) {
|
||||
p.SetValue(newEnumFlag(target, options...))
|
||||
}
|
||||
|
||||
// Enums allows a set of values from a set of options.
|
||||
func (p *parserMixin) Enums(options ...string) (target *[]string) {
|
||||
target = new([]string)
|
||||
p.EnumsVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumsVar(target *[]string, options ...string) {
|
||||
p.SetValue(newEnumsFlag(target, options...))
|
||||
}
|
||||
|
||||
// A Counter increments a number each time it is encountered.
|
||||
func (p *parserMixin) Counter() (target *int) {
|
||||
target = new(int)
|
||||
p.CounterVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) CounterVar(target *int) {
|
||||
p.SetValue(newCounterValue(target))
|
||||
}
|
262
vendor/github.com/alecthomas/kingpin/templates.go
generated
vendored
262
vendor/github.com/alecthomas/kingpin/templates.go
generated
vendored
|
@ -1,262 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
// Default usage template.
|
||||
var DefaultUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
{{if len .Context.SelectedCommand.Commands}}\
|
||||
Subcommands:
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Usage template where command's optional flags are listed separately
|
||||
var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.Flags|RequiredFlags}}\
|
||||
Required flags:
|
||||
{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags|OptionalFlags}}\
|
||||
Optional flags:
|
||||
{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
Subcommands:
|
||||
{{if .Context.SelectedCommand.Commands}}\
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Usage template with compactly formatted commands.
|
||||
var CompactUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommandList"}}\
|
||||
{{range .}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{end}}\
|
||||
{{template "FormatCommandList" .Commands}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
{{if .Context.SelectedCommand.Commands}}\
|
||||
Commands:
|
||||
{{.Context.SelectedCommand}}
|
||||
{{template "FormatCommandList" .Context.SelectedCommand.Commands}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommandList" .App.Commands}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
var ManPageTemplate = `{{define "FormatFlags"}}\
|
||||
{{range .Flags}}\
|
||||
{{if not .Hidden}}\
|
||||
.TP
|
||||
\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR
|
||||
{{.Help}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}{{if .Default}}*{{end}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
.SS
|
||||
\fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR
|
||||
.PP
|
||||
{{.Help}}
|
||||
{{template "FormatFlags" .}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}\\fR
|
||||
{{end}}\
|
||||
|
||||
.TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}"
|
||||
.SH "NAME"
|
||||
{{.App.Name}}
|
||||
.SH "SYNOPSIS"
|
||||
.TP
|
||||
\fB{{.App.Name}}{{template "FormatUsage" .App}}
|
||||
.SH "DESCRIPTION"
|
||||
{{.App.Help}}
|
||||
.SH "OPTIONS"
|
||||
{{template "FormatFlags" .App}}\
|
||||
{{if .App.Commands}}\
|
||||
.SH "COMMANDS"
|
||||
{{template "FormatCommands" .App}}\
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Default usage template.
|
||||
var LongHelpTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
var BashCompletionTemplate = `
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
|
||||
`
|
||||
|
||||
var ZshCompletionTemplate = `
|
||||
#compdef {{.App.Name}}
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
`
|
211
vendor/github.com/alecthomas/kingpin/usage.go
generated
vendored
211
vendor/github.com/alecthomas/kingpin/usage.go
generated
vendored
|
@ -1,211 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/doc"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
)
|
||||
|
||||
var (
|
||||
preIndent = " "
|
||||
)
|
||||
|
||||
func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) {
|
||||
// Find size of first column.
|
||||
s := 0
|
||||
for _, row := range rows {
|
||||
if c := len(row[0]); c > s && c < 30 {
|
||||
s = c
|
||||
}
|
||||
}
|
||||
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
offsetStr := strings.Repeat(" ", s+padding)
|
||||
|
||||
for _, row := range rows {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent)
|
||||
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
||||
fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "")
|
||||
if len(row[0]) >= 30 {
|
||||
fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", lines[0])
|
||||
for _, line := range lines[1:] {
|
||||
fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage writes application usage to w. It parses args to determine
|
||||
// appropriate help context, such as which command to show help for.
|
||||
func (a *Application) Usage(args []string) {
|
||||
context, err := a.parseContext(true, args)
|
||||
a.FatalIfError(err, "")
|
||||
if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func formatAppUsage(app *ApplicationModel) string {
|
||||
s := []string{app.Name}
|
||||
if len(app.Flags) > 0 {
|
||||
s = append(s, app.FlagSummary())
|
||||
}
|
||||
if len(app.Args) > 0 {
|
||||
s = append(s, app.ArgSummary())
|
||||
}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string {
|
||||
s := []string{app.Name, cmd.String()}
|
||||
if len(app.Flags) > 0 {
|
||||
s = append(s, app.FlagSummary())
|
||||
}
|
||||
if len(app.Args) > 0 {
|
||||
s = append(s, app.ArgSummary())
|
||||
}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
func formatFlag(haveShort bool, flag *FlagModel) string {
|
||||
flagString := ""
|
||||
if flag.Short != 0 {
|
||||
flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name)
|
||||
} else {
|
||||
if haveShort {
|
||||
flagString += fmt.Sprintf(" --%s", flag.Name)
|
||||
} else {
|
||||
flagString += fmt.Sprintf("--%s", flag.Name)
|
||||
}
|
||||
}
|
||||
if !flag.IsBoolFlag() {
|
||||
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
|
||||
}
|
||||
if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() {
|
||||
flagString += " ..."
|
||||
}
|
||||
return flagString
|
||||
}
|
||||
|
||||
type templateParseContext struct {
|
||||
SelectedCommand *CmdModel
|
||||
*FlagGroupModel
|
||||
*ArgGroupModel
|
||||
}
|
||||
|
||||
type templateContext struct {
|
||||
App *ApplicationModel
|
||||
Width int
|
||||
Context *templateParseContext
|
||||
}
|
||||
|
||||
// UsageForContext displays usage information from a ParseContext (obtained from
|
||||
// Application.ParseContext() or Action(f) callbacks).
|
||||
func (a *Application) UsageForContext(context *ParseContext) error {
|
||||
return a.UsageForContextWithTemplate(context, 2, a.usageTemplate)
|
||||
}
|
||||
|
||||
// UsageForContextWithTemplate is the base usage function. You generally don't need to use this.
|
||||
func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error {
|
||||
width := guessWidth(a.usageWriter)
|
||||
funcs := template.FuncMap{
|
||||
"Indent": func(level int) string {
|
||||
return strings.Repeat(" ", level*indent)
|
||||
},
|
||||
"Wrap": func(indent int, s string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
indentText := strings.Repeat(" ", indent)
|
||||
doc.ToText(buf, s, indentText, " "+indentText, width-indent)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatFlag": formatFlag,
|
||||
"FlagsToTwoColumns": func(f []*FlagModel) [][2]string {
|
||||
rows := [][2]string{}
|
||||
haveShort := false
|
||||
for _, flag := range f {
|
||||
if flag.Short != 0 {
|
||||
haveShort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, flag := range f {
|
||||
if !flag.Hidden {
|
||||
rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help})
|
||||
}
|
||||
}
|
||||
return rows
|
||||
},
|
||||
"RequiredFlags": func(f []*FlagModel) []*FlagModel {
|
||||
requiredFlags := []*FlagModel{}
|
||||
for _, flag := range f {
|
||||
if flag.Required {
|
||||
requiredFlags = append(requiredFlags, flag)
|
||||
}
|
||||
}
|
||||
return requiredFlags
|
||||
},
|
||||
"OptionalFlags": func(f []*FlagModel) []*FlagModel {
|
||||
optionalFlags := []*FlagModel{}
|
||||
for _, flag := range f {
|
||||
if !flag.Required {
|
||||
optionalFlags = append(optionalFlags, flag)
|
||||
}
|
||||
}
|
||||
return optionalFlags
|
||||
},
|
||||
"ArgsToTwoColumns": func(a []*ArgModel) [][2]string {
|
||||
rows := [][2]string{}
|
||||
for _, arg := range a {
|
||||
s := "<" + arg.Name + ">"
|
||||
if !arg.Required {
|
||||
s = "[" + s + "]"
|
||||
}
|
||||
rows = append(rows, [2]string{s, arg.Help})
|
||||
}
|
||||
return rows
|
||||
},
|
||||
"FormatTwoColumns": func(rows [][2]string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
formatTwoColumns(buf, indent, indent, width, rows)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
formatTwoColumns(buf, indent, padding, width, rows)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatAppUsage": formatAppUsage,
|
||||
"FormatCommandUsage": formatCmdUsage,
|
||||
"IsCumulative": func(value Value) bool {
|
||||
r, ok := value.(remainderArg)
|
||||
return ok && r.IsCumulative()
|
||||
},
|
||||
"Char": func(c rune) string {
|
||||
return string(c)
|
||||
},
|
||||
}
|
||||
t, err := template.New("usage").Funcs(funcs).Parse(tmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var selectedCommand *CmdModel
|
||||
if context.SelectedCommand != nil {
|
||||
selectedCommand = context.SelectedCommand.Model()
|
||||
}
|
||||
ctx := templateContext{
|
||||
App: a.Model(),
|
||||
Width: width,
|
||||
Context: &templateParseContext{
|
||||
SelectedCommand: selectedCommand,
|
||||
FlagGroupModel: context.flags.Model(),
|
||||
ArgGroupModel: context.arguments.Model(),
|
||||
},
|
||||
}
|
||||
return t.Execute(a.usageWriter, ctx)
|
||||
}
|
470
vendor/github.com/alecthomas/kingpin/values.go
generated
vendored
470
vendor/github.com/alecthomas/kingpin/values.go
generated
vendored
|
@ -1,470 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
//go:generate go run ./cmd/genvalues/main.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
// NOTE: Most of the base type values were lifted from:
|
||||
// http://golang.org/src/pkg/flag/flag.go?s=20146:20222
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag.
|
||||
// (The default value is represented as a string.)
|
||||
//
|
||||
// If a Value has an IsBoolFlag() bool method returning true, the command-line
|
||||
// parser makes --name equivalent to -name=true rather than using the next
|
||||
// command-line argument, and adds a --no-name counterpart for negating the
|
||||
// flag.
|
||||
type Value interface {
|
||||
String() string
|
||||
Set(string) error
|
||||
}
|
||||
|
||||
// Getter is an interface that allows the contents of a Value to be retrieved.
|
||||
// It wraps the Value interface, rather than being part of it, because it
|
||||
// appeared after Go 1 and its compatibility rules. All Value types provided
|
||||
// by this package satisfy the Getter interface.
|
||||
type Getter interface {
|
||||
Value
|
||||
Get() interface{}
|
||||
}
|
||||
|
||||
// Optional interface to indicate boolean flags that don't accept a value, and
|
||||
// implicitly have a --no-<x> negation counterpart.
|
||||
type boolFlag interface {
|
||||
Value
|
||||
IsBoolFlag() bool
|
||||
}
|
||||
|
||||
// Optional interface for arguments that cumulatively consume all remaining
|
||||
// input.
|
||||
type remainderArg interface {
|
||||
Value
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
// Optional interface for flags that can be repeated.
|
||||
type repeatableFlag interface {
|
||||
Value
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
type accumulator struct {
|
||||
element func(value interface{}) Value
|
||||
typ reflect.Type
|
||||
slice reflect.Value
|
||||
}
|
||||
|
||||
// Use reflection to accumulate values into a slice.
|
||||
//
|
||||
// target := []string{}
|
||||
// newAccumulator(&target, func (value interface{}) Value {
|
||||
// return newStringValue(value.(*string))
|
||||
// })
|
||||
func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator {
|
||||
typ := reflect.TypeOf(slice)
|
||||
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice {
|
||||
panic("expected a pointer to a slice")
|
||||
}
|
||||
return &accumulator{
|
||||
element: element,
|
||||
typ: typ.Elem().Elem(),
|
||||
slice: reflect.ValueOf(slice),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *accumulator) String() string {
|
||||
out := []string{}
|
||||
s := a.slice.Elem()
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
out = append(out, a.element(s.Index(i).Addr().Interface()).String())
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func (a *accumulator) Set(value string) error {
|
||||
e := reflect.New(a.typ)
|
||||
if err := a.element(e.Interface()).Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
slice := reflect.Append(a.slice.Elem(), e.Elem())
|
||||
a.slice.Elem().Set(slice)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *accumulator) Get() interface{} {
|
||||
return a.slice.Interface()
|
||||
}
|
||||
|
||||
func (a *accumulator) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
func newDurationValue(p *time.Duration) *durationValue {
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() interface{} { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
// -- map[string]string Value
|
||||
type stringMapValue map[string]string
|
||||
|
||||
func newStringMapValue(p *map[string]string) *stringMapValue {
|
||||
return (*stringMapValue)(p)
|
||||
}
|
||||
|
||||
var stringMapRegex = regexp.MustCompile("[:=]")
|
||||
|
||||
func (s *stringMapValue) Set(value string) error {
|
||||
parts := stringMapRegex.Split(value, 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected KEY=VALUE got '%s'", value)
|
||||
}
|
||||
(*s)[parts[0]] = parts[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringMapValue) Get() interface{} {
|
||||
return (map[string]string)(*s)
|
||||
}
|
||||
|
||||
func (s *stringMapValue) String() string {
|
||||
return fmt.Sprintf("%s", map[string]string(*s))
|
||||
}
|
||||
|
||||
func (s *stringMapValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// -- net.IP Value
|
||||
type ipValue net.IP
|
||||
|
||||
func newIPValue(p *net.IP) *ipValue {
|
||||
return (*ipValue)(p)
|
||||
}
|
||||
|
||||
func (i *ipValue) Set(value string) error {
|
||||
if ip := net.ParseIP(value); ip == nil {
|
||||
return fmt.Errorf("'%s' is not an IP address", value)
|
||||
} else {
|
||||
*i = *(*ipValue)(&ip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipValue) Get() interface{} {
|
||||
return (net.IP)(*i)
|
||||
}
|
||||
|
||||
func (i *ipValue) String() string {
|
||||
return (*net.IP)(i).String()
|
||||
}
|
||||
|
||||
// -- *net.TCPAddr Value
|
||||
type tcpAddrValue struct {
|
||||
addr **net.TCPAddr
|
||||
}
|
||||
|
||||
func newTCPAddrValue(p **net.TCPAddr) *tcpAddrValue {
|
||||
return &tcpAddrValue{p}
|
||||
}
|
||||
|
||||
func (i *tcpAddrValue) Set(value string) error {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", value); err != nil {
|
||||
return fmt.Errorf("'%s' is not a valid TCP address: %s", value, err)
|
||||
} else {
|
||||
*i.addr = addr
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tcpAddrValue) Get() interface{} {
|
||||
return (*net.TCPAddr)(*t.addr)
|
||||
}
|
||||
|
||||
func (i *tcpAddrValue) String() string {
|
||||
return (*i.addr).String()
|
||||
}
|
||||
|
||||
// -- existingFile Value
|
||||
|
||||
type fileStatValue struct {
|
||||
path *string
|
||||
predicate func(os.FileInfo) error
|
||||
}
|
||||
|
||||
func newFileStatValue(p *string, predicate func(os.FileInfo) error) *fileStatValue {
|
||||
return &fileStatValue{
|
||||
path: p,
|
||||
predicate: predicate,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *fileStatValue) Set(value string) error {
|
||||
if s, err := os.Stat(value); os.IsNotExist(err) {
|
||||
return fmt.Errorf("path '%s' does not exist", value)
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if err := e.predicate(s); err != nil {
|
||||
return err
|
||||
}
|
||||
*e.path = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileStatValue) Get() interface{} {
|
||||
return (string)(*f.path)
|
||||
}
|
||||
|
||||
func (e *fileStatValue) String() string {
|
||||
return *e.path
|
||||
}
|
||||
|
||||
// -- os.File value
|
||||
|
||||
type fileValue struct {
|
||||
f **os.File
|
||||
flag int
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
func newFileValue(p **os.File, flag int, perm os.FileMode) *fileValue {
|
||||
return &fileValue{p, flag, perm}
|
||||
}
|
||||
|
||||
func (f *fileValue) Set(value string) error {
|
||||
if fd, err := os.OpenFile(value, f.flag, f.perm); err != nil {
|
||||
return err
|
||||
} else {
|
||||
*f.f = fd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileValue) Get() interface{} {
|
||||
return (*os.File)(*f.f)
|
||||
}
|
||||
|
||||
func (f *fileValue) String() string {
|
||||
if *f.f == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return (*f.f).Name()
|
||||
}
|
||||
|
||||
// -- url.URL Value
|
||||
type urlValue struct {
|
||||
u **url.URL
|
||||
}
|
||||
|
||||
func newURLValue(p **url.URL) *urlValue {
|
||||
return &urlValue{p}
|
||||
}
|
||||
|
||||
func (u *urlValue) Set(value string) error {
|
||||
if url, err := url.Parse(value); err != nil {
|
||||
return fmt.Errorf("invalid URL: %s", err)
|
||||
} else {
|
||||
*u.u = url
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlValue) Get() interface{} {
|
||||
return (*url.URL)(*u.u)
|
||||
}
|
||||
|
||||
func (u *urlValue) String() string {
|
||||
if *u.u == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return (*u.u).String()
|
||||
}
|
||||
|
||||
// -- []*url.URL Value
|
||||
type urlListValue []*url.URL
|
||||
|
||||
func newURLListValue(p *[]*url.URL) *urlListValue {
|
||||
return (*urlListValue)(p)
|
||||
}
|
||||
|
||||
func (u *urlListValue) Set(value string) error {
|
||||
if url, err := url.Parse(value); err != nil {
|
||||
return fmt.Errorf("invalid URL: %s", err)
|
||||
} else {
|
||||
*u = append(*u, url)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlListValue) Get() interface{} {
|
||||
return ([]*url.URL)(*u)
|
||||
}
|
||||
|
||||
func (u *urlListValue) String() string {
|
||||
out := []string{}
|
||||
for _, url := range *u {
|
||||
out = append(out, url.String())
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func (u *urlListValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// A flag whose value must be in a set of options.
|
||||
type enumValue struct {
|
||||
value *string
|
||||
options []string
|
||||
}
|
||||
|
||||
func newEnumFlag(target *string, options ...string) *enumValue {
|
||||
return &enumValue{
|
||||
value: target,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *enumValue) String() string {
|
||||
return *a.value
|
||||
}
|
||||
|
||||
func (a *enumValue) Set(value string) error {
|
||||
for _, v := range a.options {
|
||||
if v == value {
|
||||
*a.value = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumValue) Get() interface{} {
|
||||
return (string)(*e.value)
|
||||
}
|
||||
|
||||
// -- []string Enum Value
|
||||
type enumsValue struct {
|
||||
value *[]string
|
||||
options []string
|
||||
}
|
||||
|
||||
func newEnumsFlag(target *[]string, options ...string) *enumsValue {
|
||||
return &enumsValue{
|
||||
value: target,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *enumsValue) Set(value string) error {
|
||||
for _, v := range s.options {
|
||||
if v == value {
|
||||
*s.value = append(*s.value, value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumsValue) Get() interface{} {
|
||||
return ([]string)(*e.value)
|
||||
}
|
||||
|
||||
func (s *enumsValue) String() string {
|
||||
return strings.Join(*s.value, ",")
|
||||
}
|
||||
|
||||
func (s *enumsValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// -- units.Base2Bytes Value
|
||||
type bytesValue units.Base2Bytes
|
||||
|
||||
func newBytesValue(p *units.Base2Bytes) *bytesValue {
|
||||
return (*bytesValue)(p)
|
||||
}
|
||||
|
||||
func (d *bytesValue) Set(s string) error {
|
||||
v, err := units.ParseBase2Bytes(s)
|
||||
*d = bytesValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *bytesValue) Get() interface{} { return units.Base2Bytes(*d) }
|
||||
|
||||
func (d *bytesValue) String() string { return (*units.Base2Bytes)(d).String() }
|
||||
|
||||
func newExistingFileValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||
if s.IsDir() {
|
||||
return fmt.Errorf("'%s' is a directory", s.Name())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func newExistingDirValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||
if !s.IsDir() {
|
||||
return fmt.Errorf("'%s' is a file", s.Name())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func newExistingFileOrDirValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error { return nil })
|
||||
}
|
||||
|
||||
type counterValue int
|
||||
|
||||
func newCounterValue(n *int) *counterValue {
|
||||
return (*counterValue)(n)
|
||||
}
|
||||
|
||||
func (c *counterValue) Set(s string) error {
|
||||
*c++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counterValue) Get() interface{} { return (int)(*c) }
|
||||
func (c *counterValue) IsBoolFlag() bool { return true }
|
||||
func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) }
|
||||
func (c *counterValue) IsCumulative() bool { return true }
|
||||
|
||||
func resolveHost(value string) (net.IP, error) {
|
||||
if ip := net.ParseIP(value); ip != nil {
|
||||
return ip, nil
|
||||
} else {
|
||||
if addr, err := net.ResolveIPAddr("ip", value); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return addr.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
25
vendor/github.com/alecthomas/kingpin/values.json
generated
vendored
25
vendor/github.com/alecthomas/kingpin/values.json
generated
vendored
|
@ -1,25 +0,0 @@
|
|||
[
|
||||
{"type": "bool", "parser": "strconv.ParseBool(s)"},
|
||||
{"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"},
|
||||
{"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"},
|
||||
{"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"},
|
||||
{"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"},
|
||||
{"type": "uint32", "parser": "strconv.ParseUint(s, 0, 32)"},
|
||||
{"type": "uint64", "parser": "strconv.ParseUint(s, 0, 64)"},
|
||||
{"type": "int", "parser": "strconv.ParseFloat(s, 64)", "plural": "Ints"},
|
||||
{"type": "int8", "parser": "strconv.ParseInt(s, 0, 8)"},
|
||||
{"type": "int16", "parser": "strconv.ParseInt(s, 0, 16)"},
|
||||
{"type": "int32", "parser": "strconv.ParseInt(s, 0, 32)"},
|
||||
{"type": "int64", "parser": "strconv.ParseInt(s, 0, 64)"},
|
||||
{"type": "float64", "parser": "strconv.ParseFloat(s, 64)"},
|
||||
{"type": "float32", "parser": "strconv.ParseFloat(s, 32)"},
|
||||
{"name": "Duration", "type": "time.Duration", "no_value_parser": true},
|
||||
{"name": "IP", "type": "net.IP", "no_value_parser": true},
|
||||
{"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true},
|
||||
{"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true},
|
||||
{"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true},
|
||||
{"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true},
|
||||
{"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"},
|
||||
{"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."},
|
||||
{"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."}
|
||||
]
|
821
vendor/github.com/alecthomas/kingpin/values_generated.go
generated
vendored
821
vendor/github.com/alecthomas/kingpin/values_generated.go
generated
vendored
|
@ -1,821 +0,0 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This file is autogenerated by "go generate .". Do not modify.
|
||||
|
||||
// -- bool Value
|
||||
type boolValue struct{ v *bool }
|
||||
|
||||
func newBoolValue(p *bool) *boolValue {
|
||||
return &boolValue{p}
|
||||
}
|
||||
|
||||
func (f *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err == nil {
|
||||
*f.v = (bool)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *boolValue) Get() interface{} { return (bool)(*f.v) }
|
||||
|
||||
func (f *boolValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Bool parses the next command-line value as bool.
|
||||
func (p *parserMixin) Bool() (target *bool) {
|
||||
target = new(bool)
|
||||
p.BoolVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) BoolVar(target *bool) {
|
||||
p.SetValue(newBoolValue(target))
|
||||
}
|
||||
|
||||
// BoolList accumulates bool values into a slice.
|
||||
func (p *parserMixin) BoolList() (target *[]bool) {
|
||||
target = new([]bool)
|
||||
p.BoolListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) BoolListVar(target *[]bool) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newBoolValue(v.(*bool))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- string Value
|
||||
type stringValue struct{ v *string }
|
||||
|
||||
func newStringValue(p *string) *stringValue {
|
||||
return &stringValue{p}
|
||||
}
|
||||
|
||||
func (f *stringValue) Set(s string) error {
|
||||
v, err := s, error(nil)
|
||||
if err == nil {
|
||||
*f.v = (string)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *stringValue) Get() interface{} { return (string)(*f.v) }
|
||||
|
||||
func (f *stringValue) String() string { return string(*f.v) }
|
||||
|
||||
// String parses the next command-line value as string.
|
||||
func (p *parserMixin) String() (target *string) {
|
||||
target = new(string)
|
||||
p.StringVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) StringVar(target *string) {
|
||||
p.SetValue(newStringValue(target))
|
||||
}
|
||||
|
||||
// Strings accumulates string values into a slice.
|
||||
func (p *parserMixin) Strings() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.StringsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) StringsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newStringValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint Value
|
||||
type uintValue struct{ v *uint }
|
||||
|
||||
func newUintValue(p *uint) *uintValue {
|
||||
return &uintValue{p}
|
||||
}
|
||||
|
||||
func (f *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (uint)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uintValue) Get() interface{} { return (uint)(*f.v) }
|
||||
|
||||
func (f *uintValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint parses the next command-line value as uint.
|
||||
func (p *parserMixin) Uint() (target *uint) {
|
||||
target = new(uint)
|
||||
p.UintVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) UintVar(target *uint) {
|
||||
p.SetValue(newUintValue(target))
|
||||
}
|
||||
|
||||
// Uints accumulates uint values into a slice.
|
||||
func (p *parserMixin) Uints() (target *[]uint) {
|
||||
target = new([]uint)
|
||||
p.UintsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) UintsVar(target *[]uint) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUintValue(v.(*uint))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint8 Value
|
||||
type uint8Value struct{ v *uint8 }
|
||||
|
||||
func newUint8Value(p *uint8) *uint8Value {
|
||||
return &uint8Value{p}
|
||||
}
|
||||
|
||||
func (f *uint8Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 8)
|
||||
if err == nil {
|
||||
*f.v = (uint8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint8Value) Get() interface{} { return (uint8)(*f.v) }
|
||||
|
||||
func (f *uint8Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint8 parses the next command-line value as uint8.
|
||||
func (p *parserMixin) Uint8() (target *uint8) {
|
||||
target = new(uint8)
|
||||
p.Uint8Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint8Var(target *uint8) {
|
||||
p.SetValue(newUint8Value(target))
|
||||
}
|
||||
|
||||
// Uint8List accumulates uint8 values into a slice.
|
||||
func (p *parserMixin) Uint8List() (target *[]uint8) {
|
||||
target = new([]uint8)
|
||||
p.Uint8ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint8ListVar(target *[]uint8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint8Value(v.(*uint8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint16 Value
|
||||
type uint16Value struct{ v *uint16 }
|
||||
|
||||
func newUint16Value(p *uint16) *uint16Value {
|
||||
return &uint16Value{p}
|
||||
}
|
||||
|
||||
func (f *uint16Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 16)
|
||||
if err == nil {
|
||||
*f.v = (uint16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint16Value) Get() interface{} { return (uint16)(*f.v) }
|
||||
|
||||
func (f *uint16Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint16 parses the next command-line value as uint16.
|
||||
func (p *parserMixin) Uint16() (target *uint16) {
|
||||
target = new(uint16)
|
||||
p.Uint16Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint16Var(target *uint16) {
|
||||
p.SetValue(newUint16Value(target))
|
||||
}
|
||||
|
||||
// Uint16List accumulates uint16 values into a slice.
|
||||
func (p *parserMixin) Uint16List() (target *[]uint16) {
|
||||
target = new([]uint16)
|
||||
p.Uint16ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint16ListVar(target *[]uint16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint16Value(v.(*uint16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint32 Value
|
||||
type uint32Value struct{ v *uint32 }
|
||||
|
||||
func newUint32Value(p *uint32) *uint32Value {
|
||||
return &uint32Value{p}
|
||||
}
|
||||
|
||||
func (f *uint32Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 32)
|
||||
if err == nil {
|
||||
*f.v = (uint32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint32Value) Get() interface{} { return (uint32)(*f.v) }
|
||||
|
||||
func (f *uint32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint32 parses the next command-line value as uint32.
|
||||
func (p *parserMixin) Uint32() (target *uint32) {
|
||||
target = new(uint32)
|
||||
p.Uint32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint32Var(target *uint32) {
|
||||
p.SetValue(newUint32Value(target))
|
||||
}
|
||||
|
||||
// Uint32List accumulates uint32 values into a slice.
|
||||
func (p *parserMixin) Uint32List() (target *[]uint32) {
|
||||
target = new([]uint32)
|
||||
p.Uint32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint32ListVar(target *[]uint32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint32Value(v.(*uint32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint64 Value
|
||||
type uint64Value struct{ v *uint64 }
|
||||
|
||||
func newUint64Value(p *uint64) *uint64Value {
|
||||
return &uint64Value{p}
|
||||
}
|
||||
|
||||
func (f *uint64Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (uint64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint64Value) Get() interface{} { return (uint64)(*f.v) }
|
||||
|
||||
func (f *uint64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint64 parses the next command-line value as uint64.
|
||||
func (p *parserMixin) Uint64() (target *uint64) {
|
||||
target = new(uint64)
|
||||
p.Uint64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint64Var(target *uint64) {
|
||||
p.SetValue(newUint64Value(target))
|
||||
}
|
||||
|
||||
// Uint64List accumulates uint64 values into a slice.
|
||||
func (p *parserMixin) Uint64List() (target *[]uint64) {
|
||||
target = new([]uint64)
|
||||
p.Uint64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint64ListVar(target *[]uint64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint64Value(v.(*uint64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int Value
|
||||
type intValue struct{ v *int }
|
||||
|
||||
func newIntValue(p *int) *intValue {
|
||||
return &intValue{p}
|
||||
}
|
||||
|
||||
func (f *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
*f.v = (int)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *intValue) Get() interface{} { return (int)(*f.v) }
|
||||
|
||||
func (f *intValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int parses the next command-line value as int.
|
||||
func (p *parserMixin) Int() (target *int) {
|
||||
target = new(int)
|
||||
p.IntVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IntVar(target *int) {
|
||||
p.SetValue(newIntValue(target))
|
||||
}
|
||||
|
||||
// Ints accumulates int values into a slice.
|
||||
func (p *parserMixin) Ints() (target *[]int) {
|
||||
target = new([]int)
|
||||
p.IntsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IntsVar(target *[]int) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIntValue(v.(*int))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int8 Value
|
||||
type int8Value struct{ v *int8 }
|
||||
|
||||
func newInt8Value(p *int8) *int8Value {
|
||||
return &int8Value{p}
|
||||
}
|
||||
|
||||
func (f *int8Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 8)
|
||||
if err == nil {
|
||||
*f.v = (int8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int8Value) Get() interface{} { return (int8)(*f.v) }
|
||||
|
||||
func (f *int8Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int8 parses the next command-line value as int8.
|
||||
func (p *parserMixin) Int8() (target *int8) {
|
||||
target = new(int8)
|
||||
p.Int8Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int8Var(target *int8) {
|
||||
p.SetValue(newInt8Value(target))
|
||||
}
|
||||
|
||||
// Int8List accumulates int8 values into a slice.
|
||||
func (p *parserMixin) Int8List() (target *[]int8) {
|
||||
target = new([]int8)
|
||||
p.Int8ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int8ListVar(target *[]int8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt8Value(v.(*int8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int16 Value
|
||||
type int16Value struct{ v *int16 }
|
||||
|
||||
func newInt16Value(p *int16) *int16Value {
|
||||
return &int16Value{p}
|
||||
}
|
||||
|
||||
func (f *int16Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 16)
|
||||
if err == nil {
|
||||
*f.v = (int16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int16Value) Get() interface{} { return (int16)(*f.v) }
|
||||
|
||||
func (f *int16Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int16 parses the next command-line value as int16.
|
||||
func (p *parserMixin) Int16() (target *int16) {
|
||||
target = new(int16)
|
||||
p.Int16Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int16Var(target *int16) {
|
||||
p.SetValue(newInt16Value(target))
|
||||
}
|
||||
|
||||
// Int16List accumulates int16 values into a slice.
|
||||
func (p *parserMixin) Int16List() (target *[]int16) {
|
||||
target = new([]int16)
|
||||
p.Int16ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int16ListVar(target *[]int16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt16Value(v.(*int16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int32 Value
|
||||
type int32Value struct{ v *int32 }
|
||||
|
||||
func newInt32Value(p *int32) *int32Value {
|
||||
return &int32Value{p}
|
||||
}
|
||||
|
||||
func (f *int32Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 32)
|
||||
if err == nil {
|
||||
*f.v = (int32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int32Value) Get() interface{} { return (int32)(*f.v) }
|
||||
|
||||
func (f *int32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int32 parses the next command-line value as int32.
|
||||
func (p *parserMixin) Int32() (target *int32) {
|
||||
target = new(int32)
|
||||
p.Int32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int32Var(target *int32) {
|
||||
p.SetValue(newInt32Value(target))
|
||||
}
|
||||
|
||||
// Int32List accumulates int32 values into a slice.
|
||||
func (p *parserMixin) Int32List() (target *[]int32) {
|
||||
target = new([]int32)
|
||||
p.Int32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int32ListVar(target *[]int32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt32Value(v.(*int32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int64 Value
|
||||
type int64Value struct{ v *int64 }
|
||||
|
||||
func newInt64Value(p *int64) *int64Value {
|
||||
return &int64Value{p}
|
||||
}
|
||||
|
||||
func (f *int64Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (int64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int64Value) Get() interface{} { return (int64)(*f.v) }
|
||||
|
||||
func (f *int64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int64 parses the next command-line value as int64.
|
||||
func (p *parserMixin) Int64() (target *int64) {
|
||||
target = new(int64)
|
||||
p.Int64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int64Var(target *int64) {
|
||||
p.SetValue(newInt64Value(target))
|
||||
}
|
||||
|
||||
// Int64List accumulates int64 values into a slice.
|
||||
func (p *parserMixin) Int64List() (target *[]int64) {
|
||||
target = new([]int64)
|
||||
p.Int64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int64ListVar(target *[]int64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt64Value(v.(*int64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float64 Value
|
||||
type float64Value struct{ v *float64 }
|
||||
|
||||
func newFloat64Value(p *float64) *float64Value {
|
||||
return &float64Value{p}
|
||||
}
|
||||
|
||||
func (f *float64Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
*f.v = (float64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float64Value) Get() interface{} { return (float64)(*f.v) }
|
||||
|
||||
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Float64 parses the next command-line value as float64.
|
||||
func (p *parserMixin) Float64() (target *float64) {
|
||||
target = new(float64)
|
||||
p.Float64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float64Var(target *float64) {
|
||||
p.SetValue(newFloat64Value(target))
|
||||
}
|
||||
|
||||
// Float64List accumulates float64 values into a slice.
|
||||
func (p *parserMixin) Float64List() (target *[]float64) {
|
||||
target = new([]float64)
|
||||
p.Float64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float64ListVar(target *[]float64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat64Value(v.(*float64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float32 Value
|
||||
type float32Value struct{ v *float32 }
|
||||
|
||||
func newFloat32Value(p *float32) *float32Value {
|
||||
return &float32Value{p}
|
||||
}
|
||||
|
||||
func (f *float32Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
if err == nil {
|
||||
*f.v = (float32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float32Value) Get() interface{} { return (float32)(*f.v) }
|
||||
|
||||
func (f *float32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Float32 parses the next command-line value as float32.
|
||||
func (p *parserMixin) Float32() (target *float32) {
|
||||
target = new(float32)
|
||||
p.Float32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float32Var(target *float32) {
|
||||
p.SetValue(newFloat32Value(target))
|
||||
}
|
||||
|
||||
// Float32List accumulates float32 values into a slice.
|
||||
func (p *parserMixin) Float32List() (target *[]float32) {
|
||||
target = new([]float32)
|
||||
p.Float32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float32ListVar(target *[]float32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat32Value(v.(*float32))
|
||||
}))
|
||||
}
|
||||
|
||||
// DurationList accumulates time.Duration values into a slice.
|
||||
func (p *parserMixin) DurationList() (target *[]time.Duration) {
|
||||
target = new([]time.Duration)
|
||||
p.DurationListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) DurationListVar(target *[]time.Duration) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newDurationValue(v.(*time.Duration))
|
||||
}))
|
||||
}
|
||||
|
||||
// IPList accumulates net.IP values into a slice.
|
||||
func (p *parserMixin) IPList() (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
p.IPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// TCPList accumulates *net.TCPAddr values into a slice.
|
||||
func (p *parserMixin) TCPList() (target *[]*net.TCPAddr) {
|
||||
target = new([]*net.TCPAddr)
|
||||
p.TCPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) TCPListVar(target *[]*net.TCPAddr) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newTCPAddrValue(v.(**net.TCPAddr))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFiles accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingFiles() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingFilesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingDirs accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingDirs() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingDirsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFilesOrDirs accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingFilesOrDirs() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingFilesOrDirsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileOrDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- *regexp.Regexp Value
|
||||
type regexpValue struct{ v **regexp.Regexp }
|
||||
|
||||
func newRegexpValue(p **regexp.Regexp) *regexpValue {
|
||||
return ®expValue{p}
|
||||
}
|
||||
|
||||
func (f *regexpValue) Set(s string) error {
|
||||
v, err := regexp.Compile(s)
|
||||
if err == nil {
|
||||
*f.v = (*regexp.Regexp)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *regexpValue) Get() interface{} { return (*regexp.Regexp)(*f.v) }
|
||||
|
||||
func (f *regexpValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Regexp parses the next command-line value as *regexp.Regexp.
|
||||
func (p *parserMixin) Regexp() (target **regexp.Regexp) {
|
||||
target = new(*regexp.Regexp)
|
||||
p.RegexpVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpVar(target **regexp.Regexp) {
|
||||
p.SetValue(newRegexpValue(target))
|
||||
}
|
||||
|
||||
// RegexpList accumulates *regexp.Regexp values into a slice.
|
||||
func (p *parserMixin) RegexpList() (target *[]*regexp.Regexp) {
|
||||
target = new([]*regexp.Regexp)
|
||||
p.RegexpListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newRegexpValue(v.(**regexp.Regexp))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- net.IP Value
|
||||
type resolvedIPValue struct{ v *net.IP }
|
||||
|
||||
func newResolvedIPValue(p *net.IP) *resolvedIPValue {
|
||||
return &resolvedIPValue{p}
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Set(s string) error {
|
||||
v, err := resolveHost(s)
|
||||
if err == nil {
|
||||
*f.v = (net.IP)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Get() interface{} { return (net.IP)(*f.v) }
|
||||
|
||||
func (f *resolvedIPValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Resolve a hostname or IP to an IP.
|
||||
func (p *parserMixin) ResolvedIP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.ResolvedIPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPVar(target *net.IP) {
|
||||
p.SetValue(newResolvedIPValue(target))
|
||||
}
|
||||
|
||||
// ResolvedIPList accumulates net.IP values into a slice.
|
||||
func (p *parserMixin) ResolvedIPList() (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
p.ResolvedIPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newResolvedIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- []byte Value
|
||||
type hexBytesValue struct{ v *[]byte }
|
||||
|
||||
func newHexBytesValue(p *[]byte) *hexBytesValue {
|
||||
return &hexBytesValue{p}
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Set(s string) error {
|
||||
v, err := hex.DecodeString(s)
|
||||
if err == nil {
|
||||
*f.v = ([]byte)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Get() interface{} { return ([]byte)(*f.v) }
|
||||
|
||||
func (f *hexBytesValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Bytes as a hex string.
|
||||
func (p *parserMixin) HexBytes() (target *[]byte) {
|
||||
target = new([]byte)
|
||||
p.HexBytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesVar(target *[]byte) {
|
||||
p.SetValue(newHexBytesValue(target))
|
||||
}
|
||||
|
||||
// HexBytesList accumulates []byte values into a slice.
|
||||
func (p *parserMixin) HexBytesList() (target *[][]byte) {
|
||||
target = new([][]byte)
|
||||
p.HexBytesListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesListVar(target *[][]byte) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newHexBytesValue(v.(*[]byte))
|
||||
}))
|
||||
}
|
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
|||
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.
|
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
|
@ -1,25 +0,0 @@
|
|||
# Go's `text/template` package with newline elision
|
||||
|
||||
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
||||
|
||||
eg.
|
||||
|
||||
```
|
||||
{{if true}}\
|
||||
hello
|
||||
{{end}}\
|
||||
```
|
||||
|
||||
Will result in:
|
||||
|
||||
```
|
||||
hello\n
|
||||
```
|
||||
|
||||
Rather than:
|
||||
|
||||
```
|
||||
\n
|
||||
hello\n
|
||||
\n
|
||||
```
|
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
|
@ -1,406 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package template implements data-driven templates for generating textual output.
|
||||
|
||||
To generate HTML output, see package html/template, which has the same interface
|
||||
as this package but automatically secures HTML output against certain attacks.
|
||||
|
||||
Templates are executed by applying them to a data structure. Annotations in the
|
||||
template refer to elements of the data structure (typically a field of a struct
|
||||
or a key in a map) to control execution and derive values to be displayed.
|
||||
Execution of the template walks the structure and sets the cursor, represented
|
||||
by a period '.' and called "dot", to the value at the current location in the
|
||||
structure as execution proceeds.
|
||||
|
||||
The input text for a template is UTF-8-encoded text in any format.
|
||||
"Actions"--data evaluations or control structures--are delimited by
|
||||
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||
Actions may not span newlines, although comments can.
|
||||
|
||||
Once parsed, a template may be executed safely in parallel.
|
||||
|
||||
Here is a trivial example that prints "17 items are made of wool".
|
||||
|
||||
type Inventory struct {
|
||||
Material string
|
||||
Count uint
|
||||
}
|
||||
sweaters := Inventory{"wool", 17}
|
||||
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||
if err != nil { panic(err) }
|
||||
err = tmpl.Execute(os.Stdout, sweaters)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
More intricate examples appear below.
|
||||
|
||||
Actions
|
||||
|
||||
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||
data, defined in detail below.
|
||||
|
||||
*/
|
||||
// {{/* a comment */}}
|
||||
// A comment; discarded. May contain newlines.
|
||||
// Comments do not nest and must start and end at the
|
||||
// delimiters, as shown here.
|
||||
/*
|
||||
|
||||
{{pipeline}}
|
||||
The default textual representation of the value of the pipeline
|
||||
is copied to the output.
|
||||
|
||||
{{if pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, T1 is executed. The empty values are false, 0, any
|
||||
nil pointer or interface value, and any array, slice, map, or
|
||||
string of length zero.
|
||||
Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, T0 is executed;
|
||||
otherwise, T1 is executed. Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||
To simplify the appearance of if-else chains, the else action
|
||||
of an if may include another if directly; the effect is exactly
|
||||
the same as writing
|
||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||
|
||||
{{range pipeline}} T1 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, nothing is output;
|
||||
otherwise, dot is set to the successive elements of the array,
|
||||
slice, or map and T1 is executed. If the value is a map and the
|
||||
keys are of basic type with a defined order ("comparable"), the
|
||||
elements will be visited in sorted key order.
|
||||
|
||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, dot is unaffected and
|
||||
T0 is executed; otherwise, dot is set to the successive elements
|
||||
of the array, slice, or map and T1 is executed.
|
||||
|
||||
{{template "name"}}
|
||||
The template with the specified name is executed with nil data.
|
||||
|
||||
{{template "name" pipeline}}
|
||||
The template with the specified name is executed with dot set
|
||||
to the value of the pipeline.
|
||||
|
||||
{{with pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, dot is set to the value of the pipeline and T1 is
|
||||
executed.
|
||||
|
||||
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, dot is unaffected and T0
|
||||
is executed; otherwise, dot is set to the value of the pipeline
|
||||
and T1 is executed.
|
||||
|
||||
Arguments
|
||||
|
||||
An argument is a simple value, denoted by one of the following.
|
||||
|
||||
- A boolean, string, character, integer, floating-point, imaginary
|
||||
or complex constant in Go syntax. These behave like Go's untyped
|
||||
constants, although raw strings may not span newlines.
|
||||
- The keyword nil, representing an untyped Go nil.
|
||||
- The character '.' (period):
|
||||
.
|
||||
The result is the value of dot.
|
||||
- A variable name, which is a (possibly empty) alphanumeric string
|
||||
preceded by a dollar sign, such as
|
||||
$piOver2
|
||||
or
|
||||
$
|
||||
The result is the value of the variable.
|
||||
Variables are described below.
|
||||
- The name of a field of the data, which must be a struct, preceded
|
||||
by a period, such as
|
||||
.Field
|
||||
The result is the value of the field. Field invocations may be
|
||||
chained:
|
||||
.Field1.Field2
|
||||
Fields can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Field2
|
||||
- The name of a key of the data, which must be a map, preceded
|
||||
by a period, such as
|
||||
.Key
|
||||
The result is the map element value indexed by the key.
|
||||
Key invocations may be chained and combined with fields to any
|
||||
depth:
|
||||
.Field1.Key1.Field2.Key2
|
||||
Although the key must be an alphanumeric identifier, unlike with
|
||||
field names they do not need to start with an upper case letter.
|
||||
Keys can also be evaluated on variables, including chaining:
|
||||
$x.key1.key2
|
||||
- The name of a niladic method of the data, preceded by a period,
|
||||
such as
|
||||
.Method
|
||||
The result is the value of invoking the method with dot as the
|
||||
receiver, dot.Method(). Such a method must have one return value (of
|
||||
any type) or two return values, the second of which is an error.
|
||||
If it has two and the returned error is non-nil, execution terminates
|
||||
and an error is returned to the caller as the value of Execute.
|
||||
Method invocations may be chained and combined with fields and keys
|
||||
to any depth:
|
||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Method1.Field
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
The result is the value of invoking the function, fun(). The return
|
||||
types and values behave as in methods. Functions and function
|
||||
names are described below.
|
||||
- A parenthesized instance of one the above, for grouping. The result
|
||||
may be accessed by a field or map key invocation.
|
||||
print (.F1 arg1) (.F2 arg2)
|
||||
(.StructValuedMethod "arg").Field
|
||||
|
||||
Arguments may evaluate to any type; if they are pointers the implementation
|
||||
automatically indirects to the base type when required.
|
||||
If an evaluation yields a function value, such as a function-valued
|
||||
field of a struct, the function is not invoked automatically, but it
|
||||
can be used as a truth value for an if action and the like. To invoke
|
||||
it, use the call function, defined below.
|
||||
|
||||
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||
value (argument) or a function or method call, possibly with multiple arguments:
|
||||
|
||||
Argument
|
||||
The result is the value of evaluating the argument.
|
||||
.Method [Argument...]
|
||||
The method can be alone or the last element of a chain but,
|
||||
unlike methods in the middle of a chain, it can take arguments.
|
||||
The result is the value of calling the method with the
|
||||
arguments:
|
||||
dot.Method(Argument1, etc.)
|
||||
functionName [Argument...]
|
||||
The result is the value of calling the function associated
|
||||
with the name:
|
||||
function(Argument1, etc.)
|
||||
Functions and function names are described below.
|
||||
|
||||
Pipelines
|
||||
|
||||
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||
characters '|'. In a chained pipeline, the result of the each command is
|
||||
passed as the last argument of the following command. The output of the final
|
||||
command in the pipeline is the value of the pipeline.
|
||||
|
||||
The output of a command will be either one value or two values, the second of
|
||||
which has type error. If that second value is present and evaluates to
|
||||
non-nil, execution terminates and the error is returned to the caller of
|
||||
Execute.
|
||||
|
||||
Variables
|
||||
|
||||
A pipeline inside an action may initialize a variable to capture the result.
|
||||
The initialization has syntax
|
||||
|
||||
$variable := pipeline
|
||||
|
||||
where $variable is the name of the variable. An action that declares a
|
||||
variable produces no output.
|
||||
|
||||
If a "range" action initializes a variable, the variable is set to the
|
||||
successive elements of the iteration. Also, a "range" may declare two
|
||||
variables, separated by a comma:
|
||||
|
||||
range $index, $element := pipeline
|
||||
|
||||
in which case $index and $element are set to the successive values of the
|
||||
array/slice index or map key and element, respectively. Note that if there is
|
||||
only one variable, it is assigned the element; this is opposite to the
|
||||
convention in Go range clauses.
|
||||
|
||||
A variable's scope extends to the "end" action of the control structure ("if",
|
||||
"with", or "range") in which it is declared, or to the end of the template if
|
||||
there is no such control structure. A template invocation does not inherit
|
||||
variables from the point of its invocation.
|
||||
|
||||
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||
to the starting value of dot.
|
||||
|
||||
Examples
|
||||
|
||||
Here are some example one-line templates demonstrating pipelines and variables.
|
||||
All produce the quoted word "output":
|
||||
|
||||
{{"\"output\""}}
|
||||
A string constant.
|
||||
{{`"output"`}}
|
||||
A raw string constant.
|
||||
{{printf "%q" "output"}}
|
||||
A function call.
|
||||
{{"output" | printf "%q"}}
|
||||
A function call whose final argument comes from the previous
|
||||
command.
|
||||
{{printf "%q" (print "out" "put")}}
|
||||
A parenthesized argument.
|
||||
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||
A more elaborate call.
|
||||
{{"output" | printf "%s" | printf "%q"}}
|
||||
A longer chain.
|
||||
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||
A with action using dot.
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
A with action that creates and uses a variable.
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
A with action that uses the variable in another action.
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
The same, but pipelined.
|
||||
|
||||
Functions
|
||||
|
||||
During execution functions are found in two function maps: first in the
|
||||
template, then in the global function map. By default, no functions are defined
|
||||
in the template but the Funcs method can be used to add them.
|
||||
|
||||
Predefined global functions are named as follows.
|
||||
|
||||
and
|
||||
Returns the boolean AND of its arguments by returning the
|
||||
first empty argument or the last argument, that is,
|
||||
"and x y" behaves as "if x then y else x". All the
|
||||
arguments are evaluated.
|
||||
call
|
||||
Returns the result of calling the first argument, which
|
||||
must be a function, with the remaining arguments as parameters.
|
||||
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||
Y is a func-valued field, map entry, or the like.
|
||||
The first argument must be the result of an evaluation
|
||||
that yields a value of function type (as distinct from
|
||||
a predefined function such as print). The function must
|
||||
return either one or two result values, the second of which
|
||||
is of type error. If the arguments don't match the function
|
||||
or the returned error value is non-nil, execution stops.
|
||||
html
|
||||
Returns the escaped HTML equivalent of the textual
|
||||
representation of its arguments.
|
||||
index
|
||||
Returns the result of indexing its first argument by the
|
||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||
js
|
||||
Returns the escaped JavaScript equivalent of the textual
|
||||
representation of its arguments.
|
||||
len
|
||||
Returns the integer length of its argument.
|
||||
not
|
||||
Returns the boolean negation of its single argument.
|
||||
or
|
||||
Returns the boolean OR of its arguments by returning the
|
||||
first non-empty argument or the last argument, that is,
|
||||
"or x y" behaves as "if x then x else y". All the
|
||||
arguments are evaluated.
|
||||
print
|
||||
An alias for fmt.Sprint
|
||||
printf
|
||||
An alias for fmt.Sprintf
|
||||
println
|
||||
An alias for fmt.Sprintln
|
||||
urlquery
|
||||
Returns the escaped value of the textual representation of
|
||||
its arguments in a form suitable for embedding in a URL query.
|
||||
|
||||
The boolean functions take any zero value to be false and a non-zero
|
||||
value to be true.
|
||||
|
||||
There is also a set of binary comparison operators defined as
|
||||
functions:
|
||||
|
||||
eq
|
||||
Returns the boolean truth of arg1 == arg2
|
||||
ne
|
||||
Returns the boolean truth of arg1 != arg2
|
||||
lt
|
||||
Returns the boolean truth of arg1 < arg2
|
||||
le
|
||||
Returns the boolean truth of arg1 <= arg2
|
||||
gt
|
||||
Returns the boolean truth of arg1 > arg2
|
||||
ge
|
||||
Returns the boolean truth of arg1 >= arg2
|
||||
|
||||
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||
arguments and compares the second and subsequent to the first,
|
||||
returning in effect
|
||||
|
||||
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||
|
||||
(Unlike with || in Go, however, eq is a function call and all the
|
||||
arguments will be evaluated.)
|
||||
|
||||
The comparison functions work on basic types only (or named basic
|
||||
types, such as "type Celsius float32"). They implement the Go rules
|
||||
for comparison of values, except that size and exact type are
|
||||
ignored, so any integer value, signed or unsigned, may be compared
|
||||
with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all
|
||||
unsigned integers.) However, as usual, one may not compare an int
|
||||
with a float32 and so on.
|
||||
|
||||
Associated templates
|
||||
|
||||
Each template is named by a string specified when it is created. Also, each
|
||||
template is associated with zero or more other templates that it may invoke by
|
||||
name; such associations are transitive and form a name space of templates.
|
||||
|
||||
A template may use a template invocation to instantiate another associated
|
||||
template; see the explanation of the "template" action above. The name must be
|
||||
that of a template associated with the template that contains the invocation.
|
||||
|
||||
Nested template definitions
|
||||
|
||||
When parsing a template, another template may be defined and associated with the
|
||||
template being parsed. Template definitions must appear at the top level of the
|
||||
template, much like global variables in a Go program.
|
||||
|
||||
The syntax of such definitions is to surround each template declaration with a
|
||||
"define" and "end" action.
|
||||
|
||||
The define action names the template being created by providing a string
|
||||
constant. Here is a simple example:
|
||||
|
||||
`{{define "T1"}}ONE{{end}}
|
||||
{{define "T2"}}TWO{{end}}
|
||||
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||
{{template "T3"}}`
|
||||
|
||||
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||
when it is executed. Finally it invokes T3. If executed this template will
|
||||
produce the text
|
||||
|
||||
ONE TWO
|
||||
|
||||
By construction, a template may reside in only one association. If it's
|
||||
necessary to have a template addressable from multiple associations, the
|
||||
template definition must be parsed multiple times to create distinct *Template
|
||||
values, or must be copied with the Clone or AddParseTree method.
|
||||
|
||||
Parse may be called multiple times to assemble the various associated templates;
|
||||
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||
related templates stored in files.
|
||||
|
||||
A template may be executed directly or through ExecuteTemplate, which executes
|
||||
an associated template identified by name. To invoke our example above, we
|
||||
might write,
|
||||
|
||||
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
or to invoke a particular template explicitly by name,
|
||||
|
||||
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
*/
|
||||
package template
|
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
|
@ -1,845 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// state represents the state of an execution. It's not part of the
|
||||
// template so that multiple executions of the same template
|
||||
// can execute in parallel.
|
||||
type state struct {
|
||||
tmpl *Template
|
||||
wr io.Writer
|
||||
node parse.Node // current node, for errors
|
||||
vars []variable // push-down stack of variable values.
|
||||
}
|
||||
|
||||
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||
type variable struct {
|
||||
name string
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// push pushes a new variable on the stack.
|
||||
func (s *state) push(name string, value reflect.Value) {
|
||||
s.vars = append(s.vars, variable{name, value})
|
||||
}
|
||||
|
||||
// mark returns the length of the variable stack.
|
||||
func (s *state) mark() int {
|
||||
return len(s.vars)
|
||||
}
|
||||
|
||||
// pop pops the variable stack up to the mark.
|
||||
func (s *state) pop(mark int) {
|
||||
s.vars = s.vars[0:mark]
|
||||
}
|
||||
|
||||
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||
func (s *state) setVar(n int, value reflect.Value) {
|
||||
s.vars[len(s.vars)-n].value = value
|
||||
}
|
||||
|
||||
// varValue returns the value of the named variable.
|
||||
func (s *state) varValue(name string) reflect.Value {
|
||||
for i := s.mark() - 1; i >= 0; i-- {
|
||||
if s.vars[i].name == name {
|
||||
return s.vars[i].value
|
||||
}
|
||||
}
|
||||
s.errorf("undefined variable: %s", name)
|
||||
return zero
|
||||
}
|
||||
|
||||
var zero reflect.Value
|
||||
|
||||
// at marks the state to be on node n, for error reporting.
|
||||
func (s *state) at(node parse.Node) {
|
||||
s.node = node
|
||||
}
|
||||
|
||||
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||
// so it can be used safely inside a Printf format string.
|
||||
func doublePercent(str string) string {
|
||||
if strings.Contains(str, "%") {
|
||||
str = strings.Replace(str, "%", "%%", -1)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (s *state) errorf(format string, args ...interface{}) {
|
||||
name := doublePercent(s.tmpl.Name())
|
||||
if s.node == nil {
|
||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||
} else {
|
||||
location, context := s.tmpl.ErrorContext(s.node)
|
||||
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||
}
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// errRecover is the handler that turns panics into returns from the top
|
||||
// level of Parse.
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTemplate applies the template associated with t that has the given name
|
||||
// to the specified data object and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
tmpl := t.tmpl[name]
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||
}
|
||||
return tmpl.Execute(wr, data)
|
||||
}
|
||||
|
||||
// Execute applies a parsed template to the specified data object,
|
||||
// and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||
defer errRecover(&err)
|
||||
value := reflect.ValueOf(data)
|
||||
state := &state{
|
||||
tmpl: t,
|
||||
wr: wr,
|
||||
vars: []variable{{"$", value}},
|
||||
}
|
||||
t.init()
|
||||
if t.Tree == nil || t.Root == nil {
|
||||
var b bytes.Buffer
|
||||
for name, tmpl := range t.tmpl {
|
||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", name)
|
||||
}
|
||||
var s string
|
||||
if b.Len() > 0 {
|
||||
s = "; defined templates are: " + b.String()
|
||||
}
|
||||
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||
}
|
||||
state.walk(value, t.Root)
|
||||
return
|
||||
}
|
||||
|
||||
// Walk functions step through the major pieces of the template structure,
|
||||
// generating output as they go.
|
||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||
s.at(node)
|
||||
switch node := node.(type) {
|
||||
case *parse.ActionNode:
|
||||
// Do not pop variables so they persist until next end.
|
||||
// Also, if the action declares variables, don't print the result.
|
||||
val := s.evalPipeline(dot, node.Pipe)
|
||||
if len(node.Pipe.Decl) == 0 {
|
||||
s.printValue(node, val)
|
||||
}
|
||||
case *parse.IfNode:
|
||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||
case *parse.ListNode:
|
||||
for _, node := range node.Nodes {
|
||||
s.walk(dot, node)
|
||||
}
|
||||
case *parse.RangeNode:
|
||||
s.walkRange(dot, node)
|
||||
case *parse.TemplateNode:
|
||||
s.walkTemplate(dot, node)
|
||||
case *parse.TextNode:
|
||||
if _, err := s.wr.Write(node.Text); err != nil {
|
||||
s.errorf("%s", err)
|
||||
}
|
||||
case *parse.WithNode:
|
||||
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||
default:
|
||||
s.errorf("unknown node: %s", node)
|
||||
}
|
||||
}
|
||||
|
||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||
// are identical in behavior except that 'with' sets dot.
|
||||
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||
defer s.pop(s.mark())
|
||||
val := s.evalPipeline(dot, pipe)
|
||||
truth, ok := isTrue(val)
|
||||
if !ok {
|
||||
s.errorf("if/with can't use %v", val)
|
||||
}
|
||||
if truth {
|
||||
if typ == parse.NodeWith {
|
||||
s.walk(val, list)
|
||||
} else {
|
||||
s.walk(dot, list)
|
||||
}
|
||||
} else if elseList != nil {
|
||||
s.walk(dot, elseList)
|
||||
}
|
||||
}
|
||||
|
||||
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||
// and whether the value has a meaningful truth value.
|
||||
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||
if !val.IsValid() {
|
||||
// Something like var x interface{}, never set. It's a form of nil.
|
||||
return false, true
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
truth = val.Len() > 0
|
||||
case reflect.Bool:
|
||||
truth = val.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
truth = val.Complex() != 0
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||
truth = !val.IsNil()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
truth = val.Int() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
truth = val.Float() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
truth = val.Uint() != 0
|
||||
case reflect.Struct:
|
||||
truth = true // Struct values are always true.
|
||||
default:
|
||||
return
|
||||
}
|
||||
return truth, true
|
||||
}
|
||||
|
||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||
s.at(r)
|
||||
defer s.pop(s.mark())
|
||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||
// mark top of stack before any variables in the body are pushed.
|
||||
mark := s.mark()
|
||||
oneIteration := func(index, elem reflect.Value) {
|
||||
// Set top var (lexically the second if there are two) to the element.
|
||||
if len(r.Pipe.Decl) > 0 {
|
||||
s.setVar(1, elem)
|
||||
}
|
||||
// Set next var (lexically the first if there are two) to the index.
|
||||
if len(r.Pipe.Decl) > 1 {
|
||||
s.setVar(2, index)
|
||||
}
|
||||
s.walk(elem, r.List)
|
||||
s.pop(mark)
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||
}
|
||||
return
|
||||
case reflect.Map:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for _, key := range sortKeys(val.MapKeys()) {
|
||||
oneIteration(key, val.MapIndex(key))
|
||||
}
|
||||
return
|
||||
case reflect.Chan:
|
||||
if val.IsNil() {
|
||||
break
|
||||
}
|
||||
i := 0
|
||||
for ; ; i++ {
|
||||
elem, ok := val.Recv()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
oneIteration(reflect.ValueOf(i), elem)
|
||||
}
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
return
|
||||
case reflect.Invalid:
|
||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||
default:
|
||||
s.errorf("range can't iterate over %v", val)
|
||||
}
|
||||
if r.ElseList != nil {
|
||||
s.walk(dot, r.ElseList)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||
s.at(t)
|
||||
tmpl := s.tmpl.tmpl[t.Name]
|
||||
if tmpl == nil {
|
||||
s.errorf("template %q not defined", t.Name)
|
||||
}
|
||||
// Variables declared by the pipeline persist.
|
||||
dot = s.evalPipeline(dot, t.Pipe)
|
||||
newState := *s
|
||||
newState.tmpl = tmpl
|
||||
// No dynamic scoping: template invocations inherit no variables.
|
||||
newState.vars = []variable{{"$", dot}}
|
||||
newState.walk(dot, tmpl.Root)
|
||||
}
|
||||
|
||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||
// values from the data structure by examining fields, calling methods, and so on.
|
||||
// The printing of those values happens only through walk functions.
|
||||
|
||||
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||
// pipeline has a variable declaration, the variable will be pushed on the
|
||||
// stack. Callers should therefore pop the stack after they are finished
|
||||
// executing commands depending on the pipeline value.
|
||||
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||
if pipe == nil {
|
||||
return
|
||||
}
|
||||
s.at(pipe)
|
||||
for _, cmd := range pipe.Cmds {
|
||||
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||
// If the object has type interface{}, dig down one level to the thing inside.
|
||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||
}
|
||||
}
|
||||
for _, variable := range pipe.Decl {
|
||||
s.push(variable.Ident[0], value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||
if len(args) > 1 || final.IsValid() {
|
||||
s.errorf("can't give argument to non-function %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||
firstWord := cmd.Args[0]
|
||||
switch n := firstWord.(type) {
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||
case *parse.ChainNode:
|
||||
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||
case *parse.IdentifierNode:
|
||||
// Must be a function.
|
||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||
case *parse.PipeNode:
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||
return s.evalPipeline(dot, n)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||
}
|
||||
s.at(firstWord)
|
||||
s.notAFunction(cmd.Args, final)
|
||||
switch word := firstWord.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(word.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.NilNode:
|
||||
s.errorf("nil is not a command")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(word)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(word.Text)
|
||||
}
|
||||
s.errorf("can't evaluate command %q", firstWord)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// idealConstant is called to return the value of a number in a context where
|
||||
// we don't know the type. In that case, the syntax of the number tells us
|
||||
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||
// a uint ideal constant in this situation - the value must be of int type.
|
||||
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||
// These are ideal constants but we don't know the type
|
||||
// and we have no context. (If it was a method argument,
|
||||
// we'd know what we need.) The syntax guides us to some extent.
|
||||
s.at(constant)
|
||||
switch {
|
||||
case constant.IsComplex:
|
||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||
return reflect.ValueOf(constant.Float64)
|
||||
case constant.IsInt:
|
||||
n := int(constant.Int64)
|
||||
if int64(n) != constant.Int64 {
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case constant.IsUint:
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func isHexConstant(s string) bool {
|
||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||
}
|
||||
|
||||
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(field)
|
||||
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(chain)
|
||||
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||
pipe := s.evalArg(dot, nil, chain.Node)
|
||||
if len(chain.Field) == 0 {
|
||||
s.errorf("internal error: no fields in evalChainNode")
|
||||
}
|
||||
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||
s.at(variable)
|
||||
value := s.varValue(variable.Ident[0])
|
||||
if len(variable.Ident) == 1 {
|
||||
s.notAFunction(args, final)
|
||||
return value
|
||||
}
|
||||
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||
}
|
||||
|
||||
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||
// dot is the environment in which to evaluate arguments, while
|
||||
// receiver is the value being walked along the chain.
|
||||
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
n := len(ident)
|
||||
for i := 0; i < n-1; i++ {
|
||||
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||
}
|
||||
// Now if it's a method, it gets the arguments.
|
||||
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||
}
|
||||
|
||||
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(node)
|
||||
name := node.Ident
|
||||
function, ok := findFunction(name, s.tmpl)
|
||||
if !ok {
|
||||
s.errorf("%q is not a defined function", name)
|
||||
}
|
||||
return s.evalCall(dot, function, cmd, name, args, final)
|
||||
}
|
||||
|
||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||
// The 'final' argument represents the return value from the preceding
|
||||
// value of the pipeline, if any.
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||
if !receiver.IsValid() {
|
||||
return zero
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, _ = indirect(receiver)
|
||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||
// we see all methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||
}
|
||||
hasArgs := len(args) > 1 || final.IsValid()
|
||||
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||
receiver, isNil := indirect(receiver)
|
||||
if isNil {
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
}
|
||||
switch receiver.Kind() {
|
||||
case reflect.Struct:
|
||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||
if ok {
|
||||
field := receiver.FieldByIndex(tField.Index)
|
||||
if tField.PkgPath != "" { // field is unexported
|
||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||
}
|
||||
// If it's a function, we must call it.
|
||||
if hasArgs {
|
||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||
}
|
||||
return field
|
||||
}
|
||||
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||
case reflect.Map:
|
||||
// If it's a map, attempt to use the field name as a key.
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||
if hasArgs {
|
||||
s.errorf("%s is not a method but has arguments", fieldName)
|
||||
}
|
||||
return receiver.MapIndex(nameVal)
|
||||
}
|
||||
}
|
||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
var (
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
)
|
||||
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
typ := fun.Type()
|
||||
numIn := len(args)
|
||||
if final.IsValid() {
|
||||
numIn++
|
||||
}
|
||||
numFixed := len(args)
|
||||
if typ.IsVariadic() {
|
||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||
if numIn < numFixed {
|
||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||
}
|
||||
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||
}
|
||||
// Build the arg list.
|
||||
argv := make([]reflect.Value, numIn)
|
||||
// Args must be evaluated. Fixed args first.
|
||||
i := 0
|
||||
for ; i < numFixed && i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||
}
|
||||
// Now the ... args.
|
||||
if typ.IsVariadic() {
|
||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||
for ; i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, argType, args[i])
|
||||
}
|
||||
}
|
||||
// Add final value if necessary.
|
||||
if final.IsValid() {
|
||||
t := typ.In(typ.NumIn() - 1)
|
||||
if typ.IsVariadic() {
|
||||
t = t.Elem()
|
||||
}
|
||||
argv[i] = s.validateType(final, t)
|
||||
}
|
||||
result := fun.Call(argv)
|
||||
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
s.at(node)
|
||||
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||
}
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
func canBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateType guarantees that the value is valid and assignable to the type.
|
||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||
if !value.IsValid() {
|
||||
if typ == nil || canBeNil(typ) {
|
||||
// An untyped nil interface{}. Accept as a proper nil value.
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("invalid value; expected %s", typ)
|
||||
}
|
||||
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||
value = value.Elem()
|
||||
if value.Type().AssignableTo(typ) {
|
||||
return value
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
// Does one dereference or indirection work? We could do more, as we
|
||||
// do with method receivers, but that gets messy and method receivers
|
||||
// are much more constrained, so it makes more sense there than here.
|
||||
// Besides, one is almost always all you need.
|
||||
switch {
|
||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||
value = value.Elem()
|
||||
if !value.IsValid() {
|
||||
s.errorf("dereference of nil pointer of type %s", typ)
|
||||
}
|
||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||
value = value.Addr()
|
||||
default:
|
||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch arg := n.(type) {
|
||||
case *parse.DotNode:
|
||||
return s.validateType(dot, typ)
|
||||
case *parse.NilNode:
|
||||
if canBeNil(typ) {
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("cannot assign nil to %s", typ)
|
||||
case *parse.FieldNode:
|
||||
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||
case *parse.VariableNode:
|
||||
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||
case *parse.PipeNode:
|
||||
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||
case *parse.ChainNode:
|
||||
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return s.evalBool(typ, n)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return s.evalComplex(typ, n)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return s.evalFloat(typ, n)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return s.evalInteger(typ, n)
|
||||
case reflect.Interface:
|
||||
if typ.NumMethod() == 0 {
|
||||
return s.evalEmptyInterface(dot, n)
|
||||
}
|
||||
case reflect.String:
|
||||
return s.evalString(typ, n)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return s.evalUnsignedInteger(typ, n)
|
||||
}
|
||||
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.BoolNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetBool(n.True)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected bool; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.StringNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetString(n.Text)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected string; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetInt(n.Int64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetUint(n.Uint64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected unsigned integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetFloat(n.Float64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected float; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetComplex(n.Complex128)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected complex; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch n := n.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(n.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, nil, zero)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, n, n, nil, zero)
|
||||
case *parse.NilNode:
|
||||
// NilNode is handled in evalArg, the only place that calls here.
|
||||
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(n)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(n.Text)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, nil, zero)
|
||||
case *parse.PipeNode:
|
||||
return s.evalPipeline(dot, n)
|
||||
}
|
||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
// We indirect through pointers and empty interfaces (only) because
|
||||
// non-empty interfaces have methods we might need.
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// printValue writes the textual representation of the value to the output of
|
||||
// the template.
|
||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||
s.at(n)
|
||||
iface, ok := printableValue(v)
|
||||
if !ok {
|
||||
s.errorf("can't print %s of type %s", n, v.Type())
|
||||
}
|
||||
fmt.Fprint(s.wr, iface)
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "<no value>", true
|
||||
}
|
||||
|
||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||
v = v.Addr()
|
||||
} else {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
// Types to help sort the keys in a map for reproducible output.
|
||||
|
||||
type rvs []reflect.Value
|
||||
|
||||
func (x rvs) Len() int { return len(x) }
|
||||
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
type rvInts struct{ rvs }
|
||||
|
||||
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||
|
||||
type rvUints struct{ rvs }
|
||||
|
||||
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||
|
||||
type rvFloats struct{ rvs }
|
||||
|
||||
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||
|
||||
type rvStrings struct{ rvs }
|
||||
|
||||
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||
|
||||
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||
if len(v) <= 1 {
|
||||
return v
|
||||
}
|
||||
switch v[0].Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sort.Sort(rvFloats{v})
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sort.Sort(rvInts{v})
|
||||
case reflect.String:
|
||||
sort.Sort(rvStrings{v})
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
sort.Sort(rvUints{v})
|
||||
}
|
||||
return v
|
||||
}
|
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
|
@ -1,598 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||
// Each function must have either a single return value, or two return values of
|
||||
// which the second has type error. In that case, if the second (error)
|
||||
// return value evaluates to non-nil during execution, execution terminates and
|
||||
// Execute returns that error.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
}
|
||||
|
||||
var builtinFuncs = createValueFuncs(builtins)
|
||||
|
||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||
m := make(map[string]reflect.Value)
|
||||
addValueFuncs(m, funcMap)
|
||||
return m
|
||||
}
|
||||
|
||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
v := reflect.ValueOf(fn)
|
||||
if v.Kind() != reflect.Func {
|
||||
panic("value for " + name + " not a function")
|
||||
}
|
||||
if !goodFunc(v.Type()) {
|
||||
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||
}
|
||||
out[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||
// call addValueFuncs first.
|
||||
func addFuncs(out, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
out[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// goodFunc checks that the function or method has the right result signature.
|
||||
func goodFunc(typ reflect.Type) bool {
|
||||
// We allow functions with 1 result or 2 results where the second is an error.
|
||||
switch {
|
||||
case typ.NumOut() == 1:
|
||||
return true
|
||||
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findFunction looks for a function in the template, and global map.
|
||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
if tmpl != nil && tmpl.common != nil {
|
||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
// Indexing.
|
||||
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
for _, i := range indices {
|
||||
index := reflect.ValueOf(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
return nil, fmt.Errorf("index of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return nil, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
case reflect.Map:
|
||||
if !index.IsValid() {
|
||||
index = reflect.Zero(v.Type().Key())
|
||||
}
|
||||
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||
}
|
||||
}
|
||||
return v.Interface(), nil
|
||||
}
|
||||
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
func length(item interface{}) (int, error) {
|
||||
v, isNil := indirect(reflect.ValueOf(item))
|
||||
if isNil {
|
||||
return 0, fmt.Errorf("len of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||
}
|
||||
|
||||
// Function invocation
|
||||
|
||||
// call returns the result of evaluating the first argument as a function.
|
||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(fn)
|
||||
typ := v.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||
}
|
||||
numIn := typ.NumIn()
|
||||
var dddType reflect.Type
|
||||
if typ.IsVariadic() {
|
||||
if len(args) < numIn-1 {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||
}
|
||||
dddType = typ.In(numIn - 1).Elem()
|
||||
} else {
|
||||
if len(args) != numIn {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||
}
|
||||
}
|
||||
argv := make([]reflect.Value, len(args))
|
||||
for i, arg := range args {
|
||||
value := reflect.ValueOf(arg)
|
||||
// Compute the expected type. Clumsy because of variadics.
|
||||
var argType reflect.Type
|
||||
if !typ.IsVariadic() || i < numIn-1 {
|
||||
argType = typ.In(i)
|
||||
} else {
|
||||
argType = dddType
|
||||
}
|
||||
if !value.IsValid() && canBeNil(argType) {
|
||||
value = reflect.Zero(argType)
|
||||
}
|
||||
if !value.Type().AssignableTo(argType) {
|
||||
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||
}
|
||||
argv[i] = value
|
||||
}
|
||||
result := v.Call(argv)
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
return result[0].Interface(), result[1].Interface().(error)
|
||||
}
|
||||
return result[0].Interface(), nil
|
||||
}
|
||||
|
||||
// Boolean logic.
|
||||
|
||||
func truth(a interface{}) bool {
|
||||
t, _ := isTrue(reflect.ValueOf(a))
|
||||
return t
|
||||
}
|
||||
|
||||
// and computes the Boolean AND of its arguments, returning
|
||||
// the first false argument it encounters, or the last argument.
|
||||
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if !truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if !truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// or computes the Boolean OR of its arguments, returning
|
||||
// the first true argument it encounters, or the last argument.
|
||||
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// not returns the Boolean negation of its argument.
|
||||
func not(arg interface{}) (truth bool) {
|
||||
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||
return !truth
|
||||
}
|
||||
|
||||
// Comparison.
|
||||
|
||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
errBadComparison = errors.New("incompatible types for comparison")
|
||||
errNoComparison = errors.New("missing argument for comparison")
|
||||
)
|
||||
|
||||
type kind int
|
||||
|
||||
const (
|
||||
invalidKind kind = iota
|
||||
boolKind
|
||||
complexKind
|
||||
intKind
|
||||
floatKind
|
||||
integerKind
|
||||
stringKind
|
||||
uintKind
|
||||
)
|
||||
|
||||
func basicKind(v reflect.Value) (kind, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return boolKind, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intKind, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return uintKind, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return floatKind, nil
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return complexKind, nil
|
||||
case reflect.String:
|
||||
return stringKind, nil
|
||||
}
|
||||
return invalidKind, errBadComparisonType
|
||||
}
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
for _, arg := range arg2 {
|
||||
v2 := reflect.ValueOf(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
if truth {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ne evaluates the comparison a != b.
|
||||
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||
// != is the inverse of ==.
|
||||
equal, err := eq(arg1, arg2)
|
||||
return !equal, err
|
||||
}
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := reflect.ValueOf(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
return truth, nil
|
||||
}
|
||||
|
||||
// le evaluates the comparison <= b.
|
||||
func le(arg1, arg2 interface{}) (bool, error) {
|
||||
// <= is < or ==.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if lessThan || err != nil {
|
||||
return lessThan, err
|
||||
}
|
||||
return eq(arg1, arg2)
|
||||
}
|
||||
|
||||
// gt evaluates the comparison a > b.
|
||||
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||
// > is the inverse of <=.
|
||||
lessOrEqual, err := le(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessOrEqual, nil
|
||||
}
|
||||
|
||||
// ge evaluates the comparison a >= b.
|
||||
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||
// >= is the inverse of <.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessThan, nil
|
||||
}
|
||||
|
||||
// HTML escaping.
|
||||
|
||||
var (
|
||||
htmlQuot = []byte(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []byte(">")
|
||||
)
|
||||
|
||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||
func HTMLEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i, c := range b {
|
||||
var html []byte
|
||||
switch c {
|
||||
case '"':
|
||||
html = htmlQuot
|
||||
case '\'':
|
||||
html = htmlApos
|
||||
case '&':
|
||||
html = htmlAmp
|
||||
case '<':
|
||||
html = htmlLt
|
||||
case '>':
|
||||
html = htmlGt
|
||||
default:
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
w.Write(html)
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||
func HTMLEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
HTMLEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func HTMLEscaper(args ...interface{}) string {
|
||||
return HTMLEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// JavaScript escaping.
|
||||
|
||||
var (
|
||||
jsLowUni = []byte(`\u00`)
|
||||
hex = []byte("0123456789ABCDEF")
|
||||
|
||||
jsBackslash = []byte(`\\`)
|
||||
jsApos = []byte(`\'`)
|
||||
jsQuot = []byte(`\"`)
|
||||
jsLt = []byte(`\x3C`)
|
||||
jsGt = []byte(`\x3E`)
|
||||
)
|
||||
|
||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||
func JSEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := b[i]
|
||||
|
||||
if !jsIsSpecial(rune(c)) {
|
||||
// fast path: nothing to do
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
|
||||
if c < utf8.RuneSelf {
|
||||
// Quotes, slashes and angle brackets get quoted.
|
||||
// Control characters get written as \u00XX.
|
||||
switch c {
|
||||
case '\\':
|
||||
w.Write(jsBackslash)
|
||||
case '\'':
|
||||
w.Write(jsApos)
|
||||
case '"':
|
||||
w.Write(jsQuot)
|
||||
case '<':
|
||||
w.Write(jsLt)
|
||||
case '>':
|
||||
w.Write(jsGt)
|
||||
default:
|
||||
w.Write(jsLowUni)
|
||||
t, b := c>>4, c&0x0f
|
||||
w.Write(hex[t : t+1])
|
||||
w.Write(hex[b : b+1])
|
||||
}
|
||||
} else {
|
||||
// Unicode rune.
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.IsPrint(r) {
|
||||
w.Write(b[i : i+size])
|
||||
} else {
|
||||
fmt.Fprintf(w, "\\u%04X", r)
|
||||
}
|
||||
i += size - 1
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||
func JSEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
JSEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func jsIsSpecial(r rune) bool {
|
||||
switch r {
|
||||
case '\\', '\'', '"', '<', '>':
|
||||
return true
|
||||
}
|
||||
return r < ' ' || utf8.RuneSelf <= r
|
||||
}
|
||||
|
||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func JSEscaper(args ...interface{}) string {
|
||||
return JSEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||
// its arguments in a form suitable for embedding in a URL query.
|
||||
func URLQueryEscaper(args ...interface{}) string {
|
||||
return url.QueryEscape(evalArgs(args))
|
||||
}
|
||||
|
||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||
// fmt.Sprint(args...)
|
||||
// except that each argument is indirected (if a pointer), as required,
|
||||
// using the same rules as the default string evaluation during template
|
||||
// execution.
|
||||
func evalArgs(args []interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
// Fast path for simple common case.
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
for i, arg := range args {
|
||||
a, ok := printableValue(reflect.ValueOf(arg))
|
||||
if ok {
|
||||
args[i] = a
|
||||
} // else left fmt do its thing
|
||||
}
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return s
|
||||
}
|
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
|
@ -1,108 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Helper functions to make constructing templates easier.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Functions and methods to parse templates.
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFiles creates a new Template and parses the template definitions from
|
||||
// the named files. The returned template's name will have the (base) name and
|
||||
// (parsed) contents of the first file. There must be at least one file.
|
||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||
func ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(nil, filenames...)
|
||||
}
|
||||
|
||||
// ParseFiles parses the named files and associates the resulting templates with
|
||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||
// otherwise it is t. There must be at least one file.
|
||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
||||
// parseFiles is the helper for the method and function. If the argument
|
||||
// template is nil, it is created from the first file.
|
||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||
if len(filenames) == 0 {
|
||||
// Not really a problem, but be consistent.
|
||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(b)
|
||||
name := filepath.Base(filename)
|
||||
// First template becomes return value if not already defined,
|
||||
// and we use that one for subsequent New calls to associate
|
||||
// all the templates together. Also, if this file has the same name
|
||||
// as t, this file becomes the contents of t, so
|
||||
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||
// works. Otherwise we create a new template associated with t.
|
||||
var tmpl *Template
|
||||
if t == nil {
|
||||
t = New(name)
|
||||
}
|
||||
if name == t.Name() {
|
||||
tmpl = t
|
||||
} else {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
_, err = tmpl.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ParseGlob creates a new Template and parses the template definitions from the
|
||||
// files identified by the pattern, which must match at least one file. The
|
||||
// returned template will have the (base) name and (parsed) contents of the
|
||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||
// ParseFiles with the list of files matched by the pattern.
|
||||
func ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(nil, pattern)
|
||||
}
|
||||
|
||||
// ParseGlob parses the template definitions in the files identified by the
|
||||
// pattern and associates the resulting templates with t. The pattern is
|
||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||
// pattern.
|
||||
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(t, pattern)
|
||||
}
|
||||
|
||||
// parseGlob is the implementation of the function and method ParseGlob.
|
||||
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||
filenames, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(filenames) == 0 {
|
||||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||
}
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
|
@ -1,556 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ > itemKeyword:
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemBool // boolean constant
|
||||
itemChar // printable ASCII character; grab bag for comma etc.
|
||||
itemCharConstant // character constant
|
||||
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||
itemEOF
|
||||
itemField // alphanumeric identifier starting with '.'
|
||||
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||
itemLeftDelim // left action delimiter
|
||||
itemLeftParen // '(' inside action
|
||||
itemNumber // simple number, including imaginary
|
||||
itemPipe // pipe symbol
|
||||
itemRawString // raw quoted string (includes quotes)
|
||||
itemRightDelim // right action delimiter
|
||||
itemElideNewline // elide newline after right delim
|
||||
itemRightParen // ')' inside action
|
||||
itemSpace // run of spaces separating arguments
|
||||
itemString // quoted string (includes quotes)
|
||||
itemText // plain text
|
||||
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||
// Keywords appear after all the rest.
|
||||
itemKeyword // used only to delimit the keywords
|
||||
itemDot // the cursor, spelled '.'
|
||||
itemDefine // define keyword
|
||||
itemElse // else keyword
|
||||
itemEnd // end keyword
|
||||
itemIf // if keyword
|
||||
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||
itemRange // range keyword
|
||||
itemTemplate // template keyword
|
||||
itemWith // with keyword
|
||||
)
|
||||
|
||||
var key = map[string]itemType{
|
||||
".": itemDot,
|
||||
"define": itemDefine,
|
||||
"else": itemElse,
|
||||
"end": itemEnd,
|
||||
"if": itemIf,
|
||||
"range": itemRange,
|
||||
"nil": itemNil,
|
||||
"template": itemTemplate,
|
||||
"with": itemWith,
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action
|
||||
rightDelim string // end of action
|
||||
state stateFn // the next lexing function to enter
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
lastPos Pos // position of most recent item returned by nextItem
|
||||
items chan item // channel of scanned items
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
}
|
||||
if right == "" {
|
||||
right = rightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexText; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
const (
|
||||
leftDelim = "{{"
|
||||
rightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
)
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
func lexText(l *lexer) stateFn {
|
||||
for {
|
||||
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
return lexLeftDelim
|
||||
}
|
||||
if l.next() == eof {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||
func lexLeftDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.leftDelim))
|
||||
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||
return lexComment
|
||||
}
|
||||
l.emit(itemLeftDelim)
|
||||
l.parenDepth = 0
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexComment scans a comment. The left comment marker is known to be present.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(leftComment))
|
||||
i := strings.Index(l.input[l.pos:], rightComment)
|
||||
if i < 0 {
|
||||
return l.errorf("unclosed comment")
|
||||
}
|
||||
l.pos += Pos(i + len(rightComment))
|
||||
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
return l.errorf("comment ends before closing delimiter")
|
||||
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.ignore()
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||
func lexRightDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.emit(itemRightDelim)
|
||||
if l.peek() == '\\' {
|
||||
l.pos++
|
||||
l.emit(itemElideNewline)
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
func lexInsideAction(l *lexer) stateFn {
|
||||
// Either number, quoted string, or identifier.
|
||||
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||
// Pipe symbols separate and are emitted.
|
||||
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
if l.parenDepth == 0 {
|
||||
return lexRightDelim
|
||||
}
|
||||
return l.errorf("unclosed left paren")
|
||||
}
|
||||
switch r := l.next(); {
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("unclosed action")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == ':':
|
||||
if l.next() != '=' {
|
||||
return l.errorf("expected :=")
|
||||
}
|
||||
l.emit(itemColonEquals)
|
||||
case r == '|':
|
||||
l.emit(itemPipe)
|
||||
case r == '"':
|
||||
return lexQuote
|
||||
case r == '`':
|
||||
return lexRawQuote
|
||||
case r == '$':
|
||||
return lexVariable
|
||||
case r == '\'':
|
||||
return lexChar
|
||||
case r == '.':
|
||||
// special look-ahead for ".field" so we don't break l.backup().
|
||||
if l.pos < Pos(len(l.input)) {
|
||||
r := l.input[l.pos]
|
||||
if r < '0' || '9' < r {
|
||||
return lexField
|
||||
}
|
||||
}
|
||||
fallthrough // '.' can start a number.
|
||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case isAlphaNumeric(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
return lexInsideAction
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right paren %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return lexInsideAction
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters.
|
||||
// One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.emit(itemSpace)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
switch {
|
||||
case key[word] > itemKeyword:
|
||||
l.emit(key[word])
|
||||
case word[0] == '.':
|
||||
l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
l.emit(itemBool)
|
||||
default:
|
||||
l.emit(itemIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexField scans a field: .Alphanumeric.
|
||||
// The . has been scanned.
|
||||
func lexField(l *lexer) stateFn {
|
||||
return lexFieldOrVariable(l, itemField)
|
||||
}
|
||||
|
||||
// lexVariable scans a Variable: $Alphanumeric.
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||
l.emit(itemVariable)
|
||||
return lexInsideAction
|
||||
}
|
||||
return lexFieldOrVariable(l, itemVariable)
|
||||
}
|
||||
|
||||
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||
// The . or $ has been scanned.
|
||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||
if typ == itemVariable {
|
||||
l.emit(itemVariable)
|
||||
} else {
|
||||
l.emit(itemDot)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
var r rune
|
||||
for {
|
||||
r = l.next()
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
l.emit(typ)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// atTerminator reports whether the input is at valid termination character to
|
||||
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||
// day to implement arithmetic.
|
||||
func (l *lexer) atTerminator() bool {
|
||||
r := l.peek()
|
||||
if isSpace(r) || isEndOfLine(r) {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case eof, '.', ',', '|', ':', ')', '(':
|
||||
return true
|
||||
}
|
||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||
// bad choice of delimiter.
|
||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexChar scans a character constant. The initial quote is already
|
||||
// scanned. Syntax checking is done by the parser.
|
||||
func lexChar(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated character constant")
|
||||
case '\'':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemCharConstant)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(itemComplex)
|
||||
} else {
|
||||
l.emit(itemNumber)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
func (l *lexer) scanNumber() bool {
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Is it imaginary?
|
||||
l.accept("i")
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lexQuote scans a quoted string.
|
||||
func lexQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case '"':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexRawQuote scans a raw quoted string.
|
||||
func lexRawQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated raw quoted string")
|
||||
case '`':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemRawString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
|
@ -1,834 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse nodes.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||
|
||||
// A Node is an element in the parse tree. The interface is trivial.
|
||||
// The interface contains an unexported method so that only
|
||||
// types local to this package can satisfy it.
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
// Copy does a deep copy of the Node and all its components.
|
||||
// To avoid type assertions, some XxxNodes also have specialized
|
||||
// CopyXxx methods that return *XxxNode.
|
||||
Copy() Node
|
||||
Position() Pos // byte position of start of node in full original input string
|
||||
// tree returns the containing *Tree.
|
||||
// It is unexported so all implementations of Node are in this package.
|
||||
tree() *Tree
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota // Plain text.
|
||||
NodeAction // A non-control action such as a field evaluation.
|
||||
NodeBool // A boolean constant.
|
||||
NodeChain // A sequence of field accesses.
|
||||
NodeCommand // An element of a pipeline.
|
||||
NodeDot // The cursor, dot.
|
||||
nodeElse // An else action. Not added to tree.
|
||||
nodeEnd // An end action. Not added to tree.
|
||||
NodeField // A field or method name.
|
||||
NodeIdentifier // An identifier; always a function name.
|
||||
NodeIf // An if action.
|
||||
NodeList // A list of Nodes.
|
||||
NodeNil // An untyped nil constant.
|
||||
NodeNumber // A numerical constant.
|
||||
NodePipe // A pipeline of commands.
|
||||
NodeRange // A range action.
|
||||
NodeString // A string constant.
|
||||
NodeTemplate // A template invocation action.
|
||||
NodeVariable // A $ variable.
|
||||
NodeWith // A with action.
|
||||
)
|
||||
|
||||
// Nodes.
|
||||
|
||||
// ListNode holds a sequence of nodes.
|
||||
type ListNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Nodes []Node // The element nodes in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newList(pos Pos) *ListNode {
|
||||
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||
}
|
||||
|
||||
func (l *ListNode) append(n Node) {
|
||||
l.Nodes = append(l.Nodes, n)
|
||||
}
|
||||
|
||||
func (l *ListNode) tree() *Tree {
|
||||
return l.tr
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
for _, n := range l.Nodes {
|
||||
fmt.Fprint(b, n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) CopyList() *ListNode {
|
||||
if l == nil {
|
||||
return l
|
||||
}
|
||||
n := l.tr.newList(l.Pos)
|
||||
for _, elem := range l.Nodes {
|
||||
n.append(elem.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *ListNode) Copy() Node {
|
||||
return l.CopyList()
|
||||
}
|
||||
|
||||
// TextNode holds plain text.
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Text []byte // The text; may span newlines.
|
||||
}
|
||||
|
||||
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() string {
|
||||
return fmt.Sprintf(textFormat, t.Text)
|
||||
}
|
||||
|
||||
func (t *TextNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TextNode) Copy() Node {
|
||||
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||
}
|
||||
|
||||
// PipeNode holds a pipeline with optional declaration
|
||||
type PipeNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Decl []*VariableNode // Variable declarations in lexical order.
|
||||
Cmds []*CommandNode // The commands in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||
}
|
||||
|
||||
func (p *PipeNode) append(command *CommandNode) {
|
||||
p.Cmds = append(p.Cmds, command)
|
||||
}
|
||||
|
||||
func (p *PipeNode) String() string {
|
||||
s := ""
|
||||
if len(p.Decl) > 0 {
|
||||
for i, v := range p.Decl {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += v.String()
|
||||
}
|
||||
s += " := "
|
||||
}
|
||||
for i, c := range p.Cmds {
|
||||
if i > 0 {
|
||||
s += " | "
|
||||
}
|
||||
s += c.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PipeNode) tree() *Tree {
|
||||
return p.tr
|
||||
}
|
||||
|
||||
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||
if p == nil {
|
||||
return p
|
||||
}
|
||||
var decl []*VariableNode
|
||||
for _, d := range p.Decl {
|
||||
decl = append(decl, d.Copy().(*VariableNode))
|
||||
}
|
||||
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||
for _, c := range p.Cmds {
|
||||
n.append(c.Copy().(*CommandNode))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *PipeNode) Copy() Node {
|
||||
return p.CopyPipe()
|
||||
}
|
||||
|
||||
// ActionNode holds an action (something bounded by delimiters).
|
||||
// Control actions have their own nodes; ActionNode represents simple
|
||||
// ones such as field evaluations and parenthesized pipelines.
|
||||
type ActionNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline in the action.
|
||||
}
|
||||
|
||||
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (a *ActionNode) String() string {
|
||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
return a.tr
|
||||
}
|
||||
|
||||
func (a *ActionNode) Copy() Node {
|
||||
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||
|
||||
}
|
||||
|
||||
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||
type CommandNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||
}
|
||||
|
||||
func (c *CommandNode) append(arg Node) {
|
||||
c.Args = append(c.Args, arg)
|
||||
}
|
||||
|
||||
func (c *CommandNode) String() string {
|
||||
s := ""
|
||||
for i, arg := range c.Args {
|
||||
if i > 0 {
|
||||
s += " "
|
||||
}
|
||||
if arg, ok := arg.(*PipeNode); ok {
|
||||
s += "(" + arg.String() + ")"
|
||||
continue
|
||||
}
|
||||
s += arg.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CommandNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *CommandNode) Copy() Node {
|
||||
if c == nil {
|
||||
return c
|
||||
}
|
||||
n := c.tr.newCommand(c.Pos)
|
||||
for _, c := range c.Args {
|
||||
n.append(c.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IdentifierNode holds an identifier.
|
||||
type IdentifierNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident string // The identifier's name.
|
||||
}
|
||||
|
||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||
func NewIdentifier(ident string) *IdentifierNode {
|
||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||
}
|
||||
|
||||
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||
i.Pos = pos
|
||||
return i
|
||||
}
|
||||
|
||||
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||
i.tr = t
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) String() string {
|
||||
return i.Ident
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) tree() *Tree {
|
||||
return i.tr
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) Copy() Node {
|
||||
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||
}
|
||||
|
||||
// VariableNode holds a list of variable names, possibly with chained field
|
||||
// accesses. The dollar sign is part of the (first) name.
|
||||
type VariableNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // Variable name and fields in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||
}
|
||||
|
||||
func (v *VariableNode) String() string {
|
||||
s := ""
|
||||
for i, id := range v.Ident {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
}
|
||||
s += id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *VariableNode) tree() *Tree {
|
||||
return v.tr
|
||||
}
|
||||
|
||||
func (v *VariableNode) Copy() Node {
|
||||
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||
}
|
||||
|
||||
// DotNode holds the special identifier '.'.
|
||||
type DotNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||
}
|
||||
|
||||
func (d *DotNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeDot
|
||||
}
|
||||
|
||||
func (d *DotNode) String() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (d *DotNode) tree() *Tree {
|
||||
return d.tr
|
||||
}
|
||||
|
||||
func (d *DotNode) Copy() Node {
|
||||
return d.tr.newDot(d.Pos)
|
||||
}
|
||||
|
||||
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||
type NilNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||
}
|
||||
|
||||
func (n *NilNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeNil
|
||||
}
|
||||
|
||||
func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func (n *NilNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NilNode) Copy() Node {
|
||||
return n.tr.newNil(n.Pos)
|
||||
}
|
||||
|
||||
// FieldNode holds a field (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The period is dropped from each ident.
|
||||
type FieldNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
s := ""
|
||||
for _, id := range f.Ident {
|
||||
s += "." + id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *FieldNode) tree() *Tree {
|
||||
return f.tr
|
||||
}
|
||||
|
||||
func (f *FieldNode) Copy() Node {
|
||||
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||
}
|
||||
|
||||
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The periods are dropped from each ident.
|
||||
type ChainNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Node Node
|
||||
Field []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||
}
|
||||
|
||||
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||
func (c *ChainNode) Add(field string) {
|
||||
if len(field) == 0 || field[0] != '.' {
|
||||
panic("no dot in field")
|
||||
}
|
||||
field = field[1:] // Remove leading dot.
|
||||
if field == "" {
|
||||
panic("empty field")
|
||||
}
|
||||
c.Field = append(c.Field, field)
|
||||
}
|
||||
|
||||
func (c *ChainNode) String() string {
|
||||
s := c.Node.String()
|
||||
if _, ok := c.Node.(*PipeNode); ok {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
for _, field := range c.Field {
|
||||
s += "." + field
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *ChainNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *ChainNode) Copy() Node {
|
||||
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||
}
|
||||
|
||||
// BoolNode holds a boolean constant.
|
||||
type BoolNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
True bool // The value of the boolean constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||
}
|
||||
|
||||
func (b *BoolNode) String() string {
|
||||
if b.True {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (b *BoolNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BoolNode) Copy() Node {
|
||||
return b.tr.newBool(b.Pos, b.True)
|
||||
}
|
||||
|
||||
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||
// The value is parsed and stored under all the types that can represent the value.
|
||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||
type NumberNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
IsInt bool // Number has an integral value.
|
||||
IsUint bool // Number has an unsigned integral value.
|
||||
IsFloat bool // Number has a floating-point value.
|
||||
IsComplex bool // Number is complex.
|
||||
Int64 int64 // The signed integer value.
|
||||
Uint64 uint64 // The unsigned integer value.
|
||||
Float64 float64 // The floating-point value.
|
||||
Complex128 complex128 // The complex value.
|
||||
Text string // The original textual representation from the input.
|
||||
}
|
||||
|
||||
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||
switch typ {
|
||||
case itemCharConstant:
|
||||
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "'" {
|
||||
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||
}
|
||||
n.Int64 = int64(rune)
|
||||
n.IsInt = true
|
||||
n.Uint64 = uint64(rune)
|
||||
n.IsUint = true
|
||||
n.Float64 = float64(rune) // odd but those are the rules.
|
||||
n.IsFloat = true
|
||||
return n, nil
|
||||
case itemComplex:
|
||||
// fmt.Sscan can parse the pair, so let it do the work.
|
||||
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.IsComplex = true
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
// Imaginary constants can only be complex unless they are zero.
|
||||
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||
if err == nil {
|
||||
n.IsComplex = true
|
||||
n.Complex128 = complex(0, f)
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
// Do integer test first so we get 0x123 etc.
|
||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||
if err == nil {
|
||||
n.IsUint = true
|
||||
n.Uint64 = u
|
||||
}
|
||||
i, err := strconv.ParseInt(text, 0, 64)
|
||||
if err == nil {
|
||||
n.IsInt = true
|
||||
n.Int64 = i
|
||||
if i == 0 {
|
||||
n.IsUint = true // in case of -0.
|
||||
n.Uint64 = u
|
||||
}
|
||||
}
|
||||
// If an integer extraction succeeded, promote the float.
|
||||
if n.IsInt {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Int64)
|
||||
} else if n.IsUint {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Uint64)
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil {
|
||||
n.IsFloat = true
|
||||
n.Float64 = f
|
||||
// If a floating-point extraction succeeded, extract the int if needed.
|
||||
if !n.IsInt && float64(int64(f)) == f {
|
||||
n.IsInt = true
|
||||
n.Int64 = int64(f)
|
||||
}
|
||||
if !n.IsUint && float64(uint64(f)) == f {
|
||||
n.IsUint = true
|
||||
n.Uint64 = uint64(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||
// These all require that the imaginary part be zero.
|
||||
func (n *NumberNode) simplifyComplex() {
|
||||
n.IsFloat = imag(n.Complex128) == 0
|
||||
if n.IsFloat {
|
||||
n.Float64 = real(n.Complex128)
|
||||
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||
if n.IsInt {
|
||||
n.Int64 = int64(n.Float64)
|
||||
}
|
||||
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||
if n.IsUint {
|
||||
n.Uint64 = uint64(n.Float64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NumberNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
func (n *NumberNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NumberNode) Copy() Node {
|
||||
nn := new(NumberNode)
|
||||
*nn = *n // Easy, fast, correct.
|
||||
return nn
|
||||
}
|
||||
|
||||
// StringNode holds a string constant. The value has been "unquoted".
|
||||
type StringNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Quoted string // The original text of the string, with quotes.
|
||||
Text string // The string, after quote processing.
|
||||
}
|
||||
|
||||
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||
}
|
||||
|
||||
func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
func (s *StringNode) tree() *Tree {
|
||||
return s.tr
|
||||
}
|
||||
|
||||
func (s *StringNode) Copy() Node {
|
||||
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||
}
|
||||
|
||||
// endNode represents an {{end}} action.
|
||||
// It does not appear in the final parse tree.
|
||||
type endNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||
}
|
||||
|
||||
func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
}
|
||||
|
||||
func (e *endNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *endNode) Copy() Node {
|
||||
return e.tr.newEnd(e.Pos)
|
||||
}
|
||||
|
||||
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||
type elseNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
}
|
||||
|
||||
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||
}
|
||||
|
||||
func (e *elseNode) Type() NodeType {
|
||||
return nodeElse
|
||||
}
|
||||
|
||||
func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
}
|
||||
|
||||
func (e *elseNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *elseNode) Copy() Node {
|
||||
return e.tr.newElse(e.Pos, e.Line)
|
||||
}
|
||||
|
||||
// BranchNode is the common representation of if, range, and with.
|
||||
type BranchNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline to be evaluated.
|
||||
List *ListNode // What to execute if the value is non-empty.
|
||||
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||
}
|
||||
|
||||
func (b *BranchNode) String() string {
|
||||
name := ""
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
name = "if"
|
||||
case NodeRange:
|
||||
name = "range"
|
||||
case NodeWith:
|
||||
name = "with"
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
if b.ElseList != nil {
|
||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||
}
|
||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BranchNode) Copy() Node {
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeRange:
|
||||
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeWith:
|
||||
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
}
|
||||
|
||||
// IfNode represents an {{if}} action and its commands.
|
||||
type IfNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (i *IfNode) Copy() Node {
|
||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// RangeNode represents a {{range}} action and its commands.
|
||||
type RangeNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (r *RangeNode) Copy() Node {
|
||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// WithNode represents a {{with}} action and its commands.
|
||||
type WithNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (w *WithNode) Copy() Node {
|
||||
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// TemplateNode represents a {{template}} action.
|
||||
type TemplateNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Name string // The name of the template (unquoted).
|
||||
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||
}
|
||||
|
||||
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (t *TemplateNode) String() string {
|
||||
if t.Pipe == nil {
|
||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||
}
|
||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TemplateNode) Copy() Node {
|
||||
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||
}
|
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
|
@ -1,700 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package parse builds parse trees for templates as defined by text/template
|
||||
// and html/template. Clients should use those packages to construct templates
|
||||
// rather than this one, which provides shared internal data structures not
|
||||
// intended for general use.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tree is the representation of a single parsed template.
|
||||
type Tree struct {
|
||||
Name string // name of the template represented by the tree.
|
||||
ParseName string // name of the top-level template during parsing, for error messages.
|
||||
Root *ListNode // top-level root of the tree.
|
||||
text string // text parsed to create the template (or its parent)
|
||||
// Parsing only; cleared after parse.
|
||||
funcs []map[string]interface{}
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead for parser.
|
||||
peekCount int
|
||||
vars []string // variables defined at the moment.
|
||||
}
|
||||
|
||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||
func (t *Tree) Copy() *Tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return &Tree{
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||
// templates described in the argument string. The top-level template will be
|
||||
// given the specified name. If an error is encountered, parsing stops and an
|
||||
// empty map is returned with the error.
|
||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||
treeSet = make(map[string]*Tree)
|
||||
t := New(name)
|
||||
t.text = text
|
||||
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||
return
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (t *Tree) next() item {
|
||||
if t.peekCount > 0 {
|
||||
t.peekCount--
|
||||
} else {
|
||||
t.token[0] = t.lex.nextItem()
|
||||
}
|
||||
return t.token[t.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (t *Tree) backup() {
|
||||
t.peekCount++
|
||||
}
|
||||
|
||||
// backup2 backs the input stream up two tokens.
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup2(t1 item) {
|
||||
t.token[1] = t1
|
||||
t.peekCount = 2
|
||||
}
|
||||
|
||||
// backup3 backs the input stream up three tokens
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||
t.token[1] = t1
|
||||
t.token[2] = t2
|
||||
t.peekCount = 3
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (t *Tree) peek() item {
|
||||
if t.peekCount > 0 {
|
||||
return t.token[t.peekCount-1]
|
||||
}
|
||||
t.peekCount = 1
|
||||
t.token[0] = t.lex.nextItem()
|
||||
return t.token[0]
|
||||
}
|
||||
|
||||
// nextNonSpace returns the next non-space token.
|
||||
func (t *Tree) nextNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// peekNonSpace returns but does not consume the next non-space token.
|
||||
func (t *Tree) peekNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.backup()
|
||||
return token
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
||||
// New allocates a new parse tree with the given name.
|
||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||
return &Tree{
|
||||
Name: name,
|
||||
funcs: funcs,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||
// which can occur in old code.
|
||||
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||
pos := int(n.Position())
|
||||
tree := n.tree()
|
||||
if tree == nil {
|
||||
tree = t
|
||||
}
|
||||
text := tree.text[:pos]
|
||||
byteNum := strings.LastIndex(text, "\n")
|
||||
if byteNum == -1 {
|
||||
byteNum = pos // On first line.
|
||||
} else {
|
||||
byteNum++ // After the newline.
|
||||
byteNum = pos - byteNum
|
||||
}
|
||||
lineNum := 1 + strings.Count(text, "\n")
|
||||
context = n.String()
|
||||
if len(context) > 20 {
|
||||
context = fmt.Sprintf("%.20s...", context)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||
t.Root = nil
|
||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// error terminates processing.
|
||||
func (t *Tree) error(err error) {
|
||||
t.errorf("%s", err)
|
||||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (t *Tree) expect(expected itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
t.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (t *Tree) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startParse initializes the parser, using the lexer.
|
||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||
t.Root = nil
|
||||
t.lex = lex
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
func (t *Tree) stopParse() {
|
||||
t.lex = nil
|
||||
t.vars = nil
|
||||
t.funcs = nil
|
||||
}
|
||||
|
||||
// Parse parses the template definition string to construct a representation of
|
||||
// the template for execution. If either action delimiter string is empty, the
|
||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||
// the treeSet map.
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||
t.text = text
|
||||
t.parse(treeSet)
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// add adds tree to the treeSet.
|
||||
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||
tree := treeSet[t.Name]
|
||||
if tree == nil || IsEmptyTree(tree.Root) {
|
||||
treeSet[t.Name] = t
|
||||
return
|
||||
}
|
||||
if !IsEmptyTree(t.Root) {
|
||||
t.errorf("template: multiple definition of template %q", t.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||
func IsEmptyTree(n Node) bool {
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
return true
|
||||
case *ActionNode:
|
||||
case *IfNode:
|
||||
case *ListNode:
|
||||
for _, node := range n.Nodes {
|
||||
if !IsEmptyTree(node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *RangeNode:
|
||||
case *TemplateNode:
|
||||
case *TextNode:
|
||||
return len(bytes.TrimSpace(n.Text)) == 0
|
||||
case *WithNode:
|
||||
default:
|
||||
panic("unknown node: " + n.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parse is the top-level parser for a template, essentially the same
|
||||
// as itemList except it also parses {{define}} actions.
|
||||
// It runs to EOF.
|
||||
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||
t.Root = t.newList(t.peek().pos)
|
||||
for t.peek().typ != itemEOF {
|
||||
if t.peek().typ == itemLeftDelim {
|
||||
delim := t.next()
|
||||
if t.nextNonSpace().typ == itemDefine {
|
||||
newT := New("definition") // name will be updated once we know it.
|
||||
newT.text = t.text
|
||||
newT.ParseName = t.ParseName
|
||||
newT.startParse(t.funcs, t.lex)
|
||||
newT.parseDefinition(treeSet)
|
||||
continue
|
||||
}
|
||||
t.backup2(delim)
|
||||
}
|
||||
n := t.textOrAction()
|
||||
if n.Type() == nodeEnd {
|
||||
t.errorf("unexpected %s", n)
|
||||
}
|
||||
t.Root.append(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||
// installs the definition in the treeSet map. The "define" keyword has already
|
||||
// been scanned.
|
||||
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||
const context = "define clause"
|
||||
name := t.expectOneOf(itemString, itemRawString, context)
|
||||
var err error
|
||||
t.Name, err = strconv.Unquote(name.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
t.expect(itemRightDelim, context)
|
||||
var end Node
|
||||
t.Root, end = t.itemList()
|
||||
if end.Type() != nodeEnd {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
}
|
||||
|
||||
// itemList:
|
||||
// textOrAction*
|
||||
// Terminates at {{end}} or {{else}}, returned separately.
|
||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
list = t.newList(t.peekNonSpace().pos)
|
||||
for t.peekNonSpace().typ != itemEOF {
|
||||
n := t.textOrAction()
|
||||
switch n.Type() {
|
||||
case nodeEnd, nodeElse:
|
||||
return list, n
|
||||
}
|
||||
list.append(n)
|
||||
}
|
||||
t.errorf("unexpected EOF")
|
||||
return
|
||||
}
|
||||
|
||||
// textOrAction:
|
||||
// text | action
|
||||
func (t *Tree) textOrAction() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElideNewline:
|
||||
return t.elideNewline()
|
||||
case itemText:
|
||||
return t.newText(token.pos, token.val)
|
||||
case itemLeftDelim:
|
||||
return t.action()
|
||||
default:
|
||||
t.unexpected(token, "input")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// elideNewline:
|
||||
// Remove newlines trailing rightDelim if \\ is present.
|
||||
func (t *Tree) elideNewline() Node {
|
||||
token := t.peek()
|
||||
if token.typ != itemText {
|
||||
t.unexpected(token, "input")
|
||||
return nil
|
||||
}
|
||||
|
||||
t.next()
|
||||
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||
diff := len(token.val) - len(stripped)
|
||||
if diff > 0 {
|
||||
// This is a bit nasty. We mutate the token in-place to remove
|
||||
// preceding newlines.
|
||||
token.pos += Pos(diff)
|
||||
token.val = stripped
|
||||
}
|
||||
return t.newText(token.pos, token.val)
|
||||
}
|
||||
|
||||
// Action:
|
||||
// control
|
||||
// command ("|" command)*
|
||||
// Left delim is past. Now get actions.
|
||||
// First word could be a keyword such as range.
|
||||
func (t *Tree) action() (n Node) {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElse:
|
||||
return t.elseControl()
|
||||
case itemEnd:
|
||||
return t.endControl()
|
||||
case itemIf:
|
||||
return t.ifControl()
|
||||
case itemRange:
|
||||
return t.rangeControl()
|
||||
case itemTemplate:
|
||||
return t.templateControl()
|
||||
case itemWith:
|
||||
return t.withControl()
|
||||
}
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||
}
|
||||
|
||||
// Pipeline:
|
||||
// declarations? command ('|' command)*
|
||||
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||
var decl []*VariableNode
|
||||
pos := t.peekNonSpace().pos
|
||||
// Are there declarations?
|
||||
for {
|
||||
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||
t.next()
|
||||
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||
// argument variable rather than a declaration. So remember the token
|
||||
// adjacent to the variable so we can push it back if necessary.
|
||||
tokenAfterVariable := t.peek()
|
||||
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||
t.nextNonSpace()
|
||||
variable := t.newVariable(v.pos, v.val)
|
||||
decl = append(decl, variable)
|
||||
t.vars = append(t.vars, v.val)
|
||||
if next.typ == itemChar && next.val == "," {
|
||||
if context == "range" && len(decl) < 2 {
|
||||
continue
|
||||
}
|
||||
t.errorf("too many declarations in %s", context)
|
||||
}
|
||||
} else if tokenAfterVariable.typ == itemSpace {
|
||||
t.backup3(v, tokenAfterVariable)
|
||||
} else {
|
||||
t.backup2(v)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||
for {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemRightDelim, itemRightParen:
|
||||
if len(pipe.Cmds) == 0 {
|
||||
t.errorf("missing value for %s", context)
|
||||
}
|
||||
if token.typ == itemRightParen {
|
||||
t.backup()
|
||||
}
|
||||
return
|
||||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||
t.backup()
|
||||
pipe.append(t.command())
|
||||
default:
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||
defer t.popVars(len(t.vars))
|
||||
line = t.lex.lineNumber()
|
||||
pipe = t.pipeline(context)
|
||||
var next Node
|
||||
list, next = t.itemList()
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeElse:
|
||||
if allowElseIf {
|
||||
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||
// the elseControl will have left the "if" token pending. Treat
|
||||
// {{if a}}_{{else if b}}_{{end}}
|
||||
// as
|
||||
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||
// is assumed. This technique works even for long if-else-if chains.
|
||||
// TODO: Should we allow else-if in with and range?
|
||||
if t.peek().typ == itemIf {
|
||||
t.next() // Consume the "if" token.
|
||||
elseList = t.newList(next.Position())
|
||||
elseList.append(t.ifControl())
|
||||
// Do not consume the next item - only one {{end}} required.
|
||||
break
|
||||
}
|
||||
}
|
||||
elseList, next = t.itemList()
|
||||
if next.Type() != nodeEnd {
|
||||
t.errorf("expected end; found %s", next)
|
||||
}
|
||||
}
|
||||
return pipe.Position(), line, pipe, list, elseList
|
||||
}
|
||||
|
||||
// If:
|
||||
// {{if pipeline}} itemList {{end}}
|
||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) ifControl() Node {
|
||||
return t.newIf(t.parseControl(true, "if"))
|
||||
}
|
||||
|
||||
// Range:
|
||||
// {{range pipeline}} itemList {{end}}
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
return t.newRange(t.parseControl(false, "range"))
|
||||
}
|
||||
|
||||
// With:
|
||||
// {{with pipeline}} itemList {{end}}
|
||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) withControl() Node {
|
||||
return t.newWith(t.parseControl(false, "with"))
|
||||
}
|
||||
|
||||
// End:
|
||||
// {{end}}
|
||||
// End keyword is past.
|
||||
func (t *Tree) endControl() Node {
|
||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||
}
|
||||
|
||||
// Else:
|
||||
// {{else}}
|
||||
// Else keyword is past.
|
||||
func (t *Tree) elseControl() Node {
|
||||
// Special case for "else if".
|
||||
peek := t.peekNonSpace()
|
||||
if peek.typ == itemIf {
|
||||
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||
}
|
||||
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||
}
|
||||
|
||||
// Template:
|
||||
// {{template stringValue pipeline}}
|
||||
// Template keyword is past. The name must be something that can evaluate
|
||||
// to a string.
|
||||
func (t *Tree) templateControl() Node {
|
||||
var name string
|
||||
token := t.nextNonSpace()
|
||||
switch token.typ {
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
name = s
|
||||
default:
|
||||
t.unexpected(token, "template invocation")
|
||||
}
|
||||
var pipe *PipeNode
|
||||
if t.nextNonSpace().typ != itemRightDelim {
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
pipe = t.pipeline("template")
|
||||
}
|
||||
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||
}
|
||||
|
||||
// command:
|
||||
// operand (space operand)*
|
||||
// space-separated arguments up to a pipeline character or right delimiter.
|
||||
// we consume the pipe character but leave the right delim to terminate the action.
|
||||
func (t *Tree) command() *CommandNode {
|
||||
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||
for {
|
||||
t.peekNonSpace() // skip leading spaces.
|
||||
operand := t.operand()
|
||||
if operand != nil {
|
||||
cmd.append(operand)
|
||||
}
|
||||
switch token := t.next(); token.typ {
|
||||
case itemSpace:
|
||||
continue
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemRightDelim, itemRightParen:
|
||||
t.backup()
|
||||
case itemPipe:
|
||||
default:
|
||||
t.errorf("unexpected %s in operand; missing space?", token)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(cmd.Args) == 0 {
|
||||
t.errorf("empty command")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// operand:
|
||||
// term .Field*
|
||||
// An operand is a space-separated component of a command,
|
||||
// a term possibly followed by field accesses.
|
||||
// A nil return means the next item is not an operand.
|
||||
func (t *Tree) operand() Node {
|
||||
node := t.term()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if t.peek().typ == itemField {
|
||||
chain := t.newChain(t.peek().pos, node)
|
||||
for t.peek().typ == itemField {
|
||||
chain.Add(t.next().val)
|
||||
}
|
||||
// Compatibility with original API: If the term is of type NodeField
|
||||
// or NodeVariable, just put more fields on the original.
|
||||
// Otherwise, keep the Chain node.
|
||||
// TODO: Switch to Chains always when we can.
|
||||
switch node.Type() {
|
||||
case NodeField:
|
||||
node = t.newField(chain.Position(), chain.String())
|
||||
case NodeVariable:
|
||||
node = t.newVariable(chain.Position(), chain.String())
|
||||
default:
|
||||
node = chain
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// term:
|
||||
// literal (number, string, nil, boolean)
|
||||
// function (identifier)
|
||||
// .
|
||||
// .Field
|
||||
// $
|
||||
// '(' pipeline ')'
|
||||
// A term is a simple "expression".
|
||||
// A nil return means the next item is not a term.
|
||||
func (t *Tree) term() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemIdentifier:
|
||||
if !t.hasFunction(token.val) {
|
||||
t.errorf("function %q not defined", token.val)
|
||||
}
|
||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||
case itemDot:
|
||||
return t.newDot(token.pos)
|
||||
case itemNil:
|
||||
return t.newNil(token.pos)
|
||||
case itemVariable:
|
||||
return t.useVar(token.pos, token.val)
|
||||
case itemField:
|
||||
return t.newField(token.pos, token.val)
|
||||
case itemBool:
|
||||
return t.newBool(token.pos, token.val == "true")
|
||||
case itemCharConstant, itemComplex, itemNumber:
|
||||
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return number
|
||||
case itemLeftParen:
|
||||
pipe := t.pipeline("parenthesized pipeline")
|
||||
if token := t.next(); token.typ != itemRightParen {
|
||||
t.errorf("unclosed right paren: unexpected %s", token)
|
||||
}
|
||||
return pipe
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return t.newString(token.pos, token.val, s)
|
||||
}
|
||||
t.backup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasFunction reports if a function name exists in the Tree's maps.
|
||||
func (t *Tree) hasFunction(name string) bool {
|
||||
for _, funcMap := range t.funcs {
|
||||
if funcMap == nil {
|
||||
continue
|
||||
}
|
||||
if funcMap[name] != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// popVars trims the variable list to the specified length
|
||||
func (t *Tree) popVars(n int) {
|
||||
t.vars = t.vars[:n]
|
||||
}
|
||||
|
||||
// useVar returns a node for a variable reference. It errors if the
|
||||
// variable is not defined.
|
||||
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||
v := t.newVariable(pos, name)
|
||||
for _, varName := range t.vars {
|
||||
if varName == v.Ident[0] {
|
||||
return v
|
||||
}
|
||||
}
|
||||
t.errorf("undefined variable %q", v.Ident[0])
|
||||
return nil
|
||||
}
|
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
|
@ -1,218 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// common holds the information shared by related templates.
|
||||
type common struct {
|
||||
tmpl map[string]*Template
|
||||
// We use two maps, one for parsing and one for execution.
|
||||
// This separation makes the API cleaner since it doesn't
|
||||
// expose reflection to the client.
|
||||
parseFuncs FuncMap
|
||||
execFuncs map[string]reflect.Value
|
||||
}
|
||||
|
||||
// Template is the representation of a parsed template. The *parse.Tree
|
||||
// field is exported only for use by html/template and should be treated
|
||||
// as unexported by all other clients.
|
||||
type Template struct {
|
||||
name string
|
||||
*parse.Tree
|
||||
*common
|
||||
leftDelim string
|
||||
rightDelim string
|
||||
}
|
||||
|
||||
// New allocates a new template with the given name.
|
||||
func New(name string) *Template {
|
||||
return &Template{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the template.
|
||||
func (t *Template) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// New allocates a new template associated with the given one and with the same
|
||||
// delimiters. The association, which is transitive, allows one template to
|
||||
// invoke another with a {{template}} action.
|
||||
func (t *Template) New(name string) *Template {
|
||||
t.init()
|
||||
return &Template{
|
||||
name: name,
|
||||
common: t.common,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Template) init() {
|
||||
if t.common == nil {
|
||||
t.common = new(common)
|
||||
t.tmpl = make(map[string]*Template)
|
||||
t.parseFuncs = make(FuncMap)
|
||||
t.execFuncs = make(map[string]reflect.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the template, including all associated
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
nt := t.copy(nil)
|
||||
nt.init()
|
||||
nt.tmpl[t.name] = nt
|
||||
for k, v := range t.tmpl {
|
||||
if k == t.name { // Already installed.
|
||||
continue
|
||||
}
|
||||
// The associated templates share nt's common structure.
|
||||
tmpl := v.copy(nt.common)
|
||||
nt.tmpl[k] = tmpl
|
||||
}
|
||||
for k, v := range t.parseFuncs {
|
||||
nt.parseFuncs[k] = v
|
||||
}
|
||||
for k, v := range t.execFuncs {
|
||||
nt.execFuncs[k] = v
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
func (t *Template) copy(c *common) *Template {
|
||||
nt := New(t.name)
|
||||
nt.Tree = t.Tree
|
||||
nt.common = c
|
||||
nt.leftDelim = t.leftDelim
|
||||
nt.rightDelim = t.rightDelim
|
||||
return nt
|
||||
}
|
||||
|
||||
// AddParseTree creates a new template with the name and parse tree
|
||||
// and associates it with t.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
if t.common != nil && t.tmpl[name] != nil {
|
||||
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
nt := t.New(name)
|
||||
nt.Tree = tree
|
||||
t.tmpl[name] = nt
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// Templates returns a slice of the templates associated with t, including t
|
||||
// itself.
|
||||
func (t *Template) Templates() []*Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
// Return a slice so we don't expose the map.
|
||||
m := make([]*Template, 0, len(t.tmpl))
|
||||
for _, v := range t.tmpl {
|
||||
m = append(m, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Delims sets the action delimiters to the specified strings, to be used in
|
||||
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||
// definitions will inherit the settings. An empty delimiter stands for the
|
||||
// corresponding default: {{ or }}.
|
||||
// The return value is the template, so calls can be chained.
|
||||
func (t *Template) Delims(left, right string) *Template {
|
||||
t.leftDelim = left
|
||||
t.rightDelim = right
|
||||
return t
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type. However, it is legal to overwrite elements of the map. The return
|
||||
// value is the template, so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.init()
|
||||
addValueFuncs(t.execFuncs, funcMap)
|
||||
addFuncs(t.parseFuncs, funcMap)
|
||||
return t
|
||||
}
|
||||
|
||||
// Lookup returns the template with the given name that is associated with t,
|
||||
// or nil if there is no such template.
|
||||
func (t *Template) Lookup(name string) *Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
return t.tmpl[name]
|
||||
}
|
||||
|
||||
// Parse parses a string into a template. Nested template definitions will be
|
||||
// associated with the top-level template t. Parse may be called multiple times
|
||||
// to parse definitions of templates to associate with t. It is an error if a
|
||||
// resulting template is non-empty (contains content other than template
|
||||
// definitions) and would replace a non-empty template with the same name.
|
||||
// (In multiple calls to Parse with the same receiver template, only one call
|
||||
// can contain text other than space, comments, and template definitions.)
|
||||
func (t *Template) Parse(text string) (*Template, error) {
|
||||
t.init()
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||
for name, tree := range trees {
|
||||
// If the name we parsed is the name of this template, overwrite this template.
|
||||
// The associate method checks it's not a redefinition.
|
||||
tmpl := t
|
||||
if name != t.name {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||
return nil, err
|
||||
} else if replace {
|
||||
tmpl.Tree = tree
|
||||
}
|
||||
tmpl.leftDelim = t.leftDelim
|
||||
tmpl.rightDelim = t.rightDelim
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// associate installs the new template into the group of templates associated
|
||||
// with t. It is an error to reuse a name except to overwrite an empty
|
||||
// template. The two are already known to share the common structure.
|
||||
// The boolean return value reports wither to store this tree as t.Tree.
|
||||
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||
if new.common != t.common {
|
||||
panic("internal error: associate not common")
|
||||
}
|
||||
name := new.name
|
||||
if old := t.tmpl[name]; old != nil {
|
||||
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||
if newIsEmpty {
|
||||
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||
return false, nil
|
||||
}
|
||||
if !oldIsEmpty {
|
||||
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
}
|
||||
t.tmpl[name] = new
|
||||
return true, nil
|
||||
}
|
19
vendor/github.com/alecthomas/units/COPYING
generated
vendored
19
vendor/github.com/alecthomas/units/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.
|
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
|
@ -1,11 +0,0 @@
|
|||
# Units - Helpful unit multipliers and functions for Go
|
||||
|
||||
The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package.
|
||||
|
||||
It allows for code like this:
|
||||
|
||||
```go
|
||||
n, err := ParseBase2Bytes("1KB")
|
||||
// n == 1024
|
||||
n = units.Mebibyte * 512
|
||||
```
|
83
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
83
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
|
@ -1,83 +0,0 @@
|
|||
package units
|
||||
|
||||
// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte,
|
||||
// etc.).
|
||||
type Base2Bytes int64
|
||||
|
||||
// Base-2 byte units.
|
||||
const (
|
||||
Kibibyte Base2Bytes = 1024
|
||||
KiB = Kibibyte
|
||||
Mebibyte = Kibibyte * 1024
|
||||
MiB = Mebibyte
|
||||
Gibibyte = Mebibyte * 1024
|
||||
GiB = Gibibyte
|
||||
Tebibyte = Gibibyte * 1024
|
||||
TiB = Tebibyte
|
||||
Pebibyte = Tebibyte * 1024
|
||||
PiB = Pebibyte
|
||||
Exbibyte = Pebibyte * 1024
|
||||
EiB = Exbibyte
|
||||
)
|
||||
|
||||
var (
|
||||
bytesUnitMap = MakeUnitMap("iB", "B", 1024)
|
||||
oldBytesUnitMap = MakeUnitMap("B", "B", 1024)
|
||||
)
|
||||
|
||||
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
|
||||
// and KiB are both 1024.
|
||||
func ParseBase2Bytes(s string) (Base2Bytes, error) {
|
||||
n, err := ParseUnit(s, bytesUnitMap)
|
||||
if err != nil {
|
||||
n, err = ParseUnit(s, oldBytesUnitMap)
|
||||
}
|
||||
return Base2Bytes(n), err
|
||||
}
|
||||
|
||||
func (b Base2Bytes) String() string {
|
||||
return ToString(int64(b), 1024, "iB", "B")
|
||||
}
|
||||
|
||||
var (
|
||||
metricBytesUnitMap = MakeUnitMap("B", "B", 1000)
|
||||
)
|
||||
|
||||
// MetricBytes are SI byte units (1000 bytes in a kilobyte).
|
||||
type MetricBytes SI
|
||||
|
||||
// SI base-10 byte units.
|
||||
const (
|
||||
Kilobyte MetricBytes = 1000
|
||||
KB = Kilobyte
|
||||
Megabyte = Kilobyte * 1000
|
||||
MB = Megabyte
|
||||
Gigabyte = Megabyte * 1000
|
||||
GB = Gigabyte
|
||||
Terabyte = Gigabyte * 1000
|
||||
TB = Terabyte
|
||||
Petabyte = Terabyte * 1000
|
||||
PB = Petabyte
|
||||
Exabyte = Petabyte * 1000
|
||||
EB = Exabyte
|
||||
)
|
||||
|
||||
// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes.
|
||||
func ParseMetricBytes(s string) (MetricBytes, error) {
|
||||
n, err := ParseUnit(s, metricBytesUnitMap)
|
||||
return MetricBytes(n), err
|
||||
}
|
||||
|
||||
func (m MetricBytes) String() string {
|
||||
return ToString(int64(m), 1000, "B", "B")
|
||||
}
|
||||
|
||||
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
|
||||
// respectively. That is, KiB represents 1024 and KB represents 1000.
|
||||
func ParseStrictBytes(s string) (int64, error) {
|
||||
n, err := ParseUnit(s, bytesUnitMap)
|
||||
if err != nil {
|
||||
n, err = ParseUnit(s, metricBytesUnitMap)
|
||||
}
|
||||
return int64(n), err
|
||||
}
|
13
vendor/github.com/alecthomas/units/doc.go
generated
vendored
13
vendor/github.com/alecthomas/units/doc.go
generated
vendored
|
@ -1,13 +0,0 @@
|
|||
// Package units provides helpful unit multipliers and functions for Go.
|
||||
//
|
||||
// The goal of this package is to have functionality similar to the time [1] package.
|
||||
//
|
||||
//
|
||||
// [1] http://golang.org/pkg/time/
|
||||
//
|
||||
// It allows for code like this:
|
||||
//
|
||||
// n, err := ParseBase2Bytes("1KB")
|
||||
// // n == 1024
|
||||
// n = units.Mebibyte * 512
|
||||
package units
|
26
vendor/github.com/alecthomas/units/si.go
generated
vendored
26
vendor/github.com/alecthomas/units/si.go
generated
vendored
|
@ -1,26 +0,0 @@
|
|||
package units
|
||||
|
||||
// SI units.
|
||||
type SI int64
|
||||
|
||||
// SI unit multiples.
|
||||
const (
|
||||
Kilo SI = 1000
|
||||
Mega = Kilo * 1000
|
||||
Giga = Mega * 1000
|
||||
Tera = Giga * 1000
|
||||
Peta = Tera * 1000
|
||||
Exa = Peta * 1000
|
||||
)
|
||||
|
||||
func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 {
|
||||
return map[string]float64{
|
||||
shortSuffix: 1,
|
||||
"K" + suffix: float64(scale),
|
||||
"M" + suffix: float64(scale * scale),
|
||||
"G" + suffix: float64(scale * scale * scale),
|
||||
"T" + suffix: float64(scale * scale * scale * scale),
|
||||
"P" + suffix: float64(scale * scale * scale * scale * scale),
|
||||
"E" + suffix: float64(scale * scale * scale * scale * scale * scale),
|
||||
}
|
||||
}
|
138
vendor/github.com/alecthomas/units/util.go
generated
vendored
138
vendor/github.com/alecthomas/units/util.go
generated
vendored
|
@ -1,138 +0,0 @@
|
|||
package units
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
siUnits = []string{"", "K", "M", "G", "T", "P", "E"}
|
||||
)
|
||||
|
||||
func ToString(n int64, scale int64, suffix, baseSuffix string) string {
|
||||
mn := len(siUnits)
|
||||
out := make([]string, mn)
|
||||
for i, m := range siUnits {
|
||||
if n%scale != 0 || i == 0 && n == 0 {
|
||||
s := suffix
|
||||
if i == 0 {
|
||||
s = baseSuffix
|
||||
}
|
||||
out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s)
|
||||
}
|
||||
n /= scale
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(out, "")
|
||||
}
|
||||
|
||||
// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123
|
||||
var errLeadingInt = errors.New("units: bad [0-9]*") // never printed
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
func leadingInt(s string) (x int64, rem string, err error) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
if x >= (1<<63-10)/10 {
|
||||
// overflow
|
||||
return 0, "", errLeadingInt
|
||||
}
|
||||
x = x*10 + int64(c) - '0'
|
||||
}
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
func ParseUnit(s string, unitMap map[string]float64) (int64, error) {
|
||||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
||||
orig := s
|
||||
f := float64(0)
|
||||
neg := false
|
||||
|
||||
// Consume [-+]?
|
||||
if s != "" {
|
||||
c := s[0]
|
||||
if c == '-' || c == '+' {
|
||||
neg = c == '-'
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
// Special case: if all that is left is "0", this is zero.
|
||||
if s == "0" {
|
||||
return 0, nil
|
||||
}
|
||||
if s == "" {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
for s != "" {
|
||||
g := float64(0) // this element of the sequence
|
||||
|
||||
var x int64
|
||||
var err error
|
||||
|
||||
// The next character must be [0-9.]
|
||||
if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
// Consume [0-9]*
|
||||
pl := len(s)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
g = float64(x)
|
||||
pre := pl != len(s) // whether we consumed anything before a period
|
||||
|
||||
// Consume (\.[0-9]*)?
|
||||
post := false
|
||||
if s != "" && s[0] == '.' {
|
||||
s = s[1:]
|
||||
pl := len(s)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
scale := 1.0
|
||||
for n := pl - len(s); n > 0; n-- {
|
||||
scale *= 10
|
||||
}
|
||||
g += float64(x) / scale
|
||||
post = pl != len(s)
|
||||
}
|
||||
if !pre && !post {
|
||||
// no digits (e.g. ".s" or "-.s")
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
|
||||
// Consume unit.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '.' || ('0' <= c && c <= '9') {
|
||||
break
|
||||
}
|
||||
}
|
||||
u := s[:i]
|
||||
s = s[i:]
|
||||
unit, ok := unitMap[u]
|
||||
if !ok {
|
||||
return 0, errors.New("units: unknown unit " + u + " in " + orig)
|
||||
}
|
||||
|
||||
f += g * unit
|
||||
}
|
||||
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
if f < float64(-1<<63) || f > float64(1<<63-1) {
|
||||
return 0, errors.New("units: overflow parsing unit")
|
||||
}
|
||||
return int64(f), nil
|
||||
}
|
191
vendor/github.com/codeskyblue/dockerignore/LICENSE
generated
vendored
191
vendor/github.com/codeskyblue/dockerignore/LICENSE
generated
vendored
|
@ -1,191 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2015 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
93
vendor/github.com/codeskyblue/dockerignore/README.md
generated
vendored
93
vendor/github.com/codeskyblue/dockerignore/README.md
generated
vendored
|
@ -1,93 +0,0 @@
|
|||
# dockerignore
|
||||
[![GoDoc](https://godoc.org/github.com/codeskyblue/dockerignore?status.svg)](https://godoc.org/github.com/codeskyblue/dockerignore)
|
||||
|
||||
go library parse gitignore file, source code most from [docker](https://github.com/docker/docker)
|
||||
|
||||
## Usage
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
ignore "github.com/codeskyblue/dockerignore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// patterns, err := ignore.ReadIgnoreFile(".gitignore")
|
||||
rd := ioutil.NopCloser(bytes.NewBufferString("*.exe"))
|
||||
patterns, err := ignore.ReadIgnore(rd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
isSkip, err := ignore.Matches("hello.exe", patterns)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Should skipped true, got %v", isSkip)
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
The Go lib interprets a `.dockerignore` like file as a newline-separated list of patterns similar to the file globs of Unix shells.
|
||||
For the purposes of matching, the root of the context is considered to be both the working and the root directory.
|
||||
For example, the patterns /foo/bar and foo/bar both exclude a file or directory named bar in the foo subdirectory of PATH or in the root of the git repository located at URL.
|
||||
Neither excludes anything else.
|
||||
|
||||
Here is an example .dockerignore file:
|
||||
|
||||
*/temp*
|
||||
*/*/temp*
|
||||
temp?
|
||||
|
||||
This file causes the following build behavior:
|
||||
|
||||
Rule | Behavior
|
||||
------------|----------
|
||||
`*/temp*` | Exclude files and directories whose names start with temp in any immediate subdirectory of the root. For example, the plain file `/somedir/temporary.txt` is excluded, as is the directory `/somedir/temp`.
|
||||
`*/*/temp*` | Exclude files and directories starting with temp from any subdirectory that is two levels below the root. For example, `/somedir/subdir/temporary.txt` is excluded.
|
||||
`temp?` | Exclude files and directories in the root directory whose names are a one-character extension of temp. For example, `/tempa` and `/tempb` are excluded.
|
||||
|
||||
Matching is done using Go’s filepath.Match rules.
|
||||
A preprocessing step removes leading and trailing whitespace and eliminates `.` and `..` elements using Go’s filepath.Clean.
|
||||
Lines that are blank after preprocessing are ignored.
|
||||
|
||||
Lines starting with `!` (exclamation mark) can be used to make exceptions to exclusions.
|
||||
The following is an example `.dockerignore` file that uses this mechanism:
|
||||
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
All markdown files except `README.md` are excluded from the context.
|
||||
|
||||
The placement of `!` exception rules influences the behavior: the last line of the `.dockerignore` that matches a particular file determines whether it is included or excluded.
|
||||
Consider the following example:
|
||||
|
||||
*.md
|
||||
!README*.md
|
||||
README-secret.md
|
||||
|
||||
No markdown files are included in the context except README files other than `README-secret.md`
|
||||
|
||||
Now consider this example:
|
||||
|
||||
*.md
|
||||
README-secret.md
|
||||
!README*.md
|
||||
|
||||
All of the README files are included.
|
||||
The middle line has no effect because `!README*.md` matches `README-secret.md` and comes last.
|
||||
|
||||
You can even use the `.dockerignore` file to exclude the Dockerfile and `.dockerignore` files.
|
||||
These files are still sent to the daemon because it needs them to do its job.
|
||||
But the ADD and COPY commands do not copy them to the the image.
|
||||
|
||||
Finally, you may want to specify which files to include in the context, rather than which to exclude.
|
||||
To achieve this, specify `*` as the first pattern, followed by one or more `!` exception patterns.
|
||||
|
||||
Note: For historical reasons, the pattern `.` is ignored.
|
||||
|
||||
## LICENCE
|
||||
Folow the docker license, this lib use [APACHE V2 LICENSE](LICENSE)
|
249
vendor/github.com/codeskyblue/dockerignore/ignore.go
generated
vendored
249
vendor/github.com/codeskyblue/dockerignore/ignore.go
generated
vendored
|
@ -1,249 +0,0 @@
|
|||
package dockerignore
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
// exclusion return true if the specified pattern is an exclusion
|
||||
func exclusion(pattern string) bool {
|
||||
return pattern[0] == '!'
|
||||
}
|
||||
|
||||
// empty return true if the specified pattern is empty
|
||||
func empty(pattern string) bool {
|
||||
return pattern == "" || strings.HasPrefix(pattern, "#")
|
||||
}
|
||||
|
||||
// Read ignore from file
|
||||
func ReadIgnoreFile(filename string) ([]string, error) {
|
||||
igrd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
//if os.IsNotExist(err){
|
||||
// return []string{}, nil
|
||||
//}
|
||||
return nil, err
|
||||
}
|
||||
return ReadIgnore(igrd)
|
||||
}
|
||||
|
||||
// ReadIgnore reads a .dockerignore file and returns the list of file patterns
|
||||
// to ignore. Note this will trim whitespace from each line as well
|
||||
// as use GO's "clean" func to get the shortest/cleanest path for each.
|
||||
func ReadIgnore(reader io.ReadCloser) ([]string, error) {
|
||||
if reader == nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer reader.Close()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var excludes []string
|
||||
|
||||
for scanner.Scan() {
|
||||
pattern := strings.TrimSpace(scanner.Text())
|
||||
if empty(pattern) {
|
||||
continue
|
||||
}
|
||||
pattern = filepath.Clean(pattern)
|
||||
excludes = append(excludes, pattern)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("Error reading .dockerignore: %v", err)
|
||||
}
|
||||
return excludes, nil
|
||||
}
|
||||
|
||||
// CleanPatterns takes a slice of patterns returns a new
|
||||
// slice of patterns cleaned with filepath.Clean, stripped
|
||||
// of any empty patterns and lets the caller know whether the
|
||||
// slice contains any exception patterns (prefixed with !).
|
||||
func cleanPatterns(patterns []string) ([]string, [][]string, bool, error) {
|
||||
// Loop over exclusion patterns and:
|
||||
// 1. Clean them up.
|
||||
// 2. Indicate whether we are dealing with any exception rules.
|
||||
// 3. Error if we see a single exclusion marker on it's own (!).
|
||||
cleanedPatterns := []string{}
|
||||
patternDirs := [][]string{}
|
||||
exceptions := false
|
||||
for _, pattern := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if empty(pattern) {
|
||||
continue
|
||||
}
|
||||
if exclusion(pattern) {
|
||||
if len(pattern) == 1 {
|
||||
return nil, nil, false, errors.New("Illegal exclusion pattern: !")
|
||||
}
|
||||
exceptions = true
|
||||
}
|
||||
pattern = filepath.Clean(pattern)
|
||||
cleanedPatterns = append(cleanedPatterns, pattern)
|
||||
if exclusion(pattern) {
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
patternDirs = append(patternDirs, strings.Split(pattern, "/"))
|
||||
}
|
||||
|
||||
return cleanedPatterns, patternDirs, exceptions, nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
patterns, patDirs, _, err := cleanPatterns(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return optimizedMatches(file, patterns, patDirs)
|
||||
}
|
||||
|
||||
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go.
|
||||
// It will assume that the inputs have been preprocessed and therefore the function
|
||||
// doesn't need to do as much error checking and clean-up. This was done to avoid
|
||||
// repeating these steps on each file being checked during the archive process.
|
||||
// The more generic fileutils.Matches() can't make these assumptions.
|
||||
func optimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
|
||||
matched := false
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, "/")
|
||||
|
||||
for i, pattern := range patterns {
|
||||
negative := false
|
||||
|
||||
if exclusion(pattern) {
|
||||
negative = true
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
|
||||
match, err := regexpMatch(pattern, file)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error in pattern (%s): %s", pattern, err)
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
if len(patDirs[i]) <= len(parentPathDirs) {
|
||||
match, _ = regexpMatch(strings.Join(patDirs[i], "/"),
|
||||
strings.Join(parentPathDirs[:len(patDirs[i])], "/"))
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !negative
|
||||
}
|
||||
}
|
||||
|
||||
//if matched {
|
||||
//log.Debugf("Skipping excluded path: %s", file)
|
||||
//}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// regexpMatch tries to match the logic of filepath.Match but
|
||||
// does so using regexp logic. We do this so that we can expand the
|
||||
// wildcard set to include other things, like "**" to mean any number
|
||||
// of directories. This means that we should be backwards compatible
|
||||
// with filepath.Match(). We'll end up supporting more stuff, due to
|
||||
// the fact that we're using regexp, but that's ok - it does no harm.
|
||||
func regexpMatch(pattern, path string) (bool, error) {
|
||||
regStr := "^"
|
||||
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(pattern, path); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
sl := string(os.PathSeparator)
|
||||
escSL := sl
|
||||
if sl == `\` {
|
||||
escSL += `\`
|
||||
}
|
||||
|
||||
for scan.Peek() != scanner.EOF {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
regStr += ".*"
|
||||
} else {
|
||||
// is "**"
|
||||
regStr += "((.*" + escSL + ")|([^" + escSL + "]*))"
|
||||
}
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
} else if strings.Index(".$", string(ch)) != -1 {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += `\` + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char. Note that a trailing \ in the pattern
|
||||
// will be left alone (but need to escape it)
|
||||
if sl == `\` {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += `\` + string(scan.Next())
|
||||
} else {
|
||||
regStr += `\`
|
||||
}
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
regStr += "$"
|
||||
|
||||
res, err := regexp.MatchString(regStr, path)
|
||||
|
||||
// Map regexp's error to filepath's so no one knows we're not using filepath
|
||||
if err != nil {
|
||||
err = filepath.ErrBadPattern
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
49
vendor/github.com/codeskyblue/go-accesslog/README.markdown
generated
vendored
49
vendor/github.com/codeskyblue/go-accesslog/README.markdown
generated
vendored
|
@ -1,49 +0,0 @@
|
|||
Custom format HTTP access logger in golang
|
||||
==========================================
|
||||
|
||||
## Description
|
||||
|
||||
A library to build your own HTTP access logger.
|
||||
|
||||
## Usage
|
||||
|
||||
Provide a class that implements `accesslog.Logger` interface to make a logging HTTP handler.
|
||||
|
||||
``` golang
|
||||
type LogRecord struct {
|
||||
Time time.Time
|
||||
Ip, Method, Uri, Protocol, Username string
|
||||
Status int
|
||||
Size int64
|
||||
ElapsedTime time.Duration
|
||||
CustomRecords map[string]string
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Log(record LogRecord)
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
``` golang
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
accesslog "github.com/mash/go-accesslog"
|
||||
)
|
||||
|
||||
type logger struct {
|
||||
}
|
||||
|
||||
func (l logger) Log(record accesslog.LogRecord) {
|
||||
log.Println(record.Method + " " + record.Uri)
|
||||
}
|
||||
|
||||
func main() {
|
||||
l := logger{}
|
||||
handler := http.FileServer(http.Dir("."))
|
||||
http.ListenAndServe(":8080", accesslog.NewLoggingHandler(handler, l))
|
||||
}
|
||||
```
|
172
vendor/github.com/codeskyblue/go-accesslog/accesslog.go
generated
vendored
172
vendor/github.com/codeskyblue/go-accesslog/accesslog.go
generated
vendored
|
@ -1,172 +0,0 @@
|
|||
package accesslog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogRecord struct {
|
||||
Time time.Time
|
||||
Ip, Method, Uri, Protocol, Username, Host string
|
||||
Status int
|
||||
Size int64
|
||||
ElapsedTime time.Duration
|
||||
RequestHeader http.Header
|
||||
CustomRecords map[string]string
|
||||
}
|
||||
|
||||
type LoggingWriter struct {
|
||||
http.ResponseWriter
|
||||
logRecord LogRecord
|
||||
}
|
||||
|
||||
func (r *LoggingWriter) Write(p []byte) (int, error) {
|
||||
if r.logRecord.Status == 0 {
|
||||
// The status will be StatusOK if WriteHeader has not been called yet
|
||||
r.logRecord.Status = http.StatusOK
|
||||
}
|
||||
written, err := r.ResponseWriter.Write(p)
|
||||
r.logRecord.Size += int64(written)
|
||||
return written, err
|
||||
}
|
||||
|
||||
func (r *LoggingWriter) WriteHeader(status int) {
|
||||
r.logRecord.Status = status
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
// w.(accesslogger.LoggingWriter).SetCustomLogRecord("X-User-Id", "3")
|
||||
func (r *LoggingWriter) SetCustomLogRecord(key, value string) {
|
||||
if r.logRecord.CustomRecords == nil {
|
||||
r.logRecord.CustomRecords = map[string]string{}
|
||||
}
|
||||
r.logRecord.CustomRecords[key] = value
|
||||
}
|
||||
|
||||
func (r *LoggingWriter) CloseNotify() <-chan bool {
|
||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *LoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("ResponseWriter doesn't support Hijacker interface")
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Log(record LogRecord)
|
||||
}
|
||||
|
||||
type LoggingHandler struct {
|
||||
handler http.Handler
|
||||
logger Logger
|
||||
logBefore bool
|
||||
}
|
||||
|
||||
func NewLoggingHandler(handler http.Handler, logger Logger) http.Handler {
|
||||
return &LoggingHandler{
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
logBefore: false,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAroundLoggingHandler(handler http.Handler, logger Logger) http.Handler {
|
||||
return &LoggingHandler{
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
logBefore: true,
|
||||
}
|
||||
}
|
||||
|
||||
func NewLoggingMiddleware(logger Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
handler := NewLoggingHandler(next, logger)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func NewAroundLoggingMiddleware(logger Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
handler := NewAroundLoggingHandler(next, logger)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// readIp return the real ip when behide nginx or apache
|
||||
func (h *LoggingHandler) realIp(r *http.Request) string {
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
if ip != "127.0.0.1" {
|
||||
return ip
|
||||
}
|
||||
// Check if behide nginx or apache
|
||||
xRealIP := r.Header.Get("X-Real-Ip")
|
||||
xForwardedFor := r.Header.Get("X-Forwarded-For")
|
||||
|
||||
for _, address := range strings.Split(xForwardedFor, ",") {
|
||||
address = strings.TrimSpace(address)
|
||||
if address != "" {
|
||||
return address
|
||||
}
|
||||
}
|
||||
|
||||
if xRealIP != "" {
|
||||
return xRealIP
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
func (h *LoggingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
ip := h.realIp(r)
|
||||
username := "-"
|
||||
if r.URL.User != nil {
|
||||
if name := r.URL.User.Username(); name != "" {
|
||||
username = name
|
||||
}
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
writer := &LoggingWriter{
|
||||
ResponseWriter: rw,
|
||||
logRecord: LogRecord{
|
||||
Time: startTime.UTC(),
|
||||
Ip: ip,
|
||||
Method: r.Method,
|
||||
Uri: r.RequestURI,
|
||||
Username: username,
|
||||
Protocol: r.Proto,
|
||||
Host: r.Host,
|
||||
Status: 0,
|
||||
Size: 0,
|
||||
ElapsedTime: time.Duration(0),
|
||||
RequestHeader: r.Header,
|
||||
},
|
||||
}
|
||||
|
||||
if h.logBefore {
|
||||
writer.SetCustomLogRecord("at", "before")
|
||||
h.logger.Log(writer.logRecord)
|
||||
}
|
||||
h.handler.ServeHTTP(writer, r)
|
||||
finishTime := time.Now()
|
||||
|
||||
writer.logRecord.Time = finishTime.UTC()
|
||||
writer.logRecord.ElapsedTime = finishTime.Sub(startTime)
|
||||
|
||||
if h.logBefore {
|
||||
writer.SetCustomLogRecord("at", "after")
|
||||
}
|
||||
h.logger.Log(writer.logRecord)
|
||||
}
|
13
vendor/github.com/codeskyblue/openid-go/LICENSE
generated
vendored
13
vendor/github.com/codeskyblue/openid-go/LICENSE
generated
vendored
|
@ -1,13 +0,0 @@
|
|||
Copyright 2015 Yohann Coppel
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
38
vendor/github.com/codeskyblue/openid-go/README.md
generated
vendored
38
vendor/github.com/codeskyblue/openid-go/README.md
generated
vendored
|
@ -1,38 +0,0 @@
|
|||
# openid.go
|
||||
|
||||
This is a consumer (Relying party) implementation of OpenId 2.0,
|
||||
written in Go.
|
||||
|
||||
go get -u github.com/yohcop/openid-go
|
||||
|
||||
[![Build Status](https://travis-ci.org/yohcop/openid-go.svg?branch=master)](https://travis-ci.org/yohcop/openid-go)
|
||||
|
||||
## Github
|
||||
|
||||
Be awesome! Feel free to clone and use according to the licence.
|
||||
If you make a useful change that can benefit others, send a
|
||||
pull request! This ensures that one version has all the good stuff
|
||||
and doesn't fall behind.
|
||||
|
||||
## Code example
|
||||
|
||||
See `_example/` for a simple webserver using the openID
|
||||
implementation. Also, read the comment about the NonceStore towards
|
||||
the top of that file. The example must be run for the openid-go
|
||||
directory, like so:
|
||||
|
||||
go run _example/server.go
|
||||
|
||||
## App Engine
|
||||
|
||||
In order to use this on Google App Engine, you need to create an instance with a custom `*http.Client` provided by [urlfetch](https://cloud.google.com/appengine/docs/go/urlfetch/).
|
||||
|
||||
```go
|
||||
oid := openid.NewOpenID(urlfetch.Client(appengine.NewContext(r)))
|
||||
oid.RedirectURL(...)
|
||||
oid.Verify(...)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the [Apache v2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html).
|
57
vendor/github.com/codeskyblue/openid-go/discover.go
generated
vendored
57
vendor/github.com/codeskyblue/openid-go/discover.go
generated
vendored
|
@ -1,57 +0,0 @@
|
|||
package openid
|
||||
|
||||
// 7.3.1. Discovered Information
|
||||
// Upon successful completion of discovery, the Relying Party will
|
||||
// have one or more sets of the following information (see the
|
||||
// Terminology section for definitions). If more than one set of the
|
||||
// following information has been discovered, the precedence rules
|
||||
// defined in [XRI_Resolution_2.0] are to be applied.
|
||||
// - OP Endpoint URL
|
||||
// - Protocol Version
|
||||
// If the end user did not enter an OP Identifier, the following
|
||||
// information will also be present:
|
||||
// - Claimed Identifier
|
||||
// - OP-Local Identifier
|
||||
// If the end user entered an OP Identifier, there is no Claimed
|
||||
// Identifier. For the purposes of making OpenID Authentication
|
||||
// requests, the value
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" MUST be
|
||||
// used as both the Claimed Identifier and the OP-Local Identifier
|
||||
// when an OP Identifier is entered.
|
||||
func Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
return defaultInstance.Discover(id)
|
||||
}
|
||||
|
||||
func (oid *OpenID) Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
// From OpenID specs, 7.2: Normalization
|
||||
if id, err = Normalize(id); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// From OpenID specs, 7.3: Discovery.
|
||||
|
||||
// If the identifier is an XRI, [XRI_Resolution_2.0] will yield an
|
||||
// XRDS document that contains the necessary information. It
|
||||
// should also be noted that Relying Parties can take advantage of
|
||||
// XRI Proxy Resolvers, such as the one provided by XDI.org at
|
||||
// http://www.xri.net. This will remove the need for the RPs to
|
||||
// perform XRI Resolution locally.
|
||||
|
||||
// XRI not supported.
|
||||
|
||||
// If it is a URL, the Yadis protocol [Yadis] SHALL be first
|
||||
// attempted. If it succeeds, the result is again an XRDS
|
||||
// document.
|
||||
if opEndpoint, opLocalID, err = yadisDiscovery(id, oid.urlGetter); err != nil {
|
||||
// If the Yadis protocol fails and no valid XRDS document is
|
||||
// retrieved, or no Service Elements are found in the XRDS
|
||||
// document, the URL is retrieved and HTML-Based discovery SHALL be
|
||||
// attempted.
|
||||
opEndpoint, opLocalID, claimedID, err = htmlDiscovery(id, oid.urlGetter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
return
|
||||
}
|
69
vendor/github.com/codeskyblue/openid-go/discovery_cache.go
generated
vendored
69
vendor/github.com/codeskyblue/openid-go/discovery_cache.go
generated
vendored
|
@ -1,69 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type DiscoveredInfo interface {
|
||||
OpEndpoint() string
|
||||
OpLocalID() string
|
||||
ClaimedID() string
|
||||
// ProtocolVersion: it's always openId 2.
|
||||
}
|
||||
|
||||
type DiscoveryCache interface {
|
||||
Put(id string, info DiscoveredInfo)
|
||||
// Return a discovered info, or nil.
|
||||
Get(id string) DiscoveredInfo
|
||||
}
|
||||
|
||||
type SimpleDiscoveredInfo struct {
|
||||
opEndpoint string
|
||||
opLocalID string
|
||||
claimedID string
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) OpEndpoint() string {
|
||||
return s.opEndpoint
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) OpLocalID() string {
|
||||
return s.opLocalID
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) ClaimedID() string {
|
||||
return s.claimedID
|
||||
}
|
||||
|
||||
type SimpleDiscoveryCache struct {
|
||||
cache map[string]DiscoveredInfo
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSimpleDiscoveryCache() *SimpleDiscoveryCache {
|
||||
return &SimpleDiscoveryCache{cache: map[string]DiscoveredInfo{}, mutex: &sync.Mutex{}}
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveryCache) Put(id string, info DiscoveredInfo) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.cache[id] = info
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveryCache) Get(id string) DiscoveredInfo {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if info, has := s.cache[id]; has {
|
||||
return info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareDiscoveredInfo(a DiscoveredInfo, opEndpoint, opLocalID, claimedID string) bool {
|
||||
return a != nil &&
|
||||
a.OpEndpoint() == opEndpoint &&
|
||||
a.OpLocalID() == opLocalID &&
|
||||
a.ClaimedID() == claimedID
|
||||
}
|
31
vendor/github.com/codeskyblue/openid-go/getter.go
generated
vendored
31
vendor/github.com/codeskyblue/openid-go/getter.go
generated
vendored
|
@ -1,31 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Interface that simplifies testing.
|
||||
type httpGetter interface {
|
||||
Get(uri string, headers map[string]string) (resp *http.Response, err error)
|
||||
Post(uri string, form url.Values) (resp *http.Response, err error)
|
||||
}
|
||||
|
||||
type defaultGetter struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (dg *defaultGetter) Get(uri string, headers map[string]string) (resp *http.Response, err error) {
|
||||
request, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for h, v := range headers {
|
||||
request.Header.Add(h, v)
|
||||
}
|
||||
return dg.client.Do(request)
|
||||
}
|
||||
|
||||
func (dg *defaultGetter) Post(uri string, form url.Values) (resp *http.Response, err error) {
|
||||
return dg.client.PostForm(uri, form)
|
||||
}
|
75
vendor/github.com/codeskyblue/openid-go/html_discovery.go
generated
vendored
75
vendor/github.com/codeskyblue/openid-go/html_discovery.go
generated
vendored
|
@ -1,75 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func htmlDiscovery(id string, getter httpGetter) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
resp, err := getter.Get(id, nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
opEndpoint, opLocalID, err = findProviderFromHeadLink(resp.Body)
|
||||
return opEndpoint, opLocalID, resp.Request.URL.String(), err
|
||||
}
|
||||
|
||||
func findProviderFromHeadLink(input io.Reader) (opEndpoint, opLocalID string, err error) {
|
||||
tokenizer := html.NewTokenizer(input)
|
||||
inHead := false
|
||||
for {
|
||||
tt := tokenizer.Next()
|
||||
switch tt {
|
||||
case html.ErrorToken:
|
||||
// Even if the document is malformed after we found a
|
||||
// valid <link> tag, ignore and let's be happy with our
|
||||
// openid2.provider and potentially openid2.local_id as well.
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", tokenizer.Err()
|
||||
case html.StartTagToken, html.EndTagToken, html.SelfClosingTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == "head" {
|
||||
if tt == html.StartTagToken {
|
||||
inHead = true
|
||||
} else {
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", errors.New(
|
||||
"LINK with rel=openid2.provider not found")
|
||||
}
|
||||
} else if inHead && tk.Data == "link" {
|
||||
provider := false
|
||||
localID := false
|
||||
href := ""
|
||||
for _, attr := range tk.Attr {
|
||||
if attr.Key == "rel" {
|
||||
if attr.Val == "openid2.provider" {
|
||||
provider = true
|
||||
} else if attr.Val == "openid2.local_id" {
|
||||
localID = true
|
||||
}
|
||||
} else if attr.Key == "href" {
|
||||
href = attr.Val
|
||||
}
|
||||
}
|
||||
if provider && !localID && len(href) > 0 {
|
||||
opEndpoint = href
|
||||
} else if !provider && localID && len(href) > 0 {
|
||||
opLocalID = href
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// At this point we should probably have returned either from
|
||||
// a closing </head> or a tokenizer error (no </head> found).
|
||||
// But just in case.
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", errors.New("LINK rel=openid2.provider not found")
|
||||
}
|
87
vendor/github.com/codeskyblue/openid-go/nonce_store.go
generated
vendored
87
vendor/github.com/codeskyblue/openid-go/nonce_store.go
generated
vendored
|
@ -1,87 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var maxNonceAge = flag.Duration("openid-max-nonce-age",
|
||||
60*time.Second,
|
||||
"Maximum accepted age for openid nonces. The bigger, the more"+
|
||||
"memory is needed to store used nonces.")
|
||||
|
||||
type NonceStore interface {
|
||||
// Returns nil if accepted, an error otherwise.
|
||||
Accept(endpoint, nonce string) error
|
||||
}
|
||||
|
||||
type Nonce struct {
|
||||
T time.Time
|
||||
S string
|
||||
}
|
||||
|
||||
type SimpleNonceStore struct {
|
||||
store map[string][]*Nonce
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSimpleNonceStore() *SimpleNonceStore {
|
||||
return &SimpleNonceStore{store: map[string][]*Nonce{}, mutex: &sync.Mutex{}}
|
||||
}
|
||||
|
||||
func (d *SimpleNonceStore) Accept(endpoint, nonce string) error {
|
||||
// Value: A string 255 characters or less in length, that MUST be
|
||||
// unique to this particular successful authentication response.
|
||||
if len(nonce) < 20 || len(nonce) > 256 {
|
||||
return errors.New("Invalid nonce")
|
||||
}
|
||||
|
||||
// The nonce MUST start with the current time on the server, and MAY
|
||||
// contain additional ASCII characters in the range 33-126 inclusive
|
||||
// (printable non-whitespace characters), as necessary to make each
|
||||
// response unique. The date and time MUST be formatted as specified in
|
||||
// section 5.6 of [RFC3339], with the following restrictions:
|
||||
|
||||
// All times must be in the UTC timezone, indicated with a "Z". No
|
||||
// fractional seconds are allowed For example:
|
||||
// 2005-05-15T17:11:51ZUNIQUE
|
||||
ts, err := time.Parse(time.RFC3339, nonce[0:20])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
diff := now.Sub(ts)
|
||||
if diff > *maxNonceAge {
|
||||
return fmt.Errorf("Nonce too old: %ds", diff.Seconds())
|
||||
}
|
||||
|
||||
s := nonce[20:]
|
||||
|
||||
// Meh.. now we have to use a mutex, to protect that map from
|
||||
// concurrent access. Could put a go routine in charge of it
|
||||
// though.
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if nonces, hasOp := d.store[endpoint]; hasOp {
|
||||
// Delete old nonces while we are at it.
|
||||
newNonces := []*Nonce{{ts, s}}
|
||||
for _, n := range nonces {
|
||||
if n.T == ts && n.S == s {
|
||||
// If return early, just ignore the filtered list
|
||||
// we have been building so far...
|
||||
return errors.New("Nonce already used")
|
||||
}
|
||||
if now.Sub(n.T) < *maxNonceAge {
|
||||
newNonces = append(newNonces, n)
|
||||
}
|
||||
}
|
||||
d.store[endpoint] = newNonces
|
||||
} else {
|
||||
d.store[endpoint] = []*Nonce{{ts, s}}
|
||||
}
|
||||
return nil
|
||||
}
|
64
vendor/github.com/codeskyblue/openid-go/normalizer.go
generated
vendored
64
vendor/github.com/codeskyblue/openid-go/normalizer.go
generated
vendored
|
@ -1,64 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Normalize(id string) (string, error) {
|
||||
id = strings.TrimSpace(id)
|
||||
if len(id) == 0 {
|
||||
return "", errors.New("No id provided")
|
||||
}
|
||||
|
||||
// 7.2 from openID 2.0 spec.
|
||||
|
||||
//If the user's input starts with the "xri://" prefix, it MUST be
|
||||
//stripped off, so that XRIs are used in the canonical form.
|
||||
if strings.HasPrefix(id, "xri://") {
|
||||
id = id[6:]
|
||||
return id, errors.New("XRI identifiers not supported")
|
||||
}
|
||||
|
||||
// If the first character of the resulting string is an XRI
|
||||
// Global Context Symbol ("=", "@", "+", "$", "!") or "(", as
|
||||
// defined in Section 2.2.1 of [XRI_Syntax_2.0], then the input
|
||||
// SHOULD be treated as an XRI.
|
||||
if b := id[0]; b == '=' || b == '@' || b == '+' || b == '$' || b == '!' {
|
||||
return id, errors.New("XRI identifiers not supported")
|
||||
}
|
||||
|
||||
// Otherwise, the input SHOULD be treated as an http URL; if it
|
||||
// does not include a "http" or "https" scheme, the Identifier
|
||||
// MUST be prefixed with the string "http://". If the URL
|
||||
// contains a fragment part, it MUST be stripped off together
|
||||
// with the fragment delimiter character "#". See Section 11.5.2 for
|
||||
// more information.
|
||||
if !strings.HasPrefix(id, "http://") && !strings.HasPrefix(id,
|
||||
"https://") {
|
||||
id = "http://" + id
|
||||
}
|
||||
if fragmentIndex := strings.Index(id, "#"); fragmentIndex != -1 {
|
||||
id = id[0:fragmentIndex]
|
||||
}
|
||||
if u, err := url.ParseRequestURI(id); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
if u.Host == "" {
|
||||
return "", errors.New("Invalid address provided as id")
|
||||
}
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
id = u.String()
|
||||
}
|
||||
|
||||
// URL Identifiers MUST then be further normalized by both
|
||||
// following redirects when retrieving their content and finally
|
||||
// applying the rules in Section 6 of [RFC3986] to the final
|
||||
// destination URL. This final URL MUST be noted by the Relying
|
||||
// Party as the Claimed Identifier and be used when requesting
|
||||
// authentication.
|
||||
return id, nil
|
||||
}
|
15
vendor/github.com/codeskyblue/openid-go/openid.go
generated
vendored
15
vendor/github.com/codeskyblue/openid-go/openid.go
generated
vendored
|
@ -1,15 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type OpenID struct {
|
||||
urlGetter httpGetter
|
||||
}
|
||||
|
||||
func NewOpenID(client *http.Client) *OpenID {
|
||||
return &OpenID{urlGetter: &defaultGetter{client: client}}
|
||||
}
|
||||
|
||||
var defaultInstance = NewOpenID(http.DefaultClient)
|
59
vendor/github.com/codeskyblue/openid-go/redirect.go
generated
vendored
59
vendor/github.com/codeskyblue/openid-go/redirect.go
generated
vendored
|
@ -1,59 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RedirectURL(id, callbackURL, realm string) (string, error) {
|
||||
return defaultInstance.RedirectURL(id, callbackURL, realm)
|
||||
}
|
||||
|
||||
func (oid *OpenID) RedirectURL(id, callbackURL, realm string) (string, error) {
|
||||
opEndpoint, opLocalID, claimedID, err := oid.Discover(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return BuildRedirectURL(opEndpoint, opLocalID, claimedID, callbackURL, realm)
|
||||
}
|
||||
|
||||
func BuildRedirectURL(opEndpoint, opLocalID, claimedID, returnTo, realm string) (string, error) {
|
||||
values := make(url.Values)
|
||||
values.Add("openid.ns", "http://specs.openid.net/auth/2.0")
|
||||
values.Add("openid.mode", "checkid_setup")
|
||||
values.Add("openid.return_to", returnTo)
|
||||
|
||||
// 9.1. Request Parameters
|
||||
// "openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
|
||||
if len(claimedID) > 0 {
|
||||
values.Add("openid.claimed_id", claimedID)
|
||||
if len(opLocalID) > 0 {
|
||||
values.Add("openid.identity", opLocalID)
|
||||
} else {
|
||||
// If a different OP-Local Identifier is not specified,
|
||||
// the claimed identifier MUST be used as the value for openid.identity.
|
||||
values.Add("openid.identity", claimedID)
|
||||
}
|
||||
} else {
|
||||
// 7.3.1. Discovered Information
|
||||
// If the end user entered an OP Identifier, there is no Claimed Identifier.
|
||||
// For the purposes of making OpenID Authentication requests, the value
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" MUST be used as both the
|
||||
// Claimed Identifier and the OP-Local Identifier when an OP Identifier is entered.
|
||||
values.Add("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select")
|
||||
values.Add("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select")
|
||||
}
|
||||
|
||||
if len(realm) > 0 {
|
||||
values.Add("openid.realm", realm)
|
||||
}
|
||||
|
||||
// ssx: quick dirty patch
|
||||
values.Add("openid.ns.sreg", "http://openid.net/extensions/sreg/1.1")
|
||||
values.Add("openid.sreg.required", "nickname,email,fullname")
|
||||
|
||||
if strings.Contains(opEndpoint, "?") {
|
||||
return opEndpoint + "&" + values.Encode(), nil
|
||||
}
|
||||
return opEndpoint + "?" + values.Encode(), nil
|
||||
}
|
250
vendor/github.com/codeskyblue/openid-go/verify.go
generated
vendored
250
vendor/github.com/codeskyblue/openid-go/verify.go
generated
vendored
|
@ -1,250 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
|
||||
return defaultInstance.Verify(uri, cache, nonceStore)
|
||||
}
|
||||
|
||||
func (oid *OpenID) Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
|
||||
parsedURL, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
values, err := url.ParseQuery(parsedURL.RawQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 11. Verifying Assertions
|
||||
// When the Relying Party receives a positive assertion, it MUST
|
||||
// verify the following before accepting the assertion:
|
||||
|
||||
// - The value of "openid.signed" contains all the required fields.
|
||||
// (Section 10.1)
|
||||
if err = verifySignedFields(values); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - The signature on the assertion is valid (Section 11.4)
|
||||
if err = verifySignature(uri, values, oid.urlGetter); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - The value of "openid.return_to" matches the URL of the current
|
||||
// request (Section 11.1)
|
||||
if err = verifyReturnTo(parsedURL, values); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - Discovered information matches the information in the assertion
|
||||
// (Section 11.2)
|
||||
if err = oid.verifyDiscovered(parsedURL, values, cache); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - An assertion has not yet been accepted from this OP with the
|
||||
// same value for "openid.response_nonce" (Section 11.3)
|
||||
if err = verifyNonce(values, nonceStore); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If all four of these conditions are met, assertion is now
|
||||
// verified. If the assertion contained a Claimed Identifier, the
|
||||
// user is now authenticated with that identifier.
|
||||
return values.Get("openid.claimed_id"), nil
|
||||
}
|
||||
|
||||
// 10.1. Positive Assertions
|
||||
// openid.signed - Comma-separated list of signed fields.
|
||||
// This entry consists of the fields without the "openid." prefix that the signature covers.
|
||||
// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle",
|
||||
// and if present in the response, "claimed_id" and "identity".
|
||||
func verifySignedFields(vals url.Values) error {
|
||||
ok := map[string]bool{
|
||||
"op_endpoint": false,
|
||||
"return_to": false,
|
||||
"response_nonce": false,
|
||||
"assoc_handle": false,
|
||||
"claimed_id": vals.Get("openid.claimed_id") == "",
|
||||
"identity": vals.Get("openid.identity") == "",
|
||||
}
|
||||
signed := strings.Split(vals.Get("openid.signed"), ",")
|
||||
for _, sf := range signed {
|
||||
ok[sf] = true
|
||||
}
|
||||
for k, v := range ok {
|
||||
if !v {
|
||||
return fmt.Errorf("%v must be signed but isn't", k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 11.1. Verifying the Return URL
|
||||
// To verify that the "openid.return_to" URL matches the URL that is processing this assertion:
|
||||
// - The URL scheme, authority, and path MUST be the same between the two
|
||||
// URLs.
|
||||
// - Any query parameters that are present in the "openid.return_to" URL
|
||||
// MUST also be present with the same values in the URL of the HTTP
|
||||
// request the RP received.
|
||||
func verifyReturnTo(uri *url.URL, vals url.Values) error {
|
||||
returnTo := vals.Get("openid.return_to")
|
||||
rp, err := url.Parse(returnTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uri.Scheme != rp.Scheme ||
|
||||
uri.Host != rp.Host ||
|
||||
uri.Path != rp.Path {
|
||||
return errors.New(
|
||||
"Scheme, host or path don't match in return_to URL")
|
||||
}
|
||||
qp, err := url.ParseQuery(rp.RawQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return compareQueryParams(qp, vals)
|
||||
}
|
||||
|
||||
// Any parameter in q1 must also be present in q2, and values must match.
|
||||
func compareQueryParams(q1, q2 url.Values) error {
|
||||
for k := range q1 {
|
||||
v1 := q1.Get(k)
|
||||
v2 := q2.Get(k)
|
||||
if v1 != v2 {
|
||||
return fmt.Errorf(
|
||||
"URLs query params don't match: Param %s different: %s vs %s",
|
||||
k, v1, v2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oid *OpenID) verifyDiscovered(uri *url.URL, vals url.Values, cache DiscoveryCache) error {
|
||||
version := vals.Get("openid.ns")
|
||||
if version != "http://specs.openid.net/auth/2.0" {
|
||||
return errors.New("Bad protocol version")
|
||||
}
|
||||
|
||||
endpoint := vals.Get("openid.op_endpoint")
|
||||
if len(endpoint) == 0 {
|
||||
return errors.New("missing openid.op_endpoint url param")
|
||||
}
|
||||
localID := vals.Get("openid.identity")
|
||||
if len(localID) == 0 {
|
||||
return errors.New("no localId to verify")
|
||||
}
|
||||
claimedID := vals.Get("openid.claimed_id")
|
||||
if len(claimedID) == 0 {
|
||||
// If no Claimed Identifier is present in the response, the
|
||||
// assertion is not about an identifier and the RP MUST NOT use the
|
||||
// User-supplied Identifier associated with the current OpenID
|
||||
// authentication transaction to identify the user. Extension
|
||||
// information in the assertion MAY still be used.
|
||||
// --- This library does not support this case. So claimed
|
||||
// identifier must be present.
|
||||
return errors.New("no claimed_id to verify")
|
||||
}
|
||||
|
||||
// 11.2. Verifying Discovered Information
|
||||
|
||||
// If the Claimed Identifier in the assertion is a URL and contains a
|
||||
// fragment, the fragment part and the fragment delimiter character "#"
|
||||
// MUST NOT be used for the purposes of verifying the discovered
|
||||
// information.
|
||||
claimedIDVerify := claimedID
|
||||
if fragmentIndex := strings.Index(claimedID, "#"); fragmentIndex != -1 {
|
||||
claimedIDVerify = claimedID[0:fragmentIndex]
|
||||
}
|
||||
|
||||
// If the Claimed Identifier is included in the assertion, it
|
||||
// MUST have been discovered by the Relying Party and the
|
||||
// information in the assertion MUST be present in the
|
||||
// discovered information. The Claimed Identifier MUST NOT be an
|
||||
// OP Identifier.
|
||||
if discovered := cache.Get(claimedIDVerify); discovered != nil &&
|
||||
discovered.OpEndpoint() == endpoint &&
|
||||
discovered.OpLocalID() == localID &&
|
||||
discovered.ClaimedID() == claimedIDVerify {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the Claimed Identifier was not previously discovered by the
|
||||
// Relying Party (the "openid.identity" in the request was
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" or a different
|
||||
// Identifier, or if the OP is sending an unsolicited positive
|
||||
// assertion), the Relying Party MUST perform discovery on the Claimed
|
||||
// Identifier in the response to make sure that the OP is authorized to
|
||||
// make assertions about the Claimed Identifier.
|
||||
if ep, _, _, err := oid.Discover(claimedID); err == nil {
|
||||
if ep == endpoint {
|
||||
// This claimed ID points to the same endpoint, therefore this
|
||||
// endpoint is authorized to make assertions about that claimed ID.
|
||||
// TODO: There may be multiple endpoints found during discovery.
|
||||
// They should all be checked.
|
||||
cache.Put(claimedIDVerify, &SimpleDiscoveredInfo{opEndpoint: endpoint, opLocalID: localID, claimedID: claimedIDVerify})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("Could not verify the claimed ID")
|
||||
}
|
||||
|
||||
func verifyNonce(vals url.Values, store NonceStore) error {
|
||||
nonce := vals.Get("openid.response_nonce")
|
||||
endpoint := vals.Get("openid.op_endpoint")
|
||||
return store.Accept(endpoint, nonce)
|
||||
}
|
||||
|
||||
func verifySignature(uri string, vals url.Values, getter httpGetter) error {
|
||||
// To have the signature verification performed by the OP, the
|
||||
// Relying Party sends a direct request to the OP. To verify the
|
||||
// signature, the OP uses a private association that was generated
|
||||
// when it issued the positive assertion.
|
||||
|
||||
// 11.4.2.1. Request Parameters
|
||||
params := make(url.Values)
|
||||
// openid.mode: Value: "check_authentication"
|
||||
params.Add("openid.mode", "check_authentication")
|
||||
// Exact copies of all fields from the authentication response,
|
||||
// except for "openid.mode".
|
||||
for k, vs := range vals {
|
||||
if k == "openid.mode" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
params.Add(k, v)
|
||||
}
|
||||
}
|
||||
resp, err := getter.Post(vals.Get("openid.op_endpoint"), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
response := string(content)
|
||||
lines := strings.Split(response, "\n")
|
||||
|
||||
isValid := false
|
||||
nsValid := false
|
||||
for _, l := range lines {
|
||||
if l == "is_valid:true" {
|
||||
isValid = true
|
||||
} else if l == "ns:http://specs.openid.net/auth/2.0" {
|
||||
nsValid = true
|
||||
}
|
||||
}
|
||||
if isValid && nsValid {
|
||||
// Yay !
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("Could not verify assertion with provider")
|
||||
}
|
83
vendor/github.com/codeskyblue/openid-go/xrds.go
generated
vendored
83
vendor/github.com/codeskyblue/openid-go/xrds.go
generated
vendored
|
@ -1,83 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: As per 11.2 in openid 2 specs, a service may have multiple
|
||||
// URIs. We don't care for discovery really, but we do care for
|
||||
// verification though.
|
||||
type XrdsIdentifier struct {
|
||||
Type []string `xml:"Type"`
|
||||
URI string `xml:"URI"`
|
||||
LocalID string `xml:"LocalID"`
|
||||
Priority int `xml:"priority,attr"`
|
||||
}
|
||||
|
||||
type Xrd struct {
|
||||
Service []*XrdsIdentifier `xml:"Service"`
|
||||
}
|
||||
|
||||
type XrdsDocument struct {
|
||||
XMLName xml.Name `xml:"XRDS"`
|
||||
Xrd *Xrd `xml:"XRD"`
|
||||
}
|
||||
|
||||
func parseXrds(input []byte) (opEndpoint, opLocalID string, err error) {
|
||||
xrdsDoc := &XrdsDocument{}
|
||||
err = xml.Unmarshal(input, xrdsDoc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if xrdsDoc.Xrd == nil {
|
||||
return "", "", errors.New("XRDS document missing XRD tag")
|
||||
}
|
||||
|
||||
// 7.3.2.2. Extracting Authentication Data
|
||||
// Once the Relying Party has obtained an XRDS document, it
|
||||
// MUST first search the document (following the rules
|
||||
// described in [XRI_Resolution_2.0]) for an OP Identifier
|
||||
// Element. If none is found, the RP will search for a Claimed
|
||||
// Identifier Element.
|
||||
for _, service := range xrdsDoc.Xrd.Service {
|
||||
// 7.3.2.1.1. OP Identifier Element
|
||||
// An OP Identifier Element is an <xrd:Service> element with the
|
||||
// following information:
|
||||
// An <xrd:Type> tag whose text content is
|
||||
// "http://specs.openid.net/auth/2.0/server".
|
||||
// An <xrd:URI> tag whose text content is the OP Endpoint URL
|
||||
if service.hasType("http://specs.openid.net/auth/2.0/server") {
|
||||
opEndpoint = strings.TrimSpace(service.URI)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, service := range xrdsDoc.Xrd.Service {
|
||||
// 7.3.2.1.2. Claimed Identifier Element
|
||||
// A Claimed Identifier Element is an <xrd:Service> element
|
||||
// with the following information:
|
||||
// An <xrd:Type> tag whose text content is
|
||||
// "http://specs.openid.net/auth/2.0/signon".
|
||||
// An <xrd:URI> tag whose text content is the OP Endpoint
|
||||
// URL.
|
||||
// An <xrd:LocalID> tag (optional) whose text content is the
|
||||
// OP-Local Identifier.
|
||||
if service.hasType("http://specs.openid.net/auth/2.0/signon") {
|
||||
opEndpoint = strings.TrimSpace(service.URI)
|
||||
opLocalID = strings.TrimSpace(service.LocalID)
|
||||
return
|
||||
}
|
||||
}
|
||||
return "", "", errors.New("Could not find a compatible service")
|
||||
}
|
||||
|
||||
func (xrdsi *XrdsIdentifier) hasType(tpe string) bool {
|
||||
for _, t := range xrdsi.Type {
|
||||
if t == tpe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
119
vendor/github.com/codeskyblue/openid-go/yadis_discovery.go
generated
vendored
119
vendor/github.com/codeskyblue/openid-go/yadis_discovery.go
generated
vendored
|
@ -1,119 +0,0 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var yadisHeaders = map[string]string{
|
||||
"Accept": "application/xrds+xml"}
|
||||
|
||||
func yadisDiscovery(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
|
||||
// Section 6.2.4 of Yadis 1.0 specifications.
|
||||
// The Yadis Protocol is initiated by the Relying Party Agent
|
||||
// with an initial HTTP request using the Yadis URL.
|
||||
|
||||
// This request MUST be either a GET or a HEAD request.
|
||||
|
||||
// A GET or HEAD request MAY include an HTTP Accept
|
||||
// request-header (HTTP 14.1) specifying MIME media type,
|
||||
// application/xrds+xml.
|
||||
resp, err := getter.Get(id, yadisHeaders)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Section 6.2.5 from Yadis 1.0 spec: Response
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
|
||||
// The response MUST be one of:
|
||||
// (see 6.2.6 for precedence)
|
||||
if l := resp.Header.Get("X-XRDS-Location"); l != "" {
|
||||
// 2. HTTP response-headers that include an X-XRDS-Location
|
||||
// response-header, together with a document
|
||||
return getYadisResourceDescriptor(l, getter)
|
||||
} else if strings.Contains(contentType, "text/html") {
|
||||
// 1. An HTML document with a <head> element that includes a
|
||||
// <meta> element with http-equiv attribute, X-XRDS-Location,
|
||||
|
||||
metaContent, err := findMetaXrdsLocation(resp.Body)
|
||||
if err == nil {
|
||||
return getYadisResourceDescriptor(metaContent, getter)
|
||||
}
|
||||
return "", "", err
|
||||
} else if strings.Contains(contentType, "application/xrds+xml") {
|
||||
// 4. A document of MIME media type, application/xrds+xml.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
return parseXrds(body)
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
// 3. HTTP response-headers only, which MAY include an
|
||||
// X-XRDS-Location response-header, a content-type
|
||||
// response-header specifying MIME media type,
|
||||
// application/xrds+xml, or both.
|
||||
// (this is handled by one of the 2 previous if statements)
|
||||
return "", "", errors.New("No expected header, or content type")
|
||||
}
|
||||
|
||||
// Similar as above, but we expect an absolute Yadis document URL.
|
||||
func getYadisResourceDescriptor(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
|
||||
resp, err := getter.Get(id, yadisHeaders)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 4. A document of MIME media type, application/xrds+xml.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
return parseXrds(body)
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Search for
|
||||
// <head>
|
||||
// <meta http-equiv="X-XRDS-Location" content="....">
|
||||
func findMetaXrdsLocation(input io.Reader) (location string, err error) {
|
||||
tokenizer := html.NewTokenizer(input)
|
||||
inHead := false
|
||||
for {
|
||||
tt := tokenizer.Next()
|
||||
switch tt {
|
||||
case html.ErrorToken:
|
||||
return "", tokenizer.Err()
|
||||
case html.StartTagToken, html.EndTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == "head" {
|
||||
if tt == html.StartTagToken {
|
||||
inHead = true
|
||||
} else {
|
||||
return "", errors.New("Meta X-XRDS-Location not found")
|
||||
}
|
||||
} else if inHead && tk.Data == "meta" {
|
||||
ok := false
|
||||
content := ""
|
||||
for _, attr := range tk.Attr {
|
||||
if attr.Key == "http-equiv" &&
|
||||
strings.ToLower(attr.Val) == "x-xrds-location" {
|
||||
ok = true
|
||||
} else if attr.Key == "content" {
|
||||
content = attr.Val
|
||||
}
|
||||
}
|
||||
if ok && len(content) > 0 {
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("Meta X-XRDS-Location not found")
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue