46 Commits
1.0.5 ... 1.0.6

Author SHA1 Message Date
codeskyblue
bd4e9250c0 fix docker build 2020-10-10 15:05:59 +08:00
codeskyblue
00940603ca change go version to go13 2020-10-10 13:49:51 +08:00
codeskyblue
621dc6c22f use move instead copy when handle big upload files 2020-10-09 20:05:23 +08:00
shengxiang
85b2bd5dc4 Merge pull request #64 from chenwx36/master
better features
2019-03-02 21:56:55 +08:00
chenwenxiao
fdec6aa9ac conflict resolve 2019-02-22 17:23:32 +08:00
chenwenxiao
eaa82b8605 fix bug 2019-02-22 16:51:29 +08:00
chenwenxiao
f4bdbeee73 fix bug 2019-02-22 16:26:55 +08:00
chenwenxiao
905412e91b fix bug 2019-02-22 16:24:34 +08:00
chenwenxiao
9318d19383 modify archive zip path & modify delete confirm prompt & modify delete fail message 2019-02-22 15:48:32 +08:00
chenwenxiao
d62e765ae7 Merge remote-tracking branch 'origin/master' 2019-02-22 10:55:18 +08:00
chenwenxiao
097ff92f17 show path name where create folder 2019-02-22 10:54:58 +08:00
adolli
fe28075b35 more about the oauth2 proxy mode 2019-02-22 10:54:37 +08:00
chenwenxiao
3339651d88 add delete path
add pathname check
2019-02-22 10:40:47 +08:00
codeskyblue
c8d67edf2c format code and remove useless code 2019-02-21 20:23:29 +08:00
chenwenxiao
41ba422675 delete log 2019-02-21 15:48:20 +08:00
chenwenxiao
f9f3142543 oauth2 2019-02-21 15:41:09 +08:00
codeskyblue
9442e00997 unzip ignore .ghs.yml 2019-02-15 17:51:32 +08:00
codeskyblue
1bbc103672 fix test 2019-02-15 17:41:35 +08:00
codeskyblue
a16f689e99 add unzip support when upload 2019-02-15 17:28:46 +08:00
codeskyblue
6d5aae4dde unzip upload, not tested 2019-02-14 17:58:22 +08:00
codeskyblue
9f9d518689 fix click search button not search bug, close #50 2019-01-25 23:49:47 +08:00
codeskyblue
23441ea0c2 if ngnix is https, skip using plistproxy 2019-01-25 13:27:39 +08:00
hzsunshx
d22065f7e0 add location.search 2019-01-25 09:49:32 +08:00
hzsunshx
8e3ddf6f3c fix #58 2019-01-25 09:25:53 +08:00
hzsunshx
0fdd7d5906 change /-/json/ to /?json=true 2019-01-24 18:08:15 +08:00
codeskyblue
e0ced3f803 fix title bug, close #56 2019-01-09 08:44:11 +08:00
shengxiang
261a04d83f Merge pull request #43 from FX-HAO/master
Add support for creating directory via HTTP requests (#35)
2018-12-15 10:47:11 +08:00
Fuxin Hao
d0fd2cc458 Add support for creating directory via HTTP requests (#35) 2018-12-14 11:59:08 +08:00
shengxiang
7056ab85f8 Merge pull request #40 from gengjiawen/patch-2
Add docker badge
2018-12-02 18:36:14 +08:00
Jiawen Geng
84d280a9a4 Add docker layer 2018-12-01 19:14:33 +08:00
codeskyblue
21ef8634cf upgrade to go1.11 2018-11-26 10:32:22 +08:00
shengxiang
392f251bc2 Merge pull request #34 from adolli/patch-1
update README
2018-09-29 17:39:32 +08:00
adolli
727a1cdbfa update README
fix English expressions
2018-09-28 11:29:11 +08:00
codeskyblue
5333393394 fix qrcode can not fix well with chinese 2018-09-26 21:04:58 +08:00
codeskyblue
6556afc08a support change upload with filename 2018-09-26 20:06:09 +08:00
codeskyblue
c012da6e2e fix fix fix fix 2018-09-26 17:48:51 +08:00
codeskyblue
44fb4d2d09 fix fix fix 2018-09-26 17:15:18 +08:00
codeskyblue
64299ce9f9 fix and fix 2018-09-26 17:06:12 +08:00
codeskyblue
4f6226d83d update travis again 2018-09-26 16:49:39 +08:00
codeskyblue
afe8cb7692 skip cleanup 2018-09-26 16:44:42 +08:00
codeskyblue
a80617c85f last test 2018-09-26 16:40:05 +08:00
codeskyblue
fac40bac1c add push manifest support 2018-09-26 16:25:02 +08:00
codeskyblue
cdc5b318a0 add version info 2018-09-26 16:10:27 +08:00
codeskyblue
b0403b60d9 fix docker usage 2018-09-26 13:57:53 +08:00
codeskyblue
d7b886751b update travis for docker push 2018-09-26 13:52:24 +08:00
codeskyblue
22555ed424 support change to parent dir in search result 2018-09-26 10:08:43 +08:00
223 changed files with 485 additions and 45711 deletions

2
.gitignore vendored
View File

@@ -27,3 +27,5 @@ _testmain.go
gohttpserver
bindata_assetfs.go
assets_vfsdata.go
*.un~
*.swp

View File

@@ -1,8 +1,17 @@
sudo: required
services:
- docker
language: go
go:
- "1.10"
- "1.13"
env:
- GO111MODULE=on
script:
- go test -v
addons:
apt:
packages:
- docker-ce
deploy:
- provider: script
skip_cleanup: true
@@ -10,3 +19,8 @@ deploy:
on:
tags: true
condition: $TRAVIS_OS_NAME = linux
- provider: script
skip_cleanup: true
script: bash docker/push_images && bash docker/push_manifest
on:
branch: master

View File

@@ -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"]

View File

@@ -1,5 +1,6 @@
# gohttpserver
[![Build Status](https://travis-ci.org/codeskyblue/gohttpserver.svg?branch=master)](https://travis-ci.org/codeskyblue/gohttpserver)
[![](https://images.microbadger.com/badges/image/codeskyblue/gohttpserver.svg)](https://microbadger.com/images/codeskyblue/gohttpserver "Get your own image badge on microbadger.com")
- Goal: Make the best HTTP File Server.
- Features: Human-friendly UI, file uploading support, direct QR-code generation for Apple & Android install package.
@@ -54,6 +55,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
```
@@ -84,7 +86,7 @@ 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 \
codeskyblue/gohttpserver \
--auth-type http --auth-http username:password
```
@@ -92,10 +94,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 \
codeskyblue/gohttpserver \
--auth-type openid
```
To build image yourself, please change the PWD to the root of this repo.
```bash
cd gohttpserver/
docker build -t codeskyblue/gohttpserver -f docker/Dockerfile .
```
## Authentication options
- Enable basic http authentication
@@ -109,6 +118,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/bitly/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 +207,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 +218,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 +254,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

View File

@@ -4,7 +4,7 @@
<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>
<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 +49,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>
@@ -119,21 +132,28 @@
<!-- ?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="/{{f.path}}/?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>

View File

@@ -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: {
@@ -138,14 +154,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, name);
} else {
parts.push(location.pathname, name);
parts.push(pathname, name);
}
var urlPath = location.protocol + "//" + pathJoin(parts);
return noEncode ? urlPath : encodeURI(urlPath);
@@ -155,14 +179,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 + "/" + f.path + location.search + sep + "download=true";
},
shouldHaveQrcode: function (name) {
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
@@ -223,41 +249,48 @@ var vm = new Vue({
showInfo: function (f) {
console.log(f);
$.ajax({
url: pathJoin(["/-/info", location.pathname, f.name]),
url: pathJoin(["/", location.pathname, 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, "/", 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;
}
}
@@ -267,13 +300,14 @@ var vm = new Vue({
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 == "/") {
@@ -324,21 +358,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 +390,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) {

View File

@@ -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

15
docker/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM golang:1.13
WORKDIR /app/gohttpserver
ADD . /app/gohttpserver
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-X main.VERSION=docker' -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 /app/gohttpserver/gohttpserver .
EXPOSE 8000
ENTRYPOINT [ "/app/gohttpserver", "--root=/app/public" ]
CMD []

15
docker/Dockerfile.armhf Normal file
View File

@@ -0,0 +1,15 @@
FROM golang:1.13
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
View 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

24
docker/push_manifest Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
#
# push manifest
if [[ ! -d $HOME/.docker ]]
then
mkdir $HOME/.docker
fi
set -ex
sed -i '/experimental/d' $HOME/.docker/config.json
sed -i '1a"experimental": "enabled",' $HOME/.docker/config.json
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

30
go.mod Normal file
View File

@@ -0,0 +1,30 @@
module github.com/codeskyblue/gohttpserver
require (
github.com/alecthomas/kingpin v2.2.6+incompatible
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
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/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 // indirect
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/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux v1.6.2
github.com/gorilla/sessions v1.1.3
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/pkg/errors v0.8.0
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca
github.com/stretchr/testify v1.3.0
github.com/ulikunitz/xz v0.5.5 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
golang.org/x/text v0.3.0
)
go 1.13

View File

@@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
@@ -82,20 +83,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 +100,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 +136,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)
@@ -158,20 +161,25 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
// only can delete file now
path := mux.Vars(req)["path"]
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))
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 +191,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 +219,57 @@ 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)
dst, err := os.Create(dstPath)
if err != nil {
log.Println("Create file:", err)
http.Error(w, "File create "+err.Error(), http.StatusInternalServerError)
filename := req.FormValue("filename")
if filename == "" {
filename = header.Filename
}
if err := checkFilename(filename); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
dstPath := filepath.Join(dirpath, filename)
// Large file (>32MB) will store in tmp directory
// The quickest operation is call os.Move instead of os.Copy
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
}
_, copyErr = io.Copy(dst, file)
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 +307,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 +326,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 +355,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 +396,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 +408,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 +806,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
View File

@@ -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) {

View File

@@ -160,7 +160,10 @@ func main() {
u.Scheme = "https"
ss.PlistProxy = u.String()
}
if ss.PlistProxy != "" {
log.Printf("plistproxy: %s", strconv.Quote(ss.PlistProxy))
}
var hdlr http.Handler = ss
hdlr = accesslog.NewLoggingHandler(hdlr, logger)
@@ -177,6 +180,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
View 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)
})
}

View File

@@ -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()
}

View File

@@ -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.

View File

@@ -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"})
}
```

View File

@@ -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
)

View File

@@ -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}},
}
}

View File

@@ -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}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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})
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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(`;`),
}
}

View File

@@ -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,
}
}

View File

@@ -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,
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)}
}

View File

@@ -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}
}

View File

@@ -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
}

View File

@@ -1,7 +0,0 @@
// +build appengine
package plist
func zeroCopy8BitString(buf []byte, off int, len int) string {
return string(buf[off : off+len])
}

View File

@@ -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.

View File

@@ -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>
```

View File

@@ -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
}

View File

@@ -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, "_"))
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -1,9 +0,0 @@
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
package kingpin
import "io"
func guessWidth(w io.Writer) int {
return 80
}

View File

@@ -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
}

View File

@@ -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(),
}
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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}}
`

View File

@@ -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)
}

View File

@@ -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
}
}
}

View File

@@ -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."}
]

View File

@@ -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 &regexpValue{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))
}))
}

View File

@@ -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.

View File

@@ -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
```

View File

@@ -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

View File

@@ -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
}

View File

@@ -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("&#34;") // shorter than "&quot;"
htmlApos = []byte("&#39;") // shorter than "&apos;" and apos was not in HTML until HTML5
htmlAmp = []byte("&amp;")
htmlLt = []byte("&lt;")
htmlGt = []byte("&gt;")
)
// 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
}

View File

@@ -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...)
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
```

View File

@@ -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
}

View File

@@ -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

View File

@@ -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),
}
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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 Gos filepath.Match rules.
A preprocessing step removes leading and trailing whitespace and eliminates `.` and `..` elements using Gos 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)

View File

@@ -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
}

View File

@@ -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))
}
```

View File

@@ -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)
}

View File

@@ -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.

View File

@@ -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).

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://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
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View File

@@ -1,31 +0,0 @@
The following files were ported to Go from C files of libyaml, and thus
are still covered by their original copyright and license:
apic.go
emitterc.go
parserc.go
readerc.go
scannerc.go
writerc.go
yamlh.go
yamlprivateh.go
Copyright (c) 2006 Kirill Simonov
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.

View File

@@ -1,13 +0,0 @@
Copyright 2011-2016 Canonical Ltd.
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.

View File

@@ -1,133 +0,0 @@
# YAML support for the Go language
Introduction
------------
The yaml package enables Go programs to comfortably encode and decode YAML
values. It was developed within [Canonical](https://www.canonical.com) as
part of the [juju](https://juju.ubuntu.com) project, and is based on a
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
C library to parse and generate YAML data quickly and reliably.
Compatibility
-------------
The yaml package supports most of YAML 1.1 and 1.2, including support for
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
implemented, and base-60 floats from YAML 1.1 are purposefully not
supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
The import path for the package is *gopkg.in/yaml.v2*.
To install it, run:
go get gopkg.in/yaml.v2
API documentation
-----------------
If opened in a browser, the import path itself leads to the API documentation:
* [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
API stability
-------------
The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
License
-------
The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
Example
-------
```Go
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v2"
)
var data = `
a: Easy!
b:
c: 2
d: [3, 4]
`
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
}
func main() {
t := T{}
err := yaml.Unmarshal([]byte(data), &t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t:\n%v\n\n", t)
d, err := yaml.Marshal(&t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t dump:\n%s\n\n", string(d))
m := make(map[interface{}]interface{})
err = yaml.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m:\n%v\n\n", m)
d, err = yaml.Marshal(&m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m dump:\n%s\n\n", string(d))
}
```
This example will generate the following output:
```
--- t:
{Easy! {2 [3 4]}}
--- t dump:
a: Easy!
b:
c: 2
d: [3, 4]
--- m:
map[a:Easy! b:map[c:2 d:[3 4]]]
--- m dump:
a: Easy!
b:
c: 2
d:
- 3
- 4
```

View File

@@ -1,739 +0,0 @@
package yaml
import (
"io"
)
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
// Check if we can move the queue at the beginning of the buffer.
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
if parser.tokens_head != len(parser.tokens) {
copy(parser.tokens, parser.tokens[parser.tokens_head:])
}
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
parser.tokens_head = 0
}
parser.tokens = append(parser.tokens, *token)
if pos < 0 {
return
}
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
parser.tokens[parser.tokens_head+pos] = *token
}
// Create a new parser object.
func yaml_parser_initialize(parser *yaml_parser_t) bool {
*parser = yaml_parser_t{
raw_buffer: make([]byte, 0, input_raw_buffer_size),
buffer: make([]byte, 0, input_buffer_size),
}
return true
}
// Destroy a parser object.
func yaml_parser_delete(parser *yaml_parser_t) {
*parser = yaml_parser_t{}
}
// String read handler.
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
if parser.input_pos == len(parser.input) {
return 0, io.EOF
}
n = copy(buffer, parser.input[parser.input_pos:])
parser.input_pos += n
return n, nil
}
// Reader read handler.
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
return parser.input_reader.Read(buffer)
}
// Set a string input.
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
if parser.read_handler != nil {
panic("must set the input source only once")
}
parser.read_handler = yaml_string_read_handler
parser.input = input
parser.input_pos = 0
}
// Set a file input.
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
if parser.read_handler != nil {
panic("must set the input source only once")
}
parser.read_handler = yaml_reader_read_handler
parser.input_reader = r
}
// Set the source encoding.
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
if parser.encoding != yaml_ANY_ENCODING {
panic("must set the encoding only once")
}
parser.encoding = encoding
}
// Create a new emitter object.
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
*emitter = yaml_emitter_t{
buffer: make([]byte, output_buffer_size),
raw_buffer: make([]byte, 0, output_raw_buffer_size),
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
events: make([]yaml_event_t, 0, initial_queue_size),
}
}
// Destroy an emitter object.
func yaml_emitter_delete(emitter *yaml_emitter_t) {
*emitter = yaml_emitter_t{}
}
// String write handler.
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
return nil
}
// yaml_writer_write_handler uses emitter.output_writer to write the
// emitted text.
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
_, err := emitter.output_writer.Write(buffer)
return err
}
// Set a string output.
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
if emitter.write_handler != nil {
panic("must set the output target only once")
}
emitter.write_handler = yaml_string_write_handler
emitter.output_buffer = output_buffer
}
// Set a file output.
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
if emitter.write_handler != nil {
panic("must set the output target only once")
}
emitter.write_handler = yaml_writer_write_handler
emitter.output_writer = w
}
// Set the output encoding.
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
if emitter.encoding != yaml_ANY_ENCODING {
panic("must set the output encoding only once")
}
emitter.encoding = encoding
}
// Set the canonical output style.
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
emitter.canonical = canonical
}
//// Set the indentation increment.
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
if indent < 2 || indent > 9 {
indent = 2
}
emitter.best_indent = indent
}
// Set the preferred line width.
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
if width < 0 {
width = -1
}
emitter.best_width = width
}
// Set if unescaped non-ASCII characters are allowed.
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
emitter.unicode = unicode
}
// Set the preferred line break character.
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
emitter.line_break = line_break
}
///*
// * Destroy a token object.
// */
//
//YAML_DECLARE(void)
//yaml_token_delete(yaml_token_t *token)
//{
// assert(token); // Non-NULL token object expected.
//
// switch (token.type)
// {
// case YAML_TAG_DIRECTIVE_TOKEN:
// yaml_free(token.data.tag_directive.handle);
// yaml_free(token.data.tag_directive.prefix);
// break;
//
// case YAML_ALIAS_TOKEN:
// yaml_free(token.data.alias.value);
// break;
//
// case YAML_ANCHOR_TOKEN:
// yaml_free(token.data.anchor.value);
// break;
//
// case YAML_TAG_TOKEN:
// yaml_free(token.data.tag.handle);
// yaml_free(token.data.tag.suffix);
// break;
//
// case YAML_SCALAR_TOKEN:
// yaml_free(token.data.scalar.value);
// break;
//
// default:
// break;
// }
//
// memset(token, 0, sizeof(yaml_token_t));
//}
//
///*
// * Check if a string is a valid UTF-8 sequence.
// *
// * Check 'reader.c' for more details on UTF-8 encoding.
// */
//
//static int
//yaml_check_utf8(yaml_char_t *start, size_t length)
//{
// yaml_char_t *end = start+length;
// yaml_char_t *pointer = start;
//
// while (pointer < end) {
// unsigned char octet;
// unsigned int width;
// unsigned int value;
// size_t k;
//
// octet = pointer[0];
// width = (octet & 0x80) == 0x00 ? 1 :
// (octet & 0xE0) == 0xC0 ? 2 :
// (octet & 0xF0) == 0xE0 ? 3 :
// (octet & 0xF8) == 0xF0 ? 4 : 0;
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
// if (!width) return 0;
// if (pointer+width > end) return 0;
// for (k = 1; k < width; k ++) {
// octet = pointer[k];
// if ((octet & 0xC0) != 0x80) return 0;
// value = (value << 6) + (octet & 0x3F);
// }
// if (!((width == 1) ||
// (width == 2 && value >= 0x80) ||
// (width == 3 && value >= 0x800) ||
// (width == 4 && value >= 0x10000))) return 0;
//
// pointer += width;
// }
//
// return 1;
//}
//
// Create STREAM-START.
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
*event = yaml_event_t{
typ: yaml_STREAM_START_EVENT,
encoding: encoding,
}
}
// Create STREAM-END.
func yaml_stream_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
typ: yaml_STREAM_END_EVENT,
}
}
// Create DOCUMENT-START.
func yaml_document_start_event_initialize(
event *yaml_event_t,
version_directive *yaml_version_directive_t,
tag_directives []yaml_tag_directive_t,
implicit bool,
) {
*event = yaml_event_t{
typ: yaml_DOCUMENT_START_EVENT,
version_directive: version_directive,
tag_directives: tag_directives,
implicit: implicit,
}
}
// Create DOCUMENT-END.
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
*event = yaml_event_t{
typ: yaml_DOCUMENT_END_EVENT,
implicit: implicit,
}
}
///*
// * Create ALIAS.
// */
//
//YAML_DECLARE(int)
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
//{
// mark yaml_mark_t = { 0, 0, 0 }
// anchor_copy *yaml_char_t = NULL
//
// assert(event) // Non-NULL event object is expected.
// assert(anchor) // Non-NULL anchor is expected.
//
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
//
// anchor_copy = yaml_strdup(anchor)
// if (!anchor_copy)
// return 0
//
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
//
// return 1
//}
// Create SCALAR.
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
*event = yaml_event_t{
typ: yaml_SCALAR_EVENT,
anchor: anchor,
tag: tag,
value: value,
implicit: plain_implicit,
quoted_implicit: quoted_implicit,
style: yaml_style_t(style),
}
return true
}
// Create SEQUENCE-START.
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
*event = yaml_event_t{
typ: yaml_SEQUENCE_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
return true
}
// Create SEQUENCE-END.
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
*event = yaml_event_t{
typ: yaml_SEQUENCE_END_EVENT,
}
return true
}
// Create MAPPING-START.
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
*event = yaml_event_t{
typ: yaml_MAPPING_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
}
// Create MAPPING-END.
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
typ: yaml_MAPPING_END_EVENT,
}
}
// Destroy an event object.
func yaml_event_delete(event *yaml_event_t) {
*event = yaml_event_t{}
}
///*
// * Create a document object.
// */
//
//YAML_DECLARE(int)
//yaml_document_initialize(document *yaml_document_t,
// version_directive *yaml_version_directive_t,
// tag_directives_start *yaml_tag_directive_t,
// tag_directives_end *yaml_tag_directive_t,
// start_implicit int, end_implicit int)
//{
// struct {
// error yaml_error_type_t
// } context
// struct {
// start *yaml_node_t
// end *yaml_node_t
// top *yaml_node_t
// } nodes = { NULL, NULL, NULL }
// version_directive_copy *yaml_version_directive_t = NULL
// struct {
// start *yaml_tag_directive_t
// end *yaml_tag_directive_t
// top *yaml_tag_directive_t
// } tag_directives_copy = { NULL, NULL, NULL }
// value yaml_tag_directive_t = { NULL, NULL }
// mark yaml_mark_t = { 0, 0, 0 }
//
// assert(document) // Non-NULL document object is expected.
// assert((tag_directives_start && tag_directives_end) ||
// (tag_directives_start == tag_directives_end))
// // Valid tag directives are expected.
//
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
//
// if (version_directive) {
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
// if (!version_directive_copy) goto error
// version_directive_copy.major = version_directive.major
// version_directive_copy.minor = version_directive.minor
// }
//
// if (tag_directives_start != tag_directives_end) {
// tag_directive *yaml_tag_directive_t
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
// goto error
// for (tag_directive = tag_directives_start
// tag_directive != tag_directives_end; tag_directive ++) {
// assert(tag_directive.handle)
// assert(tag_directive.prefix)
// if (!yaml_check_utf8(tag_directive.handle,
// strlen((char *)tag_directive.handle)))
// goto error
// if (!yaml_check_utf8(tag_directive.prefix,
// strlen((char *)tag_directive.prefix)))
// goto error
// value.handle = yaml_strdup(tag_directive.handle)
// value.prefix = yaml_strdup(tag_directive.prefix)
// if (!value.handle || !value.prefix) goto error
// if (!PUSH(&context, tag_directives_copy, value))
// goto error
// value.handle = NULL
// value.prefix = NULL
// }
// }
//
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
// tag_directives_copy.start, tag_directives_copy.top,
// start_implicit, end_implicit, mark, mark)
//
// return 1
//
//error:
// STACK_DEL(&context, nodes)
// yaml_free(version_directive_copy)
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
// yaml_free(value.handle)
// yaml_free(value.prefix)
// }
// STACK_DEL(&context, tag_directives_copy)
// yaml_free(value.handle)
// yaml_free(value.prefix)
//
// return 0
//}
//
///*
// * Destroy a document object.
// */
//
//YAML_DECLARE(void)
//yaml_document_delete(document *yaml_document_t)
//{
// struct {
// error yaml_error_type_t
// } context
// tag_directive *yaml_tag_directive_t
//
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
//
// assert(document) // Non-NULL document object is expected.
//
// while (!STACK_EMPTY(&context, document.nodes)) {
// node yaml_node_t = POP(&context, document.nodes)
// yaml_free(node.tag)
// switch (node.type) {
// case YAML_SCALAR_NODE:
// yaml_free(node.data.scalar.value)
// break
// case YAML_SEQUENCE_NODE:
// STACK_DEL(&context, node.data.sequence.items)
// break
// case YAML_MAPPING_NODE:
// STACK_DEL(&context, node.data.mapping.pairs)
// break
// default:
// assert(0) // Should not happen.
// }
// }
// STACK_DEL(&context, document.nodes)
//
// yaml_free(document.version_directive)
// for (tag_directive = document.tag_directives.start
// tag_directive != document.tag_directives.end
// tag_directive++) {
// yaml_free(tag_directive.handle)
// yaml_free(tag_directive.prefix)
// }
// yaml_free(document.tag_directives.start)
//
// memset(document, 0, sizeof(yaml_document_t))
//}
//
///**
// * Get a document node.
// */
//
//YAML_DECLARE(yaml_node_t *)
//yaml_document_get_node(document *yaml_document_t, index int)
//{
// assert(document) // Non-NULL document object is expected.
//
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
// return document.nodes.start + index - 1
// }
// return NULL
//}
//
///**
// * Get the root object.
// */
//
//YAML_DECLARE(yaml_node_t *)
//yaml_document_get_root_node(document *yaml_document_t)
//{
// assert(document) // Non-NULL document object is expected.
//
// if (document.nodes.top != document.nodes.start) {
// return document.nodes.start
// }
// return NULL
//}
//
///*
// * Add a scalar node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_scalar(document *yaml_document_t,
// tag *yaml_char_t, value *yaml_char_t, length int,
// style yaml_scalar_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// value_copy *yaml_char_t = NULL
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
// assert(value) // Non-NULL value is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (length < 0) {
// length = strlen((char *)value)
// }
//
// if (!yaml_check_utf8(value, length)) goto error
// value_copy = yaml_malloc(length+1)
// if (!value_copy) goto error
// memcpy(value_copy, value, length)
// value_copy[length] = '\0'
//
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// yaml_free(tag_copy)
// yaml_free(value_copy)
//
// return 0
//}
//
///*
// * Add a sequence node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_sequence(document *yaml_document_t,
// tag *yaml_char_t, style yaml_sequence_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// struct {
// start *yaml_node_item_t
// end *yaml_node_item_t
// top *yaml_node_item_t
// } items = { NULL, NULL, NULL }
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
//
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
// style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// STACK_DEL(&context, items)
// yaml_free(tag_copy)
//
// return 0
//}
//
///*
// * Add a mapping node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_mapping(document *yaml_document_t,
// tag *yaml_char_t, style yaml_mapping_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// struct {
// start *yaml_node_pair_t
// end *yaml_node_pair_t
// top *yaml_node_pair_t
// } pairs = { NULL, NULL, NULL }
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
//
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
// style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// STACK_DEL(&context, pairs)
// yaml_free(tag_copy)
//
// return 0
//}
//
///*
// * Append an item to a sequence node.
// */
//
//YAML_DECLARE(int)
//yaml_document_append_sequence_item(document *yaml_document_t,
// sequence int, item int)
//{
// struct {
// error yaml_error_type_t
// } context
//
// assert(document) // Non-NULL document is required.
// assert(sequence > 0
// && document.nodes.start + sequence <= document.nodes.top)
// // Valid sequence id is required.
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
// // A sequence node is required.
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
// // Valid item id is required.
//
// if (!PUSH(&context,
// document.nodes.start[sequence-1].data.sequence.items, item))
// return 0
//
// return 1
//}
//
///*
// * Append a pair of a key and a value to a mapping node.
// */
//
//YAML_DECLARE(int)
//yaml_document_append_mapping_pair(document *yaml_document_t,
// mapping int, key int, value int)
//{
// struct {
// error yaml_error_type_t
// } context
//
// pair yaml_node_pair_t
//
// assert(document) // Non-NULL document is required.
// assert(mapping > 0
// && document.nodes.start + mapping <= document.nodes.top)
// // Valid mapping id is required.
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
// // A mapping node is required.
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
// // Valid key id is required.
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
// // Valid value id is required.
//
// pair.key = key
// pair.value = value
//
// if (!PUSH(&context,
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
// return 0
//
// return 1
//}
//
//

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