update urfavecli and prune deps

This commit is contained in:
Brad Rydzewski 2017-03-16 18:17:10 +08:00
parent a95b118cb3
commit ebf74bae98
100 changed files with 5263 additions and 16419 deletions

View file

@ -27,6 +27,7 @@ pipeline:
acl: public-read
bucket: downloads.drone.io
source: release/**/*.*
target: /0.6.0/
access_key: ${AWS_ACCESS_KEY_ID}
secret_key: ${AWS_SECRET_ACCESS_KEY}
when:

View file

@ -1 +1 @@
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjgKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuOAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gR08xNVZFTkRPUkVYUEVSSU1FTlQ9MQogICAgICAtIEdPUEFUSD0vZ28KICAgIGNvbW1hbmRzOgogICAgICAtIGV4cG9ydCBQQVRIPSRQQVRIOiRHT1BBVEgvYmluCiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IHB1c2gKCgogICMgYXJjaGl2ZToKICAjICAgaW1hZ2U6IHBsdWdpbnMvczMKICAjICAgYWNsOiBwdWJsaWMtcmVhZAogICMgICBidWNrZXQ6IGRvd25sb2Fkcy5kcm9uZS5pbwogICMgICBzb3VyY2U6IHJlbGVhc2UvKiovKi4qCiAgIyAgIGFjY2Vzc19rZXk6ICR7QVdTX0FDQ0VTU19LRVlfSUR9CiAgIyAgIHNlY3JldF9rZXk6ICR7QVdTX1NFQ1JFVF9BQ0NFU1NfS0VZfQogICMgICB3aGVuOgogICMgICAgIGV2ZW50OiBwdXNoCiAgIyAgICAgYnJhbmNoOiBtYXN0ZXIKCiAgcHVibGlzaDoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZHJvbmUvZHJvbmUKICAgIHVzZXJuYW1lOiAke0RPQ0tFUl9VU0VSTkFNRX0KICAgIHBhc3N3b3JkOiAke0RPQ0tFUl9QQVNTV09SRH0KICAgIHRhZzogWyAwLjYgXQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgZXZlbnQ6IHB1c2gKCiAgbm90aWZ5OgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCiAgICB3ZWJob29rOiAke0dJVFRFUl9XRUJIT09LfQogICAgd2hlbjoKICAgICAgc3RhdHVzOiBbIHN1Y2Nlc3MsIGZhaWx1cmUgXQogICAgICBldmVudDogWyBwdXNoLCBwdWxsX3JlcXVlc3QgXQoKc2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogcG9zdGdyZXM6OS40LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9cG9zdGdyZXMKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjYuMjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMK.ewtVFWTXLuf3sGDbSdt5RloiAm8jdZIzqHp1M5wd8z8
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjgKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuOAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gR08xNVZFTkRPUkVYUEVSSU1FTlQ9MQogICAgICAtIEdPUEFUSD0vZ28KICAgIGNvbW1hbmRzOgogICAgICAtIGV4cG9ydCBQQVRIPSRQQVRIOiRHT1BBVEgvYmluCiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IHB1c2gKCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICBidWNrZXQ6IGRvd25sb2Fkcy5kcm9uZS5pbwogICAgc291cmNlOiByZWxlYXNlLyoqLyouKgogICAgdGFyZ2V0OiAvMC42LjAvCiAgICBhY2Nlc3Nfa2V5OiAke0FXU19BQ0NFU1NfS0VZX0lEfQogICAgc2VjcmV0X2tleTogJHtBV1NfU0VDUkVUX0FDQ0VTU19LRVl9CiAgICB3aGVuOgogICAgICBldmVudDogcHVzaAogICAgICBicmFuY2g6IG1hc3RlcgoKICBwdWJsaXNoOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBkcm9uZS9kcm9uZQogICAgdXNlcm5hbWU6ICR7RE9DS0VSX1VTRVJOQU1FfQogICAgcGFzc3dvcmQ6ICR7RE9DS0VSX1BBU1NXT1JEfQogICAgdGFnOiBbIDAuNiBdCiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBldmVudDogcHVzaAoKICBub3RpZnk6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKICAgIHdlYmhvb2s6ICR7R0lUVEVSX1dFQkhPT0t9CiAgICB3aGVuOgogICAgICBzdGF0dXM6IFsgc3VjY2VzcywgZmFpbHVyZSBdCiAgICAgIGV2ZW50OiBbIHB1c2gsIHB1bGxfcmVxdWVzdCBdCgpzZXJ2aWNlczoKICBwb3N0Z3JlczoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjQuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNi4yNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwo.-w5oLW1ORcBymNExC1Q-y5Ju6P0-9D2GKRjaTFKP3vg

View file

@ -1,27 +0,0 @@
Copyright (c) 2009 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,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google 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,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View file

@ -1,250 +0,0 @@
// Copyright 2012 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 ssh
import (
"encoding/base64"
"errors"
"io"
"sync"
)
// See [PROTOCOL.agent], section 3.
const (
// 3.2 Requests from client to agent for protocol 2 key operations
agentRequestIdentities = 11
agentSignRequest = 13
agentAddIdentity = 17
agentRemoveIdentity = 18
agentRemoveAllIdentities = 19
agentAddIdConstrained = 25
// 3.3 Key-type independent requests from client to agent
agentAddSmartcardKey = 20
agentRemoveSmartcardKey = 21
agentLock = 22
agentUnlock = 23
agentAddSmartcardKeyConstrained = 26
// 3.4 Generic replies from agent to client
agentFailure = 5
agentSuccess = 6
// 3.6 Replies from agent to client for protocol 2 key operations
agentIdentitiesAnswer = 12
agentSignResponse = 14
// 3.7 Key constraint identifiers
agentConstrainLifetime = 1
agentConstrainConfirm = 2
)
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
// is a sanity check, not a limit in the spec.
const maxAgentResponseBytes = 16 << 20
// Agent messages:
// These structures mirror the wire format of the corresponding ssh agent
// messages found in [PROTOCOL.agent].
type failureAgentMsg struct{}
type successAgentMsg struct{}
// See [PROTOCOL.agent], section 2.5.2.
type requestIdentitiesAgentMsg struct{}
// See [PROTOCOL.agent], section 2.5.2.
type identitiesAnswerAgentMsg struct {
NumKeys uint32
Keys []byte `ssh:"rest"`
}
// See [PROTOCOL.agent], section 2.6.2.
type signRequestAgentMsg struct {
KeyBlob []byte
Data []byte
Flags uint32
}
// See [PROTOCOL.agent], section 2.6.2.
type signResponseAgentMsg struct {
SigBlob []byte
}
// AgentKey represents a protocol 2 key as defined in [PROTOCOL.agent],
// section 2.5.2.
type AgentKey struct {
blob []byte
Comment string
}
// String returns the storage form of an agent key with the format, base64
// encoded serialized key, and the comment if it is not empty.
func (ak *AgentKey) String() string {
algo, _, ok := parseString(ak.blob)
if !ok {
return "ssh: malformed key"
}
s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob)
if ak.Comment != "" {
s += " " + ak.Comment
}
return s
}
// Key returns an agent's public key as one of the supported key or certificate types.
func (ak *AgentKey) Key() (PublicKey, error) {
if key, _, ok := ParsePublicKey(ak.blob); ok {
return key, nil
}
return nil, errors.New("ssh: failed to parse key blob")
}
func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) {
ak := new(AgentKey)
if ak.blob, in, ok = parseString(in); !ok {
return
}
comment, in, ok := parseString(in)
if !ok {
return
}
ak.Comment = string(comment)
return ak, in, true
}
// AgentClient provides a means to communicate with an ssh agent process based
// on the protocol described in [PROTOCOL.agent]?rev=1.6.
type AgentClient struct {
// conn is typically represented by using a *net.UnixConn
conn io.ReadWriter
// mu is used to prevent concurrent access to the agent
mu sync.Mutex
}
// NewAgentClient creates and returns a new *AgentClient using the
// passed in io.ReadWriter as a connection to a ssh agent.
func NewAgentClient(rw io.ReadWriter) *AgentClient {
return &AgentClient{conn: rw}
}
// sendAndReceive sends req to the agent and waits for a reply. On success,
// the reply is unmarshaled into reply and replyType is set to the first byte of
// the reply, which contains the type of the message.
func (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) {
// ac.mu prevents multiple, concurrent requests. Since the agent is typically
// on the same machine, we don't attempt to pipeline the requests.
ac.mu.Lock()
defer ac.mu.Unlock()
msg := make([]byte, stringLength(len(req)))
marshalString(msg, req)
if _, err = ac.conn.Write(msg); err != nil {
return
}
var respSizeBuf [4]byte
if _, err = io.ReadFull(ac.conn, respSizeBuf[:]); err != nil {
return
}
respSize, _, _ := parseUint32(respSizeBuf[:])
if respSize > maxAgentResponseBytes {
err = errors.New("ssh: agent reply too large")
return
}
buf := make([]byte, respSize)
if _, err = io.ReadFull(ac.conn, buf); err != nil {
return
}
return unmarshalAgentMsg(buf)
}
// RequestIdentities queries the agent for protocol 2 keys as defined in
// [PROTOCOL.agent] section 2.5.2.
func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) {
req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{})
msg, msgType, err := ac.sendAndReceive(req)
if err != nil {
return nil, err
}
switch msg := msg.(type) {
case *identitiesAnswerAgentMsg:
if msg.NumKeys > maxAgentResponseBytes/8 {
return nil, errors.New("ssh: too many keys in agent reply")
}
keys := make([]*AgentKey, msg.NumKeys)
data := msg.Keys
for i := uint32(0); i < msg.NumKeys; i++ {
var key *AgentKey
var ok bool
if key, data, ok = parseAgentKey(data); !ok {
return nil, ParseError{agentIdentitiesAnswer}
}
keys[i] = key
}
return keys, nil
case *failureAgentMsg:
return nil, errors.New("ssh: failed to list keys")
}
return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType}
}
// SignRequest requests the signing of data by the agent using a protocol 2 key
// as defined in [PROTOCOL.agent] section 2.6.2.
func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) {
req := marshal(agentSignRequest, signRequestAgentMsg{
KeyBlob: MarshalPublicKey(key),
Data: data,
})
msg, msgType, err := ac.sendAndReceive(req)
if err != nil {
return nil, err
}
switch msg := msg.(type) {
case *signResponseAgentMsg:
return msg.SigBlob, nil
case *failureAgentMsg:
return nil, errors.New("ssh: failed to sign challenge")
}
return nil, UnexpectedMessageError{agentSignResponse, msgType}
}
// unmarshalAgentMsg parses an agent message in packet, returning the parsed
// form and the message type of packet.
func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) {
if len(packet) < 1 {
return nil, 0, ParseError{0}
}
var msg interface{}
switch packet[0] {
case agentFailure:
msg = new(failureAgentMsg)
case agentSuccess:
msg = new(successAgentMsg)
case agentIdentitiesAnswer:
msg = new(identitiesAnswerAgentMsg)
case agentSignResponse:
msg = new(signResponseAgentMsg)
default:
return nil, 0, UnexpectedMessageError{0, packet[0]}
}
if err := unmarshal(msg, packet, packet[0]); err != nil {
return nil, 0, err
}
return msg, packet[0], nil
}

View file

@ -1,96 +0,0 @@
// Copyright 2012 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 ssh
import (
"io"
"sync"
)
// buffer provides a linked list buffer for data exchange
// between producer and consumer. Theoretically the buffer is
// of unlimited capacity as it does no allocation of its own.
type buffer struct {
// protects concurrent access to head, tail and closed
*sync.Cond
head *element // the buffer that will be read first
tail *element // the buffer that will be read last
closed bool
}
// An element represents a single link in a linked list.
type element struct {
buf []byte
next *element
}
// newBuffer returns an empty buffer that is not closed.
func newBuffer() *buffer {
e := new(element)
b := &buffer{
Cond: newCond(),
head: e,
tail: e,
}
return b
}
// write makes buf available for Read to receive.
// buf must not be modified after the call to write.
func (b *buffer) write(buf []byte) {
b.Cond.L.Lock()
defer b.Cond.L.Unlock()
e := &element{buf: buf}
b.tail.next = e
b.tail = e
b.Cond.Signal()
}
// eof closes the buffer. Reads from the buffer once all
// the data has been consumed will receive os.EOF.
func (b *buffer) eof() error {
b.Cond.L.Lock()
defer b.Cond.L.Unlock()
b.closed = true
b.Cond.Signal()
return nil
}
// Read reads data from the internal buffer in buf.
// Reads will block if no data is available, or until
// the buffer is closed.
func (b *buffer) Read(buf []byte) (n int, err error) {
b.Cond.L.Lock()
defer b.Cond.L.Unlock()
for len(buf) > 0 {
// if there is data in b.head, copy it
if len(b.head.buf) > 0 {
r := copy(buf, b.head.buf)
buf, b.head.buf = buf[r:], b.head.buf[r:]
n += r
continue
}
// if there is a next buffer, make it the head
if len(b.head.buf) == 0 && b.head != b.tail {
b.head = b.head.next
continue
}
// if at least one byte has been copied, return
if n > 0 {
break
}
// if nothing was read, and there is nothing outstanding
// check to see if the buffer is closed.
if b.closed {
err = io.EOF
break
}
// out of buffers, wait for producer
b.Cond.Wait()
}
return
}

View file

@ -1,378 +0,0 @@
// Copyright 2012 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 ssh
import (
"time"
)
// These constants from [PROTOCOL.certkeys] represent the algorithm names
// for certificate types supported by this package.
const (
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
)
// Certificate types are used to specify whether a certificate is for identification
// of a user or a host. Current identities are defined in [PROTOCOL.certkeys].
const (
UserCert = 1
HostCert = 2
)
type signature struct {
Format string
Blob []byte
}
type tuple struct {
Name string
Data string
}
const (
maxUint64 = 1<<64 - 1
maxInt64 = 1<<63 - 1
)
// CertTime represents an unsigned 64-bit time value in seconds starting from
// UNIX epoch. We use CertTime instead of time.Time in order to properly handle
// the "infinite" time value ^0, which would become negative when expressed as
// an int64.
type CertTime uint64
func (ct CertTime) Time() time.Time {
if ct > maxInt64 {
return time.Unix(maxInt64, 0)
}
return time.Unix(int64(ct), 0)
}
func (ct CertTime) IsInfinite() bool {
return ct == maxUint64
}
// An OpenSSHCertV01 represents an OpenSSH certificate as defined in
// [PROTOCOL.certkeys]?rev=1.8.
type OpenSSHCertV01 struct {
Nonce []byte
Key PublicKey
Serial uint64
Type uint32
KeyId string
ValidPrincipals []string
ValidAfter, ValidBefore CertTime
CriticalOptions []tuple
Extensions []tuple
Reserved []byte
SignatureKey PublicKey
Signature *signature
}
// validateOpenSSHCertV01Signature uses the cert's SignatureKey to verify that
// the cert's Signature.Blob is the result of signing the cert bytes starting
// from the algorithm string and going up to and including the SignatureKey.
func validateOpenSSHCertV01Signature(cert *OpenSSHCertV01) bool {
return cert.SignatureKey.Verify(cert.BytesForSigning(), cert.Signature.Blob)
}
var certAlgoNames = map[string]string{
KeyAlgoRSA: CertAlgoRSAv01,
KeyAlgoDSA: CertAlgoDSAv01,
KeyAlgoECDSA256: CertAlgoECDSA256v01,
KeyAlgoECDSA384: CertAlgoECDSA384v01,
KeyAlgoECDSA521: CertAlgoECDSA521v01,
}
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
// Panics if a non-certificate algorithm is passed.
func certToPrivAlgo(algo string) string {
for privAlgo, pubAlgo := range certAlgoNames {
if pubAlgo == algo {
return privAlgo
}
}
panic("unknown cert algorithm")
}
func (cert *OpenSSHCertV01) marshal(includeAlgo, includeSig bool) []byte {
algoName := cert.PublicKeyAlgo()
pubKey := cert.Key.Marshal()
sigKey := MarshalPublicKey(cert.SignatureKey)
var length int
if includeAlgo {
length += stringLength(len(algoName))
}
length += stringLength(len(cert.Nonce))
length += len(pubKey)
length += 8 // Length of Serial
length += 4 // Length of Type
length += stringLength(len(cert.KeyId))
length += lengthPrefixedNameListLength(cert.ValidPrincipals)
length += 8 // Length of ValidAfter
length += 8 // Length of ValidBefore
length += tupleListLength(cert.CriticalOptions)
length += tupleListLength(cert.Extensions)
length += stringLength(len(cert.Reserved))
length += stringLength(len(sigKey))
if includeSig {
length += signatureLength(cert.Signature)
}
ret := make([]byte, length)
r := ret
if includeAlgo {
r = marshalString(r, []byte(algoName))
}
r = marshalString(r, cert.Nonce)
copy(r, pubKey)
r = r[len(pubKey):]
r = marshalUint64(r, cert.Serial)
r = marshalUint32(r, cert.Type)
r = marshalString(r, []byte(cert.KeyId))
r = marshalLengthPrefixedNameList(r, cert.ValidPrincipals)
r = marshalUint64(r, uint64(cert.ValidAfter))
r = marshalUint64(r, uint64(cert.ValidBefore))
r = marshalTupleList(r, cert.CriticalOptions)
r = marshalTupleList(r, cert.Extensions)
r = marshalString(r, cert.Reserved)
r = marshalString(r, sigKey)
if includeSig {
r = marshalSignature(r, cert.Signature)
}
if len(r) > 0 {
panic("ssh: internal error, marshaling certificate did not fill the entire buffer")
}
return ret
}
func (cert *OpenSSHCertV01) BytesForSigning() []byte {
return cert.marshal(true, false)
}
func (cert *OpenSSHCertV01) Marshal() []byte {
return cert.marshal(false, true)
}
func (c *OpenSSHCertV01) PublicKeyAlgo() string {
algo, ok := certAlgoNames[c.Key.PublicKeyAlgo()]
if !ok {
panic("unknown cert key type")
}
return algo
}
func (c *OpenSSHCertV01) PrivateKeyAlgo() string {
return c.Key.PrivateKeyAlgo()
}
func (c *OpenSSHCertV01) Verify(data []byte, sig []byte) bool {
return c.Key.Verify(data, sig)
}
func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) {
cert := new(OpenSSHCertV01)
if cert.Nonce, in, ok = parseString(in); !ok {
return
}
privAlgo := certToPrivAlgo(algo)
cert.Key, in, ok = parsePubKey(in, privAlgo)
if !ok {
return
}
// We test PublicKeyAlgo to make sure we don't use some weird sub-cert.
if cert.Key.PublicKeyAlgo() != privAlgo {
ok = false
return
}
if cert.Serial, in, ok = parseUint64(in); !ok {
return
}
if cert.Type, in, ok = parseUint32(in); !ok {
return
}
keyId, in, ok := parseString(in)
if !ok {
return
}
cert.KeyId = string(keyId)
if cert.ValidPrincipals, in, ok = parseLengthPrefixedNameList(in); !ok {
return
}
va, in, ok := parseUint64(in)
if !ok {
return
}
cert.ValidAfter = CertTime(va)
vb, in, ok := parseUint64(in)
if !ok {
return
}
cert.ValidBefore = CertTime(vb)
if cert.CriticalOptions, in, ok = parseTupleList(in); !ok {
return
}
if cert.Extensions, in, ok = parseTupleList(in); !ok {
return
}
if cert.Reserved, in, ok = parseString(in); !ok {
return
}
sigKey, in, ok := parseString(in)
if !ok {
return
}
if cert.SignatureKey, _, ok = ParsePublicKey(sigKey); !ok {
return
}
if cert.Signature, in, ok = parseSignature(in); !ok {
return
}
ok = true
return cert, in, ok
}
func lengthPrefixedNameListLength(namelist []string) int {
length := 4 // length prefix for list
for _, name := range namelist {
length += 4 // length prefix for name
length += len(name)
}
return length
}
func marshalLengthPrefixedNameList(to []byte, namelist []string) []byte {
length := uint32(lengthPrefixedNameListLength(namelist) - 4)
to = marshalUint32(to, length)
for _, name := range namelist {
to = marshalString(to, []byte(name))
}
return to
}
func parseLengthPrefixedNameList(in []byte) (out []string, rest []byte, ok bool) {
list, rest, ok := parseString(in)
if !ok {
return
}
for len(list) > 0 {
var next []byte
if next, list, ok = parseString(list); !ok {
return nil, nil, false
}
out = append(out, string(next))
}
ok = true
return
}
func tupleListLength(tupleList []tuple) int {
length := 4 // length prefix for list
for _, t := range tupleList {
length += 4 // length prefix for t.Name
length += len(t.Name)
length += 4 // length prefix for t.Data
length += len(t.Data)
}
return length
}
func marshalTupleList(to []byte, tuplelist []tuple) []byte {
length := uint32(tupleListLength(tuplelist) - 4)
to = marshalUint32(to, length)
for _, t := range tuplelist {
to = marshalString(to, []byte(t.Name))
to = marshalString(to, []byte(t.Data))
}
return to
}
func parseTupleList(in []byte) (out []tuple, rest []byte, ok bool) {
list, rest, ok := parseString(in)
if !ok {
return
}
for len(list) > 0 {
var name, data []byte
var ok bool
name, list, ok = parseString(list)
if !ok {
return nil, nil, false
}
data, list, ok = parseString(list)
if !ok {
return nil, nil, false
}
out = append(out, tuple{string(name), string(data)})
}
ok = true
return
}
func signatureLength(sig *signature) int {
length := 4 // length prefix for signature
length += stringLength(len(sig.Format))
length += stringLength(len(sig.Blob))
return length
}
func marshalSignature(to []byte, sig *signature) []byte {
length := uint32(signatureLength(sig) - 4)
to = marshalUint32(to, length)
to = marshalString(to, []byte(sig.Format))
to = marshalString(to, sig.Blob)
return to
}
func parseSignatureBody(in []byte) (out *signature, rest []byte, ok bool) {
var format []byte
if format, in, ok = parseString(in); !ok {
return
}
out = &signature{
Format: string(format),
}
if out.Blob, in, ok = parseString(in); !ok {
return
}
return out, in, ok
}
func parseSignature(in []byte) (out *signature, rest []byte, ok bool) {
var sigBytes []byte
if sigBytes, rest, ok = parseString(in); !ok {
return
}
out, sigBytes, ok = parseSignatureBody(sigBytes)
if !ok || len(sigBytes) > 0 {
return nil, nil, false
}
return
}

View file

@ -1,594 +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 ssh
import (
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
)
// extendedDataTypeCode identifies an OpenSSL extended data type. See RFC 4254,
// section 5.2.
type extendedDataTypeCode uint32
const (
// extendedDataStderr is the extended data type that is used for stderr.
extendedDataStderr extendedDataTypeCode = 1
// minPacketLength defines the smallest valid packet
minPacketLength = 9
// channelMaxPacketSize defines the maximum packet size advertised in open messages
channelMaxPacketSize = 1 << 15 // RFC 4253 6.1, minimum 32 KiB
// channelWindowSize defines the window size advertised in open messages
channelWindowSize = 64 * channelMaxPacketSize // Like OpenSSH
)
// A Channel is an ordered, reliable, duplex stream that is multiplexed over an
// SSH connection. Channel.Read can return a ChannelRequest as an error.
type Channel interface {
// Accept accepts the channel creation request.
Accept() error
// Reject rejects the channel creation request. After calling this, no
// other methods on the Channel may be called. If they are then the
// peer is likely to signal a protocol error and drop the connection.
Reject(reason RejectionReason, message string) error
// Read may return a ChannelRequest as an error.
Read(data []byte) (int, error)
Write(data []byte) (int, error)
Close() error
// Stderr returns an io.Writer that writes to this channel with the
// extended data type set to stderr.
Stderr() io.Writer
// AckRequest either sends an ack or nack to the channel request.
AckRequest(ok bool) error
// ChannelType returns the type of the channel, as supplied by the
// client.
ChannelType() string
// ExtraData returns the arbitrary payload for this channel, as supplied
// by the client. This data is specific to the channel type.
ExtraData() []byte
}
// ChannelRequest represents a request sent on a channel, outside of the normal
// stream of bytes. It may result from calling Read on a Channel.
type ChannelRequest struct {
Request string
WantReply bool
Payload []byte
}
func (c ChannelRequest) Error() string {
return "ssh: channel request received"
}
// RejectionReason is an enumeration used when rejecting channel creation
// requests. See RFC 4254, section 5.1.
type RejectionReason uint32
const (
Prohibited RejectionReason = iota + 1
ConnectionFailed
UnknownChannelType
ResourceShortage
)
// String converts the rejection reason to human readable form.
func (r RejectionReason) String() string {
switch r {
case Prohibited:
return "administratively prohibited"
case ConnectionFailed:
return "connect failed"
case UnknownChannelType:
return "unknown channel type"
case ResourceShortage:
return "resource shortage"
}
return fmt.Sprintf("unknown reason %d", int(r))
}
type channel struct {
packetConn // the underlying transport
localId, remoteId uint32
remoteWin window
maxPacket uint32
isClosed uint32 // atomic bool, non zero if true
}
func (c *channel) sendWindowAdj(n int) error {
msg := windowAdjustMsg{
PeersId: c.remoteId,
AdditionalBytes: uint32(n),
}
return c.writePacket(marshal(msgChannelWindowAdjust, msg))
}
// sendEOF sends EOF to the remote side. RFC 4254 Section 5.3
func (c *channel) sendEOF() error {
return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{
PeersId: c.remoteId,
}))
}
// sendClose informs the remote side of our intent to close the channel.
func (c *channel) sendClose() error {
return c.packetConn.writePacket(marshal(msgChannelClose, channelCloseMsg{
PeersId: c.remoteId,
}))
}
func (c *channel) sendChannelOpenFailure(reason RejectionReason, message string) error {
reject := channelOpenFailureMsg{
PeersId: c.remoteId,
Reason: reason,
Message: message,
Language: "en",
}
return c.writePacket(marshal(msgChannelOpenFailure, reject))
}
func (c *channel) writePacket(b []byte) error {
if c.closed() {
return io.EOF
}
if uint32(len(b)) > c.maxPacket {
return fmt.Errorf("ssh: cannot write %d bytes, maxPacket is %d bytes", len(b), c.maxPacket)
}
return c.packetConn.writePacket(b)
}
func (c *channel) closed() bool {
return atomic.LoadUint32(&c.isClosed) > 0
}
func (c *channel) setClosed() bool {
return atomic.CompareAndSwapUint32(&c.isClosed, 0, 1)
}
type serverChan struct {
channel
// immutable once created
chanType string
extraData []byte
serverConn *ServerConn
myWindow uint32
theyClosed bool // indicates the close msg has been received from the remote side
theySentEOF bool
isDead uint32
err error
pendingRequests []ChannelRequest
pendingData []byte
head, length int
// This lock is inferior to serverConn.lock
cond *sync.Cond
}
func (c *serverChan) Accept() error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
confirm := channelOpenConfirmMsg{
PeersId: c.remoteId,
MyId: c.localId,
MyWindow: c.myWindow,
MaxPacketSize: c.maxPacket,
}
return c.writePacket(marshal(msgChannelOpenConfirm, confirm))
}
func (c *serverChan) Reject(reason RejectionReason, message string) error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
return c.sendChannelOpenFailure(reason, message)
}
func (c *serverChan) handlePacket(packet interface{}) {
c.cond.L.Lock()
defer c.cond.L.Unlock()
switch packet := packet.(type) {
case *channelRequestMsg:
req := ChannelRequest{
Request: packet.Request,
WantReply: packet.WantReply,
Payload: packet.RequestSpecificData,
}
c.pendingRequests = append(c.pendingRequests, req)
c.cond.Signal()
case *channelCloseMsg:
c.theyClosed = true
c.cond.Signal()
case *channelEOFMsg:
c.theySentEOF = true
c.cond.Signal()
case *windowAdjustMsg:
if !c.remoteWin.add(packet.AdditionalBytes) {
panic("illegal window update")
}
default:
panic("unknown packet type")
}
}
func (c *serverChan) handleData(data []byte) {
c.cond.L.Lock()
defer c.cond.L.Unlock()
// The other side should never send us more than our window.
if len(data)+c.length > len(c.pendingData) {
// TODO(agl): we should tear down the channel with a protocol
// error.
return
}
c.myWindow -= uint32(len(data))
for i := 0; i < 2; i++ {
tail := c.head + c.length
if tail >= len(c.pendingData) {
tail -= len(c.pendingData)
}
n := copy(c.pendingData[tail:], data)
data = data[n:]
c.length += n
}
c.cond.Signal()
}
func (c *serverChan) Stderr() io.Writer {
return extendedDataChannel{c: c, t: extendedDataStderr}
}
// extendedDataChannel is an io.Writer that writes any data to c as extended
// data of the given type.
type extendedDataChannel struct {
t extendedDataTypeCode
c *serverChan
}
func (edc extendedDataChannel) Write(data []byte) (n int, err error) {
const headerLength = 13 // 1 byte message type, 4 bytes remoteId, 4 bytes extended message type, 4 bytes data length
c := edc.c
for len(data) > 0 {
space := min(c.maxPacket-headerLength, len(data))
if space, err = c.getWindowSpace(space); err != nil {
return 0, err
}
todo := data
if uint32(len(todo)) > space {
todo = todo[:space]
}
packet := make([]byte, headerLength+len(todo))
packet[0] = msgChannelExtendedData
marshalUint32(packet[1:], c.remoteId)
marshalUint32(packet[5:], uint32(edc.t))
marshalUint32(packet[9:], uint32(len(todo)))
copy(packet[13:], todo)
if err = c.writePacket(packet); err != nil {
return
}
n += len(todo)
data = data[len(todo):]
}
return
}
func (c *serverChan) Read(data []byte) (n int, err error) {
n, err, windowAdjustment := c.read(data)
if windowAdjustment > 0 {
packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{
PeersId: c.remoteId,
AdditionalBytes: windowAdjustment,
})
err = c.writePacket(packet)
}
return
}
func (c *serverChan) read(data []byte) (n int, err error, windowAdjustment uint32) {
c.cond.L.Lock()
defer c.cond.L.Unlock()
if c.err != nil {
return 0, c.err, 0
}
for {
if c.theySentEOF || c.theyClosed || c.dead() {
return 0, io.EOF, 0
}
if len(c.pendingRequests) > 0 {
req := c.pendingRequests[0]
if len(c.pendingRequests) == 1 {
c.pendingRequests = nil
} else {
oldPendingRequests := c.pendingRequests
c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1)
copy(c.pendingRequests, oldPendingRequests[1:])
}
return 0, req, 0
}
if c.length > 0 {
tail := min(uint32(c.head+c.length), len(c.pendingData))
n = copy(data, c.pendingData[c.head:tail])
c.head += n
c.length -= n
if c.head == len(c.pendingData) {
c.head = 0
}
windowAdjustment = uint32(len(c.pendingData)-c.length) - c.myWindow
if windowAdjustment < uint32(len(c.pendingData)/2) {
windowAdjustment = 0
}
c.myWindow += windowAdjustment
return
}
c.cond.Wait()
}
panic("unreachable")
}
// getWindowSpace takes, at most, max bytes of space from the peer's window. It
// returns the number of bytes actually reserved.
func (c *serverChan) getWindowSpace(max uint32) (uint32, error) {
if c.dead() || c.closed() {
return 0, io.EOF
}
return c.remoteWin.reserve(max), nil
}
func (c *serverChan) dead() bool {
return atomic.LoadUint32(&c.isDead) > 0
}
func (c *serverChan) setDead() {
atomic.StoreUint32(&c.isDead, 1)
}
func (c *serverChan) Write(data []byte) (n int, err error) {
const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length
for len(data) > 0 {
space := min(c.maxPacket-headerLength, len(data))
if space, err = c.getWindowSpace(space); err != nil {
return 0, err
}
todo := data
if uint32(len(todo)) > space {
todo = todo[:space]
}
packet := make([]byte, headerLength+len(todo))
packet[0] = msgChannelData
marshalUint32(packet[1:], c.remoteId)
marshalUint32(packet[5:], uint32(len(todo)))
copy(packet[9:], todo)
if err = c.writePacket(packet); err != nil {
return
}
n += len(todo)
data = data[len(todo):]
}
return
}
// Close signals the intent to close the channel.
func (c *serverChan) Close() error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
if !c.setClosed() {
return errors.New("ssh: channel already closed")
}
return c.sendClose()
}
func (c *serverChan) AckRequest(ok bool) error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
if !ok {
ack := channelRequestFailureMsg{
PeersId: c.remoteId,
}
return c.writePacket(marshal(msgChannelFailure, ack))
}
ack := channelRequestSuccessMsg{
PeersId: c.remoteId,
}
return c.writePacket(marshal(msgChannelSuccess, ack))
}
func (c *serverChan) ChannelType() string {
return c.chanType
}
func (c *serverChan) ExtraData() []byte {
return c.extraData
}
// A clientChan represents a single RFC 4254 channel multiplexed
// over a SSH connection.
type clientChan struct {
channel
stdin *chanWriter
stdout *chanReader
stderr *chanReader
msg chan interface{}
}
// newClientChan returns a partially constructed *clientChan
// using the local id provided. To be usable clientChan.remoteId
// needs to be assigned once known.
func newClientChan(cc packetConn, id uint32) *clientChan {
c := &clientChan{
channel: channel{
packetConn: cc,
localId: id,
remoteWin: window{Cond: newCond()},
},
msg: make(chan interface{}, 16),
}
c.stdin = &chanWriter{
channel: &c.channel,
}
c.stdout = &chanReader{
channel: &c.channel,
buffer: newBuffer(),
}
c.stderr = &chanReader{
channel: &c.channel,
buffer: newBuffer(),
}
return c
}
// waitForChannelOpenResponse, if successful, fills out
// the remoteId and records any initial window advertisement.
func (c *clientChan) waitForChannelOpenResponse() error {
switch msg := (<-c.msg).(type) {
case *channelOpenConfirmMsg:
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
return errors.New("ssh: invalid MaxPacketSize from peer")
}
// fixup remoteId field
c.remoteId = msg.MyId
c.maxPacket = msg.MaxPacketSize
c.remoteWin.add(msg.MyWindow)
return nil
case *channelOpenFailureMsg:
return errors.New(safeString(msg.Message))
}
return errors.New("ssh: unexpected packet")
}
// Close signals the intent to close the channel.
func (c *clientChan) Close() error {
if !c.setClosed() {
return errors.New("ssh: channel already closed")
}
c.stdout.eof()
c.stderr.eof()
return c.sendClose()
}
// A chanWriter represents the stdin of a remote process.
type chanWriter struct {
*channel
// indicates the writer has been closed. eof is owned by the
// caller of Write/Close.
eof bool
}
// Write writes data to the remote process's standard input.
func (w *chanWriter) Write(data []byte) (written int, err error) {
const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length
for len(data) > 0 {
if w.eof || w.closed() {
err = io.EOF
return
}
// never send more data than maxPacket even if
// there is sufficient window.
n := min(w.maxPacket-headerLength, len(data))
r := w.remoteWin.reserve(n)
n = r
remoteId := w.remoteId
packet := []byte{
msgChannelData,
byte(remoteId >> 24), byte(remoteId >> 16), byte(remoteId >> 8), byte(remoteId),
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
}
if err = w.writePacket(append(packet, data[:n]...)); err != nil {
break
}
data = data[n:]
written += int(n)
}
return
}
func min(a uint32, b int) uint32 {
if a < uint32(b) {
return a
}
return uint32(b)
}
func (w *chanWriter) Close() error {
w.eof = true
return w.sendEOF()
}
// A chanReader represents stdout or stderr of a remote process.
type chanReader struct {
*channel // the channel backing this reader
*buffer
}
// Read reads data from the remote process's stdout or stderr.
func (r *chanReader) Read(buf []byte) (int, error) {
n, err := r.buffer.Read(buf)
if err != nil {
if err == io.EOF {
return n, err
}
return 0, err
}
err = r.sendWindowAdj(n)
if err == io.EOF && n > 0 {
// sendWindowAdjust can return io.EOF if the remote peer has
// closed the connection, however we want to defer forwarding io.EOF to the
// caller of Read until the buffer has been drained.
err = nil
}
return n, err
}

View file

@ -1,100 +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 ssh
import (
"crypto/aes"
"crypto/cipher"
"crypto/rc4"
)
// streamDump is used to dump the initial keystream for stream ciphers. It is a
// a write-only buffer, and not intended for reading so do not require a mutex.
var streamDump [512]byte
// noneCipher implements cipher.Stream and provides no encryption. It is used
// by the transport before the first key-exchange.
type noneCipher struct{}
func (c noneCipher) XORKeyStream(dst, src []byte) {
copy(dst, src)
}
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(c, iv), nil
}
func newRC4(key, iv []byte) (cipher.Stream, error) {
return rc4.NewCipher(key)
}
type cipherMode struct {
keySize int
ivSize int
skip int
createFunc func(key, iv []byte) (cipher.Stream, error)
}
func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
if len(key) < c.keySize {
panic("ssh: key length too small for cipher")
}
if len(iv) < c.ivSize {
panic("ssh: iv too small for cipher")
}
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
if err != nil {
return nil, err
}
for remainingToDump := c.skip; remainingToDump > 0; {
dumpThisTime := remainingToDump
if dumpThisTime > len(streamDump) {
dumpThisTime = len(streamDump)
}
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
remainingToDump -= dumpThisTime
}
return stream, nil
}
// Specifies a default set of ciphers and a preference order. This is based on
// OpenSSH's default client preference order, minus algorithms that are not
// implemented.
var DefaultCipherOrder = []string{
"aes128-ctr", "aes192-ctr", "aes256-ctr",
"arcfour256", "arcfour128",
}
// cipherModes documents properties of supported ciphers. Ciphers not included
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, 1536, newRC4},
"arcfour256": {32, 0, 1536, newRC4},
}
// defaultKeyExchangeOrder specifies a default set of key exchange algorithms
// with preferences.
var defaultKeyExchangeOrder = []string{
// P384 and P521 are not constant-time yet, but since we don't
// reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
}

View file

@ -1,524 +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 ssh
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"sync"
)
// ClientConn represents the client side of an SSH connection.
type ClientConn struct {
transport *transport
config *ClientConfig
chanList // channels associated with this connection
forwardList // forwarded tcpip connections from the remote side
globalRequest
// Address as passed to the Dial function.
dialAddress string
serverVersion string
}
type globalRequest struct {
sync.Mutex
response chan interface{}
}
// Client returns a new SSH client connection using c as the underlying transport.
func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
return clientWithAddress(c, "", config)
}
func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientConn, error) {
conn := &ClientConn{
transport: newTransport(c, config.rand(), true /* is client */),
config: config,
globalRequest: globalRequest{response: make(chan interface{}, 1)},
dialAddress: addr,
}
if err := conn.handshake(); err != nil {
conn.transport.Close()
return nil, fmt.Errorf("handshake failed: %v", err)
}
go conn.mainLoop()
return conn, nil
}
// Close closes the connection.
func (c *ClientConn) Close() error { return c.transport.Close() }
// LocalAddr returns the local network address.
func (c *ClientConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
// RemoteAddr returns the remote network address.
func (c *ClientConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
// handshake performs the client side key exchange. See RFC 4253 Section 7.
func (c *ClientConn) handshake() error {
clientVersion := []byte(packageVersion)
if c.config.ClientVersion != "" {
clientVersion = []byte(c.config.ClientVersion)
}
serverVersion, err := exchangeVersions(c.transport.Conn, clientVersion)
if err != nil {
return err
}
c.serverVersion = string(serverVersion)
clientKexInit := kexInitMsg{
KexAlgos: c.config.Crypto.kexes(),
ServerHostKeyAlgos: supportedHostKeyAlgos,
CiphersClientServer: c.config.Crypto.ciphers(),
CiphersServerClient: c.config.Crypto.ciphers(),
MACsClientServer: c.config.Crypto.macs(),
MACsServerClient: c.config.Crypto.macs(),
CompressionClientServer: supportedCompressions,
CompressionServerClient: supportedCompressions,
}
kexInitPacket := marshal(msgKexInit, clientKexInit)
if err := c.transport.writePacket(kexInitPacket); err != nil {
return err
}
packet, err := c.transport.readPacket()
if err != nil {
return err
}
var serverKexInit kexInitMsg
if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
return err
}
algs := findAgreedAlgorithms(&clientKexInit, &serverKexInit)
if algs == nil {
return errors.New("ssh: no common algorithms")
}
if serverKexInit.FirstKexFollows && algs.kex != serverKexInit.KexAlgos[0] {
// The server sent a Kex message for the wrong algorithm,
// which we have to ignore.
if _, err := c.transport.readPacket(); err != nil {
return err
}
}
kex, ok := kexAlgoMap[algs.kex]
if !ok {
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
}
magics := handshakeMagics{
clientVersion: clientVersion,
serverVersion: serverVersion,
clientKexInit: kexInitPacket,
serverKexInit: packet,
}
result, err := kex.Client(c.transport, c.config.rand(), &magics)
if err != nil {
return err
}
err = verifyHostKeySignature(algs.hostKey, result.HostKey, result.H, result.Signature)
if err != nil {
return err
}
if checker := c.config.HostKeyChecker; checker != nil {
err = checker.Check(c.dialAddress, c.transport.RemoteAddr(), algs.hostKey, result.HostKey)
if err != nil {
return err
}
}
c.transport.prepareKeyChange(algs, result)
if err = c.transport.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
if packet, err = c.transport.readPacket(); err != nil {
return err
}
if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
return c.authenticate()
}
// Verify the host key obtained in the key exchange.
func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte, signature []byte) error {
hostKey, rest, ok := ParsePublicKey(hostKeyBytes)
if len(rest) > 0 || !ok {
return errors.New("ssh: could not parse hostkey")
}
sig, rest, ok := parseSignatureBody(signature)
if len(rest) > 0 || !ok {
return errors.New("ssh: signature parse error")
}
if sig.Format != hostKeyAlgo {
return fmt.Errorf("ssh: unexpected signature type %q", sig.Format)
}
if !hostKey.Verify(data, sig.Blob) {
return errors.New("ssh: host key signature error")
}
return nil
}
// mainLoop reads incoming messages and routes channel messages
// to their respective ClientChans.
func (c *ClientConn) mainLoop() {
defer func() {
c.transport.Close()
c.chanList.closeAll()
c.forwardList.closeAll()
}()
for {
packet, err := c.transport.readPacket()
if err != nil {
break
}
// TODO(dfc) A note on blocking channel use.
// The msg, data and dataExt channels of a clientChan can
// cause this loop to block indefinitely if the consumer does
// not service them.
switch packet[0] {
case msgChannelData:
if len(packet) < 9 {
// malformed data packet
return
}
remoteId := binary.BigEndian.Uint32(packet[1:5])
length := binary.BigEndian.Uint32(packet[5:9])
packet = packet[9:]
if length != uint32(len(packet)) {
return
}
ch, ok := c.getChan(remoteId)
if !ok {
return
}
ch.stdout.write(packet)
case msgChannelExtendedData:
if len(packet) < 13 {
// malformed data packet
return
}
remoteId := binary.BigEndian.Uint32(packet[1:5])
datatype := binary.BigEndian.Uint32(packet[5:9])
length := binary.BigEndian.Uint32(packet[9:13])
packet = packet[13:]
if length != uint32(len(packet)) {
return
}
// RFC 4254 5.2 defines data_type_code 1 to be data destined
// for stderr on interactive sessions. Other data types are
// silently discarded.
if datatype == 1 {
ch, ok := c.getChan(remoteId)
if !ok {
return
}
ch.stderr.write(packet)
}
default:
decoded, err := decode(packet)
if err != nil {
if _, ok := err.(UnexpectedMessageError); ok {
fmt.Printf("mainLoop: unexpected message: %v\n", err)
continue
}
return
}
switch msg := decoded.(type) {
case *channelOpenMsg:
c.handleChanOpen(msg)
case *channelOpenConfirmMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelOpenFailureMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelCloseMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.Close()
close(ch.msg)
c.chanList.remove(msg.PeersId)
case *channelEOFMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.stdout.eof()
// RFC 4254 is mute on how EOF affects dataExt messages but
// it is logical to signal EOF at the same time.
ch.stderr.eof()
case *channelRequestSuccessMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelRequestFailureMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelRequestMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *windowAdjustMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
if !ch.remoteWin.add(msg.AdditionalBytes) {
// invalid window update
return
}
case *globalRequestMsg:
// This handles keepalive messages and matches
// the behaviour of OpenSSH.
if msg.WantReply {
c.transport.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{}))
}
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
c.globalRequest.response <- msg
case *disconnectMsg:
return
default:
fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
}
}
}
}
// Handle channel open messages from the remote side.
func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
c.sendConnectionFailed(msg.PeersId)
}
switch msg.ChanType {
case "forwarded-tcpip":
laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData)
if !ok {
// invalid request
c.sendConnectionFailed(msg.PeersId)
return
}
l, ok := c.forwardList.lookup(*laddr)
if !ok {
// TODO: print on a more structured log.
fmt.Println("could not find forward list entry for", laddr)
// Section 7.2, implementations MUST reject spurious incoming
// connections.
c.sendConnectionFailed(msg.PeersId)
return
}
raddr, rest, ok := parseTCPAddr(rest)
if !ok {
// invalid request
c.sendConnectionFailed(msg.PeersId)
return
}
ch := c.newChan(c.transport)
ch.remoteId = msg.PeersId
ch.remoteWin.add(msg.PeersWindow)
ch.maxPacket = msg.MaxPacketSize
m := channelOpenConfirmMsg{
PeersId: ch.remoteId,
MyId: ch.localId,
MyWindow: channelWindowSize,
MaxPacketSize: channelMaxPacketSize,
}
c.transport.writePacket(marshal(msgChannelOpenConfirm, m))
l <- forward{ch, raddr}
default:
// unknown channel type
m := channelOpenFailureMsg{
PeersId: msg.PeersId,
Reason: UnknownChannelType,
Message: fmt.Sprintf("unknown channel type: %v", msg.ChanType),
Language: "en_US.UTF-8",
}
c.transport.writePacket(marshal(msgChannelOpenFailure, m))
}
}
// sendGlobalRequest sends a global request message as specified
// in RFC4254 section 4. To correctly synchronise messages, a lock
// is held internally until a response is returned.
func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) {
c.globalRequest.Lock()
defer c.globalRequest.Unlock()
if err := c.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil {
return nil, err
}
r := <-c.globalRequest.response
if r, ok := r.(*globalRequestSuccessMsg); ok {
return r, nil
}
return nil, errors.New("request failed")
}
// sendConnectionFailed rejects an incoming channel identified
// by remoteId.
func (c *ClientConn) sendConnectionFailed(remoteId uint32) error {
m := channelOpenFailureMsg{
PeersId: remoteId,
Reason: ConnectionFailed,
Message: "invalid request",
Language: "en_US.UTF-8",
}
return c.transport.writePacket(marshal(msgChannelOpenFailure, m))
}
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
// RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardlist
// requires a valid *net.TCPAddr to operate, so we enforce that restriction here.
func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
addr, b, ok := parseString(b)
if !ok {
return nil, b, false
}
port, b, ok := parseUint32(b)
if !ok {
return nil, b, false
}
ip := net.ParseIP(string(addr))
if ip == nil {
return nil, b, false
}
return &net.TCPAddr{IP: ip, Port: int(port)}, b, true
}
// Dial connects to the given network address using net.Dial and
// then initiates a SSH handshake, returning the resulting client connection.
func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
return clientWithAddress(conn, addr, config)
}
// A ClientConfig structure is used to configure a ClientConn. After one has
// been passed to an SSH function it must not be modified.
type ClientConfig struct {
// Rand provides the source of entropy for key exchange. If Rand is
// nil, the cryptographic random reader in package crypto/rand will
// be used.
Rand io.Reader
// The username to authenticate.
User string
// A slice of ClientAuth methods. Only the first instance
// of a particular RFC 4252 method will be used during authentication.
Auth []ClientAuth
// HostKeyChecker, if not nil, is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyChecker
// implies that all host keys are accepted.
HostKeyChecker HostKeyChecker
// Cryptographic-related configuration.
Crypto CryptoConfig
// The identification string that will be used for the connection.
// If empty, a reasonable default is used.
ClientVersion string
}
func (c *ClientConfig) rand() io.Reader {
if c.Rand == nil {
return rand.Reader
}
return c.Rand
}
// Thread safe channel list.
type chanList struct {
// protects concurrent access to chans
sync.Mutex
// chans are indexed by the local id of the channel, clientChan.localId.
// The PeersId value of messages received by ClientConn.mainLoop is
// used to locate the right local clientChan in this slice.
chans []*clientChan
}
// Allocate a new ClientChan with the next avail local id.
func (c *chanList) newChan(p packetConn) *clientChan {
c.Lock()
defer c.Unlock()
for i := range c.chans {
if c.chans[i] == nil {
ch := newClientChan(p, uint32(i))
c.chans[i] = ch
return ch
}
}
i := len(c.chans)
ch := newClientChan(p, uint32(i))
c.chans = append(c.chans, ch)
return ch
}
func (c *chanList) getChan(id uint32) (*clientChan, bool) {
c.Lock()
defer c.Unlock()
if id >= uint32(len(c.chans)) {
return nil, false
}
return c.chans[id], true
}
func (c *chanList) remove(id uint32) {
c.Lock()
defer c.Unlock()
c.chans[id] = nil
}
func (c *chanList) closeAll() {
c.Lock()
defer c.Unlock()
for _, ch := range c.chans {
if ch == nil {
continue
}
ch.Close()
close(ch.msg)
}
}

View file

@ -1,509 +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 ssh
import (
"errors"
"fmt"
"io"
"net"
)
// authenticate authenticates with the remote server. See RFC 4252.
func (c *ClientConn) authenticate() error {
// initiate user auth session
if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
return err
}
packet, err := c.transport.readPacket()
if err != nil {
return err
}
var serviceAccept serviceAcceptMsg
if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
return err
}
// during the authentication phase the client first attempts the "none" method
// then any untried methods suggested by the server.
tried, remain := make(map[string]bool), make(map[string]bool)
for auth := ClientAuth(new(noneAuth)); auth != nil; {
ok, methods, err := auth.auth(c.transport.sessionID, c.config.User, c.transport, c.config.rand())
if err != nil {
return err
}
if ok {
// success
return nil
}
tried[auth.method()] = true
delete(remain, auth.method())
for _, meth := range methods {
if tried[meth] {
// if we've tried meth already, skip it.
continue
}
remain[meth] = true
}
auth = nil
for _, a := range c.config.Auth {
if remain[a.method()] {
auth = a
break
}
}
}
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
}
func keys(m map[string]bool) (s []string) {
for k := range m {
s = append(s, k)
}
return
}
// HostKeyChecker represents a database of known server host keys.
type HostKeyChecker interface {
// Check is called during the handshake to check server's
// public key for unexpected changes. The hostKey argument is
// in SSH wire format. It can be parsed using
// ssh.ParsePublicKey. The address before DNS resolution is
// passed in the addr argument, so the key can also be checked
// against the hostname.
Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error
}
// A ClientAuth represents an instance of an RFC 4252 authentication method.
type ClientAuth interface {
// auth authenticates user over transport t.
// Returns true if authentication is successful.
// If authentication is not successful, a []string of alternative
// method names is returned.
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
// method returns the RFC 4252 method name.
method() string
}
// "none" authentication, RFC 4252 section 5.2.
type noneAuth int
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: "none",
})); err != nil {
return false, nil, err
}
return handleAuthResponse(c)
}
func (n *noneAuth) method() string {
return "none"
}
// "password" authentication, RFC 4252 Section 8.
type passwordAuth struct {
ClientPassword
}
func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
type passwordAuthMsg struct {
User string
Service string
Method string
Reply bool
Password string
}
pw, err := p.Password(user)
if err != nil {
return false, nil, err
}
if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
User: user,
Service: serviceSSH,
Method: "password",
Reply: false,
Password: pw,
})); err != nil {
return false, nil, err
}
return handleAuthResponse(c)
}
func (p *passwordAuth) method() string {
return "password"
}
// A ClientPassword implements access to a client's passwords.
type ClientPassword interface {
// Password returns the password to use for user.
Password(user string) (password string, err error)
}
// ClientAuthPassword returns a ClientAuth using password authentication.
func ClientAuthPassword(impl ClientPassword) ClientAuth {
return &passwordAuth{impl}
}
// ClientKeyring implements access to a client key ring.
type ClientKeyring interface {
// Key returns the i'th Publickey, or nil if no key exists at i.
Key(i int) (key PublicKey, err error)
// Sign returns a signature of the given data using the i'th key
// and the supplied random source.
Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
}
// "publickey" authentication, RFC 4252 Section 7.
type publickeyAuth struct {
ClientKeyring
}
type publickeyAuthMsg struct {
User string
Service string
Method string
// HasSig indicates to the receiver packet that the auth request is signed and
// should be used for authentication of the request.
HasSig bool
Algoname string
Pubkey string
// Sig is defined as []byte so marshal will exclude it during validateKey
Sig []byte `ssh:"rest"`
}
func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an
// enquiry to test if each key is acceptable to the remote. The second
// stage attempts to authenticate with the valid keys obtained in the
// first stage.
var index int
// a map of public keys to their index in the keyring
validKeys := make(map[int]PublicKey)
for {
key, err := p.Key(index)
if err != nil {
return false, nil, err
}
if key == nil {
// no more keys in the keyring
break
}
if ok, err := p.validateKey(key, user, c); ok {
validKeys[index] = key
} else {
if err != nil {
return false, nil, err
}
}
index++
}
// methods that may continue if this auth is not successful.
var methods []string
for i, key := range validKeys {
pubkey := MarshalPublicKey(key)
algoname := key.PublicKeyAlgo()
data := buildDataSignedForAuth(session, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
}, []byte(algoname), pubkey)
sigBlob, err := p.Sign(i, rand, data)
if err != nil {
return false, nil, err
}
// manually wrap the serialized signature in a string
s := serializeSignature(key.PublicKeyAlgo(), sigBlob)
sig := make([]byte, stringLength(len(s)))
marshalString(sig, s)
msg := publickeyAuthMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
HasSig: true,
Algoname: algoname,
Pubkey: string(pubkey),
Sig: sig,
}
p := marshal(msgUserAuthRequest, msg)
if err := c.writePacket(p); err != nil {
return false, nil, err
}
success, methods, err := handleAuthResponse(c)
if err != nil {
return false, nil, err
}
if success {
return success, methods, err
}
}
return false, methods, nil
}
// validateKey validates the key provided it is acceptable to the server.
func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (bool, error) {
pubkey := MarshalPublicKey(key)
algoname := key.PublicKeyAlgo()
msg := publickeyAuthMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
HasSig: false,
Algoname: algoname,
Pubkey: string(pubkey),
}
if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
return false, err
}
return p.confirmKeyAck(key, c)
}
func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
pubkey := MarshalPublicKey(key)
algoname := key.PublicKeyAlgo()
for {
packet, err := c.readPacket()
if err != nil {
return false, err
}
switch packet[0] {
case msgUserAuthBanner:
// TODO(gpaul): add callback to present the banner to the user
case msgUserAuthPubKeyOk:
msg := userAuthPubKeyOkMsg{}
if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil {
return false, err
}
if msg.Algo != algoname || msg.PubKey != string(pubkey) {
return false, nil
}
return true, nil
case msgUserAuthFailure:
return false, nil
default:
return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
}
panic("unreachable")
}
func (p *publickeyAuth) method() string {
return "publickey"
}
// ClientAuthKeyring returns a ClientAuth using public key authentication.
func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
return &publickeyAuth{impl}
}
// handleAuthResponse returns whether the preceding authentication request succeeded
// along with a list of remaining authentication methods to try next and
// an error if an unexpected response was received.
func handleAuthResponse(c packetConn) (bool, []string, error) {
for {
packet, err := c.readPacket()
if err != nil {
return false, nil, err
}
switch packet[0] {
case msgUserAuthBanner:
// TODO: add callback to present the banner to the user
case msgUserAuthFailure:
msg := userAuthFailureMsg{}
if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
return false, nil, err
}
return false, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
case msgDisconnect:
return false, nil, io.EOF
default:
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
}
panic("unreachable")
}
// ClientAuthAgent returns a ClientAuth using public key authentication via
// an agent.
func ClientAuthAgent(agent *AgentClient) ClientAuth {
return ClientAuthKeyring(&agentKeyring{agent: agent})
}
// agentKeyring implements ClientKeyring.
type agentKeyring struct {
agent *AgentClient
keys []*AgentKey
}
func (kr *agentKeyring) Key(i int) (key PublicKey, err error) {
if kr.keys == nil {
if kr.keys, err = kr.agent.RequestIdentities(); err != nil {
return
}
}
if i >= len(kr.keys) {
return
}
return kr.keys[i].Key()
}
func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
var key PublicKey
if key, err = kr.Key(i); err != nil {
return
}
if key == nil {
return nil, errors.New("ssh: key index out of range")
}
if sig, err = kr.agent.SignRequest(key, data); err != nil {
return
}
// Unmarshal the signature.
var ok bool
if _, sig, ok = parseString(sig); !ok {
return nil, errors.New("ssh: malformed signature response from agent")
}
if sig, _, ok = parseString(sig); !ok {
return nil, errors.New("ssh: malformed signature response from agent")
}
return sig, nil
}
// ClientKeyboardInteractive should prompt the user for the given
// questions.
type ClientKeyboardInteractive interface {
// Challenge should print the questions, optionally disabling
// echoing (eg. for passwords), and return all the answers.
// Challenge may be called multiple times in a single
// session. After successful authentication, the server may
// send a challenge with no questions, for which the user and
// instruction messages should be printed. RFC 4256 section
// 3.3 details how the UI should behave for both CLI and
// GUI environments.
Challenge(user, instruction string, questions []string, echos []bool) ([]string, error)
}
// ClientAuthKeyboardInteractive returns a ClientAuth using a
// prompt/response sequence controlled by the server.
func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
return &keyboardInteractiveAuth{impl}
}
type keyboardInteractiveAuth struct {
ClientKeyboardInteractive
}
func (k *keyboardInteractiveAuth) method() string {
return "keyboard-interactive"
}
func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
type initiateMsg struct {
User string
Service string
Method string
Language string
Submethods string
}
if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{
User: user,
Service: serviceSSH,
Method: "keyboard-interactive",
})); err != nil {
return false, nil, err
}
for {
packet, err := c.readPacket()
if err != nil {
return false, nil, err
}
// like handleAuthResponse, but with less options.
switch packet[0] {
case msgUserAuthBanner:
// TODO: Print banners during userauth.
continue
case msgUserAuthInfoRequest:
// OK
case msgUserAuthFailure:
var msg userAuthFailureMsg
if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
return false, nil, err
}
return false, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
default:
return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]}
}
var msg userAuthInfoRequestMsg
if err := unmarshal(&msg, packet, packet[0]); err != nil {
return false, nil, err
}
// Manually unpack the prompt/echo pairs.
rest := msg.Prompts
var prompts []string
var echos []bool
for i := 0; i < int(msg.NumPrompts); i++ {
prompt, r, ok := parseString(rest)
if !ok || len(r) == 0 {
return false, nil, errors.New("ssh: prompt format error")
}
prompts = append(prompts, string(prompt))
echos = append(echos, r[0] != 0)
rest = r[1:]
}
if len(rest) != 0 {
return false, nil, fmt.Errorf("ssh: junk following message %q", rest)
}
answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos)
if err != nil {
return false, nil, err
}
if len(answers) != len(prompts) {
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
}
responseLength := 1 + 4
for _, a := range answers {
responseLength += stringLength(len(a))
}
serialized := make([]byte, responseLength)
p := serialized
p[0] = msgUserAuthInfoResponse
p = p[1:]
p = marshalUint32(p, uint32(len(answers)))
for _, a := range answers {
p = marshalString(p, []byte(a))
}
if err := c.writePacket(serialized); err != nil {
return false, nil, err
}
}
}

View file

@ -1,352 +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 ssh
import (
"crypto"
"fmt"
"sync"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
)
// These are string constants in the SSH protocol.
const (
compressionNone = "none"
serviceUserAuth = "ssh-userauth"
serviceSSH = "ssh-connection"
)
var supportedKexAlgos = []string{
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
}
var supportedHostKeyAlgos = []string{
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSA, KeyAlgoDSA,
}
var supportedCompressions = []string{compressionNone}
// hashFuncs keeps the mapping of supported algorithms to their respective
// hashes needed for signature verification.
var hashFuncs = map[string]crypto.Hash{
KeyAlgoRSA: crypto.SHA1,
KeyAlgoDSA: crypto.SHA1,
KeyAlgoECDSA256: crypto.SHA256,
KeyAlgoECDSA384: crypto.SHA384,
KeyAlgoECDSA521: crypto.SHA512,
CertAlgoRSAv01: crypto.SHA1,
CertAlgoDSAv01: crypto.SHA1,
CertAlgoECDSA256v01: crypto.SHA256,
CertAlgoECDSA384v01: crypto.SHA384,
CertAlgoECDSA521v01: crypto.SHA512,
}
// UnexpectedMessageError results when the SSH message that we received didn't
// match what we wanted.
type UnexpectedMessageError struct {
expected, got uint8
}
func (u UnexpectedMessageError) Error() string {
return fmt.Sprintf("ssh: unexpected message type %d (expected %d)", u.got, u.expected)
}
// ParseError results from a malformed SSH message.
type ParseError struct {
msgType uint8
}
func (p ParseError) Error() string {
return fmt.Sprintf("ssh: parse error in message type %d", p.msgType)
}
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
for _, clientAlgo := range clientAlgos {
for _, serverAlgo := range serverAlgos {
if clientAlgo == serverAlgo {
return clientAlgo, true
}
}
}
return
}
func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) {
for _, clientCipher := range clientCiphers {
for _, serverCipher := range serverCiphers {
// reject the cipher if we have no cipherModes definition
if clientCipher == serverCipher && cipherModes[clientCipher] != nil {
return clientCipher, true
}
}
}
return
}
type algorithms struct {
kex string
hostKey string
wCipher string
rCipher string
rMAC string
wMAC string
rCompression string
wCompression string
}
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) {
var ok bool
result := &algorithms{}
result.kex, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
if !ok {
return
}
result.hostKey, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
if !ok {
return
}
result.wCipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
if !ok {
return
}
result.rCipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
if !ok {
return
}
result.wMAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if !ok {
return
}
result.rMAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if !ok {
return
}
result.wCompression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
if !ok {
return
}
result.rCompression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
if !ok {
return
}
return result
}
// Cryptographic configuration common to both ServerConfig and ClientConfig.
type CryptoConfig struct {
// The allowed key exchanges algorithms. If unspecified then a
// default set of algorithms is used.
KeyExchanges []string
// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
// used.
Ciphers []string
// The allowed MAC algorithms. If unspecified then DefaultMACOrder is used.
MACs []string
}
func (c *CryptoConfig) ciphers() []string {
if c.Ciphers == nil {
return DefaultCipherOrder
}
return c.Ciphers
}
func (c *CryptoConfig) kexes() []string {
if c.KeyExchanges == nil {
return defaultKeyExchangeOrder
}
return c.KeyExchanges
}
func (c *CryptoConfig) macs() []string {
if c.MACs == nil {
return DefaultMACOrder
}
return c.MACs
}
// serialize a signed slice according to RFC 4254 6.6. The name should
// be a key type name, rather than a cert type name.
func serializeSignature(name string, sig []byte) []byte {
length := stringLength(len(name))
length += stringLength(len(sig))
ret := make([]byte, length)
r := marshalString(ret, []byte(name))
r = marshalString(r, sig)
return ret
}
// MarshalPublicKey serializes a supported key or certificate for use
// by the SSH wire protocol. It can be used for comparison with the
// pubkey argument of ServerConfig's PublicKeyCallback as well as for
// generating an authorized_keys or host_keys file.
func MarshalPublicKey(key PublicKey) []byte {
// See also RFC 4253 6.6.
algoname := key.PublicKeyAlgo()
blob := key.Marshal()
length := stringLength(len(algoname))
length += len(blob)
ret := make([]byte, length)
r := marshalString(ret, []byte(algoname))
copy(r, blob)
return ret
}
// pubAlgoToPrivAlgo returns the private key algorithm format name that
// corresponds to a given public key algorithm format name. For most
// public keys, the private key algorithm name is the same. For some
// situations, such as openssh certificates, the private key algorithm and
// public key algorithm names differ. This accounts for those situations.
func pubAlgoToPrivAlgo(pubAlgo string) string {
switch pubAlgo {
case CertAlgoRSAv01:
return KeyAlgoRSA
case CertAlgoDSAv01:
return KeyAlgoDSA
case CertAlgoECDSA256v01:
return KeyAlgoECDSA256
case CertAlgoECDSA384v01:
return KeyAlgoECDSA384
case CertAlgoECDSA521v01:
return KeyAlgoECDSA521
}
return pubAlgo
}
// buildDataSignedForAuth returns the data that is signed in order to prove
// possession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
user := []byte(req.User)
service := []byte(req.Service)
method := []byte(req.Method)
length := stringLength(len(sessionId))
length += 1
length += stringLength(len(user))
length += stringLength(len(service))
length += stringLength(len(method))
length += 1
length += stringLength(len(algo))
length += stringLength(len(pubKey))
ret := make([]byte, length)
r := marshalString(ret, sessionId)
r[0] = msgUserAuthRequest
r = r[1:]
r = marshalString(r, user)
r = marshalString(r, service)
r = marshalString(r, method)
r[0] = 1
r = r[1:]
r = marshalString(r, algo)
r = marshalString(r, pubKey)
return ret
}
// safeString sanitises s according to RFC 4251, section 9.2.
// All control characters except tab, carriage return and newline are
// replaced by 0x20.
func safeString(s string) string {
out := []byte(s)
for i, c := range out {
if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
out[i] = 0x20
}
}
return string(out)
}
func appendU16(buf []byte, n uint16) []byte {
return append(buf, byte(n>>8), byte(n))
}
func appendU32(buf []byte, n uint32) []byte {
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}
func appendInt(buf []byte, n int) []byte {
return appendU32(buf, uint32(n))
}
func appendString(buf []byte, s string) []byte {
buf = appendU32(buf, uint32(len(s)))
buf = append(buf, s...)
return buf
}
func appendBool(buf []byte, b bool) []byte {
if b {
buf = append(buf, 1)
} else {
buf = append(buf, 0)
}
return buf
}
// newCond is a helper to hide the fact that there is no usable zero
// value for sync.Cond.
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
// window represents the buffer available to clients
// wishing to write to a channel.
type window struct {
*sync.Cond
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
}
// add adds win to the amount of window available
// for consumers.
func (w *window) add(win uint32) bool {
// a zero sized window adjust is a noop.
if win == 0 {
return true
}
w.L.Lock()
if w.win+win < win {
w.L.Unlock()
return false
}
w.win += win
// It is unusual that multiple goroutines would be attempting to reserve
// window space, but not guaranteed. Use broadcast to notify all waiters
// that additional window is available.
w.Broadcast()
w.L.Unlock()
return true
}
// reserve reserves win from the available window capacity.
// If no capacity remains, reserve will block. reserve may
// return less than requested.
func (w *window) reserve(win uint32) uint32 {
w.L.Lock()
for w.win == 0 {
w.Wait()
}
if w.win < win {
win = w.win
}
w.win -= win
w.L.Unlock()
return win
}

View file

@ -1,19 +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 ssh implements an SSH client and server.
SSH is a transport security protocol, an authentication protocol and a
family of application protocols. The most typical application level
protocol is a remote shell and this is specifically implemented. However,
the multiplexed nature of SSH is exposed to users that wish to support
others.
References:
[PROTOCOL.certkeys]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys
[PROTOCOL.agent]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
*/
package ssh

View file

@ -1,386 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"io"
"math/big"
)
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
)
// kexResult captures the outcome of a key exchange.
type kexResult struct {
// Session hash. See also RFC 4253, section 8.
H []byte
// Shared secret. See also RFC 4253, section 8.
K []byte
// Host key as hashed into H
HostKey []byte
// Signature of H
Signature []byte
// A cryptographic hash function that matches the security
// level of the key exchange algorithm. It is used for
// calculating H, and for deriving keys from H and K.
Hash crypto.Hash
// The session ID, which is the first H computed. This is used
// to signal data inside transport.
SessionID []byte
}
// handshakeMagics contains data that is always included in the
// session hash.
type handshakeMagics struct {
clientVersion, serverVersion []byte
clientKexInit, serverKexInit []byte
}
func (m *handshakeMagics) write(w io.Writer) {
writeString(w, m.clientVersion)
writeString(w, m.serverVersion)
writeString(w, m.clientKexInit)
writeString(w, m.serverKexInit)
}
// kexAlgorithm abstracts different key exchange algorithms.
type kexAlgorithm interface {
// Server runs server-side key agreement, signing the result
// with a hostkey.
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
// Client runs the client-side key agreement. Caller is
// responsible for verifying the host key signature.
Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error)
}
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
type dhGroup struct {
g, p *big.Int
}
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
if theirPublic.Sign() <= 0 || theirPublic.Cmp(group.p) >= 0 {
return nil, errors.New("ssh: DH parameter out of bounds")
}
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
}
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
hashFunc := crypto.SHA1
x, err := rand.Int(randSource, group.p)
if err != nil {
return nil, err
}
X := new(big.Int).Exp(group.g, x, group.p)
kexDHInit := kexDHInitMsg{
X: X,
}
if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
return nil, err
}
packet, err := c.readPacket()
if err != nil {
return nil, err
}
var kexDHReply kexDHReplyMsg
if err = unmarshal(&kexDHReply, packet, msgKexDHReply); err != nil {
return nil, err
}
kInt, err := group.diffieHellman(kexDHReply.Y, x)
if err != nil {
return nil, err
}
h := hashFunc.New()
magics.write(h)
writeString(h, kexDHReply.HostKey)
writeInt(h, X)
writeInt(h, kexDHReply.Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
return &kexResult{
H: h.Sum(nil),
K: K,
HostKey: kexDHReply.HostKey,
Signature: kexDHReply.Signature,
Hash: crypto.SHA1,
}, nil
}
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
hashFunc := crypto.SHA1
packet, err := c.readPacket()
if err != nil {
return
}
var kexDHInit kexDHInitMsg
if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil {
return
}
y, err := rand.Int(randSource, group.p)
if err != nil {
return
}
Y := new(big.Int).Exp(group.g, y, group.p)
kInt, err := group.diffieHellman(kexDHInit.X, y)
if err != nil {
return nil, err
}
hostKeyBytes := MarshalPublicKey(priv.PublicKey())
h := hashFunc.New()
magics.write(h)
writeString(h, hostKeyBytes)
writeInt(h, kexDHInit.X)
writeInt(h, Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
H := h.Sum(nil)
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, randSource, H)
if err != nil {
return nil, err
}
kexDHReply := kexDHReplyMsg{
HostKey: hostKeyBytes,
Y: Y,
Signature: sig,
}
packet = marshal(msgKexDHReply, kexDHReply)
err = c.writePacket(packet)
return &kexResult{
H: H,
K: K,
HostKey: hostKeyBytes,
Signature: sig,
Hash: crypto.SHA1,
}, nil
}
// ecdh performs Elliptic Curve Diffie-Hellman key exchange as
// described in RFC 5656, section 4.
type ecdh struct {
curve elliptic.Curve
}
func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
if err != nil {
return nil, err
}
kexInit := kexECDHInitMsg{
ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y),
}
serialized := marshal(msgKexECDHInit, kexInit)
if err := c.writePacket(serialized); err != nil {
return nil, err
}
packet, err := c.readPacket()
if err != nil {
return nil, err
}
var reply kexECDHReplyMsg
if err = unmarshal(&reply, packet, msgKexECDHReply); err != nil {
return nil, err
}
x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey)
if err != nil {
return nil, err
}
// generate shared secret
secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes())
h := ecHash(kex.curve).New()
magics.write(h)
writeString(h, reply.HostKey)
writeString(h, kexInit.ClientPubKey)
writeString(h, reply.EphemeralPubKey)
K := make([]byte, intLength(secret))
marshalInt(K, secret)
h.Write(K)
return &kexResult{
H: h.Sum(nil),
K: K,
HostKey: reply.HostKey,
Signature: reply.Signature,
Hash: ecHash(kex.curve),
}, nil
}
// unmarshalECKey parses and checks an EC key.
func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) {
x, y = elliptic.Unmarshal(curve, pubkey)
if x == nil {
return nil, nil, errors.New("ssh: elliptic.Unmarshal failure")
}
if !validateECPublicKey(curve, x, y) {
return nil, nil, errors.New("ssh: public key not on curve")
}
return x, y, nil
}
// validateECPublicKey checks that the point is a valid public key for
// the given curve. See [SEC1], 3.2.2
func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
if x.Sign() == 0 && y.Sign() == 0 {
return false
}
if x.Cmp(curve.Params().P) >= 0 {
return false
}
if y.Cmp(curve.Params().P) >= 0 {
return false
}
if !curve.IsOnCurve(x, y) {
return false
}
// We don't check if N * PubKey == 0, since
//
// - the NIST curves have cofactor = 1, so this is implicit.
// (We don't foresee an implementation that supports non NIST
// curves)
//
// - for ephemeral keys, we don't need to worry about small
// subgroup attacks.
return true
}
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
packet, err := c.readPacket()
if err != nil {
return nil, err
}
var kexECDHInit kexECDHInitMsg
if err = unmarshal(&kexECDHInit, packet, msgKexECDHInit); err != nil {
return nil, err
}
clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey)
if err != nil {
return nil, err
}
// We could cache this key across multiple users/multiple
// connection attempts, but the benefit is small. OpenSSH
// generates a new key for each incoming connection.
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
if err != nil {
return nil, err
}
hostKeyBytes := MarshalPublicKey(priv.PublicKey())
serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y)
// generate shared secret
secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes())
h := ecHash(kex.curve).New()
magics.write(h)
writeString(h, hostKeyBytes)
writeString(h, kexECDHInit.ClientPubKey)
writeString(h, serializedEphKey)
K := make([]byte, intLength(secret))
marshalInt(K, secret)
h.Write(K)
H := h.Sum(nil)
// H is already a hash, but the hostkey signing will apply its
// own key-specific hash algorithm.
sig, err := signAndMarshal(priv, rand, H)
if err != nil {
return nil, err
}
reply := kexECDHReplyMsg{
EphemeralPubKey: serializedEphKey,
HostKey: hostKeyBytes,
Signature: sig,
}
serialized := marshal(msgKexECDHReply, reply)
if err := c.writePacket(serialized); err != nil {
return nil, err
}
return &kexResult{
H: H,
K: K,
HostKey: reply.HostKey,
Signature: sig,
Hash: ecHash(kex.curve),
}, nil
}
var kexAlgoMap = map[string]kexAlgorithm{}
func init() {
// This is the group called diffie-hellman-group1-sha1 in RFC
// 4253 and Oakley Group 2 in RFC 2409.
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
}
// This is the group called diffie-hellman-group14-sha1 in RFC
// 4253 and Oakley Group 14 in RFC 3526.
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
}
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
}

View file

@ -1,609 +0,0 @@
// Copyright 2012 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 ssh
import (
"bytes"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
// These constants represent the algorithm names for key types supported by this
// package.
const (
KeyAlgoRSA = "ssh-rsa"
KeyAlgoDSA = "ssh-dss"
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
)
// parsePubKey parses a public key of the given algorithm.
// Use ParsePublicKey for keys with prepended algorithm.
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, ok bool) {
switch algo {
case KeyAlgoRSA:
return parseRSA(in)
case KeyAlgoDSA:
return parseDSA(in)
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
return parseECDSA(in)
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
return parseOpenSSHCertV01(in, algo)
}
return nil, nil, false
}
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
// (see sshd(8) manual page) once the options and key type fields have been
// removed.
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, ok bool) {
in = bytes.TrimSpace(in)
i := bytes.IndexAny(in, " \t")
if i == -1 {
i = len(in)
}
base64Key := in[:i]
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
n, err := base64.StdEncoding.Decode(key, base64Key)
if err != nil {
return
}
key = key[:n]
out, _, ok = ParsePublicKey(key)
if !ok {
return nil, "", false
}
comment = string(bytes.TrimSpace(in[i:]))
return
}
// ParseAuthorizedKeys parses a public key from an authorized_keys
// file used in OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, ok bool) {
for len(in) > 0 {
end := bytes.IndexByte(in, '\n')
if end != -1 {
rest = in[end+1:]
in = in[:end]
} else {
rest = nil
}
end = bytes.IndexByte(in, '\r')
if end != -1 {
in = in[:end]
}
in = bytes.TrimSpace(in)
if len(in) == 0 || in[0] == '#' {
in = rest
continue
}
i := bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if out, comment, ok = parseAuthorizedKey(in[i:]); ok {
return
}
// No key type recognised. Maybe there's an options field at
// the beginning.
var b byte
inQuote := false
var candidateOptions []string
optionStart := 0
for i, b = range in {
isEnd := !inQuote && (b == ' ' || b == '\t')
if (b == ',' && !inQuote) || isEnd {
if i-optionStart > 0 {
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
}
optionStart = i + 1
}
if isEnd {
break
}
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
inQuote = !inQuote
}
}
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
i++
}
if i == len(in) {
// Invalid line: unmatched quote
in = rest
continue
}
in = in[i:]
i = bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if out, comment, ok = parseAuthorizedKey(in[i:]); ok {
options = candidateOptions
return
}
in = rest
continue
}
return
}
// ParsePublicKey parses an SSH public key formatted for use in
// the SSH wire protocol according to RFC 4253, section 6.6.
func ParsePublicKey(in []byte) (out PublicKey, rest []byte, ok bool) {
algo, in, ok := parseString(in)
if !ok {
return
}
return parsePubKey(in, string(algo))
}
// MarshalAuthorizedKey returns a byte stream suitable for inclusion
// in an OpenSSH authorized_keys file following the format specified
// in the sshd(8) manual page.
func MarshalAuthorizedKey(key PublicKey) []byte {
b := &bytes.Buffer{}
b.WriteString(key.PublicKeyAlgo())
b.WriteByte(' ')
e := base64.NewEncoder(base64.StdEncoding, b)
e.Write(MarshalPublicKey(key))
e.Close()
b.WriteByte('\n')
return b.Bytes()
}
// PublicKey is an abstraction of different types of public keys.
type PublicKey interface {
// PrivateKeyAlgo returns the name of the encryption system.
PrivateKeyAlgo() string
// PublicKeyAlgo returns the algorithm for the public key,
// which may be different from PrivateKeyAlgo for certificates.
PublicKeyAlgo() string
// Marshal returns the serialized key data in SSH wire format,
// without the name prefix. Callers should typically use
// MarshalPublicKey().
Marshal() []byte
// Verify that sig is a signature on the given data using this
// key. This function will hash the data appropriately first.
Verify(data []byte, sigBlob []byte) bool
}
// A Signer is can create signatures that verify against a public key.
type Signer interface {
// PublicKey returns an associated PublicKey instance.
PublicKey() PublicKey
// Sign returns raw signature for the given data. This method
// will apply the hash specified for the keytype to the data.
Sign(rand io.Reader, data []byte) ([]byte, error)
}
type rsaPublicKey rsa.PublicKey
func (r *rsaPublicKey) PrivateKeyAlgo() string {
return "ssh-rsa"
}
func (r *rsaPublicKey) PublicKeyAlgo() string {
return r.PrivateKeyAlgo()
}
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
func parseRSA(in []byte) (out PublicKey, rest []byte, ok bool) {
key := new(rsa.PublicKey)
bigE, in, ok := parseInt(in)
if !ok || bigE.BitLen() > 24 {
return
}
e := bigE.Int64()
if e < 3 || e&1 == 0 {
ok = false
return
}
key.E = int(e)
if key.N, in, ok = parseInt(in); !ok {
return
}
ok = true
return (*rsaPublicKey)(key), in, ok
}
func (r *rsaPublicKey) Marshal() []byte {
// See RFC 4253, section 6.6.
e := new(big.Int).SetInt64(int64(r.E))
length := intLength(e)
length += intLength(r.N)
ret := make([]byte, length)
rest := marshalInt(ret, e)
marshalInt(rest, r.N)
return ret
}
func (r *rsaPublicKey) Verify(data []byte, sig []byte) bool {
h := crypto.SHA1.New()
h.Write(data)
digest := h.Sum(nil)
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig) == nil
}
type rsaPrivateKey struct {
*rsa.PrivateKey
}
func (r *rsaPrivateKey) PublicKey() PublicKey {
return (*rsaPublicKey)(&r.PrivateKey.PublicKey)
}
func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
h := crypto.SHA1.New()
h.Write(data)
digest := h.Sum(nil)
return rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest)
}
type dsaPublicKey dsa.PublicKey
func (r *dsaPublicKey) PrivateKeyAlgo() string {
return "ssh-dss"
}
func (r *dsaPublicKey) PublicKeyAlgo() string {
return r.PrivateKeyAlgo()
}
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
func parseDSA(in []byte) (out PublicKey, rest []byte, ok bool) {
key := new(dsa.PublicKey)
if key.P, in, ok = parseInt(in); !ok {
return
}
if key.Q, in, ok = parseInt(in); !ok {
return
}
if key.G, in, ok = parseInt(in); !ok {
return
}
if key.Y, in, ok = parseInt(in); !ok {
return
}
ok = true
return (*dsaPublicKey)(key), in, ok
}
func (r *dsaPublicKey) Marshal() []byte {
// See RFC 4253, section 6.6.
length := intLength(r.P)
length += intLength(r.Q)
length += intLength(r.G)
length += intLength(r.Y)
ret := make([]byte, length)
rest := marshalInt(ret, r.P)
rest = marshalInt(rest, r.Q)
rest = marshalInt(rest, r.G)
marshalInt(rest, r.Y)
return ret
}
func (k *dsaPublicKey) Verify(data []byte, sigBlob []byte) bool {
h := crypto.SHA1.New()
h.Write(data)
digest := h.Sum(nil)
// Per RFC 4253, section 6.6,
// The value for 'dss_signature_blob' is encoded as a string containing
// r, followed by s (which are 160-bit integers, without lengths or
// padding, unsigned, and in network byte order).
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
if len(sigBlob) != 40 {
return false
}
r := new(big.Int).SetBytes(sigBlob[:20])
s := new(big.Int).SetBytes(sigBlob[20:])
return dsa.Verify((*dsa.PublicKey)(k), digest, r, s)
}
type dsaPrivateKey struct {
*dsa.PrivateKey
}
func (k *dsaPrivateKey) PublicKey() PublicKey {
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
}
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
h := crypto.SHA1.New()
h.Write(data)
digest := h.Sum(nil)
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
if err != nil {
return nil, err
}
sig := make([]byte, 40)
copy(sig[:20], r.Bytes())
copy(sig[20:], s.Bytes())
return sig, nil
}
type ecdsaPublicKey ecdsa.PublicKey
func (key *ecdsaPublicKey) PrivateKeyAlgo() string {
return "ecdsa-sha2-" + key.nistID()
}
func (key *ecdsaPublicKey) nistID() string {
switch key.Params().BitSize {
case 256:
return "nistp256"
case 384:
return "nistp384"
case 521:
return "nistp521"
}
panic("ssh: unsupported ecdsa key size")
}
func supportedEllipticCurve(curve elliptic.Curve) bool {
return (curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521())
}
// ecHash returns the hash to match the given elliptic curve, see RFC
// 5656, section 6.2.1
func ecHash(curve elliptic.Curve) crypto.Hash {
bitSize := curve.Params().BitSize
switch {
case bitSize <= 256:
return crypto.SHA256
case bitSize <= 384:
return crypto.SHA384
}
return crypto.SHA512
}
func (key *ecdsaPublicKey) PublicKeyAlgo() string {
return key.PrivateKeyAlgo()
}
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
func parseECDSA(in []byte) (out PublicKey, rest []byte, ok bool) {
var identifier []byte
if identifier, in, ok = parseString(in); !ok {
return
}
key := new(ecdsa.PublicKey)
switch string(identifier) {
case "nistp256":
key.Curve = elliptic.P256()
case "nistp384":
key.Curve = elliptic.P384()
case "nistp521":
key.Curve = elliptic.P521()
default:
ok = false
return
}
var keyBytes []byte
if keyBytes, in, ok = parseString(in); !ok {
return
}
key.X, key.Y = elliptic.Unmarshal(key.Curve, keyBytes)
if key.X == nil || key.Y == nil {
ok = false
return
}
return (*ecdsaPublicKey)(key), in, ok
}
func (key *ecdsaPublicKey) Marshal() []byte {
// See RFC 5656, section 3.1.
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
ID := key.nistID()
length := stringLength(len(ID))
length += stringLength(len(keyBytes))
ret := make([]byte, length)
r := marshalString(ret, []byte(ID))
r = marshalString(r, keyBytes)
return ret
}
func (key *ecdsaPublicKey) Verify(data []byte, sigBlob []byte) bool {
h := ecHash(key.Curve).New()
h.Write(data)
digest := h.Sum(nil)
// Per RFC 5656, section 3.1.2,
// The ecdsa_signature_blob value has the following specific encoding:
// mpint r
// mpint s
r, rest, ok := parseInt(sigBlob)
if !ok {
return false
}
s, rest, ok := parseInt(rest)
if !ok || len(rest) > 0 {
return false
}
return ecdsa.Verify((*ecdsa.PublicKey)(key), digest, r, s)
}
type ecdsaPrivateKey struct {
*ecdsa.PrivateKey
}
func (k *ecdsaPrivateKey) PublicKey() PublicKey {
return (*ecdsaPublicKey)(&k.PrivateKey.PublicKey)
}
func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
h := ecHash(k.PrivateKey.PublicKey.Curve).New()
h.Write(data)
digest := h.Sum(nil)
r, s, err := ecdsa.Sign(rand, k.PrivateKey, digest)
if err != nil {
return nil, err
}
sig := make([]byte, intLength(r)+intLength(s))
rest := marshalInt(sig, r)
marshalInt(rest, s)
return sig, nil
}
// NewPrivateKey takes a pointer to rsa, dsa or ecdsa PrivateKey
// returns a corresponding Signer instance. EC keys should use P256,
// P384 or P521.
func NewSignerFromKey(k interface{}) (Signer, error) {
var sshKey Signer
switch t := k.(type) {
case *rsa.PrivateKey:
sshKey = &rsaPrivateKey{t}
case *dsa.PrivateKey:
sshKey = &dsaPrivateKey{t}
case *ecdsa.PrivateKey:
if !supportedEllipticCurve(t.Curve) {
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
}
sshKey = &ecdsaPrivateKey{t}
default:
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
}
return sshKey, nil
}
// NewPublicKey takes a pointer to rsa, dsa or ecdsa PublicKey
// and returns a corresponding ssh PublicKey instance. EC keys should use P256, P384 or P521.
func NewPublicKey(k interface{}) (PublicKey, error) {
var sshKey PublicKey
switch t := k.(type) {
case *rsa.PublicKey:
sshKey = (*rsaPublicKey)(t)
case *ecdsa.PublicKey:
if !supportedEllipticCurve(t.Curve) {
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
}
sshKey = (*ecdsaPublicKey)(t)
case *dsa.PublicKey:
sshKey = (*dsaPublicKey)(t)
default:
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
}
return sshKey, nil
}
// ParsePublicKey parses a PEM encoded private key. It supports
// PKCS#1, RSA, DSA and ECDSA private keys.
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("ssh: no key found")
}
var rawkey interface{}
switch block.Type {
case "RSA PRIVATE KEY":
rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
rawkey = rsa
case "EC PRIVATE KEY":
ec, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
rawkey = ec
case "DSA PRIVATE KEY":
ec, err := parseDSAPrivate(block.Bytes)
if err != nil {
return nil, err
}
rawkey = ec
default:
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
}
return NewSignerFromKey(rawkey)
}
// parseDSAPrivate parses a DSA key in ASN.1 DER encoding, as
// documented in the OpenSSL DSA manpage.
// TODO(hanwen): move this in to crypto/x509 after the Go 1.2 freeze.
func parseDSAPrivate(p []byte) (*dsa.PrivateKey, error) {
k := struct {
Version int
P *big.Int
Q *big.Int
G *big.Int
Priv *big.Int
Pub *big.Int
}{}
rest, err := asn1.Unmarshal(p, &k)
if err != nil {
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
}
if len(rest) > 0 {
return nil, errors.New("ssh: garbage after DSA key")
}
return &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: k.P,
Q: k.Q,
G: k.G,
},
Y: k.Priv,
},
X: k.Pub,
}, nil
}

View file

@ -1,58 +0,0 @@
// Copyright 2012 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 ssh
// Message authentication support
import (
"crypto/hmac"
"crypto/sha1"
"hash"
)
type macMode struct {
keySize int
new func(key []byte) hash.Hash
}
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
// a given size.
type truncatingMAC struct {
length int
hmac hash.Hash
}
func (t truncatingMAC) Write(data []byte) (int, error) {
return t.hmac.Write(data)
}
func (t truncatingMAC) Sum(in []byte) []byte {
out := t.hmac.Sum(in)
return out[:len(in)+t.length]
}
func (t truncatingMAC) Reset() {
t.hmac.Reset()
}
func (t truncatingMAC) Size() int {
return t.length
}
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
// Specifies a default set of MAC algorithms and a preference order.
// This is based on RFC 4253, section 6.4, with the removal of the
// hmac-md5 variants as they have reached the end of their useful life.
var DefaultMACOrder = []string{"hmac-sha1", "hmac-sha1-96"}
var macModes = map[string]*macMode{
"hmac-sha1": {20, func(key []byte) hash.Hash {
return hmac.New(sha1.New, key)
}},
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
return truncatingMAC{12, hmac.New(sha1.New, key)}
}},
}

View file

@ -1,659 +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 ssh
import (
"bytes"
"encoding/binary"
"io"
"math/big"
"reflect"
)
// These are SSH message type numbers. They are scattered around several
// documents but many were taken from [SSH-PARAMETERS].
const (
msgDisconnect = 1
msgIgnore = 2
msgUnimplemented = 3
msgDebug = 4
msgServiceRequest = 5
msgServiceAccept = 6
msgKexInit = 20
msgNewKeys = 21
// Diffie-Helman
msgKexDHInit = 30
msgKexDHReply = 31
msgKexECDHInit = 30
msgKexECDHReply = 31
// Standard authentication messages
msgUserAuthRequest = 50
msgUserAuthFailure = 51
msgUserAuthSuccess = 52
msgUserAuthBanner = 53
msgUserAuthPubKeyOk = 60
// Method specific messages
msgUserAuthInfoRequest = 60
msgUserAuthInfoResponse = 61
msgGlobalRequest = 80
msgRequestSuccess = 81
msgRequestFailure = 82
// Channel manipulation
msgChannelOpen = 90
msgChannelOpenConfirm = 91
msgChannelOpenFailure = 92
msgChannelWindowAdjust = 93
msgChannelData = 94
msgChannelExtendedData = 95
msgChannelEOF = 96
msgChannelClose = 97
msgChannelRequest = 98
msgChannelSuccess = 99
msgChannelFailure = 100
)
// SSH messages:
//
// These structures mirror the wire format of the corresponding SSH messages.
// They are marshaled using reflection with the marshal and unmarshal functions
// in this file. The only wrinkle is that a final member of type []byte with a
// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
// See RFC 4253, section 11.1.
type disconnectMsg struct {
Reason uint32
Message string
Language string
}
// See RFC 4253, section 7.1.
type kexInitMsg struct {
Cookie [16]byte
KexAlgos []string
ServerHostKeyAlgos []string
CiphersClientServer []string
CiphersServerClient []string
MACsClientServer []string
MACsServerClient []string
CompressionClientServer []string
CompressionServerClient []string
LanguagesClientServer []string
LanguagesServerClient []string
FirstKexFollows bool
Reserved uint32
}
// See RFC 4253, section 8.
type kexDHInitMsg struct {
X *big.Int
}
type kexECDHInitMsg struct {
ClientPubKey []byte
}
type kexECDHReplyMsg struct {
HostKey []byte
EphemeralPubKey []byte
Signature []byte
}
type kexDHReplyMsg struct {
HostKey []byte
Y *big.Int
Signature []byte
}
// See RFC 4253, section 10.
type serviceRequestMsg struct {
Service string
}
// See RFC 4253, section 10.
type serviceAcceptMsg struct {
Service string
}
// See RFC 4252, section 5.
type userAuthRequestMsg struct {
User string
Service string
Method string
Payload []byte `ssh:"rest"`
}
// See RFC 4252, section 5.1
type userAuthFailureMsg struct {
Methods []string
PartialSuccess bool
}
// See RFC 4256, section 3.2
type userAuthInfoRequestMsg struct {
User string
Instruction string
DeprecatedLanguage string
NumPrompts uint32
Prompts []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
type channelOpenMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
type channelOpenConfirmMsg struct {
PeersId uint32
MyId uint32
MyWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
type channelOpenFailureMsg struct {
PeersId uint32
Reason RejectionReason
Message string
Language string
}
type channelRequestMsg struct {
PeersId uint32
Request string
WantReply bool
RequestSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.4.
type channelRequestSuccessMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.4.
type channelRequestFailureMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.3
type channelCloseMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.3
type channelEOFMsg struct {
PeersId uint32
}
// See RFC 4254, section 4
type globalRequestMsg struct {
Type string
WantReply bool
}
// See RFC 4254, section 4
type globalRequestSuccessMsg struct {
Data []byte `ssh:"rest"`
}
// See RFC 4254, section 4
type globalRequestFailureMsg struct {
Data []byte `ssh:"rest"`
}
// See RFC 4254, section 5.2
type windowAdjustMsg struct {
PeersId uint32
AdditionalBytes uint32
}
// See RFC 4252, section 7
type userAuthPubKeyOkMsg struct {
Algo string
PubKey string
}
// unmarshal parses the SSH wire data in packet into out using
// reflection. expectedType, if non-zero, is the SSH message type that
// the packet is expected to start with. unmarshal either returns nil
// on success, or a ParseError or UnexpectedMessageError on error.
func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
if len(packet) == 0 {
return ParseError{expectedType}
}
if expectedType > 0 {
if packet[0] != expectedType {
return UnexpectedMessageError{expectedType, packet[0]}
}
packet = packet[1:]
}
v := reflect.ValueOf(out).Elem()
structType := v.Type()
var ok bool
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
t := field.Type()
switch t.Kind() {
case reflect.Bool:
if len(packet) < 1 {
return ParseError{expectedType}
}
field.SetBool(packet[0] != 0)
packet = packet[1:]
case reflect.Array:
if t.Elem().Kind() != reflect.Uint8 {
panic("array of non-uint8")
}
if len(packet) < t.Len() {
return ParseError{expectedType}
}
for j, n := 0, t.Len(); j < n; j++ {
field.Index(j).Set(reflect.ValueOf(packet[j]))
}
packet = packet[t.Len():]
case reflect.Uint32:
var u32 uint32
if u32, packet, ok = parseUint32(packet); !ok {
return ParseError{expectedType}
}
field.SetUint(uint64(u32))
case reflect.String:
var s []byte
if s, packet, ok = parseString(packet); !ok {
return ParseError{expectedType}
}
field.SetString(string(s))
case reflect.Slice:
switch t.Elem().Kind() {
case reflect.Uint8:
if structType.Field(i).Tag.Get("ssh") == "rest" {
field.Set(reflect.ValueOf(packet))
packet = nil
} else {
var s []byte
if s, packet, ok = parseString(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(s))
}
case reflect.String:
var nl []string
if nl, packet, ok = parseNameList(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(nl))
default:
panic("slice of unknown type")
}
case reflect.Ptr:
if t == bigIntType {
var n *big.Int
if n, packet, ok = parseInt(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(n))
} else {
panic("pointer to unknown type")
}
default:
panic("unknown type")
}
}
if len(packet) != 0 {
return ParseError{expectedType}
}
return nil
}
// marshal serializes the message in msg. The given message type is
// prepended if it is non-zero.
func marshal(msgType uint8, msg interface{}) []byte {
out := make([]byte, 0, 64)
if msgType > 0 {
out = append(out, msgType)
}
v := reflect.ValueOf(msg)
for i, n := 0, v.NumField(); i < n; i++ {
field := v.Field(i)
switch t := field.Type(); t.Kind() {
case reflect.Bool:
var v uint8
if field.Bool() {
v = 1
}
out = append(out, v)
case reflect.Array:
if t.Elem().Kind() != reflect.Uint8 {
panic("array of non-uint8")
}
for j, l := 0, t.Len(); j < l; j++ {
out = append(out, uint8(field.Index(j).Uint()))
}
case reflect.Uint32:
out = appendU32(out, uint32(field.Uint()))
case reflect.String:
s := field.String()
out = appendInt(out, len(s))
out = append(out, s...)
case reflect.Slice:
switch t.Elem().Kind() {
case reflect.Uint8:
if v.Type().Field(i).Tag.Get("ssh") != "rest" {
out = appendInt(out, field.Len())
}
out = append(out, field.Bytes()...)
case reflect.String:
offset := len(out)
out = appendU32(out, 0)
if n := field.Len(); n > 0 {
for j := 0; j < n; j++ {
f := field.Index(j)
if j != 0 {
out = append(out, ',')
}
out = append(out, f.String()...)
}
// overwrite length value
binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4))
}
default:
panic("slice of unknown type")
}
case reflect.Ptr:
if t == bigIntType {
var n *big.Int
nValue := reflect.ValueOf(&n)
nValue.Elem().Set(field)
needed := intLength(n)
oldLength := len(out)
if cap(out)-len(out) < needed {
newOut := make([]byte, len(out), 2*(len(out)+needed))
copy(newOut, out)
out = newOut
}
out = out[:oldLength+needed]
marshalInt(out[oldLength:], n)
} else {
panic("pointer to unknown type")
}
}
}
return out
}
var bigOne = big.NewInt(1)
func parseString(in []byte) (out, rest []byte, ok bool) {
if len(in) < 4 {
return
}
length := binary.BigEndian.Uint32(in)
if uint32(len(in)) < 4+length {
return
}
out = in[4 : 4+length]
rest = in[4+length:]
ok = true
return
}
var (
comma = []byte{','}
emptyNameList = []string{}
)
func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
contents, rest, ok := parseString(in)
if !ok {
return
}
if len(contents) == 0 {
out = emptyNameList
return
}
parts := bytes.Split(contents, comma)
out = make([]string, len(parts))
for i, part := range parts {
out[i] = string(part)
}
return
}
func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
contents, rest, ok := parseString(in)
if !ok {
return
}
out = new(big.Int)
if len(contents) > 0 && contents[0]&0x80 == 0x80 {
// This is a negative number
notBytes := make([]byte, len(contents))
for i := range notBytes {
notBytes[i] = ^contents[i]
}
out.SetBytes(notBytes)
out.Add(out, bigOne)
out.Neg(out)
} else {
// Positive number
out.SetBytes(contents)
}
ok = true
return
}
func parseUint32(in []byte) (uint32, []byte, bool) {
if len(in) < 4 {
return 0, nil, false
}
return binary.BigEndian.Uint32(in), in[4:], true
}
func parseUint64(in []byte) (uint64, []byte, bool) {
if len(in) < 8 {
return 0, nil, false
}
return binary.BigEndian.Uint64(in), in[8:], true
}
func nameListLength(namelist []string) int {
length := 4 /* uint32 length prefix */
for i, name := range namelist {
if i != 0 {
length++ /* comma */
}
length += len(name)
}
return length
}
func intLength(n *big.Int) int {
length := 4 /* length bytes */
if n.Sign() < 0 {
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bitLen := nMinus1.BitLen()
if bitLen%8 == 0 {
// The number will need 0xff padding
length++
}
length += (bitLen + 7) / 8
} else if n.Sign() == 0 {
// A zero is the zero length string
} else {
bitLen := n.BitLen()
if bitLen%8 == 0 {
// The number will need 0x00 padding
length++
}
length += (bitLen + 7) / 8
}
return length
}
func marshalUint32(to []byte, n uint32) []byte {
binary.BigEndian.PutUint32(to, n)
return to[4:]
}
func marshalUint64(to []byte, n uint64) []byte {
binary.BigEndian.PutUint64(to, n)
return to[8:]
}
func marshalInt(to []byte, n *big.Int) []byte {
lengthBytes := to
to = to[4:]
length := 0
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement
// form. So we'll subtract 1 and invert. If the
// most-significant-bit isn't set then we'll need to pad the
// beginning with 0xff in order to keep the number negative.
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bytes := nMinus1.Bytes()
for i := range bytes {
bytes[i] ^= 0xff
}
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
to[0] = 0xff
to = to[1:]
length++
}
nBytes := copy(to, bytes)
to = to[nBytes:]
length += nBytes
} else if n.Sign() == 0 {
// A zero is the zero length string
} else {
bytes := n.Bytes()
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
// We'll have to pad this with a 0x00 in order to
// stop it looking like a negative number.
to[0] = 0
to = to[1:]
length++
}
nBytes := copy(to, bytes)
to = to[nBytes:]
length += nBytes
}
lengthBytes[0] = byte(length >> 24)
lengthBytes[1] = byte(length >> 16)
lengthBytes[2] = byte(length >> 8)
lengthBytes[3] = byte(length)
return to
}
func writeInt(w io.Writer, n *big.Int) {
length := intLength(n)
buf := make([]byte, length)
marshalInt(buf, n)
w.Write(buf)
}
func writeString(w io.Writer, s []byte) {
var lengthBytes [4]byte
lengthBytes[0] = byte(len(s) >> 24)
lengthBytes[1] = byte(len(s) >> 16)
lengthBytes[2] = byte(len(s) >> 8)
lengthBytes[3] = byte(len(s))
w.Write(lengthBytes[:])
w.Write(s)
}
func stringLength(n int) int {
return 4 + n
}
func marshalString(to []byte, s []byte) []byte {
to[0] = byte(len(s) >> 24)
to[1] = byte(len(s) >> 16)
to[2] = byte(len(s) >> 8)
to[3] = byte(len(s))
to = to[4:]
copy(to, s)
return to[len(s):]
}
var bigIntType = reflect.TypeOf((*big.Int)(nil))
// Decode a packet into its corresponding message.
func decode(packet []byte) (interface{}, error) {
var msg interface{}
switch packet[0] {
case msgDisconnect:
msg = new(disconnectMsg)
case msgServiceRequest:
msg = new(serviceRequestMsg)
case msgServiceAccept:
msg = new(serviceAcceptMsg)
case msgKexInit:
msg = new(kexInitMsg)
case msgKexDHInit:
msg = new(kexDHInitMsg)
case msgKexDHReply:
msg = new(kexDHReplyMsg)
case msgUserAuthRequest:
msg = new(userAuthRequestMsg)
case msgUserAuthFailure:
msg = new(userAuthFailureMsg)
case msgUserAuthPubKeyOk:
msg = new(userAuthPubKeyOkMsg)
case msgGlobalRequest:
msg = new(globalRequestMsg)
case msgRequestSuccess:
msg = new(globalRequestSuccessMsg)
case msgRequestFailure:
msg = new(globalRequestFailureMsg)
case msgChannelOpen:
msg = new(channelOpenMsg)
case msgChannelOpenConfirm:
msg = new(channelOpenConfirmMsg)
case msgChannelOpenFailure:
msg = new(channelOpenFailureMsg)
case msgChannelWindowAdjust:
msg = new(windowAdjustMsg)
case msgChannelEOF:
msg = new(channelEOFMsg)
case msgChannelClose:
msg = new(channelCloseMsg)
case msgChannelRequest:
msg = new(channelRequestMsg)
case msgChannelSuccess:
msg = new(channelRequestSuccessMsg)
case msgChannelFailure:
msg = new(channelRequestFailureMsg)
default:
return nil, UnexpectedMessageError{0, packet[0]}
}
if err := unmarshal(msg, packet, packet[0]); err != nil {
return nil, err
}
return msg, nil
}

View file

@ -1,692 +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 ssh
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"sync"
_ "crypto/sha1"
)
type ServerConfig struct {
hostKeys []Signer
// Rand provides the source of entropy for key exchange. If Rand is
// nil, the cryptographic random reader in package crypto/rand will
// be used.
Rand io.Reader
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
NoClientAuth bool
// PasswordCallback, if non-nil, is called when a user attempts to
// authenticate using a password. It may be called concurrently from
// several goroutines.
PasswordCallback func(conn *ServerConn, user, password string) bool
// PublicKeyCallback, if non-nil, is called when a client attempts public
// key authentication. It must return true if the given public key is
// valid for the given user.
PublicKeyCallback func(conn *ServerConn, user, algo string, pubkey []byte) bool
// KeyboardInteractiveCallback, if non-nil, is called when
// keyboard-interactive authentication is selected (RFC
// 4256). The client object's Challenge function should be
// used to query the user. The callback may offer multiple
// Challenge rounds. To avoid information leaks, the client
// should be presented a challenge even if the user is
// unknown.
KeyboardInteractiveCallback func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool
// Cryptographic-related configuration.
Crypto CryptoConfig
}
func (c *ServerConfig) rand() io.Reader {
if c.Rand == nil {
return rand.Reader
}
return c.Rand
}
// AddHostKey adds a private key as a host key. If an existing host
// key exists with the same algorithm, it is overwritten.
func (s *ServerConfig) AddHostKey(key Signer) {
for i, k := range s.hostKeys {
if k.PublicKey().PublicKeyAlgo() == key.PublicKey().PublicKeyAlgo() {
s.hostKeys[i] = key
return
}
}
s.hostKeys = append(s.hostKeys, key)
}
// SetRSAPrivateKey sets the private key for a Server. A Server must have a
// private key configured in order to accept connections. The private key must
// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
// typically contains such a key.
func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
priv, err := ParsePrivateKey(pemBytes)
if err != nil {
return err
}
s.AddHostKey(priv)
return nil
}
// cachedPubKey contains the results of querying whether a public key is
// acceptable for a user. The cache only applies to a single ServerConn.
type cachedPubKey struct {
user, algo string
pubKey []byte
result bool
}
const maxCachedPubKeys = 16
// A ServerConn represents an incoming connection.
type ServerConn struct {
transport *transport
config *ServerConfig
channels map[uint32]*serverChan
nextChanId uint32
// lock protects err and channels.
lock sync.Mutex
err error
// cachedPubKeys contains the cache results of tests for public keys.
// Since SSH clients will query whether a public key is acceptable
// before attempting to authenticate with it, we end up with duplicate
// queries for public key validity.
cachedPubKeys []cachedPubKey
// User holds the successfully authenticated user name.
// It is empty if no authentication is used. It is populated before
// any authentication callback is called and not assigned to after that.
User string
// ClientVersion is the client's version, populated after
// Handshake is called. It should not be modified.
ClientVersion []byte
// Our version.
serverVersion []byte
}
// Server returns a new SSH server connection
// using c as the underlying transport.
func Server(c net.Conn, config *ServerConfig) *ServerConn {
return &ServerConn{
transport: newTransport(c, config.rand(), false /* not client */),
channels: make(map[uint32]*serverChan),
config: config,
}
}
// signAndMarshal signs the data with the appropriate algorithm,
// and serializes the result in SSH wire format.
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
sig, err := k.Sign(rand, data)
if err != nil {
return nil, err
}
return serializeSignature(k.PublicKey().PrivateKeyAlgo(), sig), nil
}
// Close closes the connection.
func (s *ServerConn) Close() error { return s.transport.Close() }
// LocalAddr returns the local network address.
func (c *ServerConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
// RemoteAddr returns the remote network address.
func (c *ServerConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
// Handshake performs an SSH transport and client authentication on the given ServerConn.
func (s *ServerConn) Handshake() error {
var err error
s.serverVersion = []byte(packageVersion)
s.ClientVersion, err = exchangeVersions(s.transport.Conn, s.serverVersion)
if err != nil {
return err
}
if err := s.clientInitHandshake(nil, nil); err != nil {
return err
}
var packet []byte
if packet, err = s.transport.readPacket(); err != nil {
return err
}
var serviceRequest serviceRequestMsg
if err := unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil {
return err
}
if serviceRequest.Service != serviceUserAuth {
return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
}
serviceAccept := serviceAcceptMsg{
Service: serviceUserAuth,
}
if err := s.transport.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
return err
}
if err := s.authenticate(); err != nil {
return err
}
return err
}
func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexInitPacket []byte) (err error) {
serverKexInit := kexInitMsg{
KexAlgos: s.config.Crypto.kexes(),
CiphersClientServer: s.config.Crypto.ciphers(),
CiphersServerClient: s.config.Crypto.ciphers(),
MACsClientServer: s.config.Crypto.macs(),
MACsServerClient: s.config.Crypto.macs(),
CompressionClientServer: supportedCompressions,
CompressionServerClient: supportedCompressions,
}
for _, k := range s.config.hostKeys {
serverKexInit.ServerHostKeyAlgos = append(
serverKexInit.ServerHostKeyAlgos, k.PublicKey().PublicKeyAlgo())
}
serverKexInitPacket := marshal(msgKexInit, serverKexInit)
if err = s.transport.writePacket(serverKexInitPacket); err != nil {
return
}
if clientKexInitPacket == nil {
clientKexInit = new(kexInitMsg)
if clientKexInitPacket, err = s.transport.readPacket(); err != nil {
return
}
if err = unmarshal(clientKexInit, clientKexInitPacket, msgKexInit); err != nil {
return
}
}
algs := findAgreedAlgorithms(clientKexInit, &serverKexInit)
if algs == nil {
return errors.New("ssh: no common algorithms")
}
if clientKexInit.FirstKexFollows && algs.kex != clientKexInit.KexAlgos[0] {
// The client sent a Kex message for the wrong algorithm,
// which we have to ignore.
if _, err = s.transport.readPacket(); err != nil {
return
}
}
var hostKey Signer
for _, k := range s.config.hostKeys {
if algs.hostKey == k.PublicKey().PublicKeyAlgo() {
hostKey = k
}
}
kex, ok := kexAlgoMap[algs.kex]
if !ok {
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
}
magics := handshakeMagics{
serverVersion: s.serverVersion,
clientVersion: s.ClientVersion,
serverKexInit: marshal(msgKexInit, serverKexInit),
clientKexInit: clientKexInitPacket,
}
result, err := kex.Server(s.transport, s.config.rand(), &magics, hostKey)
if err != nil {
return err
}
if err = s.transport.prepareKeyChange(algs, result); err != nil {
return err
}
if err = s.transport.writePacket([]byte{msgNewKeys}); err != nil {
return
}
if packet, err := s.transport.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
return
}
func isAcceptableAlgo(algo string) bool {
switch algo {
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
return true
}
return false
}
// testPubKey returns true if the given public key is acceptable for the user.
func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) {
return false
}
for _, c := range s.cachedPubKeys {
if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) {
return c.result
}
}
result := s.config.PublicKeyCallback(s, user, algo, pubKey)
if len(s.cachedPubKeys) < maxCachedPubKeys {
c := cachedPubKey{
user: user,
algo: algo,
pubKey: make([]byte, len(pubKey)),
result: result,
}
copy(c.pubKey, pubKey)
s.cachedPubKeys = append(s.cachedPubKeys, c)
}
return result
}
func (s *ServerConn) authenticate() error {
var userAuthReq userAuthRequestMsg
var err error
var packet []byte
userAuthLoop:
for {
if packet, err = s.transport.readPacket(); err != nil {
return err
}
if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil {
return err
}
if userAuthReq.Service != serviceSSH {
return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
}
switch userAuthReq.Method {
case "none":
if s.config.NoClientAuth {
break userAuthLoop
}
case "password":
if s.config.PasswordCallback == nil {
break
}
payload := userAuthReq.Payload
if len(payload) < 1 || payload[0] != 0 {
return ParseError{msgUserAuthRequest}
}
payload = payload[1:]
password, payload, ok := parseString(payload)
if !ok || len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
s.User = userAuthReq.User
if s.config.PasswordCallback(s, userAuthReq.User, string(password)) {
break userAuthLoop
}
case "keyboard-interactive":
if s.config.KeyboardInteractiveCallback == nil {
break
}
s.User = userAuthReq.User
if s.config.KeyboardInteractiveCallback(s, s.User, &sshClientKeyboardInteractive{s}) {
break userAuthLoop
}
case "publickey":
if s.config.PublicKeyCallback == nil {
break
}
payload := userAuthReq.Payload
if len(payload) < 1 {
return ParseError{msgUserAuthRequest}
}
isQuery := payload[0] == 0
payload = payload[1:]
algoBytes, payload, ok := parseString(payload)
if !ok {
return ParseError{msgUserAuthRequest}
}
algo := string(algoBytes)
pubKey, payload, ok := parseString(payload)
if !ok {
return ParseError{msgUserAuthRequest}
}
if isQuery {
// The client can query if the given public key
// would be okay.
if len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
if s.testPubKey(userAuthReq.User, algo, pubKey) {
okMsg := userAuthPubKeyOkMsg{
Algo: algo,
PubKey: string(pubKey),
}
if err = s.transport.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil {
return err
}
continue userAuthLoop
}
} else {
sig, payload, ok := parseSignature(payload)
if !ok || len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
// Ensure the public key algo and signature algo
// are supported. Compare the private key
// algorithm name that corresponds to algo with
// sig.Format. This is usually the same, but
// for certs, the names differ.
if !isAcceptableAlgo(algo) || !isAcceptableAlgo(sig.Format) || pubAlgoToPrivAlgo(algo) != sig.Format {
break
}
signedData := buildDataSignedForAuth(s.transport.sessionID, userAuthReq, algoBytes, pubKey)
key, _, ok := ParsePublicKey(pubKey)
if !ok {
return ParseError{msgUserAuthRequest}
}
if !key.Verify(signedData, sig.Blob) {
return ParseError{msgUserAuthRequest}
}
// TODO(jmpittman): Implement full validation for certificates.
s.User = userAuthReq.User
if s.testPubKey(userAuthReq.User, algo, pubKey) {
break userAuthLoop
}
}
}
var failureMsg userAuthFailureMsg
if s.config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password")
}
if s.config.PublicKeyCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "publickey")
}
if s.config.KeyboardInteractiveCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
}
if len(failureMsg.Methods) == 0 {
return errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
}
if err = s.transport.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil {
return err
}
}
packet = []byte{msgUserAuthSuccess}
if err = s.transport.writePacket(packet); err != nil {
return err
}
return nil
}
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
// asking the client on the other side of a ServerConn.
type sshClientKeyboardInteractive struct {
*ServerConn
}
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
if len(questions) != len(echos) {
return nil, errors.New("ssh: echos and questions must have equal length")
}
var prompts []byte
for i := range questions {
prompts = appendString(prompts, questions[i])
prompts = appendBool(prompts, echos[i])
}
if err := c.transport.writePacket(marshal(msgUserAuthInfoRequest, userAuthInfoRequestMsg{
Instruction: instruction,
NumPrompts: uint32(len(questions)),
Prompts: prompts,
})); err != nil {
return nil, err
}
packet, err := c.transport.readPacket()
if err != nil {
return nil, err
}
if packet[0] != msgUserAuthInfoResponse {
return nil, UnexpectedMessageError{msgUserAuthInfoResponse, packet[0]}
}
packet = packet[1:]
n, packet, ok := parseUint32(packet)
if !ok || int(n) != len(questions) {
return nil, &ParseError{msgUserAuthInfoResponse}
}
for i := uint32(0); i < n; i++ {
ans, rest, ok := parseString(packet)
if !ok {
return nil, &ParseError{msgUserAuthInfoResponse}
}
answers = append(answers, string(ans))
packet = rest
}
if len(packet) != 0 {
return nil, errors.New("ssh: junk at end of message")
}
return answers, nil
}
const defaultWindowSize = 32768
// Accept reads and processes messages on a ServerConn. It must be called
// in order to demultiplex messages to any resulting Channels.
func (s *ServerConn) Accept() (Channel, error) {
// TODO(dfc) s.lock is not held here so visibility of s.err is not guaranteed.
if s.err != nil {
return nil, s.err
}
for {
packet, err := s.transport.readPacket()
if err != nil {
s.lock.Lock()
s.err = err
s.lock.Unlock()
// TODO(dfc) s.lock protects s.channels but isn't being held here.
for _, c := range s.channels {
c.setDead()
c.handleData(nil)
}
return nil, err
}
switch packet[0] {
case msgChannelData:
if len(packet) < 9 {
// malformed data packet
return nil, ParseError{msgChannelData}
}
remoteId := binary.BigEndian.Uint32(packet[1:5])
s.lock.Lock()
c, ok := s.channels[remoteId]
if !ok {
s.lock.Unlock()
continue
}
if length := binary.BigEndian.Uint32(packet[5:9]); length > 0 {
packet = packet[9:]
c.handleData(packet[:length])
}
s.lock.Unlock()
default:
decoded, err := decode(packet)
if err != nil {
return nil, err
}
switch msg := decoded.(type) {
case *channelOpenMsg:
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
return nil, errors.New("ssh: invalid MaxPacketSize from peer")
}
c := &serverChan{
channel: channel{
packetConn: s.transport,
remoteId: msg.PeersId,
remoteWin: window{Cond: newCond()},
maxPacket: msg.MaxPacketSize,
},
chanType: msg.ChanType,
extraData: msg.TypeSpecificData,
myWindow: defaultWindowSize,
serverConn: s,
cond: newCond(),
pendingData: make([]byte, defaultWindowSize),
}
c.remoteWin.add(msg.PeersWindow)
s.lock.Lock()
c.localId = s.nextChanId
s.nextChanId++
s.channels[c.localId] = c
s.lock.Unlock()
return c, nil
case *channelRequestMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *windowAdjustMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *channelEOFMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *channelCloseMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *globalRequestMsg:
if msg.WantReply {
if err := s.transport.writePacket([]byte{msgRequestFailure}); err != nil {
return nil, err
}
}
case *kexInitMsg:
s.lock.Lock()
if err := s.clientInitHandshake(msg, packet); err != nil {
s.lock.Unlock()
return nil, err
}
s.lock.Unlock()
case *disconnectMsg:
return nil, io.EOF
default:
// Unknown message. Ignore.
}
}
}
panic("unreachable")
}
// A Listener implements a network listener (net.Listener) for SSH connections.
type Listener struct {
listener net.Listener
config *ServerConfig
}
// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
return l.listener.Addr()
}
// Close closes the listener.
func (l *Listener) Close() error {
return l.listener.Close()
}
// Accept waits for and returns the next incoming SSH connection.
// The receiver should call Handshake() in another goroutine
// to avoid blocking the accepter.
func (l *Listener) Accept() (*ServerConn, error) {
c, err := l.listener.Accept()
if err != nil {
return nil, err
}
return Server(c, l.config), nil
}
// Listen creates an SSH listener accepting connections on
// the given network address using net.Listen.
func Listen(network, addr string, config *ServerConfig) (*Listener, error) {
l, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
return &Listener{
l,
config,
}, nil
}

View file

@ -1,81 +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 ssh
// A Terminal is capable of parsing and generating virtual terminal
// data from an SSH client.
type Terminal interface {
ReadLine() (line string, err error)
SetSize(x, y int)
Write([]byte) (int, error)
}
// ServerTerminal contains the state for running a terminal that is capable of
// reading lines of input.
type ServerTerminal struct {
Term Terminal
Channel Channel
}
// parsePtyRequest parses the payload of the pty-req message and extracts the
// dimensions of the terminal. See RFC 4254, section 6.2.
func parsePtyRequest(s []byte) (width, height int, ok bool) {
_, s, ok = parseString(s)
if !ok {
return
}
width32, s, ok := parseUint32(s)
if !ok {
return
}
height32, _, ok := parseUint32(s)
width = int(width32)
height = int(height32)
if width < 1 {
ok = false
}
if height < 1 {
ok = false
}
return
}
func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
return ss.Term.Write(buf)
}
// ReadLine returns a line of input from the terminal.
func (ss *ServerTerminal) ReadLine() (line string, err error) {
for {
if line, err = ss.Term.ReadLine(); err == nil {
return
}
req, ok := err.(ChannelRequest)
if !ok {
return
}
ok = false
switch req.Request {
case "pty-req":
var width, height int
width, height, ok = parsePtyRequest(req.Payload)
ss.Term.SetSize(width, height)
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any commands, only the default shell.
ok = false
}
case "env":
ok = true
}
if req.WantReply {
ss.Channel.AckRequest(ok)
}
}
panic("unreachable")
}

View file

@ -1,626 +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 ssh
// Session implements an interactive session described in
// "RFC 4254, section 6".
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
)
type Signal string
// POSIX signals as listed in RFC 4254 Section 6.10.
const (
SIGABRT Signal = "ABRT"
SIGALRM Signal = "ALRM"
SIGFPE Signal = "FPE"
SIGHUP Signal = "HUP"
SIGILL Signal = "ILL"
SIGINT Signal = "INT"
SIGKILL Signal = "KILL"
SIGPIPE Signal = "PIPE"
SIGQUIT Signal = "QUIT"
SIGSEGV Signal = "SEGV"
SIGTERM Signal = "TERM"
SIGUSR1 Signal = "USR1"
SIGUSR2 Signal = "USR2"
)
var signals = map[Signal]int{
SIGABRT: 6,
SIGALRM: 14,
SIGFPE: 8,
SIGHUP: 1,
SIGILL: 4,
SIGINT: 2,
SIGKILL: 9,
SIGPIPE: 13,
SIGQUIT: 3,
SIGSEGV: 11,
SIGTERM: 15,
}
type TerminalModes map[uint8]uint32
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
const (
tty_OP_END = 0
VINTR = 1
VQUIT = 2
VERASE = 3
VKILL = 4
VEOF = 5
VEOL = 6
VEOL2 = 7
VSTART = 8
VSTOP = 9
VSUSP = 10
VDSUSP = 11
VREPRINT = 12
VWERASE = 13
VLNEXT = 14
VFLUSH = 15
VSWTCH = 16
VSTATUS = 17
VDISCARD = 18
IGNPAR = 30
PARMRK = 31
INPCK = 32
ISTRIP = 33
INLCR = 34
IGNCR = 35
ICRNL = 36
IUCLC = 37
IXON = 38
IXANY = 39
IXOFF = 40
IMAXBEL = 41
ISIG = 50
ICANON = 51
XCASE = 52
ECHO = 53
ECHOE = 54
ECHOK = 55
ECHONL = 56
NOFLSH = 57
TOSTOP = 58
IEXTEN = 59
ECHOCTL = 60
ECHOKE = 61
PENDIN = 62
OPOST = 70
OLCUC = 71
ONLCR = 72
OCRNL = 73
ONOCR = 74
ONLRET = 75
CS7 = 90
CS8 = 91
PARENB = 92
PARODD = 93
TTY_OP_ISPEED = 128
TTY_OP_OSPEED = 129
)
// A Session represents a connection to a remote command or shell.
type Session struct {
// Stdin specifies the remote process's standard input.
// If Stdin is nil, the remote process reads from an empty
// bytes.Buffer.
Stdin io.Reader
// Stdout and Stderr specify the remote process's standard
// output and error.
//
// If either is nil, Run connects the corresponding file
// descriptor to an instance of ioutil.Discard. There is a
// fixed amount of buffering that is shared for the two streams.
// If either blocks it may eventually cause the remote
// command to block.
Stdout io.Writer
Stderr io.Writer
*clientChan // the channel backing this session
started bool // true once Start, Run or Shell is invoked.
copyFuncs []func() error
errors chan error // one send per copyFunc
// true if pipe method is active
stdinpipe, stdoutpipe, stderrpipe bool
}
// RFC 4254 Section 6.4.
type setenvRequest struct {
PeersId uint32
Request string
WantReply bool
Name string
Value string
}
// RFC 4254 Section 6.5.
type subsystemRequestMsg struct {
PeersId uint32
Request string
WantReply bool
Subsystem string
}
// Setenv sets an environment variable that will be applied to any
// command executed by Shell or Run.
func (s *Session) Setenv(name, value string) error {
req := setenvRequest{
PeersId: s.remoteId,
Request: "env",
WantReply: true,
Name: name,
Value: value,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
return s.waitForResponse()
}
// RFC 4254 Section 6.2.
type ptyRequestMsg struct {
PeersId uint32
Request string
WantReply bool
Term string
Columns uint32
Rows uint32
Width uint32
Height uint32
Modelist string
}
// RequestPty requests the association of a pty with the session on the remote host.
func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
var tm []byte
for k, v := range termmodes {
tm = append(tm, k)
tm = appendU32(tm, v)
}
tm = append(tm, tty_OP_END)
req := ptyRequestMsg{
PeersId: s.remoteId,
Request: "pty-req",
WantReply: true,
Term: term,
Columns: uint32(w),
Rows: uint32(h),
Width: uint32(w * 8),
Height: uint32(h * 8),
Modelist: string(tm),
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
return s.waitForResponse()
}
// RequestSubsystem requests the association of a subsystem with the session on the remote host.
// A subsystem is a predefined command that runs in the background when the ssh session is initiated
func (s *Session) RequestSubsystem(subsystem string) error {
req := subsystemRequestMsg{
PeersId: s.remoteId,
Request: "subsystem",
WantReply: true,
Subsystem: subsystem,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
return s.waitForResponse()
}
// RFC 4254 Section 6.9.
type signalMsg struct {
PeersId uint32
Request string
WantReply bool
Signal string
}
// Signal sends the given signal to the remote process.
// sig is one of the SIG* constants.
func (s *Session) Signal(sig Signal) error {
req := signalMsg{
PeersId: s.remoteId,
Request: "signal",
WantReply: false,
Signal: string(sig),
}
return s.writePacket(marshal(msgChannelRequest, req))
}
// RFC 4254 Section 6.5.
type execMsg struct {
PeersId uint32
Request string
WantReply bool
Command string
}
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
func (s *Session) Start(cmd string) error {
if s.started {
return errors.New("ssh: session already started")
}
req := execMsg{
PeersId: s.remoteId,
Request: "exec",
WantReply: true,
Command: cmd,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
if err := s.waitForResponse(); err != nil {
return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
}
return s.start()
}
// Run runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start, Shell, Output,
// or CombinedOutput.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (s *Session) Run(cmd string) error {
err := s.Start(cmd)
if err != nil {
return err
}
return s.Wait()
}
// Output runs cmd on the remote host and returns its standard output.
func (s *Session) Output(cmd string) ([]byte, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
var b bytes.Buffer
s.Stdout = &b
err := s.Run(cmd)
return b.Bytes(), err
}
type singleWriter struct {
b bytes.Buffer
mu sync.Mutex
}
func (w *singleWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.b.Write(p)
}
// CombinedOutput runs cmd on the remote host and returns its combined
// standard output and standard error.
func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
if s.Stderr != nil {
return nil, errors.New("ssh: Stderr already set")
}
var b singleWriter
s.Stdout = &b
s.Stderr = &b
err := s.Run(cmd)
return b.b.Bytes(), err
}
// Shell starts a login shell on the remote host. A Session only
// accepts one call to Run, Start, Shell, Output, or CombinedOutput.
func (s *Session) Shell() error {
if s.started {
return errors.New("ssh: session already started")
}
req := channelRequestMsg{
PeersId: s.remoteId,
Request: "shell",
WantReply: true,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
if err := s.waitForResponse(); err != nil {
return fmt.Errorf("ssh: could not execute shell: %v", err)
}
return s.start()
}
func (s *Session) waitForResponse() error {
msg := <-s.msg
switch msg.(type) {
case *channelRequestSuccessMsg:
return nil
case *channelRequestFailureMsg:
return errors.New("ssh: request failed")
}
return fmt.Errorf("ssh: unknown packet %T received: %v", msg, msg)
}
func (s *Session) start() error {
s.started = true
type F func(*Session)
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
setupFd(s)
}
s.errors = make(chan error, len(s.copyFuncs))
for _, fn := range s.copyFuncs {
go func(fn func() error) {
s.errors <- fn()
}(fn)
}
return nil
}
// Wait waits for the remote command to exit.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (s *Session) Wait() error {
if !s.started {
return errors.New("ssh: session not started")
}
waitErr := s.wait()
var copyError error
for _ = range s.copyFuncs {
if err := <-s.errors; err != nil && copyError == nil {
copyError = err
}
}
if waitErr != nil {
return waitErr
}
return copyError
}
func (s *Session) wait() error {
wm := Waitmsg{status: -1}
// Wait for msg channel to be closed before returning.
for msg := range s.msg {
switch msg := msg.(type) {
case *channelRequestMsg:
switch msg.Request {
case "exit-status":
d := msg.RequestSpecificData
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
case "exit-signal":
signal, rest, ok := parseString(msg.RequestSpecificData)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.signal = safeString(string(signal))
// skip coreDumped bool
if len(rest) == 0 {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
rest = rest[1:]
errmsg, rest, ok := parseString(rest)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.msg = safeString(string(errmsg))
lang, _, ok := parseString(rest)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.lang = safeString(string(lang))
default:
// This handles keepalives and matches
// OpenSSH's behaviour.
if msg.WantReply {
s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{
PeersId: s.remoteId,
}))
}
}
default:
return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
}
}
if wm.status == 0 {
return nil
}
if wm.status == -1 {
// exit-status was never sent from server
if wm.signal == "" {
return errors.New("wait: remote command exited without exit status or exit signal")
}
wm.status = 128
if _, ok := signals[Signal(wm.signal)]; ok {
wm.status += signals[Signal(wm.signal)]
}
}
return &ExitError{wm}
}
func (s *Session) stdin() {
if s.stdinpipe {
return
}
if s.Stdin == nil {
s.Stdin = new(bytes.Buffer)
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.clientChan.stdin, s.Stdin)
if err1 := s.clientChan.stdin.Close(); err == nil && err1 != io.EOF {
err = err1
}
return err
})
}
func (s *Session) stdout() {
if s.stdoutpipe {
return
}
if s.Stdout == nil {
s.Stdout = ioutil.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stdout, s.clientChan.stdout)
return err
})
}
func (s *Session) stderr() {
if s.stderrpipe {
return
}
if s.Stderr == nil {
s.Stderr = ioutil.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stderr, s.clientChan.stderr)
return err
})
}
// StdinPipe returns a pipe that will be connected to the
// remote command's standard input when the command starts.
func (s *Session) StdinPipe() (io.WriteCloser, error) {
if s.Stdin != nil {
return nil, errors.New("ssh: Stdin already set")
}
if s.started {
return nil, errors.New("ssh: StdinPipe after process started")
}
s.stdinpipe = true
return s.clientChan.stdin, nil
}
// StdoutPipe returns a pipe that will be connected to the
// remote command's standard output when the command starts.
// There is a fixed amount of buffering that is shared between
// stdout and stderr streams. If the StdoutPipe reader is
// not serviced fast enough it may eventually cause the
// remote command to block.
func (s *Session) StdoutPipe() (io.Reader, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
if s.started {
return nil, errors.New("ssh: StdoutPipe after process started")
}
s.stdoutpipe = true
return s.clientChan.stdout, nil
}
// StderrPipe returns a pipe that will be connected to the
// remote command's standard error when the command starts.
// There is a fixed amount of buffering that is shared between
// stdout and stderr streams. If the StderrPipe reader is
// not serviced fast enough it may eventually cause the
// remote command to block.
func (s *Session) StderrPipe() (io.Reader, error) {
if s.Stderr != nil {
return nil, errors.New("ssh: Stderr already set")
}
if s.started {
return nil, errors.New("ssh: StderrPipe after process started")
}
s.stderrpipe = true
return s.clientChan.stderr, nil
}
// NewSession returns a new interactive session on the remote host.
func (c *ClientConn) NewSession() (*Session, error) {
ch := c.newChan(c.transport)
if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenMsg{
ChanType: "session",
PeersId: ch.localId,
PeersWindow: channelWindowSize,
MaxPacketSize: channelMaxPacketSize,
})); err != nil {
c.chanList.remove(ch.localId)
return nil, err
}
if err := ch.waitForChannelOpenResponse(); err != nil {
c.chanList.remove(ch.localId)
return nil, fmt.Errorf("ssh: unable to open session: %v", err)
}
return &Session{
clientChan: ch,
}, nil
}
// An ExitError reports unsuccessful completion of a remote command.
type ExitError struct {
Waitmsg
}
func (e *ExitError) Error() string {
return e.Waitmsg.String()
}
// Waitmsg stores the information about an exited remote command
// as reported by Wait.
type Waitmsg struct {
status int
signal string
msg string
lang string
}
// ExitStatus returns the exit status of the remote command.
func (w Waitmsg) ExitStatus() int {
return w.status
}
// Signal returns the exit signal of the remote command if
// it was terminated violently.
func (w Waitmsg) Signal() string {
return w.signal
}
// Msg returns the exit message given by the remote command
func (w Waitmsg) Msg() string {
return w.msg
}
// Lang returns the language tag. See RFC 3066
func (w Waitmsg) Lang() string {
return w.lang
}
func (w Waitmsg) String() string {
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
}

View file

@ -1,367 +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 ssh
import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
)
// Listen requests the remote peer open a listening socket
// on addr. Incoming connections will be available by calling
// Accept on the returned net.Listener.
func (c *ClientConn) Listen(n, addr string) (net.Listener, error) {
laddr, err := net.ResolveTCPAddr(n, addr)
if err != nil {
return nil, err
}
return c.ListenTCP(laddr)
}
// Automatic port allocation is broken with OpenSSH before 6.0. See
// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In
// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,
// rather than the actual port number. This means you can never open
// two different listeners with auto allocated ports. We work around
// this by trying explicit ports until we succeed.
const openSSHPrefix = "OpenSSH_"
var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))
// isBrokenOpenSSHVersion returns true if the given version string
// specifies a version of OpenSSH that is known to have a bug in port
// forwarding.
func isBrokenOpenSSHVersion(versionStr string) bool {
i := strings.Index(versionStr, openSSHPrefix)
if i < 0 {
return false
}
i += len(openSSHPrefix)
j := i
for ; j < len(versionStr); j++ {
if versionStr[j] < '0' || versionStr[j] > '9' {
break
}
}
version, _ := strconv.Atoi(versionStr[i:j])
return version < 6
}
// autoPortListenWorkaround simulates automatic port allocation by
// trying random ports repeatedly.
func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {
var sshListener net.Listener
var err error
const tries = 10
for i := 0; i < tries; i++ {
addr := *laddr
addr.Port = 1024 + portRandomizer.Intn(60000)
sshListener, err = c.ListenTCP(&addr)
if err == nil {
laddr.Port = addr.Port
return sshListener, err
}
}
return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)
}
// RFC 4254 7.1
type channelForwardMsg struct {
Message string
WantReply bool
raddr string
rport uint32
}
// ListenTCP requests the remote peer open a listening socket
// on laddr. Incoming connections will be available by calling
// Accept on the returned net.Listener.
func (c *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) {
return c.autoPortListenWorkaround(laddr)
}
m := channelForwardMsg{
"tcpip-forward",
true, // sendGlobalRequest waits for a reply
laddr.IP.String(),
uint32(laddr.Port),
}
// send message
resp, err := c.sendGlobalRequest(m)
if err != nil {
return nil, err
}
// If the original port was 0, then the remote side will
// supply a real port number in the response.
if laddr.Port == 0 {
port, _, ok := parseUint32(resp.Data)
if !ok {
return nil, errors.New("unable to parse response")
}
laddr.Port = int(port)
}
// Register this forward, using the port number we obtained.
ch := c.forwardList.add(*laddr)
return &tcpListener{laddr, c, ch}, nil
}
// forwardList stores a mapping between remote
// forward requests and the tcpListeners.
type forwardList struct {
sync.Mutex
entries []forwardEntry
}
// forwardEntry represents an established mapping of a laddr on a
// remote ssh server to a channel connected to a tcpListener.
type forwardEntry struct {
laddr net.TCPAddr
c chan forward
}
// forward represents an incoming forwarded tcpip connection. The
// arguments to add/remove/lookup should be address as specified in
// the original forward-request.
type forward struct {
c *clientChan // the ssh client channel underlying this forward
raddr *net.TCPAddr // the raddr of the incoming connection
}
func (l *forwardList) add(addr net.TCPAddr) chan forward {
l.Lock()
defer l.Unlock()
f := forwardEntry{
addr,
make(chan forward, 1),
}
l.entries = append(l.entries, f)
return f.c
}
// remove removes the forward entry, and the channel feeding its
// listener.
func (l *forwardList) remove(addr net.TCPAddr) {
l.Lock()
defer l.Unlock()
for i, f := range l.entries {
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
l.entries = append(l.entries[:i], l.entries[i+1:]...)
close(f.c)
return
}
}
}
// closeAll closes and clears all forwards.
func (l *forwardList) closeAll() {
l.Lock()
defer l.Unlock()
for _, f := range l.entries {
close(f.c)
}
l.entries = nil
}
func (l *forwardList) lookup(addr net.TCPAddr) (chan forward, bool) {
l.Lock()
defer l.Unlock()
for _, f := range l.entries {
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
return f.c, true
}
}
return nil, false
}
type tcpListener struct {
laddr *net.TCPAddr
conn *ClientConn
in <-chan forward
}
// Accept waits for and returns the next connection to the listener.
func (l *tcpListener) Accept() (net.Conn, error) {
s, ok := <-l.in
if !ok {
return nil, io.EOF
}
return &tcpChanConn{
tcpChan: &tcpChan{
clientChan: s.c,
Reader: s.c.stdout,
Writer: s.c.stdin,
},
laddr: l.laddr,
raddr: s.raddr,
}, nil
}
// Close closes the listener.
func (l *tcpListener) Close() error {
m := channelForwardMsg{
"cancel-tcpip-forward",
true,
l.laddr.IP.String(),
uint32(l.laddr.Port),
}
l.conn.forwardList.remove(*l.laddr)
if _, err := l.conn.sendGlobalRequest(m); err != nil {
return err
}
return nil
}
// Addr returns the listener's network address.
func (l *tcpListener) Addr() net.Addr {
return l.laddr
}
// Dial initiates a connection to the addr from the remote host.
// The resulting connection has a zero LocalAddr() and RemoteAddr().
func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
// Parse the address into host and numeric port.
host, portString, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, err
}
// Use a zero address for local and remote address.
zeroAddr := &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port))
if err != nil {
return nil, err
}
return &tcpChanConn{
tcpChan: ch,
laddr: zeroAddr,
raddr: zeroAddr,
}, nil
}
// DialTCP connects to the remote address raddr on the network net,
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
// as the local address for the connection.
func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
if laddr == nil {
laddr = &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
}
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
if err != nil {
return nil, err
}
return &tcpChanConn{
tcpChan: ch,
laddr: laddr,
raddr: raddr,
}, nil
}
// RFC 4254 7.2
type channelOpenDirectMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
raddr string
rport uint32
laddr string
lport uint32
}
// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
// strings and are expected to be resolvable at the remote end.
func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpChan, error) {
ch := c.newChan(c.transport)
if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
ChanType: "direct-tcpip",
PeersId: ch.localId,
PeersWindow: channelWindowSize,
MaxPacketSize: channelMaxPacketSize,
raddr: raddr,
rport: uint32(rport),
laddr: laddr,
lport: uint32(lport),
})); err != nil {
c.chanList.remove(ch.localId)
return nil, err
}
if err := ch.waitForChannelOpenResponse(); err != nil {
c.chanList.remove(ch.localId)
return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
}
return &tcpChan{
clientChan: ch,
Reader: ch.stdout,
Writer: ch.stdin,
}, nil
}
type tcpChan struct {
*clientChan // the backing channel
io.Reader
io.Writer
}
// tcpChanConn fulfills the net.Conn interface without
// the tcpChan having to hold laddr or raddr directly.
type tcpChanConn struct {
*tcpChan
laddr, raddr net.Addr
}
// LocalAddr returns the local network address.
func (t *tcpChanConn) LocalAddr() net.Addr {
return t.laddr
}
// RemoteAddr returns the remote network address.
func (t *tcpChanConn) RemoteAddr() net.Addr {
return t.raddr
}
// SetDeadline sets the read and write deadlines associated
// with the connection.
func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
if err := t.SetReadDeadline(deadline); err != nil {
return err
}
return t.SetWriteDeadline(deadline)
}
// SetReadDeadline sets the read deadline.
// A zero value for t means Read will not time out.
// After the deadline, the error from Read will implement net.Error
// with Timeout() == true.
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error {
return errors.New("ssh: tcpChan: deadline not supported")
}
// SetWriteDeadline exists to satisfy the net.Conn interface
// but is not implemented by this type. It always returns an error.
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error {
return errors.New("ssh: tcpChan: deadline not supported")
}

View file

@ -1,426 +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 ssh
import (
"bufio"
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"errors"
"hash"
"io"
"net"
"sync"
)
const (
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
// indicates implementations SHOULD be able to handle larger packet sizes, but then
// waffles on about reasonable limits.
//
// OpenSSH caps their maxPacket at 256kb so we choose to do the same.
maxPacket = 256 * 1024
)
// packetConn represents a transport that implements packet based
// operations.
type packetConn interface {
// Encrypt and send a packet of data to the remote peer.
writePacket(packet []byte) error
// Read a packet from the connection
readPacket() ([]byte, error)
// Close closes the write-side of the connection.
Close() error
}
// transport represents the SSH connection to the remote peer.
type transport struct {
reader
writer
net.Conn
// Initial H used for the session ID. Once assigned this does
// not change, even during subsequent key exchanges.
sessionID []byte
}
// reader represents the incoming connection state.
type reader struct {
io.Reader
common
}
// writer represents the outgoing connection state.
type writer struct {
sync.Mutex // protects writer.Writer from concurrent writes
*bufio.Writer
rand io.Reader
common
}
// prepareKeyChange sets up key material for a keychange. The key changes in
// both directions are triggered by reading and writing a msgNewKey packet
// respectively.
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
t.writer.cipherAlgo = algs.wCipher
t.writer.macAlgo = algs.wMAC
t.writer.compressionAlgo = algs.wCompression
t.reader.cipherAlgo = algs.rCipher
t.reader.macAlgo = algs.rMAC
t.reader.compressionAlgo = algs.rCompression
if t.sessionID == nil {
t.sessionID = kexResult.H
}
kexResult.SessionID = t.sessionID
t.reader.pendingKeyChange <- kexResult
t.writer.pendingKeyChange <- kexResult
return nil
}
// common represents the cipher state needed to process messages in a single
// direction.
type common struct {
seqNum uint32
mac hash.Hash
cipher cipher.Stream
cipherAlgo string
macAlgo string
compressionAlgo string
dir direction
pendingKeyChange chan *kexResult
}
// Read and decrypt a single packet from the remote peer.
func (r *reader) readPacket() ([]byte, error) {
var lengthBytes = make([]byte, 5)
var macSize uint32
if _, err := io.ReadFull(r, lengthBytes); err != nil {
return nil, err
}
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
if r.mac != nil {
r.mac.Reset()
seqNumBytes := []byte{
byte(r.seqNum >> 24),
byte(r.seqNum >> 16),
byte(r.seqNum >> 8),
byte(r.seqNum),
}
r.mac.Write(seqNumBytes)
r.mac.Write(lengthBytes)
macSize = uint32(r.mac.Size())
}
length := binary.BigEndian.Uint32(lengthBytes[0:4])
paddingLength := uint32(lengthBytes[4])
if length <= paddingLength+1 {
return nil, errors.New("ssh: invalid packet length, packet too small")
}
if length > maxPacket {
return nil, errors.New("ssh: invalid packet length, packet too large")
}
packet := make([]byte, length-1+macSize)
if _, err := io.ReadFull(r, packet); err != nil {
return nil, err
}
mac := packet[length-1:]
r.cipher.XORKeyStream(packet, packet[:length-1])
if r.mac != nil {
r.mac.Write(packet[:length-1])
if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
return nil, errors.New("ssh: MAC failure")
}
}
r.seqNum++
packet = packet[:length-paddingLength-1]
if len(packet) > 0 && packet[0] == msgNewKeys {
select {
case k := <-r.pendingKeyChange:
if err := r.setupKeys(r.dir, k); err != nil {
return nil, err
}
default:
return nil, errors.New("ssh: got bogus newkeys message.")
}
}
return packet, nil
}
// Read and decrypt next packet discarding debug and noop messages.
func (t *transport) readPacket() ([]byte, error) {
for {
packet, err := t.reader.readPacket()
if err != nil {
return nil, err
}
if len(packet) == 0 {
return nil, errors.New("ssh: zero length packet")
}
if packet[0] != msgIgnore && packet[0] != msgDebug {
return packet, nil
}
}
panic("unreachable")
}
// Encrypt and send a packet of data to the remote peer.
func (w *writer) writePacket(packet []byte) error {
changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
if len(packet) > maxPacket {
return errors.New("ssh: packet too large")
}
w.Mutex.Lock()
defer w.Mutex.Unlock()
paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
if paddingLength < 4 {
paddingLength += packetSizeMultiple
}
length := len(packet) + 1 + paddingLength
lengthBytes := []byte{
byte(length >> 24),
byte(length >> 16),
byte(length >> 8),
byte(length),
byte(paddingLength),
}
padding := make([]byte, paddingLength)
_, err := io.ReadFull(w.rand, padding)
if err != nil {
return err
}
if w.mac != nil {
w.mac.Reset()
seqNumBytes := []byte{
byte(w.seqNum >> 24),
byte(w.seqNum >> 16),
byte(w.seqNum >> 8),
byte(w.seqNum),
}
w.mac.Write(seqNumBytes)
w.mac.Write(lengthBytes)
w.mac.Write(packet)
w.mac.Write(padding)
}
// TODO(dfc) lengthBytes, packet and padding should be
// subslices of a single buffer
w.cipher.XORKeyStream(lengthBytes, lengthBytes)
w.cipher.XORKeyStream(packet, packet)
w.cipher.XORKeyStream(padding, padding)
if _, err := w.Write(lengthBytes); err != nil {
return err
}
if _, err := w.Write(packet); err != nil {
return err
}
if _, err := w.Write(padding); err != nil {
return err
}
if w.mac != nil {
if _, err := w.Write(w.mac.Sum(nil)); err != nil {
return err
}
}
w.seqNum++
if err = w.Flush(); err != nil {
return err
}
if changeKeys {
select {
case k := <-w.pendingKeyChange:
err = w.setupKeys(w.dir, k)
default:
panic("ssh: no key material for msgNewKeys")
}
}
return err
}
func newTransport(conn net.Conn, rand io.Reader, isClient bool) *transport {
t := &transport{
reader: reader{
Reader: bufio.NewReader(conn),
common: common{
cipher: noneCipher{},
pendingKeyChange: make(chan *kexResult, 1),
},
},
writer: writer{
Writer: bufio.NewWriter(conn),
rand: rand,
common: common{
cipher: noneCipher{},
pendingKeyChange: make(chan *kexResult, 1),
},
},
Conn: conn,
}
if isClient {
t.reader.dir = serverKeys
t.writer.dir = clientKeys
} else {
t.reader.dir = clientKeys
t.writer.dir = serverKeys
}
return t
}
type direction struct {
ivTag []byte
keyTag []byte
macKeyTag []byte
}
// TODO(dfc) can this be made a constant ?
var (
serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
)
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
// described in RFC 4253, section 6.4. direction should either be serverKeys
// (to setup server->client keys) or clientKeys (for client->server keys).
func (c *common) setupKeys(d direction, r *kexResult) error {
cipherMode := cipherModes[c.cipherAlgo]
macMode := macModes[c.macAlgo]
iv := make([]byte, cipherMode.ivSize)
key := make([]byte, cipherMode.keySize)
macKey := make([]byte, macMode.keySize)
h := r.Hash.New()
generateKeyMaterial(iv, d.ivTag, r.K, r.H, r.SessionID, h)
generateKeyMaterial(key, d.keyTag, r.K, r.H, r.SessionID, h)
generateKeyMaterial(macKey, d.macKeyTag, r.K, r.H, r.SessionID, h)
c.mac = macMode.new(macKey)
var err error
c.cipher, err = cipherMode.createCipher(key, iv)
return err
}
// generateKeyMaterial fills out with key material generated from tag, K, H
// and sessionId, as specified in RFC 4253, section 7.2.
func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
var digestsSoFar []byte
for len(out) > 0 {
h.Reset()
h.Write(K)
h.Write(H)
if len(digestsSoFar) == 0 {
h.Write(tag)
h.Write(sessionId)
} else {
h.Write(digestsSoFar)
}
digest := h.Sum(nil)
n := copy(out, digest)
out = out[n:]
if len(out) > 0 {
digestsSoFar = append(digestsSoFar, digest...)
}
}
}
const packageVersion = "SSH-2.0-Go"
// Sends and receives a version line. The versionLine string should
// be US ASCII, start with "SSH-2.0-", and should not include a
// newline. exchangeVersions returns the other side's version line.
func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {
// Contrary to the RFC, we do not ignore lines that don't
// start with "SSH-2.0-" to make the library usable with
// nonconforming servers.
for _, c := range versionLine {
// The spec disallows non US-ASCII chars, and
// specifically forbids null chars.
if c < 32 {
return nil, errors.New("ssh: junk character in version line")
}
}
if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {
return
}
them, err = readVersion(rw)
return them, err
}
// maxVersionStringBytes is the maximum number of bytes that we'll
// accept as a version string. RFC 4253 section 4.2 limits this at 255
// chars
const maxVersionStringBytes = 255
// Read version string as specified by RFC 4253, section 4.2.
func readVersion(r io.Reader) ([]byte, error) {
versionString := make([]byte, 0, 64)
var ok bool
var buf [1]byte
for len(versionString) < maxVersionStringBytes {
_, err := io.ReadFull(r, buf[:])
if err != nil {
return nil, err
}
// The RFC says that the version should be terminated with \r\n
// but several SSH servers actually only send a \n.
if buf[0] == '\n' {
ok = true
break
}
// non ASCII chars are disallowed, but we are lenient,
// since Go doesn't use null-terminated strings.
// The RFC allows a comment after a space, however,
// all of it (version and comments) goes into the
// session hash.
versionString = append(versionString, buf[0])
}
if !ok {
return nil, errors.New("ssh: overflow reading version string")
}
// There might be a '\r' on the end which we should remove.
if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
versionString = versionString[:len(versionString)-1]
}
return versionString, nil
}

View file

@ -1,21 +0,0 @@
Copyright (C) 2013 Jeremy Saenz
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,315 +0,0 @@
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli)
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
# cli.go
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
## Installation
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install `cli.go`, simply run:
```
$ go get github.com/codegangsta/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
```
export PATH=$PATH:$GOPATH/bin
```
## Getting Started
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
cli.NewApp().Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
}
app.Run(os.Args)
}
```
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
## Example
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
}
app.Run(os.Args)
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
`cli.go` also generates neat help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```
### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
}
...
```
### Flags
Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands
Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
{
Name: "template",
Aliases: []string{"r"},
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
},
},
},
},
}
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
}
}
...
```
#### To Enable
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### To Distribute
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file to make it active in the current shell.
```
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should source the generic
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
to the name of their program (as above).
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.

View file

@ -1,333 +0,0 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"time"
)
// App is the main structure of a cli application. It is recomended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to os.Args[0]
Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program.
Usage string
// Description of the program argument format.
ArgsUsage string
// Version of the program
Version string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The action to execute when no subcommands are specified
Action func(context *Context)
// Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
return &App{
Name: os.Args[0],
HelpName: os.Args[0],
Usage: "A new cli application",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, nil)
ShowAppHelp(context)
return nerr
}
context := NewContext(a, set, nil)
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowAppHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil
}
if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowSubcommandHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}

View file

@ -1,210 +0,0 @@
package cli
import (
"fmt"
"io/ioutil"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// A longer explanation of how the command works
Description string
// A short description of the arguments of this command
ArgsUsage string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The function to call when this command is invoked
Action func(context *Context)
// List of child commands
Subcommands []Command
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command
HideHelp bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
}
// Returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) error {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard)
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
var err error
if firstFlagIndex > -1 && !c.SkipFlagParsing {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
if err != nil {
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if checkCommandHelp(context, c.Name) {
return nil
}
context.Command = c
c.Action(context)
return nil
}
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" {
app.HelpName = c.HelpName
} else {
app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
}
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
var newCmds []Command
for _, cc := range app.Commands {
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
}
app.Commands = newCmds
return app.RunAsSubcommand(ctx)
}

View file

@ -1,388 +0,0 @@
package cli
import (
"errors"
"flag"
"strconv"
"strings"
"time"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
flagSet *flag.FlagSet
setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
}
// Creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx}
}
// Looks up the value of a local int flag, returns 0 if no int flag exists
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// Looks up the value of a local bool flag, returns false if no bool flag exists
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// Looks up the value of a local boolT flag, returns false if no bool flag exists
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// Looks up the value of a local string flag, returns "" if no string flag exists
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// Looks up the value of a local generic flag, returns nil if no generic flag exists
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// Looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// Looks up the value of a global bool flag, returns false if no bool flag exists
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// Looks up the value of a global string flag, returns "" if no string flag exists
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
// Looks up the value of a global generic flag, returns nil if no generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
// Returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
}
return c.setFlags[name] == true
}
// Determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
}
return c.globalSetFlags[name]
}
// Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
type Args []string
// Returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// Returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// Returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Return the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
val, err := strconv.Atoi(f.Value.String())
if err != nil {
return 0
}
return val
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return val
}
return 0
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
return f.Value.String()
}
return ""
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*StringSlice)).Value()
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*IntSlice)).Value()
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
return f.Value
}
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return val
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return true
}
return val
}
return false
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.getName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}

View file

@ -1,497 +0,0 @@
package cli
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// This flag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
}
// This flag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// This flag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recomended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
getName() string
}
func flagSet(name string, flags []Flag) *flag.FlagSet {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
f.Apply(set)
}
return set
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// GenericFlag is the flag type for types implementing Generic
type GenericFlag struct {
Name string
Value Generic
Usage string
EnvVar string
}
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
}
func (f GenericFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []string to satisfy flag.Value
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// StringSlice is a string flag that can be specified multiple times on the
// command-line
type StringSliceFlag struct {
Name string
Value *StringSlice
Usage string
EnvVar string
}
// String returns the usage
func (f StringSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s)
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f StringSliceFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []int to satisfy flag.Value
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
} else {
*f = append(*f, tmp)
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%d", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// IntSliceFlag is an int flag that can be specified multiple times on the
// command-line
type IntSliceFlag struct {
Name string
Value *IntSlice
Usage string
EnvVar string
}
// String returns the usage
func (f IntSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f IntSliceFlag) getName() string {
return f.Name
}
// BoolFlag is a switch that defaults to false
type BoolFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolFlag) getName() string {
return f.Name
}
// BoolTFlag this represents a boolean flag that is true by default, but can
// still be set to false by --some-flag=false
type BoolTFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolTFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolTFlag) Apply(set *flag.FlagSet) {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolTFlag) getName() string {
return f.Name
}
// StringFlag represents a flag that takes as string value
type StringFlag struct {
Name string
Value string
Usage string
EnvVar string
}
// String returns the usage
func (f StringFlag) String() string {
var fmtString string
fmtString = "%s %v\t%v"
if len(f.Value) > 0 {
fmtString = "%s \"%v\"\t%v"
} else {
fmtString = "%s %v\t%v"
}
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
set.String(name, f.Value, f.Usage)
})
}
func (f StringFlag) getName() string {
return f.Name
}
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct {
Name string
Value int
Usage string
EnvVar string
}
// String returns the usage
func (f IntFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = int(envValInt)
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Int(name, f.Value, f.Usage)
})
}
func (f IntFlag) getName() string {
return f.Name
}
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct {
Name string
Value time.Duration
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Duration(name, f.Value, f.Usage)
})
}
func (f DurationFlag) getName() string {
return f.Name
}
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct {
Name string
Value float64
Usage string
EnvVar string
}
// String returns the usage
func (f Float64Flag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
}
}
}
}
eachName(f.Name, func(name string) {
set.Float64(name, f.Value, f.Usage)
})
}
func (f Float64Flag) getName() string {
return f.Name
}
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
func prefixedNames(fullName string) (prefixed string) {
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
prefixed += prefixFor(name) + name
if i < len(parts)-1 {
prefixed += ", "
}
}
return
}
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
}
return str + envText
}

View file

@ -1,246 +0,0 @@
package cli
import (
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
)
// The text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if .Version}}
VERSION:
{{.Version}}
{{end}}{{if len .Authors}}
AUTHOR(S):
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}
`
// The text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{ end }}
`
// The text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowAppHelp(c)
}
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowSubcommandHelp(c)
}
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// Prints version for the App
var VersionPrinter = printVersion
func ShowAppHelp(c *Context) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
}
// Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// Prints help for the given command
func ShowCommandHelp(ctx *Context, command string) {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return
}
}
if ctx.App.CommandNotFound != nil {
ctx.App.CommandNotFound(ctx, command)
} else {
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
}
}
// Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) {
ShowCommandHelp(c, c.Command.Name)
}
// Prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// Prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// Prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
func checkVersion(c *Context) bool {
found := false
if VersionFlag.Name != "" {
eachName(VersionFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkHelp(c *Context) bool {
found := false
if HelpFlag.Name != "" {
eachName(HelpFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkCompletions(c *Context) bool {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
}
return false
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
}
return false
}

View file

@ -1,19 +0,0 @@
Copyright (c) 2011 Dmitry Chestnykh
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,83 +0,0 @@
Package uniuri
=====================
[![Build Status](https://travis-ci.org/dchest/uniuri.png)](https://travis-ci.org/dchest/uniuri)
```go
import "github.com/dchest/uniuri"
```
Package uniuri generates random strings good for use in URIs to identify
unique objects.
Example usage:
```go
s := uniuri.New() // s is now "apHCJBl7L1OmC57n"
```
A standard string created by New() is 16 bytes in length and consists of
Latin upper and lowercase letters, and numbers (from the set of 62 allowed
characters), which means that it has ~95 bits of entropy. To get more
entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving
~119 bits of entropy, or any other desired length.
Functions read from crypto/rand random source, and panic if they fail to
read from it.
Constants
---------
```go
const (
// Standard length of uniuri string to achive ~95 bits of entropy.
StdLen = 16
// Length of uniurl string to achive ~119 bits of entropy, closest
// to what can be losslessly converted to UUIDv4 (122 bits).
UUIDLen = 20
)
```
Variables
---------
```go
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
```
Standard characters allowed in uniuri string.
Functions
---------
### func New
```go
func New() string
```
New returns a new random string of the standard length, consisting of
standard characters.
### func NewLen
```go
func NewLen(length int) string
```
NewLen returns a new random string of the provided length, consisting of
standard characters.
### func NewLenChars
```go
func NewLenChars(length int, chars []byte) string
```
NewLenChars returns a new random string of the provided length, consisting
of the provided byte slice of allowed characters (maximum 256).

View file

@ -1,71 +0,0 @@
// Package uniuri generates random strings good for use in URIs to identify
// unique objects.
//
// Example usage:
//
// s := uniuri.New() // s is now "apHCJBl7L1OmC57n"
//
// A standard string created by New() is 16 bytes in length and consists of
// Latin upper and lowercase letters, and numbers (from the set of 62 allowed
// characters), which means that it has ~95 bits of entropy. To get more
// entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving
// ~119 bits of entropy, or any other desired length.
//
// Functions read from crypto/rand random source, and panic if they fail to
// read from it.
package uniuri
import (
"crypto/rand"
"io"
)
const (
// Standard length of uniuri string to achive ~95 bits of entropy.
StdLen = 16
// Length of uniurl string to achive ~119 bits of entropy, closest
// to what can be losslessly converted to UUIDv4 (122 bits).
UUIDLen = 20
)
// Standard characters allowed in uniuri string.
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// New returns a new random string of the standard length, consisting of
// standard characters.
func New() string {
return NewLenChars(StdLen, StdChars)
}
// NewLen returns a new random string of the provided length, consisting of
// standard characters.
func NewLen(length int) string {
return NewLenChars(length, StdChars)
}
// NewLenChars returns a new random string of the provided length, consisting
// of the provided byte slice of allowed characters (maximum 256).
func NewLenChars(length int, chars []byte) string {
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
clen := byte(len(chars))
maxrb := byte(256 - (256 % len(chars)))
i := 0
for {
if _, err := io.ReadFull(rand.Reader, r); err != nil {
panic("error reading from random source: " + err.Error())
}
for _, c := range r {
if c >= maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = chars[c%clen]
i++
if i == length {
return string(b)
}
}
}
panic("unreachable")
}

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Dustin H
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,93 +0,0 @@
fscache
==========
[![GoDoc](https://godoc.org/github.com/djherbis/fscache?status.svg)](https://godoc.org/github.com/djherbis/fscache)
[![Release](https://img.shields.io/github/release/djherbis/fscache.svg)](https://github.com/djherbis/fscache/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt)
[![Build Status](https://travis-ci.org/djherbis/fscache.svg?branch=master)](https://travis-ci.org/djherbis/fscache)
[![Coverage Status](https://coveralls.io/repos/djherbis/fscache/badge.svg?branch=master)](https://coveralls.io/r/djherbis/fscache?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/djherbis/fscache)](https://goreportcard.com/report/github.com/djherbis/fscache)
Usage
------------
Streaming File Cache for #golang
fscache allows multiple readers to read from a cache while its being written to. [blog post](https://djherbis.github.io/post/fscache/)
Using the Cache directly:
```go
package main
import (
"io"
"log"
"os"
"time"
"gopkg.in/djherbis/fscache.v0"
)
func main() {
// create the cache, keys expire after 1 hour.
c, err := fscache.New("./cache", 0755, time.Hour)
if err != nil {
log.Fatal(err.Error())
}
// wipe the cache when done
defer c.Clean()
// Get() and it's streams can be called concurrently but just for example:
for i := 0; i < 3; i++ {
r, w, err := c.Get("stream")
if err != nil {
log.Fatal(err.Error())
}
if w != nil { // a new stream, write to it.
go func(){
w.Write([]byte("hello world\n"))
w.Close()
}()
}
// the stream has started, read from it
io.Copy(os.Stdout, r)
r.Close()
}
}
```
A Caching Middle-ware:
```go
package main
import(
"net/http"
"time"
"gopkg.in/djherbis/fscache.v0"
)
func main(){
c, err := fscache.New("./cache", 0700, 0)
if err != nil {
log.Fatal(err.Error())
}
handler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%v: %s", time.Now(), "hello world")
}
http.ListenAndServe(":8080", fscache.Handler(c, http.HandlerFunc(handler)))
}
```
Installation
------------
```sh
go get gopkg.in/djherbis/fscache.v0
```

View file

@ -1,85 +0,0 @@
package fscache
import (
"bytes"
"crypto/sha1"
"encoding/binary"
"io"
)
// Distributor provides a way to partition keys into Caches.
type Distributor interface {
// GetCache will always return the same Cache for the same key.
GetCache(key string) Cache
// Clean should wipe all the caches this Distributor manages
Clean() error
}
// stdDistribution distributes the keyspace evenly.
func stdDistribution(key string, n uint64) uint64 {
h := sha1.New()
io.WriteString(h, key)
buf := bytes.NewBuffer(h.Sum(nil)[:8])
i, _ := binary.ReadUvarint(buf)
return i % n
}
// NewDistributor returns a Distributor which evenly distributes the keyspace
// into the passed caches.
func NewDistributor(caches ...Cache) Distributor {
if len(caches) == 0 {
return nil
}
return &distrib{
distribution: stdDistribution,
caches: caches,
size: uint64(len(caches)),
}
}
type distrib struct {
distribution func(key string, n uint64) uint64
caches []Cache
size uint64
}
func (d *distrib) GetCache(key string) Cache {
return d.caches[d.distribution(key, d.size)]
}
// BUG(djherbis): Return an error if cleaning fails
func (d *distrib) Clean() error {
for _, c := range d.caches {
c.Clean()
}
return nil
}
// NewPartition returns a Cache which uses the Caches defined by the passed Distributor.
func NewPartition(d Distributor) Cache {
return &partition{
distributor: d,
}
}
type partition struct {
distributor Distributor
}
func (p *partition) Get(key string) (ReadAtCloser, io.WriteCloser, error) {
return p.distributor.GetCache(key).Get(key)
}
func (p *partition) Remove(key string) error {
return p.distributor.GetCache(key).Remove(key)
}
func (p *partition) Exists(key string) bool {
return p.distributor.GetCache(key).Exists(key)
}
func (p *partition) Clean() error {
return p.distributor.Clean()
}

View file

@ -1,199 +0,0 @@
package fscache
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"gopkg.in/djherbis/atime.v1"
"gopkg.in/djherbis/stream.v1"
)
// FileSystem is used as the source for a Cache.
type FileSystem interface {
// Stream FileSystem
stream.FileSystem
// Reload should look through the FileSystem and call the suplied fn
// with the key/filename pairs that are found.
Reload(func(key, name string)) error
// RemoveAll should empty the FileSystem of all files.
RemoveAll() error
// AccessTimes takes a File.Name() and returns the last time the file was read,
// and the last time it was written to.
// It will be used to check expiry of a file, and must be concurrent safe
// with modifications to the FileSystem (writes, reads etc.)
AccessTimes(name string) (rt, wt time.Time, err error)
}
type stdFs struct {
root string
}
// NewFs returns a FileSystem rooted at directory dir.
// Dir is created with perms if it doesn't exist.
func NewFs(dir string, mode os.FileMode) (FileSystem, error) {
return &stdFs{root: dir}, os.MkdirAll(dir, mode)
}
func (fs *stdFs) Reload(add func(key, name string)) error {
files, err := ioutil.ReadDir(fs.root)
if err != nil {
return err
}
addfiles := make(map[string]struct {
os.FileInfo
key string
})
for _, f := range files {
if strings.HasSuffix(f.Name(), ".key") {
continue
}
key, err := fs.getKey(f.Name())
if err != nil {
return err
}
fi, ok := addfiles[key]
if !ok || fi.ModTime().Before(f.ModTime()) {
if ok {
fs.Remove(fi.Name())
}
addfiles[key] = struct {
os.FileInfo
key string
}{
FileInfo: f,
key: key,
}
} else {
fs.Remove(f.Name())
}
}
for _, f := range addfiles {
path, err := filepath.Abs(filepath.Join(fs.root, f.Name()))
if err != nil {
return err
}
add(f.key, path)
}
return nil
}
func (fs *stdFs) Create(name string) (stream.File, error) {
name, err := fs.makeName(name)
if err != nil {
return nil, err
}
return fs.create(name)
}
func (fs *stdFs) create(name string) (stream.File, error) {
return os.OpenFile(filepath.Join(fs.root, name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
}
func (fs *stdFs) Open(name string) (stream.File, error) {
return os.Open(name)
}
func (fs *stdFs) Remove(name string) error {
os.Remove(fmt.Sprintf("%s.key", name))
return os.Remove(name)
}
func (fs *stdFs) RemoveAll() error {
return os.RemoveAll(fs.root)
}
func (fs *stdFs) AccessTimes(name string) (rt, wt time.Time, err error) {
fi, err := os.Stat(name)
if err != nil {
return rt, wt, err
}
return atime.Get(fi), fi.ModTime(), nil
}
const (
saltSize = 8
maxShort = 20
shortPrefix = "s"
longPrefix = "l"
)
func salt() string {
buf := bytes.NewBufferString("")
enc := base64.NewEncoder(base64.URLEncoding, buf)
io.CopyN(enc, rand.Reader, saltSize)
return buf.String()
}
func tob64(s string) string {
buf := bytes.NewBufferString("")
enc := base64.NewEncoder(base64.URLEncoding, buf)
enc.Write([]byte(s))
enc.Close()
return buf.String()
}
func fromb64(s string) string {
buf := bytes.NewBufferString(s)
dec := base64.NewDecoder(base64.URLEncoding, buf)
out := bytes.NewBufferString("")
io.Copy(out, dec)
return out.String()
}
func (fs *stdFs) makeName(key string) (string, error) {
b64key := tob64(key)
// short name
if len(b64key) < maxShort {
return fmt.Sprintf("%s%s%s", shortPrefix, salt(), b64key), nil
}
// long name
hash := md5.Sum([]byte(key))
name := fmt.Sprintf("%s%s%x", longPrefix, salt(), hash[:])
f, err := fs.create(fmt.Sprintf("%s.key", name))
if err != nil {
return "", err
}
_, err = f.Write([]byte(key))
f.Close()
return name, err
}
func (fs *stdFs) getKey(name string) (string, error) {
// short name
if strings.HasPrefix(name, shortPrefix) {
return fromb64(strings.TrimPrefix(name, shortPrefix)[saltSize:]), nil
}
// long name
f, err := fs.Open(filepath.Join(fs.root, fmt.Sprintf("%s.key", name)))
if err != nil {
return "", err
}
defer f.Close()
key, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
return string(key), nil
}

View file

@ -1,303 +0,0 @@
package fscache
import (
"io"
"os"
"sync"
"sync/atomic"
"time"
"gopkg.in/djherbis/stream.v1"
)
// Cache works like a concurrent-safe map for streams.
type Cache interface {
// Get manages access to the streams in the cache.
// If the key does not exist, w != nil and you can start writing to the stream.
// If the key does exist, w == nil.
// r will always be non-nil as long as err == nil and you must close r when you're done reading.
// Get can be called concurrently, and writing and reading is concurrent safe.
Get(key string) (ReadAtCloser, io.WriteCloser, error)
// Remove deletes the stream from the cache, blocking until the underlying
// file can be deleted (all active streams finish with it).
// It is safe to call Remove concurrently with Get.
Remove(key string) error
// Exists checks if a key is in the cache.
// It is safe to call Exists concurrently with Get.
Exists(key string) bool
// Clean will empty the cache and delete the cache folder.
// Clean is not safe to call while streams are being read/written.
Clean() error
}
type cache struct {
mu sync.RWMutex
files map[string]fileStream
grim Reaper
fs FileSystem
}
// ReadAtCloser is an io.ReadCloser, and an io.ReaderAt. It supports both so that Range
// Requests are possible.
type ReadAtCloser interface {
io.ReadCloser
io.ReaderAt
}
type fileStream interface {
next() (ReadAtCloser, error)
inUse() bool
io.WriteCloser
Remove() error
Name() string
}
// New creates a new Cache using NewFs(dir, perms).
// expiry is the duration after which an un-accessed key will be removed from
// the cache, a zero value expiro means never expire.
func New(dir string, perms os.FileMode, expiry time.Duration) (Cache, error) {
fs, err := NewFs(dir, perms)
if err != nil {
return nil, err
}
var grim Reaper
if expiry > 0 {
grim = &reaper{
expiry: expiry,
period: expiry,
}
}
return NewCache(fs, grim)
}
// NewCache creates a new Cache based on FileSystem fs.
// fs.Files() are loaded using the name they were created with as a key.
// Reaper is used to determine when files expire, nil means never expire.
func NewCache(fs FileSystem, grim Reaper) (Cache, error) {
c := &cache{
files: make(map[string]fileStream),
grim: grim,
fs: fs,
}
err := c.load()
if err != nil {
return nil, err
}
if grim != nil {
c.haunter()
}
return c, nil
}
func (c *cache) haunter() {
c.haunt()
time.AfterFunc(c.grim.Next(), c.haunter)
}
func (c *cache) haunt() {
c.mu.Lock()
defer c.mu.Unlock()
for key, f := range c.files {
if f.inUse() {
continue
}
lastRead, lastWrite, err := c.fs.AccessTimes(f.Name())
if err != nil {
continue
}
if c.grim.Reap(key, lastRead, lastWrite) {
delete(c.files, key)
c.fs.Remove(f.Name())
}
}
return
}
func (c *cache) load() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.fs.Reload(func(key, name string) {
c.files[key] = c.oldFile(name)
})
}
func (c *cache) Exists(key string) bool {
c.mu.RLock()
defer c.mu.RUnlock()
_, ok := c.files[key]
return ok
}
func (c *cache) Get(key string) (r ReadAtCloser, w io.WriteCloser, err error) {
c.mu.RLock()
f, ok := c.files[key]
if ok {
r, err = f.next()
c.mu.RUnlock()
return r, nil, err
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
f, ok = c.files[key]
if ok {
r, err = f.next()
return r, nil, err
}
f, err = c.newFile(key)
if err != nil {
return nil, nil, err
}
r, err = f.next()
if err != nil {
f.Close()
c.fs.Remove(f.Name())
return nil, nil, err
}
c.files[key] = f
return r, f, err
}
func (c *cache) Remove(key string) error {
c.mu.Lock()
f, ok := c.files[key]
delete(c.files, key)
c.mu.Unlock()
if ok {
return f.Remove()
}
return nil
}
func (c *cache) Clean() error {
c.mu.Lock()
defer c.mu.Unlock()
c.files = make(map[string]fileStream)
return c.fs.RemoveAll()
}
type cachedFile struct {
stream *stream.Stream
handleCounter
}
func (c *cache) newFile(name string) (fileStream, error) {
s, err := stream.NewStream(name, c.fs)
if err != nil {
return nil, err
}
cf := &cachedFile{
stream: s,
}
cf.inc()
return cf, nil
}
func (c *cache) oldFile(name string) fileStream {
return &reloadedFile{
fs: c.fs,
name: name,
}
}
type reloadedFile struct {
fs FileSystem
name string
handleCounter
io.WriteCloser // nop Write & Close methods. will never be called.
}
func (f *reloadedFile) Name() string { return f.name }
func (f *reloadedFile) Remove() error {
f.waitUntilFree()
return f.fs.Remove(f.name)
}
func (f *reloadedFile) next() (r ReadAtCloser, err error) {
r, err = f.fs.Open(f.name)
if err == nil {
f.inc()
}
return &cacheReader{r: r, cnt: &f.handleCounter}, err
}
func (f *cachedFile) Name() string { return f.stream.Name() }
func (f *cachedFile) Remove() error { return f.stream.Remove() }
func (f *cachedFile) next() (r ReadAtCloser, err error) {
reader, err := f.stream.NextReader()
if err != nil {
return nil, err
}
f.inc()
return &cacheReader{
r: reader,
cnt: &f.handleCounter,
}, nil
}
func (f *cachedFile) Write(p []byte) (int, error) {
return f.stream.Write(p)
}
func (f *cachedFile) Close() error {
defer f.dec()
return f.stream.Close()
}
type cacheReader struct {
r ReadAtCloser
cnt *handleCounter
}
func (r *cacheReader) ReadAt(p []byte, off int64) (n int, err error) {
return r.r.ReadAt(p, off)
}
func (r *cacheReader) Read(p []byte) (n int, err error) {
return r.r.Read(p)
}
func (r *cacheReader) Close() error {
defer r.cnt.dec()
return r.r.Close()
}
type handleCounter struct {
cnt int64
grp sync.WaitGroup
}
func (h *handleCounter) inc() {
h.grp.Add(1)
atomic.AddInt64(&h.cnt, 1)
}
func (h *handleCounter) dec() {
atomic.AddInt64(&h.cnt, -1)
h.grp.Done()
}
func (h *handleCounter) inUse() bool {
return atomic.LoadInt64(&h.cnt) > 0
}
func (h *handleCounter) waitUntilFree() {
h.grp.Wait()
}

View file

@ -1,41 +0,0 @@
package fscache
import (
"io"
"net/http"
)
// Handler is a caching middle-ware for http Handlers.
// It responds to http requests via the passed http.Handler, and caches the response
// using the passed cache. The cache key for the request is the req.URL.String().
// Note: It does not cache http headers. It is more efficient to set them yourself.
func Handler(c Cache, h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
url := req.URL.String()
r, w, err := c.Get(url)
if err != nil {
h.ServeHTTP(rw, req)
return
}
defer r.Close()
if w != nil {
go func() {
defer w.Close()
h.ServeHTTP(&respWrapper{
ResponseWriter: rw,
Writer: w,
}, req)
}()
}
io.Copy(rw, r)
})
}
type respWrapper struct {
http.ResponseWriter
io.Writer
}
func (r *respWrapper) Write(p []byte) (int, error) {
return r.Writer.Write(p)
}

View file

@ -1,129 +0,0 @@
package fscache
import (
"errors"
"io"
"sync"
)
type layeredCache struct {
layers []Cache
}
// NewLayered returns a Cache which stores its data in all the passed
// caches, when a key is requested it is loaded into all the caches above the first hit.
func NewLayered(caches ...Cache) Cache {
return &layeredCache{layers: caches}
}
func (l *layeredCache) Get(key string) (r ReadAtCloser, w io.WriteCloser, err error) {
var last ReadAtCloser
var writers []io.WriteCloser
for i, layer := range l.layers {
r, w, err = layer.Get(key)
if err != nil {
if len(writers) > 0 {
last.Close()
multiWC(writers...).Close()
}
return nil, nil, err
}
// hit
if w == nil {
if len(writers) > 0 {
go func(r io.ReadCloser) {
wc := multiWC(writers...)
defer r.Close()
defer wc.Close()
io.Copy(wc, r)
}(r)
return last, nil, nil
}
return r, nil, nil
}
// miss
writers = append(writers, w)
if i == len(l.layers)-1 {
if last != nil {
last.Close()
}
return r, multiWC(writers...), nil
}
if last != nil {
last.Close()
}
last = r
}
return nil, nil, errors.New("no caches")
}
func (l *layeredCache) Remove(key string) error {
var grp sync.WaitGroup
// walk upwards so that lower layers don't
// restore upper layers on Get()
for i := len(l.layers) - 1; i >= 0; i-- {
grp.Add(1)
go func(layer Cache) {
defer grp.Done()
layer.Remove(key)
}(l.layers[i])
}
grp.Wait()
return nil
}
func (l *layeredCache) Exists(key string) bool {
for _, layer := range l.layers {
if layer.Exists(key) {
return true
}
}
return false
}
func (l *layeredCache) Clean() (err error) {
for _, layer := range l.layers {
er := layer.Clean()
if er != nil {
err = er
}
}
return nil
}
func multiWC(wc ...io.WriteCloser) io.WriteCloser {
if len(wc) == 0 {
return nil
}
return &multiWriteCloser{
writers: wc,
}
}
type multiWriteCloser struct {
writers []io.WriteCloser
}
func (t *multiWriteCloser) Write(p []byte) (n int, err error) {
for _, w := range t.writers {
n, err = w.Write(p)
if err != nil {
return
}
}
return len(p), nil
}
func (t *multiWriteCloser) Close() error {
for _, w := range t.writers {
w.Close()
}
return nil
}

View file

@ -1,133 +0,0 @@
package fscache
import (
"bytes"
"errors"
"io"
"sync"
"time"
"gopkg.in/djherbis/stream.v1"
)
type memFS struct {
mu sync.RWMutex
files map[string]*memFile
}
// NewMemFs creates an in-memory FileSystem.
// It does not support persistence (Reload is a nop).
func NewMemFs() FileSystem {
return &memFS{
files: make(map[string]*memFile),
}
}
func (fs *memFS) Reload(add func(key, name string)) error {
return nil
}
func (fs *memFS) AccessTimes(name string) (rt, wt time.Time, err error) {
fs.mu.RLock()
defer fs.mu.RUnlock()
f, ok := fs.files[name]
if ok {
return f.rt, f.wt, nil
}
return rt, wt, errors.New("file has not been read")
}
func (fs *memFS) Create(key string) (stream.File, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if _, ok := fs.files[key]; ok {
return nil, errors.New("file exists")
}
file := &memFile{
name: key,
r: bytes.NewBuffer(nil),
wt: time.Now(),
}
file.memReader.memFile = file
fs.files[key] = file
return file, nil
}
func (fs *memFS) Open(name string) (stream.File, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if f, ok := fs.files[name]; ok {
f.rt = time.Now()
return &memReader{memFile: f}, nil
}
return nil, errors.New("file does not exist")
}
func (fs *memFS) Remove(key string) error {
fs.mu.Lock()
defer fs.mu.Unlock()
delete(fs.files, key)
return nil
}
func (fs *memFS) RemoveAll() error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.files = make(map[string]*memFile)
return nil
}
type memFile struct {
mu sync.RWMutex
name string
r *bytes.Buffer
memReader
rt, wt time.Time
}
func (f *memFile) Name() string {
return f.name
}
func (f *memFile) Write(p []byte) (int, error) {
if len(p) > 0 {
f.mu.Lock()
defer f.mu.Unlock()
return f.r.Write(p)
}
return len(p), nil
}
func (f *memFile) Bytes() []byte {
f.mu.RLock()
defer f.mu.RUnlock()
return f.r.Bytes()
}
func (f *memFile) Close() error {
return nil
}
type memReader struct {
*memFile
n int
}
func (r *memReader) ReadAt(p []byte, off int64) (n int, err error) {
data := r.Bytes()
if int64(len(data)) < off {
return 0, io.EOF
}
n, err = bytes.NewReader(data[off:]).ReadAt(p, 0)
return n, err
}
func (r *memReader) Read(p []byte) (n int, err error) {
n, err = bytes.NewReader(r.Bytes()[r.n:]).Read(p)
r.n += n
return n, err
}
func (r *memReader) Close() error {
return nil
}

View file

@ -1,37 +0,0 @@
package fscache
import "time"
// Reaper is used to control when streams expire from the cache.
// It is called once right after loading, and then it is run
// again after every Next() period of time.
type Reaper interface {
// Returns the amount of time to wait before the next scheduled Reaping.
Next() time.Duration
// Given a key and the last r/w times of a file, return true
// to remove the file from the cache, false to keep it.
Reap(key string, lastRead, lastWrite time.Time) bool
}
// NewReaper returns a simple reaper which runs every "period"
// and reaps files which are older than "expiry".
func NewReaper(expiry, period time.Duration) Reaper {
return &reaper{
expiry: expiry,
period: period,
}
}
type reaper struct {
period time.Duration
expiry time.Duration
}
func (g *reaper) Next() time.Duration {
return g.period
}
func (g *reaper) Reap(key string, lastRead, lastWrite time.Time) bool {
return lastRead.Before(time.Now().Add(-g.expiry))
}

View file

@ -1,206 +0,0 @@
package fscache
import (
"bytes"
"errors"
"fmt"
"io"
"net"
)
// ListenAndServe hosts a Cache for access via NewRemote
func ListenAndServe(c Cache, addr string) error {
return (&server{c: c}).ListenAndServe(addr)
}
// NewRemote returns a Cache run via ListenAndServe
func NewRemote(raddr string) Cache {
return &remote{raddr: raddr}
}
type server struct {
c Cache
}
func (s *server) ListenAndServe(addr string) error {
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
for {
c, err := l.Accept()
if err != nil {
return err
}
go s.Serve(c)
}
}
const (
actionGet = iota
actionRemove = iota
actionExists = iota
actionClean = iota
)
func getKey(r io.Reader) string {
dec := newDecoder(r)
buf := bytes.NewBufferString("")
io.Copy(buf, dec)
return buf.String()
}
func sendKey(w io.Writer, key string) {
enc := newEncoder(w)
enc.Write([]byte(key))
enc.Close()
}
func (s *server) Serve(c net.Conn) {
var action int
fmt.Fscanf(c, "%d\n", &action)
switch action {
case actionGet:
s.get(c, getKey(c))
case actionRemove:
s.c.Remove(getKey(c))
case actionExists:
s.exists(c, getKey(c))
case actionClean:
s.c.Clean()
}
}
func (s *server) exists(c net.Conn, key string) {
if s.c.Exists(key) {
fmt.Fprintf(c, "%d\n", 1)
} else {
fmt.Fprintf(c, "%d\n", 0)
}
}
func (s *server) get(c net.Conn, key string) {
r, w, err := s.c.Get(key)
if err != nil {
return // handle this better
}
defer r.Close()
if w != nil {
go func() {
fmt.Fprintf(c, "%d\n", 1)
io.Copy(w, newDecoder(c))
w.Close()
}()
} else {
fmt.Fprintf(c, "%d\n", 0)
}
enc := newEncoder(c)
io.Copy(enc, r)
enc.Close()
}
type remote struct {
raddr string
}
func (rmt *remote) Get(key string) (r ReadAtCloser, w io.WriteCloser, err error) {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return nil, nil, err
}
fmt.Fprintf(c, "%d\n", actionGet)
sendKey(c, key)
var i int
fmt.Fscanf(c, "%d\n", &i)
var ch chan struct{}
switch i {
case 0:
ch = make(chan struct{}) // close net.Conn on reader close
case 1:
ch = make(chan struct{}, 1) // two closes before net.Conn close
w = &safeCloser{
c: c,
ch: ch,
w: newEncoder(c),
}
default:
return nil, nil, errors.New("bad bad bad")
}
r = &safeCloser{
c: c,
ch: ch,
r: newDecoder(c),
}
return r, w, nil
}
type safeCloser struct {
c net.Conn
ch chan<- struct{}
r ReadAtCloser
w io.WriteCloser
}
func (s *safeCloser) ReadAt(p []byte, off int64) (int, error) {
return s.r.ReadAt(p, off)
}
func (s *safeCloser) Read(p []byte) (int, error) { return s.r.Read(p) }
func (s *safeCloser) Write(p []byte) (int, error) { return s.w.Write(p) }
// Close only closes the underlying connection when ch is full.
func (s *safeCloser) Close() (err error) {
if s.r != nil {
err = s.r.Close()
} else if s.w != nil {
err = s.w.Close()
}
select {
case s.ch <- struct{}{}:
return err
default:
return s.c.Close()
}
}
func (rmt *remote) Exists(key string) bool {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return false
}
fmt.Fprintf(c, "%d\n", actionExists)
sendKey(c, key)
var i int
fmt.Fscanf(c, "%d\n", &i)
return i == 1
}
func (rmt *remote) Remove(key string) error {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return err
}
fmt.Fprintf(c, "%d\n", actionRemove)
sendKey(c, key)
return nil
}
func (rmt *remote) Clean() error {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return err
}
fmt.Fprintf(c, "%d\n", actionClean)
return nil
}

View file

@ -1,72 +0,0 @@
package fscache
import (
"encoding/json"
"errors"
"io"
)
type decoder interface {
Decode(interface{}) error
}
type encoder interface {
Encode(interface{}) error
}
type pktReader struct {
dec decoder
}
type pktWriter struct {
enc encoder
}
type packet struct {
Err int
Data []byte
}
const eof = 1
func (t *pktReader) ReadAt(p []byte, off int64) (n int, err error) {
// TODO not implemented
return 0, errors.New("not implemented")
}
func (t *pktReader) Read(p []byte) (int, error) {
var pkt packet
err := t.dec.Decode(&pkt)
if err != nil {
return 0, err
}
if pkt.Err == eof {
return 0, io.EOF
}
return copy(p, pkt.Data), nil
}
func (t *pktReader) Close() error {
return nil
}
func (t *pktWriter) Write(p []byte) (int, error) {
pkt := packet{Data: p}
err := t.enc.Encode(pkt)
if err != nil {
return 0, err
}
return len(p), nil
}
func (t *pktWriter) Close() error {
return t.enc.Encode(packet{Err: eof})
}
func newEncoder(w io.Writer) io.WriteCloser {
return &pktWriter{enc: json.NewEncoder(w)}
}
func newDecoder(r io.Reader) ReadAtCloser {
return &pktReader{dec: json.NewDecoder(r)}
}

View file

@ -1,424 +0,0 @@
# amber
--
import "github.com/eknkc/amber"
Amber is an elegant templating engine for Go Programming Language
It is inspired from HAML and Jade
### Tags
A tag is simply a word:
html
is converted to
<html></html>
It is possible to add ID and CLASS attributes to tags:
div#main
span.time
are converted to
<div id="main"></div>
<span class="time"></span>
Any arbitrary attribute name / value pair can be added this way:
a[href="http://www.google.com"]
You can mix multiple attributes together
a#someid[href="/"][title="Main Page"].main.link Click Link
gets converted to
<a id="someid" class="main link" href="/" title="Main Page">Click Link</a>
It is also possible to define these attributes within the block of a tag
a
#someid
[href="/"]
[title="Main Page"]
.main
.link
| Click Link
### Doctypes
To add a doctype, use `!!!` or `doctype` keywords:
!!! transitional
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
or use `doctype`
doctype 5
// <!DOCTYPE html>
Available options: `5`, `default`, `xml`, `transitional`, `strict`, `frameset`, `1.1`, `basic`, `mobile`
### Tag Content
For single line tag text, you can just append the text after tag name:
p Testing!
would yield
<p>Testing!</p>
For multi line tag text, or nested tags, use indentation:
html
head
title Page Title
body
div#content
p
| This is a long page content
| These lines are all part of the parent p
a[href="/"] Go To Main Page
### Data
Input template data can be reached by key names directly. For example, assuming the template has been
executed with following JSON data:
{
"Name": "Ekin",
"LastName": "Koc",
"Repositories": [
"amber",
"dateformat"
],
"Avatar": "/images/ekin.jpg",
"Friends": 17
}
It is possible to interpolate fields using `#{}`
p Welcome #{Name}!
would print
<p>Welcome Ekin!</p>
Attributes can have field names as well
a[title=Name][href="/ekin.koc"]
would print
<a title="Ekin" href="/ekin.koc"></a>
### Expressions
Amber can expand basic expressions. For example, it is possible to concatenate strings with + operator:
p Welcome #{Name + " " + LastName}
Arithmetic expressions are also supported:
p You need #{50 - Friends} more friends to reach 50!
Expressions can be used within attributes
img[alt=Name + " " + LastName][src=Avatar]
### Variables
It is possible to define dynamic variables within templates,
all variables must start with a $ character and can be assigned as in the following example:
div
$fullname = Name + " " + LastName
p Welcome #{$fullname}
If you need to access the supplied data itself (i.e. the object containing Name, LastName etc fields.) you can use `$` variable
p $.Name
### Conditions
For conditional blocks, it is possible to use `if <expression>`
div
if Friends > 10
p You have more than 10 friends
else if Friends > 5
p You have more than 5 friends
else
p You need more friends
Again, it is possible to use arithmetic and boolean operators
div
if Name == "Ekin" && LastName == "Koc"
p Hey! I know you..
There is a special syntax for conditional attributes. Only block attributes can have conditions;
div
.hasfriends ? Friends > 0
This would yield a div with `hasfriends` class only if the `Friends > 0` condition holds. It is
perfectly fine to use the same method for other types of attributes:
div
#foo ? Name == "Ekin"
[bar=baz] ? len(Repositories) > 0
### Iterations
It is possible to iterate over arrays and maps using `each`:
each $repo in Repositories
p #{$repo}
would print
p amber
p dateformat
It is also possible to iterate over values and indexes at the same time
each $i, $repo in Repositories
p
.even ? $i % 2 == 0
.odd ? $i % 2 == 1
### Mixins
Mixins (reusable template blocks that accept arguments) can be defined:
mixin surprise
span Surprise!
mixin link($href, $title, $text)
a[href=$href][title=$title] #{$text}
and then called multiple times within a template (or even within another mixin definition):
div
+surprise
+surprise
+link("http://google.com", "Google", "Check out Google")
Template data, variables, expressions, etc., can all be passed as arguments:
+link(GoogleUrl, $googleTitle, "Check out " + $googleTitle)
### Imports
A template can import other templates using `import`:
a.amber
p this is template a
b.amber
p this is template b
c.amber
div
import a
import b
gets compiled to
div
p this is template a
p this is template b
### Inheritance
A template can inherit other templates. In order to inherit another template, an `extends` keyword should be used.
Parent template can define several named blocks and child template can modify the blocks.
master.amber
!!! 5
html
head
block meta
meta[name="description"][content="This is a great website"]
title
block title
| Default title
body
block content
subpage.amber
extends master
block title
| Some sub page!
block append meta
// This will be added after the description meta tag. It is also possible
// to prepend someting to an existing block
meta[name="keywords"][content="foo bar"]
block content
div#main
p Some content here
### License
(The MIT License)
Copyright (c) 2012 Ekin Koc <ekin@eknkc.com>
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.
## Usage
```go
var DefaultOptions = Options{true, false}
var DefaultDirOptions = DirOptions{".amber", true}
```
#### func Compile
```go
func Compile(input string, options Options) (*template.Template, error)
```
Parses and compiles the supplied amber template string. Returns corresponding Go
Template (html/templates) instance. Necessary runtime functions will be injected
and the template will be ready to be executed.
#### func CompileFile
```go
func CompileFile(filename string, options Options) (*template.Template, error)
```
Parses and compiles the contents of supplied filename. Returns corresponding Go
Template (html/templates) instance. Necessary runtime functions will be injected
and the template will be ready to be executed.
#### func CompileDir
```go
func CompileDir(dirname string, dopt DirOptions, opt Options) (map[string]*template.Template, error)
```
Parses and compiles the contents of a supplied directory name. Returns a mapping of template name (extension stripped) to corresponding Go Template (html/template) instance. Necessary runtime functions will be injected and the template will be ready to be executed.
If there are templates in subdirectories, its key in the map will be it's path relative to `dirname`. For example:
```
templates/
|-- index.amber
|-- layouts/
|-- base.amber
```
```go
templates, err := amber.CompileDir("templates/", amber.DefaultDirOptions, amber.DefaultOptions)
templates["index"] // index.amber Go Template
templates["layouts/base"] // base.amber Go Template
```
By default, the search will be recursive and will match only files ending in ".amber". If recursive is turned off, it will only search the top level of the directory. Specified extension must start with a period.
#### type Compiler
```go
type Compiler struct {
// Compiler options
Options
}
```
Compiler is the main interface of Amber Template Engine. In order to use an
Amber template, it is required to create a Compiler and compile an Amber source
to native Go template.
compiler := amber.New()
// Parse the input file
err := compiler.ParseFile("./input.amber")
if err == nil {
// Compile input file to Go template
tpl, err := compiler.Compile()
if err == nil {
// Check built in html/template documentation for further details
tpl.Execute(os.Stdout, somedata)
}
}
#### func New
```go
func New() *Compiler
```
Create and initialize a new Compiler
#### func (*Compiler) Compile
```go
func (c *Compiler) Compile() (*template.Template, error)
```
Compile amber and create a Go Template (html/templates) instance. Necessary
runtime functions will be injected and the template will be ready to be
executed.
#### func (*Compiler) CompileString
```go
func (c *Compiler) CompileString() (string, error)
```
Compile template and return the Go Template source You would not be using this
unless debugging / checking the output. Please use Compile method to obtain a
template instance directly.
#### func (*Compiler) CompileWriter
```go
func (c *Compiler) CompileWriter(out io.Writer) (err error)
```
Compile amber and write the Go Template source into given io.Writer instance You
would not be using this unless debugging / checking the output. Please use
Compile method to obtain a template instance directly.
#### func (*Compiler) Parse
```go
func (c *Compiler) Parse(input string) (err error)
```
Parse given raw amber template string.
#### func (*Compiler) ParseFile
```go
func (c *Compiler) ParseFile(filename string) (err error)
```
Parse the amber template file in given path
#### type Options
```go
type Options struct {
// Setting if pretty printing is enabled.
// Pretty printing ensures that the output html is properly indented and in human readable form.
// If disabled, produced HTML is compact. This might be more suitable in production environments.
// Defaukt: true
PrettyPrint bool
// Setting if line number emiting is enabled
// In this form, Amber emits line number comments in the output template. It is usable in debugging environments.
// Default: false
LineNumbers bool
}
```
#### type DirOptions
```go
// Used to provide options to directory compilation
type DirOptions struct {
// File extension to match for compilation
Ext string
// Whether or not to walk subdirectories
Recursive bool
}
```

View file

@ -1,777 +0,0 @@
package amber
import (
"bytes"
"container/list"
"errors"
"fmt"
"go/ast"
gp "go/parser"
gt "go/token"
"html/template"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/eknkc/amber/parser"
)
var builtinFunctions = [...]string{
"len",
"print",
"printf",
"println",
"urlquery",
"js",
"json",
"index",
"html",
"unescaped",
}
// Compiler is the main interface of Amber Template Engine.
// In order to use an Amber template, it is required to create a Compiler and
// compile an Amber source to native Go template.
// compiler := amber.New()
// // Parse the input file
// err := compiler.ParseFile("./input.amber")
// if err == nil {
// // Compile input file to Go template
// tpl, err := compiler.Compile()
// if err == nil {
// // Check built in html/template documentation for further details
// tpl.Execute(os.Stdout, somedata)
// }
// }
type Compiler struct {
// Compiler options
Options
filename string
node parser.Node
indentLevel int
newline bool
buffer *bytes.Buffer
tempvarIndex int
mixins map[string]*parser.Mixin
}
// New creates and initialize a new Compiler.
func New() *Compiler {
compiler := new(Compiler)
compiler.filename = ""
compiler.tempvarIndex = 0
compiler.PrettyPrint = true
compiler.Options = DefaultOptions
compiler.mixins = make(map[string]*parser.Mixin)
return compiler
}
// Options defines template output behavior.
type Options struct {
// Setting if pretty printing is enabled.
// Pretty printing ensures that the output html is properly indented and in human readable form.
// If disabled, produced HTML is compact. This might be more suitable in production environments.
// Default: true
PrettyPrint bool
// Setting if line number emitting is enabled
// In this form, Amber emits line number comments in the output template. It is usable in debugging environments.
// Default: false
LineNumbers bool
}
// DirOptions is used to provide options to directory compilation.
type DirOptions struct {
// File extension to match for compilation
Ext string
// Whether or not to walk subdirectories
Recursive bool
}
// DefaultOptions sets pretty-printing to true and line numbering to false.
var DefaultOptions = Options{true, false}
// DefaultDirOptions sets expected file extension to ".amber" and recursive search for templates within a directory to true.
var DefaultDirOptions = DirOptions{".amber", true}
// Compile parses and compiles the supplied amber template string. Returns corresponding Go Template (html/templates) instance.
// Necessary runtime functions will be injected and the template will be ready to be executed.
func Compile(input string, options Options) (*template.Template, error) {
comp := New()
comp.Options = options
err := comp.Parse(input)
if err != nil {
return nil, err
}
return comp.Compile()
}
// Compile parses and compiles the supplied amber template []byte.
// Returns corresponding Go Template (html/templates) instance.
// Necessary runtime functions will be injected and the template will be ready to be executed.
func CompileData(input []byte, filename string, options Options) (*template.Template, error) {
comp := New()
comp.Options = options
err := comp.ParseData(input, filename)
if err != nil {
return nil, err
}
return comp.Compile()
}
// MustCompile is the same as Compile, except the input is assumed error free. If else, panic.
func MustCompile(input string, options Options) *template.Template {
t, err := Compile(input, options)
if err != nil {
panic(err)
}
return t
}
// CompileFile parses and compiles the contents of supplied filename. Returns corresponding Go Template (html/templates) instance.
// Necessary runtime functions will be injected and the template will be ready to be executed.
func CompileFile(filename string, options Options) (*template.Template, error) {
comp := New()
comp.Options = options
err := comp.ParseFile(filename)
if err != nil {
return nil, err
}
return comp.Compile()
}
// MustCompileFile is the same as CompileFile, except the input is assumed error free. If else, panic.
func MustCompileFile(filename string, options Options) *template.Template {
t, err := CompileFile(filename, options)
if err != nil {
panic(err)
}
return t
}
// CompileDir parses and compiles the contents of a supplied directory path, with options.
// Returns a map of a template identifier (key) to a Go Template instance.
// Ex: if the dirname="templates/" had a file "index.amber" the key would be "index"
// If option for recursive is True, this parses every file of relevant extension
// in all subdirectories. The key then is the path e.g: "layouts/layout"
func CompileDir(dirname string, dopt DirOptions, opt Options) (map[string]*template.Template, error) {
dir, err := os.Open(dirname)
if err != nil {
return nil, err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return nil, err
}
compiled := make(map[string]*template.Template)
for _, file := range files {
// filename is for example "index.amber"
filename := file.Name()
fileext := filepath.Ext(filename)
// If recursive is true and there's a subdirectory, recurse
if dopt.Recursive && file.IsDir() {
dirpath := filepath.Join(dirname, filename)
subcompiled, err := CompileDir(dirpath, dopt, opt)
if err != nil {
return nil, err
}
// Copy templates from subdirectory into parent template mapping
for k, v := range subcompiled {
// Concat with parent directory name for unique paths
key := filepath.Join(filename, k)
compiled[key] = v
}
} else if fileext == dopt.Ext {
// Otherwise compile the file and add to mapping
fullpath := filepath.Join(dirname, filename)
tmpl, err := CompileFile(fullpath, opt)
if err != nil {
return nil, err
}
// Strip extension
key := filename[0 : len(filename)-len(fileext)]
compiled[key] = tmpl
}
}
return compiled, nil
}
// MustCompileDir is the same as CompileDir, except input is assumed error free. If else, panic.
func MustCompileDir(dirname string, dopt DirOptions, opt Options) map[string]*template.Template {
m, err := CompileDir(dirname, dopt, opt)
if err != nil {
panic(err)
}
return m
}
// Parse given raw amber template string.
func (c *Compiler) Parse(input string) (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New(r.(string))
}
}()
parser, err := parser.StringParser(input)
if err != nil {
return
}
c.node = parser.Parse()
return
}
// Parse given raw amber template bytes, and the filename that belongs with it
func (c *Compiler) ParseData(input []byte, filename string) (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New(r.(string))
}
}()
parser, err := parser.ByteParser(input)
parser.SetFilename(filename)
if err != nil {
return
}
c.node = parser.Parse()
return
}
// ParseFile parses the amber template file in given path.
func (c *Compiler) ParseFile(filename string) (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New(r.(string))
}
}()
parser, err := parser.FileParser(filename)
if err != nil {
return
}
c.node = parser.Parse()
c.filename = filename
return
}
// Compile amber and create a Go Template (html/templates) instance.
// Necessary runtime functions will be injected and the template will be ready to be executed.
func (c *Compiler) Compile() (*template.Template, error) {
return c.CompileWithName(filepath.Base(c.filename))
}
// CompileWithName is the same as Compile, but allows to specify a name for the template.
func (c *Compiler) CompileWithName(name string) (*template.Template, error) {
return c.CompileWithTemplate(template.New(name))
}
// CompileWithTemplate is the same as Compile but allows to specify a template.
func (c *Compiler) CompileWithTemplate(t *template.Template) (*template.Template, error) {
data, err := c.CompileString()
if err != nil {
return nil, err
}
tpl, err := t.Funcs(FuncMap).Parse(data)
if err != nil {
return nil, err
}
return tpl, nil
}
// CompileWriter compiles amber and writes the Go Template source into given io.Writer instance.
// You would not be using this unless debugging / checking the output. Please use Compile
// method to obtain a template instance directly.
func (c *Compiler) CompileWriter(out io.Writer) (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New(r.(string))
}
}()
c.buffer = new(bytes.Buffer)
c.visit(c.node)
if c.buffer.Len() > 0 {
c.write("\n")
}
_, err = c.buffer.WriteTo(out)
return
}
// CompileString compiles the template and returns the Go Template source.
// You would not be using this unless debugging / checking the output. Please use Compile
// method to obtain a template instance directly.
func (c *Compiler) CompileString() (string, error) {
var buf bytes.Buffer
if err := c.CompileWriter(&buf); err != nil {
return "", err
}
result := buf.String()
return result, nil
}
func (c *Compiler) visit(node parser.Node) {
defer func() {
if r := recover(); r != nil {
if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
panic(r)
}
pos := node.Pos()
if len(pos.Filename) > 0 {
panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
} else {
panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
}
}
}()
switch node.(type) {
case *parser.Block:
c.visitBlock(node.(*parser.Block))
case *parser.Doctype:
c.visitDoctype(node.(*parser.Doctype))
case *parser.Comment:
c.visitComment(node.(*parser.Comment))
case *parser.Tag:
c.visitTag(node.(*parser.Tag))
case *parser.Text:
c.visitText(node.(*parser.Text))
case *parser.Condition:
c.visitCondition(node.(*parser.Condition))
case *parser.Each:
c.visitEach(node.(*parser.Each))
case *parser.Assignment:
c.visitAssignment(node.(*parser.Assignment))
case *parser.Mixin:
c.visitMixin(node.(*parser.Mixin))
case *parser.MixinCall:
c.visitMixinCall(node.(*parser.MixinCall))
}
}
func (c *Compiler) write(value string) {
c.buffer.WriteString(value)
}
func (c *Compiler) indent(offset int, newline bool) {
if !c.PrettyPrint {
return
}
if newline && c.buffer.Len() > 0 {
c.write("\n")
}
for i := 0; i < c.indentLevel+offset; i++ {
c.write("\t")
}
}
func (c *Compiler) tempvar() string {
c.tempvarIndex++
return "$__amber_" + strconv.Itoa(c.tempvarIndex)
}
func (c *Compiler) escape(input string) string {
return strings.Replace(strings.Replace(input, `\`, `\\`, -1), `"`, `\"`, -1)
}
func (c *Compiler) visitBlock(block *parser.Block) {
for _, node := range block.Children {
if _, ok := node.(*parser.Text); !block.CanInline() && ok {
c.indent(0, true)
}
c.visit(node)
}
}
func (c *Compiler) visitDoctype(doctype *parser.Doctype) {
c.write(doctype.String())
}
func (c *Compiler) visitComment(comment *parser.Comment) {
if comment.Silent {
return
}
c.indent(0, false)
if comment.Block == nil {
c.write(`{{unescaped "<!-- ` + c.escape(comment.Value) + ` -->"}}`)
} else {
c.write(`<!-- ` + comment.Value)
c.visitBlock(comment.Block)
c.write(` -->`)
}
}
func (c *Compiler) visitCondition(condition *parser.Condition) {
c.write(`{{if ` + c.visitRawInterpolation(condition.Expression) + `}}`)
c.visitBlock(condition.Positive)
if condition.Negative != nil {
c.write(`{{else}}`)
c.visitBlock(condition.Negative)
}
c.write(`{{end}}`)
}
func (c *Compiler) visitEach(each *parser.Each) {
if each.Block == nil {
return
}
if len(each.Y) == 0 {
c.write(`{{range ` + each.X + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
} else {
c.write(`{{range ` + each.X + `, ` + each.Y + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`)
}
c.visitBlock(each.Block)
c.write(`{{end}}`)
}
func (c *Compiler) visitAssignment(assgn *parser.Assignment) {
c.write(`{{` + assgn.X + ` := ` + c.visitRawInterpolation(assgn.Expression) + `}}`)
}
func (c *Compiler) visitTag(tag *parser.Tag) {
type attrib struct {
name string
value string
condition string
}
attribs := make(map[string]*attrib)
for _, item := range tag.Attributes {
attr := new(attrib)
attr.name = item.Name
if !item.IsRaw {
attr.value = c.visitInterpolation(item.Value)
} else if item.Value == "" {
attr.value = ""
} else {
attr.value = item.Value
}
if len(item.Condition) != 0 {
attr.condition = c.visitRawInterpolation(item.Condition)
}
if attr.name == "class" && attribs["class"] != nil {
prevclass := attribs["class"]
attr.value = ` ` + attr.value
if len(attr.condition) > 0 {
attr.value = `{{if ` + attr.condition + `}}` + attr.value + `{{end}}`
attr.condition = ""
}
if len(prevclass.condition) > 0 {
prevclass.value = `{{if ` + prevclass.condition + `}}` + prevclass.value + `{{end}}`
prevclass.condition = ""
}
prevclass.value = prevclass.value + attr.value
} else {
attribs[item.Name] = attr
}
}
keys := make([]string, 0, len(attribs))
for key := range attribs {
keys = append(keys, key)
}
sort.Strings(keys)
c.indent(0, true)
c.write("<" + tag.Name)
for _, name := range keys {
value := attribs[name]
if len(value.condition) > 0 {
c.write(`{{if ` + value.condition + `}}`)
}
if value.value == "" {
c.write(` ` + name)
} else {
c.write(` ` + name + `="` + value.value + `"`)
}
if len(value.condition) > 0 {
c.write(`{{end}}`)
}
}
if tag.IsSelfClosing() {
c.write(` />`)
} else {
c.write(`>`)
if tag.Block != nil {
if !tag.Block.CanInline() {
c.indentLevel++
}
c.visitBlock(tag.Block)
if !tag.Block.CanInline() {
c.indentLevel--
c.indent(0, true)
}
}
c.write(`</` + tag.Name + `>`)
}
}
var textInterpolateRegexp = regexp.MustCompile(`#\{(.*?)\}`)
var textEscapeRegexp = regexp.MustCompile(`\{\{(.*?)\}\}`)
func (c *Compiler) visitText(txt *parser.Text) {
value := textEscapeRegexp.ReplaceAllStringFunc(txt.Value, func(value string) string {
return `{{"{{"}}` + value[2:len(value)-2] + `{{"}}"}}`
})
value = textInterpolateRegexp.ReplaceAllStringFunc(value, func(value string) string {
return c.visitInterpolation(value[2 : len(value)-1])
})
lines := strings.Split(value, "\n")
for i := 0; i < len(lines); i++ {
c.write(lines[i])
if i < len(lines)-1 {
c.write("\n")
c.indent(0, false)
}
}
}
func (c *Compiler) visitInterpolation(value string) string {
return `{{` + c.visitRawInterpolation(value) + `}}`
}
func (c *Compiler) visitRawInterpolation(value string) string {
value = strings.Replace(value, "$", "__DOLLAR__", -1)
expr, err := gp.ParseExpr(value)
if err != nil {
panic("Unable to parse expression.")
}
value = strings.Replace(c.visitExpression(expr), "__DOLLAR__", "$", -1)
return value
}
func (c *Compiler) visitExpression(outerexpr ast.Expr) string {
stack := list.New()
pop := func() string {
if stack.Front() == nil {
return ""
}
val := stack.Front().Value.(string)
stack.Remove(stack.Front())
return val
}
var exec func(ast.Expr)
exec = func(expr ast.Expr) {
switch expr.(type) {
case *ast.BinaryExpr:
{
be := expr.(*ast.BinaryExpr)
exec(be.Y)
exec(be.X)
negate := false
name := c.tempvar()
c.write(`{{` + name + ` := `)
switch be.Op {
case gt.ADD:
c.write("__amber_add ")
case gt.SUB:
c.write("__amber_sub ")
case gt.MUL:
c.write("__amber_mul ")
case gt.QUO:
c.write("__amber_quo ")
case gt.REM:
c.write("__amber_rem ")
case gt.LAND:
c.write("and ")
case gt.LOR:
c.write("or ")
case gt.EQL:
c.write("__amber_eql ")
case gt.NEQ:
c.write("__amber_eql ")
negate = true
case gt.LSS:
c.write("__amber_lss ")
case gt.GTR:
c.write("__amber_gtr ")
case gt.LEQ:
c.write("__amber_gtr ")
negate = true
case gt.GEQ:
c.write("__amber_lss ")
negate = true
default:
panic("Unexpected operator!")
}
c.write(pop() + ` ` + pop() + `}}`)
if !negate {
stack.PushFront(name)
} else {
negname := c.tempvar()
c.write(`{{` + negname + ` := not ` + name + `}}`)
stack.PushFront(negname)
}
}
case *ast.UnaryExpr:
{
ue := expr.(*ast.UnaryExpr)
exec(ue.X)
name := c.tempvar()
c.write(`{{` + name + ` := `)
switch ue.Op {
case gt.SUB:
c.write("__amber_minus ")
case gt.ADD:
c.write("__amber_plus ")
case gt.NOT:
c.write("not ")
default:
panic("Unexpected operator!")
}
c.write(pop() + `}}`)
stack.PushFront(name)
}
case *ast.ParenExpr:
exec(expr.(*ast.ParenExpr).X)
case *ast.BasicLit:
stack.PushFront(expr.(*ast.BasicLit).Value)
case *ast.Ident:
name := expr.(*ast.Ident).Name
if len(name) >= len("__DOLLAR__") && name[:len("__DOLLAR__")] == "__DOLLAR__" {
if name == "__DOLLAR__" {
stack.PushFront(`.`)
} else {
stack.PushFront(`$` + expr.(*ast.Ident).Name[len("__DOLLAR__"):])
}
} else {
stack.PushFront(`.` + expr.(*ast.Ident).Name)
}
case *ast.SelectorExpr:
se := expr.(*ast.SelectorExpr)
exec(se.X)
x := pop()
if x == "." {
x = ""
}
name := c.tempvar()
c.write(`{{` + name + ` := ` + x + `.` + se.Sel.Name + `}}`)
stack.PushFront(name)
case *ast.CallExpr:
ce := expr.(*ast.CallExpr)
for i := len(ce.Args) - 1; i >= 0; i-- {
exec(ce.Args[i])
}
name := c.tempvar()
builtin := false
if ident, ok := ce.Fun.(*ast.Ident); ok {
for _, fname := range builtinFunctions {
if fname == ident.Name {
builtin = true
break
}
}
}
if builtin {
stack.PushFront(ce.Fun.(*ast.Ident).Name)
c.write(`{{` + name + ` := ` + pop())
} else {
exec(ce.Fun)
c.write(`{{` + name + ` := call ` + pop())
}
for i := 0; i < len(ce.Args); i++ {
c.write(` `)
c.write(pop())
}
c.write(`}}`)
stack.PushFront(name)
default:
panic("Unable to parse expression. Unsupported: " + reflect.TypeOf(expr).String())
}
}
exec(outerexpr)
return pop()
}
func (c *Compiler) visitMixin(mixin *parser.Mixin) {
c.mixins[mixin.Name] = mixin
}
func (c *Compiler) visitMixinCall(mixinCall *parser.MixinCall) {
mixin := c.mixins[mixinCall.Name]
for i, arg := range mixin.Args {
c.write(fmt.Sprintf(`{{%s := %s}}`, arg, c.visitRawInterpolation(mixinCall.Args[i])))
}
c.visitBlock(mixin.Block)
}

257
vendor/github.com/eknkc/amber/doc.go generated vendored
View file

@ -1,257 +0,0 @@
/*
Package amber is an elegant templating engine for Go Programming Language.
It is inspired from HAML and Jade.
Tags
A tag is simply a word:
html
is converted to
<html></html>
It is possible to add ID and CLASS attributes to tags:
div#main
span.time
are converted to
<div id="main"></div>
<span class="time"></span>
Any arbitrary attribute name / value pair can be added this way:
a[href="http://www.google.com"]
You can mix multiple attributes together
a#someid[href="/"][title="Main Page"].main.link Click Link
gets converted to
<a id="someid" class="main link" href="/" title="Main Page">Click Link</a>
It is also possible to define these attributes within the block of a tag
a
#someid
[href="/"]
[title="Main Page"]
.main
.link
| Click Link
Doctypes
To add a doctype, use `!!!` or `doctype` keywords:
!!! transitional
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
or use `doctype`
doctype 5
// <!DOCTYPE html>
Available options: `5`, `default`, `xml`, `transitional`, `strict`, `frameset`, `1.1`, `basic`, `mobile`
Tag Content
For single line tag text, you can just append the text after tag name:
p Testing!
would yield
<p>Testing!</p>
For multi line tag text, or nested tags, use indentation:
html
head
title Page Title
body
div#content
p
| This is a long page content
| These lines are all part of the parent p
a[href="/"] Go To Main Page
Data
Input template data can be reached by key names directly. For example, assuming the template has been
executed with following JSON data:
{
"Name": "Ekin",
"LastName": "Koc",
"Repositories": [
"amber",
"dateformat"
],
"Avatar": "/images/ekin.jpg",
"Friends": 17
}
It is possible to interpolate fields using `#{}`
p Welcome #{Name}!
would print
<p>Welcome Ekin!</p>
Attributes can have field names as well
a[title=Name][href="/ekin.koc"]
would print
<a title="Ekin" href="/ekin.koc"></a>
Expressions
Amber can expand basic expressions. For example, it is possible to concatenate strings with + operator:
p Welcome #{Name + " " + LastName}
Arithmetic expressions are also supported:
p You need #{50 - Friends} more friends to reach 50!
Expressions can be used within attributes
img[alt=Name + " " + LastName][src=Avatar]
Variables
It is possible to define dynamic variables within templates,
all variables must start with a $ character and can be assigned as in the following example:
div
$fullname = Name + " " + LastName
p Welcome #{$fullname}
If you need to access the supplied data itself (i.e. the object containing Name, LastName etc fields.) you can use `$` variable
p $.Name
Conditions
For conditional blocks, it is possible to use `if <expression>`
div
if Friends > 10
p You have more than 10 friends
else if Friends > 5
p You have more than 5 friends
else
p You need more friends
Again, it is possible to use arithmetic and boolean operators
div
if Name == "Ekin" && LastName == "Koc"
p Hey! I know you..
There is a special syntax for conditional attributes. Only block attributes can have conditions;
div
.hasfriends ? Friends > 0
This would yield a div with `hasfriends` class only if the `Friends > 0` condition holds. It is
perfectly fine to use the same method for other types of attributes:
div
#foo ? Name == "Ekin"
[bar=baz] ? len(Repositories) > 0
Iterations
It is possible to iterate over arrays and maps using `each`:
each $repo in Repositories
p #{$repo}
would print
p amber
p dateformat
It is also possible to iterate over values and indexes at the same time
each $i, $repo in Repositories
p
.even ? $i % 2 == 0
.odd ? $i % 2 == 1
Includes
A template can include other templates using `include`:
a.amber
p this is template a
b.amber
p this is template b
c.amber
div
include a
include b
gets compiled to
div
p this is template a
p this is template b
Inheritance
A template can inherit other templates. In order to inherit another template, an `extends` keyword should be used.
Parent template can define several named blocks and child template can modify the blocks.
master.amber
!!! 5
html
head
block meta
meta[name="description"][content="This is a great website"]
title
block title
| Default title
body
block content
subpage.amber
extends master
block title
| Some sub page!
block append meta
// This will be added after the description meta tag. It is also possible
// to prepend something to an existing block
meta[name="keywords"][content="foo bar"]
block content
div#main
p Some content here
License
(The MIT License)
Copyright (c) 2012 Ekin Koc <ekin@eknkc.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package amber

View file

@ -1,281 +0,0 @@
package parser
import "regexp"
import "strings"
var selfClosingTags = [...]string{
"meta",
"img",
"link",
"input",
"source",
"area",
"base",
"col",
"br",
"hr",
}
var doctypes = map[string]string{
"5": `<!DOCTYPE html>`,
"default": `<!DOCTYPE html>`,
"xml": `<?xml version="1.0" encoding="utf-8" ?>`,
"transitional": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`,
"strict": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`,
"frameset": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`,
"1.1": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
"basic": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">`,
"mobile": `<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">`,
}
type Node interface {
Pos() SourcePosition
}
type SourcePosition struct {
LineNum int
ColNum int
TokenLength int
Filename string
}
func (s *SourcePosition) Pos() SourcePosition {
return *s
}
type Doctype struct {
SourcePosition
Value string
}
func newDoctype(value string) *Doctype {
dt := new(Doctype)
dt.Value = value
return dt
}
func (d *Doctype) String() string {
if defined := doctypes[d.Value]; len(defined) != 0 {
return defined
}
return `<!DOCTYPE ` + d.Value + `>`
}
type Comment struct {
SourcePosition
Value string
Block *Block
Silent bool
}
func newComment(value string) *Comment {
dt := new(Comment)
dt.Value = value
dt.Block = nil
dt.Silent = false
return dt
}
type Text struct {
SourcePosition
Value string
Raw bool
}
func newText(value string, raw bool) *Text {
dt := new(Text)
dt.Value = value
dt.Raw = raw
return dt
}
type Block struct {
SourcePosition
Children []Node
}
func newBlock() *Block {
block := new(Block)
block.Children = make([]Node, 0)
return block
}
func (b *Block) push(node Node) {
b.Children = append(b.Children, node)
}
func (b *Block) pushFront(node Node) {
b.Children = append([]Node{node}, b.Children...)
}
func (b *Block) CanInline() bool {
if len(b.Children) == 0 {
return true
}
allText := true
for _, child := range b.Children {
if txt, ok := child.(*Text); !ok || txt.Raw {
allText = false
break
}
}
return allText
}
const (
NamedBlockDefault = iota
NamedBlockAppend
NamedBlockPrepend
)
type NamedBlock struct {
Block
Name string
Modifier int
}
func newNamedBlock(name string) *NamedBlock {
bb := new(NamedBlock)
bb.Name = name
bb.Block.Children = make([]Node, 0)
bb.Modifier = NamedBlockDefault
return bb
}
type Attribute struct {
SourcePosition
Name string
Value string
IsRaw bool
Condition string
}
type Tag struct {
SourcePosition
Block *Block
Name string
IsInterpolated bool
Attributes []Attribute
}
func newTag(name string) *Tag {
tag := new(Tag)
tag.Block = nil
tag.Name = name
tag.Attributes = make([]Attribute, 0)
tag.IsInterpolated = false
return tag
}
func (t *Tag) IsSelfClosing() bool {
for _, tag := range selfClosingTags {
if tag == t.Name {
return true
}
}
return false
}
func (t *Tag) IsRawText() bool {
return t.Name == "style" || t.Name == "script"
}
type Condition struct {
SourcePosition
Positive *Block
Negative *Block
Expression string
}
func newCondition(exp string) *Condition {
cond := new(Condition)
cond.Expression = exp
return cond
}
type Each struct {
SourcePosition
X string
Y string
Expression string
Block *Block
}
func newEach(exp string) *Each {
each := new(Each)
each.Expression = exp
return each
}
type Assignment struct {
SourcePosition
X string
Expression string
}
func newAssignment(x, expression string) *Assignment {
assgn := new(Assignment)
assgn.X = x
assgn.Expression = expression
return assgn
}
type Mixin struct {
SourcePosition
Block *Block
Name string
Args []string
}
func newMixin(name, args string) *Mixin {
mixin := new(Mixin)
mixin.Name = name
delExp := regexp.MustCompile(`,\s`)
mixin.Args = delExp.Split(args, -1)
for i := 0; i < len(mixin.Args); i++ {
mixin.Args[i] = strings.TrimSpace(mixin.Args[i])
if mixin.Args[i] == "" {
mixin.Args = append(mixin.Args[:i], mixin.Args[i+1:]...)
i--
}
}
return mixin
}
type MixinCall struct {
SourcePosition
Name string
Args []string
}
func newMixinCall(name, args string) *MixinCall {
mixinCall := new(MixinCall)
mixinCall.Name = name
const t = "%s"
quoteExp := regexp.MustCompile(`"(.*?)"`)
delExp := regexp.MustCompile(`,\s`)
quotes := quoteExp.FindAllString(args, -1)
replaced := quoteExp.ReplaceAllString(args, t)
mixinCall.Args = delExp.Split(replaced, -1)
qi := 0
for i, arg := range mixinCall.Args {
if arg == t {
mixinCall.Args[i] = quotes[qi]
qi++
}
}
return mixinCall
}

View file

@ -1,454 +0,0 @@
package parser
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
)
type Parser struct {
scanner *scanner
filename string
currenttoken *token
namedBlocks map[string]*NamedBlock
parent *Parser
result *Block
}
func newParser(rdr io.Reader) *Parser {
p := new(Parser)
p.scanner = newScanner(rdr)
p.namedBlocks = make(map[string]*NamedBlock)
return p
}
func StringParser(input string) (*Parser, error) {
return newParser(bytes.NewReader([]byte(input))), nil
}
func ByteParser(input []byte) (*Parser, error) {
return newParser(bytes.NewReader(input)), nil
}
func (p *Parser) SetFilename(filename string) {
p.filename = filename
}
func FileParser(filename string) (*Parser, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
parser := newParser(bytes.NewReader(data))
parser.filename = filename
return parser, nil
}
func (p *Parser) Parse() *Block {
if p.result != nil {
return p.result
}
defer func() {
if r := recover(); r != nil {
if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" {
panic(r)
}
pos := p.pos()
if len(pos.Filename) > 0 {
panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength))
} else {
panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength))
}
}
}()
block := newBlock()
p.advance()
for {
if p.currenttoken == nil || p.currenttoken.Kind == tokEOF {
break
}
if p.currenttoken.Kind == tokBlank {
p.advance()
continue
}
block.push(p.parse())
}
if p.parent != nil {
p.parent.Parse()
for _, prev := range p.parent.namedBlocks {
ours := p.namedBlocks[prev.Name]
if ours == nil {
// Put a copy of the named block into current context, so that sub-templates can use the block
p.namedBlocks[prev.Name] = prev
continue
}
top := findTopmostParentWithNamedBlock(p, prev.Name)
nb := top.namedBlocks[prev.Name]
switch ours.Modifier {
case NamedBlockAppend:
for i := 0; i < len(ours.Children); i++ {
nb.push(ours.Children[i])
}
case NamedBlockPrepend:
for i := len(ours.Children) - 1; i >= 0; i-- {
nb.pushFront(ours.Children[i])
}
default:
nb.Children = ours.Children
}
}
block = p.parent.result
}
p.result = block
return block
}
func (p *Parser) pos() SourcePosition {
pos := p.scanner.Pos()
pos.Filename = p.filename
return pos
}
func (p *Parser) parseRelativeFile(filename string) *Parser {
if len(p.filename) == 0 {
panic("Unable to import or extend " + filename + " in a non filesystem based parser.")
}
filename = filepath.Join(filepath.Dir(p.filename), filename)
if strings.IndexRune(filepath.Base(filename), '.') < 0 {
filename = filename + ".amber"
}
parser, err := FileParser(filename)
if err != nil {
panic("Unable to read " + filename + ", Error: " + string(err.Error()))
}
return parser
}
func (p *Parser) parse() Node {
switch p.currenttoken.Kind {
case tokDoctype:
return p.parseDoctype()
case tokComment:
return p.parseComment()
case tokText:
return p.parseText()
case tokIf:
return p.parseIf()
case tokEach:
return p.parseEach()
case tokImport:
return p.parseImport()
case tokTag:
return p.parseTag()
case tokAssignment:
return p.parseAssignment()
case tokNamedBlock:
return p.parseNamedBlock()
case tokExtends:
return p.parseExtends()
case tokIndent:
return p.parseBlock(nil)
case tokMixin:
return p.parseMixin()
case tokMixinCall:
return p.parseMixinCall()
}
panic(fmt.Sprintf("Unexpected token: %d", p.currenttoken.Kind))
}
func (p *Parser) expect(typ rune) *token {
if p.currenttoken.Kind != typ {
panic("Unexpected token!")
}
curtok := p.currenttoken
p.advance()
return curtok
}
func (p *Parser) advance() {
p.currenttoken = p.scanner.Next()
}
func (p *Parser) parseExtends() *Block {
if p.parent != nil {
panic("Unable to extend multiple parent templates.")
}
tok := p.expect(tokExtends)
parser := p.parseRelativeFile(tok.Value)
parser.Parse()
p.parent = parser
return newBlock()
}
func (p *Parser) parseBlock(parent Node) *Block {
p.expect(tokIndent)
block := newBlock()
block.SourcePosition = p.pos()
for {
if p.currenttoken == nil || p.currenttoken.Kind == tokEOF || p.currenttoken.Kind == tokOutdent {
break
}
if p.currenttoken.Kind == tokBlank {
p.advance()
continue
}
if p.currenttoken.Kind == tokId ||
p.currenttoken.Kind == tokClassName ||
p.currenttoken.Kind == tokAttribute {
if tag, ok := parent.(*Tag); ok {
attr := p.expect(p.currenttoken.Kind)
cond := attr.Data["Condition"]
switch attr.Kind {
case tokId:
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", attr.Value, true, cond})
case tokClassName:
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", attr.Value, true, cond})
case tokAttribute:
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", cond})
}
continue
} else {
panic("Conditional attributes must be placed immediately within a parent tag.")
}
}
block.push(p.parse())
}
p.expect(tokOutdent)
return block
}
func (p *Parser) parseIf() *Condition {
tok := p.expect(tokIf)
cnd := newCondition(tok.Value)
cnd.SourcePosition = p.pos()
readmore:
switch p.currenttoken.Kind {
case tokIndent:
cnd.Positive = p.parseBlock(cnd)
goto readmore
case tokElse:
p.expect(tokElse)
if p.currenttoken.Kind == tokIf {
cnd.Negative = newBlock()
cnd.Negative.push(p.parseIf())
} else if p.currenttoken.Kind == tokIndent {
cnd.Negative = p.parseBlock(cnd)
} else {
panic("Unexpected token!")
}
goto readmore
}
return cnd
}
func (p *Parser) parseEach() *Each {
tok := p.expect(tokEach)
ech := newEach(tok.Value)
ech.SourcePosition = p.pos()
ech.X = tok.Data["X"]
ech.Y = tok.Data["Y"]
if p.currenttoken.Kind == tokIndent {
ech.Block = p.parseBlock(ech)
}
return ech
}
func (p *Parser) parseImport() *Block {
tok := p.expect(tokImport)
node := p.parseRelativeFile(tok.Value).Parse()
node.SourcePosition = p.pos()
return node
}
func (p *Parser) parseNamedBlock() *Block {
tok := p.expect(tokNamedBlock)
if p.namedBlocks[tok.Value] != nil {
panic("Multiple definitions of named blocks are not permitted. Block " + tok.Value + " has been re defined.")
}
block := newNamedBlock(tok.Value)
block.SourcePosition = p.pos()
if tok.Data["Modifier"] == "append" {
block.Modifier = NamedBlockAppend
} else if tok.Data["Modifier"] == "prepend" {
block.Modifier = NamedBlockPrepend
}
if p.currenttoken.Kind == tokIndent {
block.Block = *(p.parseBlock(nil))
}
p.namedBlocks[block.Name] = block
if block.Modifier == NamedBlockDefault {
return &block.Block
}
return newBlock()
}
func (p *Parser) parseDoctype() *Doctype {
tok := p.expect(tokDoctype)
node := newDoctype(tok.Value)
node.SourcePosition = p.pos()
return node
}
func (p *Parser) parseComment() *Comment {
tok := p.expect(tokComment)
cmnt := newComment(tok.Value)
cmnt.SourcePosition = p.pos()
cmnt.Silent = tok.Data["Mode"] == "silent"
if p.currenttoken.Kind == tokIndent {
cmnt.Block = p.parseBlock(cmnt)
}
return cmnt
}
func (p *Parser) parseText() *Text {
tok := p.expect(tokText)
node := newText(tok.Value, tok.Data["Mode"] == "raw")
node.SourcePosition = p.pos()
return node
}
func (p *Parser) parseAssignment() *Assignment {
tok := p.expect(tokAssignment)
node := newAssignment(tok.Data["X"], tok.Value)
node.SourcePosition = p.pos()
return node
}
func (p *Parser) parseTag() *Tag {
tok := p.expect(tokTag)
tag := newTag(tok.Value)
tag.SourcePosition = p.pos()
ensureBlock := func() {
if tag.Block == nil {
tag.Block = newBlock()
}
}
readmore:
switch p.currenttoken.Kind {
case tokIndent:
if tag.IsRawText() {
p.scanner.readRaw = true
}
block := p.parseBlock(tag)
if tag.Block == nil {
tag.Block = block
} else {
for _, c := range block.Children {
tag.Block.push(c)
}
}
case tokId:
id := p.expect(tokId)
if len(id.Data["Condition"]) > 0 {
panic("Conditional attributes must be placed in a block within a tag.")
}
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "id", id.Value, true, ""})
goto readmore
case tokClassName:
cls := p.expect(tokClassName)
if len(cls.Data["Condition"]) > 0 {
panic("Conditional attributes must be placed in a block within a tag.")
}
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), "class", cls.Value, true, ""})
goto readmore
case tokAttribute:
attr := p.expect(tokAttribute)
if len(attr.Data["Condition"]) > 0 {
panic("Conditional attributes must be placed in a block within a tag.")
}
tag.Attributes = append(tag.Attributes, Attribute{p.pos(), attr.Value, attr.Data["Content"], attr.Data["Mode"] == "raw", ""})
goto readmore
case tokText:
if p.currenttoken.Data["Mode"] != "piped" {
ensureBlock()
tag.Block.pushFront(p.parseText())
goto readmore
}
}
return tag
}
func (p *Parser) parseMixin() *Mixin {
tok := p.expect(tokMixin)
mixin := newMixin(tok.Value, tok.Data["Args"])
mixin.SourcePosition = p.pos()
if p.currenttoken.Kind == tokIndent {
mixin.Block = p.parseBlock(mixin)
}
return mixin
}
func (p *Parser) parseMixinCall() *MixinCall {
tok := p.expect(tokMixinCall)
mixinCall := newMixinCall(tok.Value, tok.Data["Args"])
mixinCall.SourcePosition = p.pos()
return mixinCall
}
func findTopmostParentWithNamedBlock(p *Parser, name string) *Parser {
top := p
for {
if top.namedBlocks[name] == nil {
return nil
}
if top.parent == nil {
return top
}
if top.parent.namedBlocks[name] != nil {
top = top.parent
}
}
}

View file

@ -1,501 +0,0 @@
package parser
import (
"bufio"
"container/list"
"fmt"
"io"
"regexp"
)
const (
tokEOF = -(iota + 1)
tokDoctype
tokComment
tokIndent
tokOutdent
tokBlank
tokId
tokClassName
tokTag
tokText
tokAttribute
tokIf
tokElse
tokEach
tokAssignment
tokImport
tokNamedBlock
tokExtends
tokMixin
tokMixinCall
)
const (
scnNewLine = iota
scnLine
scnEOF
)
type scanner struct {
reader *bufio.Reader
indentStack *list.List
stash *list.List
state int32
buffer string
line int
col int
lastTokenLine int
lastTokenCol int
lastTokenSize int
readRaw bool
}
type token struct {
Kind rune
Value string
Data map[string]string
}
func newScanner(r io.Reader) *scanner {
s := new(scanner)
s.reader = bufio.NewReader(r)
s.indentStack = list.New()
s.stash = list.New()
s.state = scnNewLine
s.line = -1
s.col = 0
return s
}
func (s *scanner) Pos() SourcePosition {
return SourcePosition{s.lastTokenLine + 1, s.lastTokenCol + 1, s.lastTokenSize, ""}
}
// Returns next token found in buffer
func (s *scanner) Next() *token {
if s.readRaw {
s.readRaw = false
return s.NextRaw()
}
s.ensureBuffer()
if stashed := s.stash.Front(); stashed != nil {
tok := stashed.Value.(*token)
s.stash.Remove(stashed)
return tok
}
switch s.state {
case scnEOF:
if outdent := s.indentStack.Back(); outdent != nil {
s.indentStack.Remove(outdent)
return &token{tokOutdent, "", nil}
}
return &token{tokEOF, "", nil}
case scnNewLine:
s.state = scnLine
if tok := s.scanIndent(); tok != nil {
return tok
}
return s.Next()
case scnLine:
if tok := s.scanMixin(); tok != nil {
return tok
}
if tok := s.scanMixinCall(); tok != nil {
return tok
}
if tok := s.scanDoctype(); tok != nil {
return tok
}
if tok := s.scanCondition(); tok != nil {
return tok
}
if tok := s.scanEach(); tok != nil {
return tok
}
if tok := s.scanImport(); tok != nil {
return tok
}
if tok := s.scanExtends(); tok != nil {
return tok
}
if tok := s.scanBlock(); tok != nil {
return tok
}
if tok := s.scanAssignment(); tok != nil {
return tok
}
if tok := s.scanTag(); tok != nil {
return tok
}
if tok := s.scanId(); tok != nil {
return tok
}
if tok := s.scanClassName(); tok != nil {
return tok
}
if tok := s.scanAttribute(); tok != nil {
return tok
}
if tok := s.scanComment(); tok != nil {
return tok
}
if tok := s.scanText(); tok != nil {
return tok
}
}
return nil
}
func (s *scanner) NextRaw() *token {
result := ""
level := 0
for {
s.ensureBuffer()
switch s.state {
case scnEOF:
return &token{tokText, result, map[string]string{"Mode": "raw"}}
case scnNewLine:
s.state = scnLine
if tok := s.scanIndent(); tok != nil {
if tok.Kind == tokIndent {
level++
} else if tok.Kind == tokOutdent {
level--
} else {
result = result + "\n"
continue
}
if level < 0 {
s.stash.PushBack(&token{tokOutdent, "", nil})
if len(result) > 0 && result[len(result)-1] == '\n' {
result = result[:len(result)-1]
}
return &token{tokText, result, map[string]string{"Mode": "raw"}}
}
}
case scnLine:
if len(result) > 0 {
result = result + "\n"
}
for i := 0; i < level; i++ {
result += "\t"
}
result = result + s.buffer
s.consume(len(s.buffer))
}
}
return nil
}
var rgxIndent = regexp.MustCompile(`^(\s+)`)
func (s *scanner) scanIndent() *token {
if len(s.buffer) == 0 {
return &token{tokBlank, "", nil}
}
var head *list.Element
for head = s.indentStack.Front(); head != nil; head = head.Next() {
value := head.Value.(*regexp.Regexp)
if match := value.FindString(s.buffer); len(match) != 0 {
s.consume(len(match))
} else {
break
}
}
newIndent := rgxIndent.FindString(s.buffer)
if len(newIndent) != 0 && head == nil {
s.indentStack.PushBack(regexp.MustCompile(regexp.QuoteMeta(newIndent)))
s.consume(len(newIndent))
return &token{tokIndent, newIndent, nil}
}
if len(newIndent) == 0 && head != nil {
for head != nil {
next := head.Next()
s.indentStack.Remove(head)
if next == nil {
return &token{tokOutdent, "", nil}
} else {
s.stash.PushBack(&token{tokOutdent, "", nil})
}
head = next
}
}
if len(newIndent) != 0 && head != nil {
panic("Mismatching indentation. Please use a coherent indent schema.")
}
return nil
}
var rgxDoctype = regexp.MustCompile(`^(!!!|doctype)\s*(.*)`)
func (s *scanner) scanDoctype() *token {
if sm := rgxDoctype.FindStringSubmatch(s.buffer); len(sm) != 0 {
if len(sm[2]) == 0 {
sm[2] = "html"
}
s.consume(len(sm[0]))
return &token{tokDoctype, sm[2], nil}
}
return nil
}
var rgxIf = regexp.MustCompile(`^if\s+(.+)$`)
var rgxElse = regexp.MustCompile(`^else\s*`)
func (s *scanner) scanCondition() *token {
if sm := rgxIf.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokIf, sm[1], nil}
}
if sm := rgxElse.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokElse, "", nil}
}
return nil
}
var rgxEach = regexp.MustCompile(`^each\s+(\$[\w0-9\-_]*)(?:\s*,\s*(\$[\w0-9\-_]*))?\s+in\s+(.+)$`)
func (s *scanner) scanEach() *token {
if sm := rgxEach.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokEach, sm[3], map[string]string{"X": sm[1], "Y": sm[2]}}
}
return nil
}
var rgxAssignment = regexp.MustCompile(`^(\$[\w0-9\-_]*)?\s*=\s*(.+)$`)
func (s *scanner) scanAssignment() *token {
if sm := rgxAssignment.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokAssignment, sm[2], map[string]string{"X": sm[1]}}
}
return nil
}
var rgxComment = regexp.MustCompile(`^\/\/(-)?\s*(.*)$`)
func (s *scanner) scanComment() *token {
if sm := rgxComment.FindStringSubmatch(s.buffer); len(sm) != 0 {
mode := "embed"
if len(sm[1]) != 0 {
mode = "silent"
}
s.consume(len(sm[0]))
return &token{tokComment, sm[2], map[string]string{"Mode": mode}}
}
return nil
}
var rgxId = regexp.MustCompile(`^#([\w-]+)(?:\s*\?\s*(.*)$)?`)
func (s *scanner) scanId() *token {
if sm := rgxId.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokId, sm[1], map[string]string{"Condition": sm[2]}}
}
return nil
}
var rgxClassName = regexp.MustCompile(`^\.([\w-]+)(?:\s*\?\s*(.*)$)?`)
func (s *scanner) scanClassName() *token {
if sm := rgxClassName.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokClassName, sm[1], map[string]string{"Condition": sm[2]}}
}
return nil
}
var rgxAttribute = regexp.MustCompile(`^\[([\w\-]+)\s*(?:=\s*(\"([^\"\\]*)\"|([^\]]+)))?\](?:\s*\?\s*(.*)$)?`)
func (s *scanner) scanAttribute() *token {
if sm := rgxAttribute.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
if len(sm[3]) != 0 || sm[2] == "" {
return &token{tokAttribute, sm[1], map[string]string{"Content": sm[3], "Mode": "raw", "Condition": sm[5]}}
}
return &token{tokAttribute, sm[1], map[string]string{"Content": sm[4], "Mode": "expression", "Condition": sm[5]}}
}
return nil
}
var rgxImport = regexp.MustCompile(`^import\s+([0-9a-zA-Z_\-\. \/]*)$`)
func (s *scanner) scanImport() *token {
if sm := rgxImport.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokImport, sm[1], nil}
}
return nil
}
var rgxExtends = regexp.MustCompile(`^extends\s+([0-9a-zA-Z_\-\. \/]*)$`)
func (s *scanner) scanExtends() *token {
if sm := rgxExtends.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokExtends, sm[1], nil}
}
return nil
}
var rgxBlock = regexp.MustCompile(`^block\s+(?:(append|prepend)\s+)?([0-9a-zA-Z_\-\. \/]*)$`)
func (s *scanner) scanBlock() *token {
if sm := rgxBlock.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokNamedBlock, sm[2], map[string]string{"Modifier": sm[1]}}
}
return nil
}
var rgxTag = regexp.MustCompile(`^(\w[-:\w]*)`)
func (s *scanner) scanTag() *token {
if sm := rgxTag.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokTag, sm[1], nil}
}
return nil
}
var rgxMixin = regexp.MustCompile(`^mixin ([a-zA-Z_]+\w*)(\(((\$\w*(,\s)?)*)\))?$`)
func (s *scanner) scanMixin() *token {
if sm := rgxMixin.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokMixin, sm[1], map[string]string{"Args": sm[3]}}
}
return nil
}
var rgxMixinCall = regexp.MustCompile(`^\+([A-Za-z_]+\w*)(\((.+(,\s)?)*\))?$`)
func (s *scanner) scanMixinCall() *token {
if sm := rgxMixinCall.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
return &token{tokMixinCall, sm[1], map[string]string{"Args": sm[3]}}
}
return nil
}
var rgxText = regexp.MustCompile(`^(\|)? ?(.*)$`)
func (s *scanner) scanText() *token {
if sm := rgxText.FindStringSubmatch(s.buffer); len(sm) != 0 {
s.consume(len(sm[0]))
mode := "inline"
if sm[1] == "|" {
mode = "piped"
}
return &token{tokText, sm[2], map[string]string{"Mode": mode}}
}
return nil
}
// Moves position forward, and removes beginning of s.buffer (len bytes)
func (s *scanner) consume(runes int) {
if len(s.buffer) < runes {
panic(fmt.Sprintf("Unable to consume %d runes from buffer.", runes))
}
s.lastTokenLine = s.line
s.lastTokenCol = s.col
s.lastTokenSize = runes
s.buffer = s.buffer[runes:]
s.col += runes
}
// Reads string into s.buffer
func (s *scanner) ensureBuffer() {
if len(s.buffer) > 0 {
return
}
buf, err := s.reader.ReadString('\n')
if err != nil && err != io.EOF {
panic(err)
} else if err != nil && len(buf) == 0 {
s.state = scnEOF
} else {
// endline "LF only" or "\n" use Unix, Linux, modern MacOS X, FreeBSD, BeOS, RISC OS
if buf[len(buf)-1] == '\n' {
buf = buf[:len(buf)-1]
}
// endline "CR+LF" or "\r\n" use internet protocols, DEC RT-11, Windows, CP/M, MS-DOS, OS/2, Symbian OS
if len(buf) > 0 && buf[len(buf)-1] == '\r' {
buf = buf[:len(buf)-1]
}
s.state = scnNewLine
s.buffer = buf
s.line += 1
s.col = 0
}
}

View file

@ -1,287 +0,0 @@
package amber
import (
"encoding/json"
"fmt"
"html/template"
"reflect"
)
var FuncMap = template.FuncMap{
"__amber_add": runtime_add,
"__amber_sub": runtime_sub,
"__amber_mul": runtime_mul,
"__amber_quo": runtime_quo,
"__amber_rem": runtime_rem,
"__amber_minus": runtime_minus,
"__amber_plus": runtime_plus,
"__amber_eql": runtime_eql,
"__amber_gtr": runtime_gtr,
"__amber_lss": runtime_lss,
"json": runtime_json,
"unescaped": runtime_unescaped,
}
func runtime_add(x, y interface{}) interface{} {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() + vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) + vy.Float()
case reflect.String:
return fmt.Sprintf("%d%s", vx.Int(), vy.String())
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() + float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() + vy.Float()
case reflect.String:
return fmt.Sprintf("%f%s", vx.Float(), vy.String())
}
}
case reflect.String:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return fmt.Sprintf("%s%d", vx.String(), vy.Int())
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%s%f", vx.String(), vy.Float())
case reflect.String:
return fmt.Sprintf("%s%s", vx.String(), vy.String())
}
}
}
return "<nil>"
}
func runtime_sub(x, y interface{}) interface{} {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() - vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) - vy.Float()
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() - float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() - vy.Float()
}
}
}
return "<nil>"
}
func runtime_mul(x, y interface{}) interface{} {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() * vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) * vy.Float()
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() * float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() * vy.Float()
}
}
}
return "<nil>"
}
func runtime_quo(x, y interface{}) interface{} {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() / vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) / vy.Float()
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() / float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() / vy.Float()
}
}
}
return "<nil>"
}
func runtime_rem(x, y interface{}) interface{} {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() % vy.Int()
}
}
}
return "<nil>"
}
func runtime_minus(x interface{}) interface{} {
vx := reflect.ValueOf(x)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return -vx.Int()
case reflect.Float32, reflect.Float64:
return -vx.Float()
}
return "<nil>"
}
func runtime_plus(x interface{}) interface{} {
vx := reflect.ValueOf(x)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return +vx.Int()
case reflect.Float32, reflect.Float64:
return +vx.Float()
}
return "<nil>"
}
func runtime_eql(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() == vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) == vy.Float()
case reflect.String:
return fmt.Sprintf("%d", vx.Int()) == vy.String()
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() == float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() == vy.Float()
case reflect.String:
return fmt.Sprintf("%f", vx.Float()) == vy.String()
}
}
case reflect.String:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.String() == fmt.Sprintf("%d", vy.Int())
case reflect.Float32, reflect.Float64:
return vx.String() == fmt.Sprintf("%f", vy.Float())
case reflect.String:
return vx.String() == fmt.Sprintf("%s", vy.String())
}
}
case reflect.Bool:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Bool() && vy.Int() != 0
case reflect.Bool:
return vx.Bool() == vy.Bool()
}
}
}
return false
}
func runtime_lss(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
switch vx.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Int() < vy.Int()
case reflect.Float32, reflect.Float64:
return float64(vx.Int()) < vy.Float()
case reflect.String:
return fmt.Sprintf("%d", vx.Int()) < vy.String()
}
}
case reflect.Float32, reflect.Float64:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.Float() < float64(vy.Int())
case reflect.Float32, reflect.Float64:
return vx.Float() < vy.Float()
case reflect.String:
return fmt.Sprintf("%f", vx.Float()) < vy.String()
}
}
case reflect.String:
{
switch vy.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16, reflect.Int8:
return vx.String() < fmt.Sprintf("%d", vy.Int())
case reflect.Float32, reflect.Float64:
return vx.String() < fmt.Sprintf("%f", vy.Float())
case reflect.String:
return vx.String() < vy.String()
}
}
}
return false
}
func runtime_gtr(x, y interface{}) bool {
return !runtime_lss(x, y) && !runtime_eql(x, y)
}
func runtime_json(x interface{}) (res string, err error) {
bres, err := json.Marshal(x)
res = string(bres)
return
}
func runtime_unescaped(x string) interface{} {
return template.HTML(x)
}

View file

@ -1,23 +0,0 @@
Copyright (c) 2014, Elazar Leibovich
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.
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 HOLDER 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,46 +0,0 @@
# go-bindata-assetfs
Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`.
[GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs)
### Installation
Install with
$ go get github.com/jteeuwen/go-bindata/...
$ go get github.com/elazarl/go-bindata-assetfs/...
### Creating embedded data
Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage,
instead of running `go-bindata` run `go-bindata-assetfs`.
The tool will create a `bindata_assetfs.go` file, which contains the embedded data.
A typical use case is
$ go-bindata-assetfs data/...
### Using assetFS in your code
The generated file provides an `assetFS()` function that returns a `http.Filesystem`
wrapping the embedded files. What you usually want to do is:
http.Handle("/", http.FileServer(assetFS()))
This would run an HTTP server serving the embedded files.
## Without running binary tool
You can always just run the `go-bindata` tool, and then
use
import "github.com/elazarl/go-bindata-assetfs"
...
http.Handle("/",
http.FileServer(
&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
to serve files embedded from the `data` directory.

View file

@ -1,147 +0,0 @@
package assetfs
import (
"bytes"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"time"
)
var (
fileTimestamp = time.Now()
)
// FakeFile implements os.FileInfo interface for a given path and size
type FakeFile struct {
// Path is the path of this file
Path string
// Dir marks of the path is a directory
Dir bool
// Len is the length of the fake file, zero if it is a directory
Len int64
}
func (f *FakeFile) Name() string {
_, name := filepath.Split(f.Path)
return name
}
func (f *FakeFile) Mode() os.FileMode {
mode := os.FileMode(0644)
if f.Dir {
return mode | os.ModeDir
}
return mode
}
func (f *FakeFile) ModTime() time.Time {
return fileTimestamp
}
func (f *FakeFile) Size() int64 {
return f.Len
}
func (f *FakeFile) IsDir() bool {
return f.Mode().IsDir()
}
func (f *FakeFile) Sys() interface{} {
return nil
}
// AssetFile implements http.File interface for a no-directory file with content
type AssetFile struct {
*bytes.Reader
io.Closer
FakeFile
}
func NewAssetFile(name string, content []byte) *AssetFile {
return &AssetFile{
bytes.NewReader(content),
ioutil.NopCloser(nil),
FakeFile{name, false, int64(len(content))}}
}
func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, errors.New("not a directory")
}
func (f *AssetFile) Size() int64 {
return f.FakeFile.Size()
}
func (f *AssetFile) Stat() (os.FileInfo, error) {
return f, nil
}
// AssetDirectory implements http.File interface for a directory
type AssetDirectory struct {
AssetFile
ChildrenRead int
Children []os.FileInfo
}
func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
fileinfos := make([]os.FileInfo, 0, len(children))
for _, child := range children {
_, err := fs.AssetDir(filepath.Join(name, child))
fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0})
}
return &AssetDirectory{
AssetFile{
bytes.NewReader(nil),
ioutil.NopCloser(nil),
FakeFile{name, true, 0},
},
0,
fileinfos}
}
func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
if count <= 0 {
return f.Children, nil
}
if f.ChildrenRead+count > len(f.Children) {
count = len(f.Children) - f.ChildrenRead
}
rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
f.ChildrenRead += count
return rv, nil
}
func (f *AssetDirectory) Stat() (os.FileInfo, error) {
return f, nil
}
// AssetFS implements http.FileSystem, allowing
// embedded files to be served from net/http package.
type AssetFS struct {
// Asset should return content of file in path if exists
Asset func(path string) ([]byte, error)
// AssetDir should return list of files in the path
AssetDir func(path string) ([]string, error)
// Prefix would be prepended to http requests
Prefix string
}
func (fs *AssetFS) Open(name string) (http.File, error) {
name = path.Join(fs.Prefix, name)
if len(name) > 0 && name[0] == '/' {
name = name[1:]
}
if children, err := fs.AssetDir(name); err == nil {
return NewAssetDirectory(name, children, fs), nil
}
b, err := fs.Asset(name)
if err != nil {
return nil, err
}
return NewAssetFile(name, b), nil
}

View file

@ -1,13 +0,0 @@
// assetfs allows packages to serve static content embedded
// with the go-bindata tool with the standard net/http package.
//
// See https://github.com/jteeuwen/go-bindata for more information
// about embedding binary data with go-bindata.
//
// Usage example, after running
// $ go-bindata data/...
// use:
// http.Handle("/",
// http.FileServer(
// &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
package assetfs

View file

@ -1,202 +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 2014 Sam Alba <sam.alba@gmail.com>
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,98 +0,0 @@
Docker client library in Go
===========================
[![GoDoc](http://godoc.org/github.com/samalba/dockerclient?status.png)](http://godoc.org/github.com/samalba/dockerclient)
Well maintained docker client library.
# How to use it?
Here is an example showing how to use it:
```go
package main
import (
"github.com/samalba/dockerclient"
"log"
"time"
"os"
)
// Callback used to listen to Docker's events
func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {
log.Printf("Received event: %#v\n", *event)
}
func main() {
// Init the client
docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)
// Get only running containers
containers, err := docker.ListContainers(false, false, "")
if err != nil {
log.Fatal(err)
}
for _, c := range containers {
log.Println(c.Id, c.Names)
}
// Inspect the first container returned
if len(containers) > 0 {
id := containers[0].Id
info, _ := docker.InspectContainer(id)
log.Println(info)
}
// Build a docker image
// some.tar contains the build context (Dockerfile any any files it needs to add/copy)
dockerBuildContext, err := os.Open("some.tar")
defer dockerBuildContext.Close()
buildImageConfig := &dockerclient.BuildImage{
Context: dockerBuildContext,
RepoName: "your_image_name",
SuppressOutput: false,
}
reader, err := docker.BuildImage(buildImageConfig)
if err != nil {
log.Fatal(err)
}
// Create a container
containerConfig := &dockerclient.ContainerConfig{
Image: "ubuntu:14.04",
Cmd: []string{"bash"},
AttachStdin: true,
Tty: true}
containerId, err := docker.CreateContainer(containerConfig, "foobar", nil)
if err != nil {
log.Fatal(err)
}
// Start the container
hostConfig := &dockerclient.HostConfig{}
err = docker.StartContainer(containerId, hostConfig)
if err != nil {
log.Fatal(err)
}
// Stop the container (with 5 seconds timeout)
docker.StopContainer(containerId, 5)
// Listen to events
docker.StartMonitorEvents(eventCallback, nil)
// Hold the execution to look at the events coming
time.Sleep(3600 * time.Second)
}
```
# Maintainers
List of people you can ping for feedback on Pull Requests or any questions.
- [Sam Alba](https://github.com/samalba)
- [Michael Crosby](https://github.com/crosbymichael)
- [Andrea Luzzardi](https://github.com/aluzzardi)
- [Victor Vieux](https://github.com/vieux)
- [Evan Hazlett](https://github.com/ehazlett)
- [Donald Huang](https://github.com/donhcd)

View file

@ -1,39 +0,0 @@
package dockerclient
import (
"bytes"
"encoding/base64"
"encoding/json"
)
// AuthConfig hold parameters for authenticating with the docker registry
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
RegistryToken string `json:"registrytoken,omitempty"`
}
// encode the auth configuration struct into base64 for the X-Registry-Auth header
func (c *AuthConfig) encode() (string, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(c); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf.Bytes()), nil
}
// ConfigFile holds parameters for authenticating during a BuildImage request
type ConfigFile struct {
Configs map[string]AuthConfig `json:"configs,omitempty"`
rootPath string
}
// encode the configuration struct into base64 for the X-Registry-Config header
func (c *ConfigFile) encode() (string, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(c); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf.Bytes()), nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
package dockerclient
var haproxyPullOutput = `{"status":"The image you are pulling has been verified","id":"haproxy:1"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4.25"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.10"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.9"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"3d894e6f7e63"}{"status":"Already exists","progressDetail":{},"id":"4d949c40bc77"}{"status":"Already exists","progressDetail":{},"id":"55e031889365"}{"status":"Already exists","progressDetail":{},"id":"c7aa675e1876"}{"status":"The image you are pulling has been verified","id":"haproxy:latest"}
{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"}
`
var statsResp = `{"read":"2015-02-02T17:06:08.187833376-05:00","network":{"rx_bytes":99988,"rx_packets":928,"rx_errors":0,"rx_dropped":0,"tx_bytes":1786548,"tx_packets":877,"tx_errors":0,"tx_dropped":0},"cpu_stats":{"cpu_usage":{"total_usage":170018598,"percpu_usage":[170018598],"usage_in_kernelmode":30000000,"usage_in_usermode":70000000},"system_cpu_usage":9020930000000,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":18022400,"max_usage":20541440,"stats":{"active_anon":6213632,"active_file":176128,"cache":11808768,"hierarchical_memory_limit":9223372036854775807,"hierarchical_memsw_limit":9223372036854775807,"inactive_anon":0,"inactive_file":11632640,"mapped_file":5165056,"pgfault":2535,"pgmajfault":13,"pgpgin":4293,"pgpgout":1937,"rss":6213632,"rss_huge":2097152,"swap":0,"total_active_anon":6213632,"total_active_file":176128,"total_cache":11808768,"total_inactive_anon":0,"total_inactive_file":11632640,"total_mapped_file":5165056,"total_pgfault":2535,"total_pgmajfault":13,"total_pgpgin":4293,"total_pgpgout":1937,"total_rss":6213632,"total_rss_huge":2097152,"total_swap":0,"total_unevictable":0,"unevictable":0},"failcnt":0,"limit":1041051648},"blkio_stats":{"io_service_bytes_recursive":[{"major":7,"minor":0,"op":"Read","value":28672},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":28672},{"major":7,"minor":0,"op":"Total","value":28672},{"major":253,"minor":0,"op":"Read","value":28672},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":28672},{"major":253,"minor":0,"op":"Total","value":28672},{"major":253,"minor":7,"op":"Read","value":11718656},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":11718656},{"major":253,"minor":7,"op":"Total","value":11718656},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_serviced_recursive":[{"major":7,"minor":0,"op":"Read","value":7},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":7},{"major":7,"minor":0,"op":"Total","value":7},{"major":253,"minor":0,"op":"Read","value":7},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":7},{"major":253,"minor":0,"op":"Total","value":7},{"major":253,"minor":7,"op":"Read","value":312},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":312},{"major":253,"minor":7,"op":"Total","value":312},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]}}`
var eventsResp = `{"status":"pull","id":"nginx:latest","time":1428620433}{"status":"create","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"start","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"die","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620442}{"status":"create","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"start","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"die","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"pull","id":"debian:latest","time":1428620453}{"status":"create","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"start","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"die","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"create","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"start","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"pause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620462}{"status":"unpause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620466}{"status":"die","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620469}`

View file

@ -1,66 +0,0 @@
package dockerclient
import (
"io"
)
type Callback func(*Event, chan error, ...interface{})
type StatCallback func(string, *Stats, chan error, ...interface{})
type Client interface {
Info() (*Info, error)
ListContainers(all, size bool, filters string) ([]Container, error)
InspectContainer(id string) (*ContainerInfo, error)
InspectImage(id string) (*ImageInfo, error)
CreateContainer(config *ContainerConfig, name string, authConfig *AuthConfig) (string, error)
ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
ContainerChanges(id string) ([]*ContainerChanges, error)
// ContainerStats takes a container ID and an optional stop channel and
// returns a StatsOrError channel. If an error is ever sent, then no
// more stats will be sent on that channel. If a stop channel is
// provided, events will stop being monitored after the stop channel is
// closed.
ContainerStats(id string, stopChan <-chan struct{}) (<-chan StatsOrError, error)
ExecCreate(config *ExecConfig) (string, error)
ExecStart(id string, config *ExecConfig) error
ExecResize(id string, width, height int) error
StartContainer(id string, config *HostConfig) error
AttachContainer(id string, options *AttachOptions) (io.ReadCloser, error)
StopContainer(id string, timeout int) error
RestartContainer(id string, timeout int) error
KillContainer(id, signal string) error
Wait(id string) <-chan WaitResult
// MonitorEvents takes options and an optional stop channel, and returns
// an EventOrError channel. If an error is ever sent, then no more
// events will be sent. If a stop channel is provided, events will stop
// being monitored after the stop channel is closed.
MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error)
StartMonitorEvents(cb Callback, ec chan error, args ...interface{})
StopAllMonitorEvents()
StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{})
StopAllMonitorStats()
TagImage(nameOrID string, repo string, tag string, force bool) error
Version() (*Version, error)
PullImage(name string, auth *AuthConfig) error
PushImage(name string, tag string, auth *AuthConfig) error
LoadImage(reader io.Reader) error
RemoveContainer(id string, force, volumes bool) error
ListImages(all bool) ([]*Image, error)
RemoveImage(name string, force bool) ([]*ImageDelete, error)
SearchImages(query, registry string, auth *AuthConfig) ([]ImageSearch, error)
PauseContainer(name string) error
UnpauseContainer(name string) error
RenameContainer(oldName string, newName string) error
ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error)
BuildImage(image *BuildImage) (io.ReadCloser, error)
ListVolumes() ([]*Volume, error)
RemoveVolume(name string) error
CreateVolume(request *VolumeCreateRequest) (*Volume, error)
ListNetworks(filters string) ([]*NetworkResource, error)
InspectNetwork(id string) (*NetworkResource, error)
CreateNetwork(config *NetworkCreate) (*NetworkCreateResponse, error)
ConnectNetwork(id, container string) error
DisconnectNetwork(id, container string, force bool) error
RemoveNetwork(id string) error
}

View file

@ -1,38 +0,0 @@
package dockerclient
import (
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
"path/filepath"
)
// TLSConfigFromCertPath returns a configuration based on PEM files in the directory
//
// path is usually what is set by the environment variable `DOCKER_CERT_PATH`,
// or `$HOME/.docker`.
func TLSConfigFromCertPath(path string) (*tls.Config, error) {
cert, err := ioutil.ReadFile(filepath.Join(path, "cert.pem"))
if err != nil {
return nil, err
}
key, err := ioutil.ReadFile(filepath.Join(path, "key.pem"))
if err != nil {
return nil, err
}
ca, err := ioutil.ReadFile(filepath.Join(path, "ca.pem"))
if err != nil {
return nil, err
}
tlsCert, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM(ca) {
return nil, errors.New("Could not add RootCA pem")
}
return tlsConfig, nil
}

View file

@ -1,637 +0,0 @@
package dockerclient
import (
"fmt"
"io"
"time"
"github.com/docker/go-units"
)
type ContainerConfig struct {
Hostname string
Domainname string
User string
AttachStdin bool
AttachStdout bool
AttachStderr bool
ExposedPorts map[string]struct{}
Tty bool
OpenStdin bool
StdinOnce bool
Env []string
Cmd []string
Image string
Volumes map[string]struct{}
WorkingDir string
Entrypoint []string
NetworkDisabled bool
MacAddress string
OnBuild []string
Labels map[string]string
StopSignal string
// FIXME: VolumeDriver have been removed since docker 1.9
VolumeDriver string
// FIXME: The following fields have been removed since API v1.18
Memory int64
MemorySwap int64
CpuShares int64
Cpuset string
PortSpecs []string
// This is used only by the create command
HostConfig HostConfig
// Network configuration support
NetworkingConfig NetworkingConfig
}
type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []map[string]string
Memory int64
MemoryReservation int64
MemorySwap int64
KernelMemory int64
CpuShares int64
CpuPeriod int64
CpusetCpus string
CpusetMems string
CpuQuota int64
BlkioWeight int64
OomKillDisable bool
MemorySwappiness int64
Privileged bool
PortBindings map[string][]PortBinding
Links []string
PublishAllPorts bool
Dns []string
DNSOptions []string
DnsSearch []string
ExtraHosts []string
VolumesFrom []string
Devices []DeviceMapping
NetworkMode string
IpcMode string
PidMode string
UTSMode string
CapAdd []string
CapDrop []string
GroupAdd []string
RestartPolicy RestartPolicy
SecurityOpt []string
ReadonlyRootfs bool
Ulimits []Ulimit
LogConfig LogConfig
CgroupParent string
ConsoleSize [2]int
VolumeDriver string
OomScoreAdj int
Tmpfs map[string]string
ShmSize int64 `json:"omitempty"`
BlkioWeightDevice []WeightDevice
BlkioDeviceReadBps []ThrottleDevice
BlkioDeviceWriteBps []ThrottleDevice
BlkioDeviceReadIOps []ThrottleDevice
BlkioDeviceWriteIOps []ThrottleDevice
}
type WeightDevice struct {
Path string
Weight uint16
}
type ThrottleDevice struct {
Path string
Rate uint64
}
type DeviceMapping struct {
PathOnHost string `json:"PathOnHost"`
PathInContainer string `json:"PathInContainer"`
CgroupPermissions string `json:"CgroupPermissions"`
}
type ExecConfig struct {
AttachStdin bool
AttachStdout bool
AttachStderr bool
Tty bool
Cmd []string
Container string
Detach bool
}
type LogOptions struct {
Follow bool
Stdout bool
Stderr bool
Timestamps bool
Tail int64
}
type AttachOptions struct {
Logs bool
Stream bool
Stdin bool
Stdout bool
Stderr bool
}
type MonitorEventsFilters struct {
Event string `json:",omitempty"`
Image string `json:",omitempty"`
Container string `json:",omitempty"`
}
type MonitorEventsOptions struct {
Since int
Until int
Filters *MonitorEventsFilters `json:",omitempty"`
}
type RestartPolicy struct {
Name string
MaximumRetryCount int64
}
type PortBinding struct {
HostIp string
HostPort string
}
type State struct {
Running bool
Paused bool
Restarting bool
OOMKilled bool
Dead bool
Pid int
ExitCode int
Error string // contains last known error when starting the container
StartedAt time.Time
FinishedAt time.Time
Ghost bool
}
// String returns a human-readable description of the state
// Stoken from docker/docker/daemon/state.go
func (s *State) String() string {
if s.Running {
if s.Paused {
return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
}
if s.Restarting {
return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
}
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
}
if s.Dead {
return "Dead"
}
if s.StartedAt.IsZero() {
return "Created"
}
if s.FinishedAt.IsZero() {
return ""
}
return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
}
// StateString returns a single string to describe state
// Stoken from docker/docker/daemon/state.go
func (s *State) StateString() string {
if s.Running {
if s.Paused {
return "paused"
}
if s.Restarting {
return "restarting"
}
return "running"
}
if s.Dead {
return "dead"
}
if s.StartedAt.IsZero() {
return "created"
}
return "exited"
}
type ImageInfo struct {
Architecture string
Author string
Comment string
Config *ContainerConfig
Container string
ContainerConfig *ContainerConfig
Created time.Time
DockerVersion string
Id string
Os string
Parent string
Size int64
VirtualSize int64
}
type ImageSearch struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"`
IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"`
}
type ContainerInfo struct {
Id string
Created string
Path string
Name string
Args []string
ExecIDs []string
Config *ContainerConfig
State *State
Image string
NetworkSettings struct {
IPAddress string `json:"IpAddress"`
IPPrefixLen int `json:"IpPrefixLen"`
Gateway string
Bridge string
Ports map[string][]PortBinding
Networks map[string]*EndpointSettings
}
SysInitPath string
ResolvConfPath string
Volumes map[string]string
HostConfig *HostConfig
}
type ContainerChanges struct {
Path string
Kind int
}
type Port struct {
IP string
PrivatePort int
PublicPort int
Type string
}
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
}
// NetworkingConfig represents the container's networking configuration for each of its interfaces
// Carries the networink configs specified in the `docker run` and `docker network connect` commands
type NetworkingConfig struct {
EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each conencting network
}
type Container struct {
Id string
Names []string
Image string
Command string
Created int64
Status string
Ports []Port
SizeRw int64
SizeRootFs int64
Labels map[string]string
NetworkSettings struct {
Networks map[string]EndpointSettings
}
}
type Actor struct {
ID string
Attributes map[string]string
}
type Event struct {
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
Type string
Action string
Actor Actor
Time int64 `json:"time,omitempty"`
TimeNano int64 `json:"timeNano,omitempty"`
}
type Version struct {
ApiVersion string
Arch string
GitCommit string
GoVersion string
KernelVersion string
Os string
Version string
}
type RespContainersCreate struct {
Id string
Warnings []string
}
type Image struct {
Created int64
Id string
Labels map[string]string
ParentId string
RepoDigests []string
RepoTags []string
Size int64
VirtualSize int64
}
// Info is the struct returned by /info
// The API is currently in flux, so Debug, MemoryLimit, SwapLimit, and
// IPv4Forwarding are interfaces because in docker 1.6.1 they are 0 or 1 but in
// master they are bools.
type Info struct {
ID string
Containers int64
Driver string
DriverStatus [][]string
ExecutionDriver string
Images int64
KernelVersion string
OperatingSystem string
NCPU int64
MemTotal int64
Name string
Labels []string
Debug interface{}
NFd int64
NGoroutines int64
SystemTime string
NEventsListener int64
InitPath string
InitSha1 string
IndexServerAddress string
MemoryLimit interface{}
SwapLimit interface{}
IPv4Forwarding interface{}
BridgeNfIptables bool
BridgeNfIp6tables bool
DockerRootDir string
HttpProxy string
HttpsProxy string
NoProxy string
}
type ImageDelete struct {
Deleted string
Untagged string
}
type StatsOrError struct {
Stats
Error error
}
type EventOrError struct {
Event
Error error
}
type WaitResult struct {
ExitCode int
Error error
}
type decodingResult struct {
result interface{}
err error
}
// The following are types for the API stats endpoint
type ThrottlingData struct {
// Number of periods with throttling active
Periods uint64 `json:"periods"`
// Number of periods when the container hit its throttling limit.
ThrottledPeriods uint64 `json:"throttled_periods"`
// Aggregate time the container was throttled for in nanoseconds.
ThrottledTime uint64 `json:"throttled_time"`
}
// All CPU stats are aggregated since container inception.
type CpuUsage struct {
// Total CPU time consumed.
// Units: nanoseconds.
TotalUsage uint64 `json:"total_usage"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage"`
// Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type CpuStats struct {
CpuUsage CpuUsage `json:"cpu_usage"`
SystemUsage uint64 `json:"system_cpu_usage"`
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type NetworkStats struct {
RxBytes uint64 `json:"rx_bytes"`
RxPackets uint64 `json:"rx_packets"`
RxErrors uint64 `json:"rx_errors"`
RxDropped uint64 `json:"rx_dropped"`
TxBytes uint64 `json:"tx_bytes"`
TxPackets uint64 `json:"tx_packets"`
TxErrors uint64 `json:"tx_errors"`
TxDropped uint64 `json:"tx_dropped"`
}
type MemoryStats struct {
Usage uint64 `json:"usage"`
MaxUsage uint64 `json:"max_usage"`
Stats map[string]uint64 `json:"stats"`
Failcnt uint64 `json:"failcnt"`
Limit uint64 `json:"limit"`
}
type BlkioStatEntry struct {
Major uint64 `json:"major"`
Minor uint64 `json:"minor"`
Op string `json:"op"`
Value uint64 `json:"value"`
}
type BlkioStats struct {
// number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"`
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"`
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"`
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"`
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"`
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
}
type Stats struct {
Read time.Time `json:"read"`
NetworkStats NetworkStats `json:"network,omitempty"`
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
}
type Ulimit struct {
Name string `json:"name"`
Soft uint64 `json:"soft"`
Hard uint64 `json:"hard"`
}
type LogConfig struct {
Type string `json:"type"`
Config map[string]string `json:"config"`
}
type BuildImage struct {
Config *ConfigFile
DockerfileName string
Context io.Reader
RemoteURL string
RepoName string
SuppressOutput bool
NoCache bool
Remove bool
ForceRemove bool
Pull bool
Memory int64
MemorySwap int64
CpuShares int64
CpuPeriod int64
CpuQuota int64
CpuSetCpus string
CpuSetMems string
CgroupParent string
BuildArgs map[string]string
Labels map[string]string // Labels hold metadata about the image
}
type Volume struct {
Name string // Name is the name of the volume
Driver string // Driver is the Driver name used to create the volume
Mountpoint string // Mountpoint is the location on disk of the volume
Labels map[string]string // Labels hold metadata about the volume
}
type VolumesListResponse struct {
Volumes []*Volume // Volumes is the list of volumes being returned
}
type VolumeCreateRequest struct {
Name string // Name is the requested name of the volume
Driver string // Driver is the name of the driver that should be used to create the volume
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
Labels map[string]string // Labels hold metadata about the volume
}
// IPAM represents IP Address Management
type IPAM struct {
Driver string
Options map[string]string //Per network IPAM driver options
Config []IPAMConfig
}
// IPAMConfig represents IPAM configurations
type IPAMConfig struct {
Subnet string `json:",omitempty"`
IPRange string `json:",omitempty"`
Gateway string `json:",omitempty"`
AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"`
}
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
}
// NetworkResource is the body of the "get network" http response message
type NetworkResource struct {
Name string
ID string `json:"Id"`
Scope string
Driver string
IPAM IPAM
//Internal bool
Containers map[string]EndpointResource
Options map[string]string
Labels map[string]string // Labels hold metadata about the network
}
// EndpointResource contains network resources allocated and used for a container in a network
type EndpointResource struct {
Name string
EndpointID string
MacAddress string
IPv4Address string
IPv6Address string
}
// NetworkCreate is the expected body of the "create network" http request message
type NetworkCreate struct {
Name string
CheckDuplicate bool
Driver string
IPAM IPAM
Internal bool
Options map[string]string
Labels map[string]string // Labels hold metadata about the network
}
// NetworkCreateResponse is the response message sent by the server for network create call
type NetworkCreateResponse struct {
ID string `json:"Id"`
Warning string
}
// NetworkConnect represents the data to be used to connect a container to the network
type NetworkConnect struct {
Container string
}
// NetworkDisconnect represents the data to be used to disconnect a container from the network
type NetworkDisconnect struct {
Container string
Force bool
}

View file

@ -1,41 +0,0 @@
package dockerclient
import (
"crypto/tls"
"net"
"net/http"
"net/url"
"time"
)
type tcpFunc func(*net.TCPConn, time.Duration) error
func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration, setUserTimeout tcpFunc) *http.Client {
httpTransport := &http.Transport{
TLSClientConfig: tlsConfig,
}
switch u.Scheme {
default:
httpTransport.Dial = func(proto, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(proto, addr, timeout)
if tcpConn, ok := conn.(*net.TCPConn); ok && setUserTimeout != nil {
// Sender can break TCP connection if the remote side doesn't
// acknowledge packets within timeout
setUserTimeout(tcpConn, timeout)
}
return conn, err
}
case "unix":
socketPath := u.Path
unixDial := func(proto, addr string) (net.Conn, error) {
return net.DialTimeout("unix", socketPath, timeout)
}
httpTransport.Dial = unixDial
// Override the main URL object so the HTTP lib won't complain
u.Scheme = "http"
u.Host = "unix.sock"
u.Path = ""
}
return &http.Client{Transport: httpTransport}
}

392
vendor/github.com/urfave/cli/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,392 @@
# Change Log
**ATTN**: This project uses [semantic versioning](http://semver.org/).
## [Unreleased]
## [1.19.1] - 2016-11-21
### Fixed
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
the `Action` for a command would cause it to error rather than calling the
function. Should not have a affected declarative cases using `func(c
*cli.Context) err)`.
- Shell completion now handles the case where the user specifies
`--generate-bash-completion` immediately after a flag that takes an argument.
Previously it call the application with `--generate-bash-completion` as the
flag value.
## [1.19.0] - 2016-11-19
### Added
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
- A `Description` field was added to `App` for a more detailed description of
the application (similar to the existing `Description` field on `Command`)
- Flag type code generation via `go generate`
- Write to stderr and exit 1 if action returns non-nil error
- Added support for TOML to the `altsrc` loader
- `SkipArgReorder` was added to allow users to skip the argument reordering.
This is useful if you want to consider all "flags" after an argument as
arguments rather than flags (the default behavior of the stdlib `flag`
library). This is backported functionality from the [removal of the flag
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
2
- For formatted errors (those implementing `ErrorFormatter`), the errors will
be formatted during output. Compatible with `pkg/errors`.
### Changed
- Raise minimum tested/supported Go version to 1.2+
### Fixed
- Consider empty environment variables as set (previously environment variables
with the equivalent of `""` would be skipped rather than their value used).
- Return an error if the value in a given environment variable cannot be parsed
as the flag type. Previously these errors were silently swallowed.
- Print full error when an invalid flag is specified (which includes the invalid flag)
- `App.Writer` defaults to `stdout` when `nil`
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
- Correctly show help message if `-h` is provided to a subcommand
- `context.(Global)IsSet` now respects environment variables. Previously it
would return `false` if a flag was specified in the environment rather than
as an argument
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
as `altsrc` where Go would complain that the types didn't match
## [1.18.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
## [1.18.0] - 2016-06-27
### Added
- `./runtests` test runner with coverage tracking by default
- testing on OS X
- testing on Windows
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
### Changed
- Use spaces for alignment in help/usage output instead of tabs, making the
output alignment consistent regardless of tab width
### Fixed
- Printing of command aliases in help text
- Printing of visible flags for both struct and struct pointer flags
- Display the `help` subcommand when using `CommandCategories`
- No longer swallows `panic`s that occur within the `Action`s themselves when
detecting the signature of the `Action` field
## [1.17.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.17.0] - 2016-05-09
### Added
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
- Support for hiding commands by setting `Hidden: true` -- this will hide the
commands in help output
### Changed
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
quoted in help text output.
- All flag types now include `(default: {value})` strings following usage when a
default value can be (reasonably) detected.
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
with non-slice flag types
- Apps now exit with a code of 3 if an unknown subcommand is specified
(previously they printed "No help topic for...", but still exited 0. This
makes it easier to script around apps built using `cli` since they can trust
that a 0 exit code indicated a successful execution.
- cleanups based on [Go Report Card
feedback](https://goreportcard.com/report/github.com/urfave/cli)
## [1.16.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.16.0] - 2016-05-02
### Added
- `Hidden` field on all flag struct types to omit from generated help text
### Changed
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
generated help text via the `Hidden` field
### Fixed
- handling of error values in `HandleAction` and `HandleExitCoder`
## [1.15.0] - 2016-04-30
### Added
- This file!
- Support for placeholders in flag usage strings
- `App.Metadata` map for arbitrary data/state management
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
parsing.
- Support for nested lookup of dot-delimited keys in structures loaded from
YAML.
### Changed
- The `App.Action` and `Command.Action` now prefer a return signature of
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
`error` is returned, there may be two outcomes:
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
automatically
- Else the error is bubbled up and returned from `App.Run`
- Specifying an `Action` with the legacy return signature of
`func(*cli.Context)` will produce a deprecation message to stderr
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
from `App.Run`
- Specifying an `Action` func that has an invalid (input) signature will
produce a non-zero exit from `App.Run`
### Deprecated
- <a name="deprecated-cli-app-runandexitonerror"></a>
`cli.App.RunAndExitOnError`, which should now be done by returning an error
that fulfills `cli.ExitCoder` to `cli.App.Run`.
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Fixed
- Added missing `*cli.Context.GlobalFloat64` method
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
### Added
- Codebeat badge
- Support for categorization via `CategorizedHelp` and `Categories` on app.
### Changed
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
### Fixed
- Ensure version is not shown in help text when `HideVersion` set.
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
### Added
- YAML file input support.
- `NArg` method on context.
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
### Added
- Custom usage error handling.
- Custom text support in `USAGE` section of help output.
- Improved help messages for empty strings.
- AppVeyor CI configuration.
### Changed
- Removed `panic` from default help printer func.
- De-duping and optimizations.
### Fixed
- Correctly handle `Before`/`After` at command level when no subcommands.
- Case of literal `-` argument causing flag reordering.
- Environment variable hints on Windows.
- Docs updates.
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
### Changed
- Use `path.Base` in `Name` and `HelpName`
- Export `GetName` on flag types.
### Fixed
- Flag parsing when skipping is enabled.
- Test output cleanup.
- Move completion check to account for empty input case.
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
### Added
- Destination scan support for flags.
- Testing against `tip` in Travis CI config.
### Changed
- Go version in Travis CI config.
### Fixed
- Removed redundant tests.
- Use correct example naming in tests.
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
### Fixed
- Remove unused var in bash completion.
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
### Added
- Coverage and reference logos in README.
### Fixed
- Use specified values in help and version parsing.
- Only display app version and help message once.
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
### Added
- More tests for existing functionality.
- `ArgsUsage` at app and command level for help text flexibility.
### Fixed
- Honor `HideHelp` and `HideVersion` in `App.Run`.
- Remove juvenile word from README.
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
### Added
- `FullName` on command with accompanying help output update.
- Set default `$PROG` in bash completion.
### Changed
- Docs formatting.
### Fixed
- Removed self-referential imports in tests.
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
### Added
- Support for `Copyright` at app level.
- `Parent` func at context level to walk up context lineage.
### Fixed
- Global flag processing at top level.
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
### Added
- Aggregate errors from `Before`/`After` funcs.
- Doc comments on flag structs.
- Include non-global flags when checking version and help.
- Travis CI config updates.
### Fixed
- Ensure slice type flags have non-nil values.
- Collect global flags from the full command hierarchy.
- Docs prose.
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
### Changed
- `HelpPrinter` signature includes output writer.
### Fixed
- Specify go 1.1+ in docs.
- Set `Writer` when running command as app.
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
### Added
- Multiple author support.
- `NumFlags` at context level.
- `Aliases` at command level.
### Deprecated
- `ShortName` at command level.
### Fixed
- Subcommand help output.
- Backward compatible support for deprecated `Author` and `Email` fields.
- Docs regarding `Names`/`Aliases`.
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
### Added
- `After` hook func support at app and command level.
### Fixed
- Use parsed context when running command as subcommand.
- Docs prose.
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
### Added
- Support for hiding `-h / --help` flags, but not `help` subcommand.
- Stop flag parsing after `--`.
### Fixed
- Help text for generic flags to specify single value.
- Use double quotes in output for defaults.
- Use `ParseInt` instead of `ParseUint` for int environment var values.
- Use `0` as base when parsing int environment var values.
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
### Added
- Support for environment variable lookup "cascade".
- Support for `Stdout` on app for output redirection.
### Fixed
- Print command help instead of app help in `ShowCommandHelp`.
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
### Added
- Docs and example code updates.
### Changed
- Default `-v / --version` flag made optional.
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
### Added
- `FlagNames` at context level.
- Exposed `VersionPrinter` var for more control over version output.
- Zsh completion hook.
- `AUTHOR` section in default app help template.
- Contribution guidelines.
- `DurationFlag` type.
## [1.2.0] - 2014-08-02
### Added
- Support for environment variable defaults on flags plus tests.
## [1.1.0] - 2014-07-15
### Added
- Bash completion.
- Optional hiding of built-in help command.
- Optional skipping of flag parsing at command level.
- `Author`, `Email`, and `Compiled` metadata on app.
- `Before` hook func support at app and command level.
- `CommandNotFound` func support at app level.
- Command reference available on context.
- `GenericFlag` type.
- `Float64Flag` type.
- `BoolTFlag` type.
- `IsSet` flag helper on context.
- More flag lookup funcs at context level.
- More tests &amp; docs.
### Changed
- Help template updates to account for presence/absence of flags.
- Separated subcommand help template.
- Exposed `HelpPrinter` var for more control over help output.
## [1.0.0] - 2013-11-01
### Added
- `help` flag in default app flag set and each command flag set.
- Custom handling of argument parsing errors.
- Command lookup by name at app level.
- `StringSliceFlag` type and supporting `StringSlice` type.
- `IntSliceFlag` type and supporting `IntSlice` type.
- Slice type flag lookups by name at context level.
- Export of app and command help functions.
- More tests &amp; docs.
## 0.1.0 - 2013-07-22
### Added
- Initial implementation.
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License
Copyright (c) 2015 Dustin H
Copyright (c) 2016 Jeremy Saenz & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,4 +19,3 @@ 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.

1381
vendor/github.com/urfave/cli/README.md generated vendored Normal file

File diff suppressed because it is too large Load diff

492
vendor/github.com/urfave/cli/app.go generated vendored Normal file
View file

@ -0,0 +1,492 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"time"
)
var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
)
// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to path.Base(os.Args[0])
Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program.
Usage string
// Text to override the USAGE section of help
UsageText string
// Description of the program argument format.
ArgsUsage string
// Version of the program
Version string
// Description of the program
Description string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// Populate on app startup, only gettable through method Categories()
categories CommandCategories
// An action to execute when the bash-completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{}
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
// Execute this function if an usage error occurs
OnUsageError OnUsageErrorFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
// ErrWriter writes error output
ErrWriter io.Writer
// Other custom info
Metadata map[string]interface{}
didSetup bool
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// NewApp creates a new cli Application with some reasonable defaults for Name,
// Usage, Version and Action.
func NewApp() *App {
return &App{
Name: filepath.Base(os.Args[0]),
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Setup runs initialization code to ensure all data structures are ready for
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
// will return early if setup has already happened.
func (a *App) Setup() {
if a.didSetup {
return
}
a.didSetup = true
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
a.categories = CommandCategories{}
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
if a.Metadata == nil {
a.Metadata = make(map[string]interface{})
}
if a.Writer == nil {
a.Writer = os.Stdout
}
}
// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
a.Setup()
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
ShowAppHelp(context)
return nerr
}
context.shellComplete = shellComplete
if checkCompletions(context) {
return nil
}
if err != nil {
if a.OnUsageError != nil {
err := a.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context)
return err
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil
}
if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil
}
if a.After != nil {
defer func() {
if afterErr := a.After(context); afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context)
HandleExitCoder(beforeErr)
err = beforeErr
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action
err = HandleAction(a.Action, context)
HandleExitCoder(err)
return err
}
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(a.errWriter(), err)
OsExiter(1)
}
}
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// parse flags
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if checkCompletions(context) {
return nil
}
if err != nil {
if a.OnUsageError != nil {
err = a.OnUsageError(context, err, true)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context)
return err
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
HandleExitCoder(beforeErr)
err = beforeErr
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
err = HandleAction(a.Action, context)
HandleExitCoder(err)
return err
}
// Command returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
// Categories returns a slice containing all the categories with the commands they contain
func (a *App) Categories() CommandCategories {
return a.categories
}
// VisibleCategories returns a slice of categories and commands that are
// Hidden=false
func (a *App) VisibleCategories() []*CommandCategory {
ret := []*CommandCategory{}
for _, category := range a.categories {
if visible := func() *CommandCategory {
for _, command := range category.Commands {
if !command.Hidden {
return category
}
}
return nil
}(); visible != nil {
ret = append(ret, visible)
}
}
return ret
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (a *App) VisibleCommands() []Command {
ret := []Command{}
for _, command := range a.Commands {
if !command.Hidden {
ret = append(ret, command)
}
}
return ret
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (a *App) VisibleFlags() []Flag {
return visibleFlags(a.Flags)
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) errWriter() io.Writer {
// When the app ErrWriter is nil use the package level one.
if a.ErrWriter == nil {
return ErrWriter
}
return a.ErrWriter
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = " <" + a.Email + ">"
}
return fmt.Sprintf("%v%v", a.Name, e)
}
// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
if a, ok := action.(ActionFunc); ok {
return a(context)
} else if a, ok := action.(func(*Context) error); ok {
return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
a(context)
return nil
} else {
return errInvalidActionType
}
}

24
vendor/github.com/urfave/cli/appveyor.yml generated vendored Normal file
View file

@ -0,0 +1,24 @@
version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\urfave\cli
environment:
GOPATH: C:\gopath
GOVERSION: 1.6
PYTHON: C:\Python27-x64
PYTHON_VERSION: 2.7.x
PYTHON_ARCH: 64
install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmrun/...
- go get -v -t ./...
build_script:
- python runtests vet
- python runtests test
- python runtests gfmrun

44
vendor/github.com/urfave/cli/category.go generated vendored Normal file
View file

@ -0,0 +1,44 @@
package cli
// CommandCategories is a slice of *CommandCategory.
type CommandCategories []*CommandCategory
// CommandCategory is a category containing commands.
type CommandCategory struct {
Name string
Commands Commands
}
func (c CommandCategories) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandCategories) Len() int {
return len(c)
}
func (c CommandCategories) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// AddCommand adds a command to a category.
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
for _, commandCategory := range c {
if commandCategory.Name == category {
commandCategory.Commands = append(commandCategory.Commands, command)
return c
}
}
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (c *CommandCategory) VisibleCommands() []Command {
ret := []Command{}
for _, command := range c.Commands {
if !command.Hidden {
ret = append(ret, command)
}
}
return ret
}

View file

@ -10,7 +10,7 @@
// app := cli.NewApp()
// app.Name = "greet"
// app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) {
// app.Action = func(c *cli.Context) error {
// println("Greetings")
// }
//
@ -18,23 +18,4 @@
// }
package cli
import (
"strings"
)
type MultiError struct {
Errors []error
}
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go

300
vendor/github.com/urfave/cli/command.go generated vendored Normal file
View file

@ -0,0 +1,300 @@
package cli
import (
"fmt"
"io/ioutil"
"sort"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// Custom text to show on USAGE section of help
UsageText string
// A longer explanation of how the command works
Description string
// A short description of the arguments of this command
ArgsUsage string
// The category the command is part of
Category string
// The function to call when checking for bash command completions
BashComplete BashCompleteFunc
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The function to call when this command is invoked
Action interface{}
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
// of deprecation period has passed, maybe?
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc
// List of child commands
Subcommands Commands
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
// but only works if all flags appear after all arguments. This behavior was
// removed n version 2 since it only works under specific conditions so we
// backport here by exposing it as an option for compatibility.
SkipArgReorder bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide this command from help or completion
Hidden bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
}
type CommandsByName []Command
func (c CommandsByName) Len() int {
return len(c)
}
func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandsByName) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Commands is a slice of Command
type Commands []Command
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if arg == "-" {
// Do nothing. A dash alone is not really a flag.
continue
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
if firstFlagIndex > -1 {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
} else {
err = set.Parse(ctx.Args().Tail())
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
if checkCommandHelp(context, c.Name) {
return nil
}
if c.After != nil {
defer func() {
afterErr := c.After(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if c.Before != nil {
err = c.Before(context)
if err != nil {
fmt.Fprintln(ctx.App.Writer, err)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
HandleExitCoder(err)
return err
}
}
if c.Action == nil {
c.Action = helpSubcommand.Action
}
context.Command = c
err = HandleAction(c.Action, context)
if err != nil {
HandleExitCoder(err)
}
return err
}
// Names returns the names including short names and aliases.
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// HasName returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
app.Metadata = ctx.App.Metadata
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" {
app.HelpName = c.HelpName
} else {
app.HelpName = app.Name
}
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
app.categories = CommandCategories{}
for _, command := range c.Subcommands {
app.categories = app.categories.AddCommand(command.Category, command)
}
sort.Sort(app.categories)
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
for index, cc := range app.Commands {
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
}
return app.RunAsSubcommand(ctx)
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags)
}

276
vendor/github.com/urfave/cli/context.go generated vendored Normal file
View file

@ -0,0 +1,276 @@
package cli
import (
"errors"
"flag"
"reflect"
"strings"
"syscall"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
shellComplete bool
flagSet *flag.FlagSet
setFlags map[string]bool
parentContext *Context
}
// NewContext creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
if parentCtx != nil {
c.shellComplete = parentCtx.shellComplete
}
return c
}
// NumFlags returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Set sets a context flag to a value.
func (c *Context) Set(name, value string) error {
return c.flagSet.Set(name, value)
}
// GlobalSet sets a context flag to a value on the global flagset
func (c *Context) GlobalSet(name, value string) error {
return globalContext(c).flagSet.Set(name, value)
}
// IsSet determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
c.flagSet.VisitAll(func(f *flag.Flag) {
if _, ok := c.setFlags[f.Name]; ok {
return
}
c.setFlags[f.Name] = false
})
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is avaliable.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
flags := c.Command.Flags
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
if c.App != nil {
flags = c.App.Flags
}
}
for _, f := range flags {
eachName(f.GetName(), func(name string) {
if isSet, ok := c.setFlags[name]; isSet || !ok {
return
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
envVarValue := val.FieldByName("EnvVar")
if !envVarValue.IsValid() {
return
}
eachName(envVarValue.String(), func(envVar string) {
envVar = strings.TrimSpace(envVar)
if _, ok := syscall.Getenv(envVar); ok {
c.setFlags[name] = true
return
}
})
})
}
}
return c.setFlags[name]
}
// GlobalIsSet determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if ctx.IsSet(name) {
return true
}
}
return false
}
// FlagNames returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// GlobalFlagNames returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Parent returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
// value returns the value of the flag coressponding to `name`
func (c *Context) value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
}
// Args contains apps console arguments
type Args []string
// Args returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// NArg returns the number of the command line arguments.
func (c *Context) NArg() int {
return len(c.Args())
}
// Get returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// First returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Tail returns the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Present checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swap swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func globalContext(ctx *Context) *Context {
if ctx == nil {
return nil
}
for {
if ctx.parentContext == nil {
return ctx
}
ctx = ctx.parentContext
}
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.GetName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}

124
vendor/github.com/urfave/cli/errors.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
package cli
import (
"fmt"
"io"
"os"
"strings"
)
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
var OsExiter = os.Exit
// ErrWriter is used to write errors to the user. This can be anything
// implementing the io.Writer interface and defaults to os.Stderr.
var ErrWriter io.Writer = os.Stderr
// MultiError is an error that wraps multiple errors.
type MultiError struct {
Errors []error
}
// NewMultiError creates a new MultiError. Pass in one or more errors.
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
// Error implements the error interface.
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface {
error
ExitCode() int
}
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct {
exitCode int
message interface{}
}
// NewExitError makes a new *ExitError
func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{
exitCode: exitCode,
message: message,
}
}
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string {
return fmt.Sprintf("%v", ee.message)
}
// ExitCode returns the exit code, fulfilling the interface required by
// `ExitCoder`
func (ee *ExitError) ExitCode() int {
return ee.exitCode
}
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
// given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice and calls OsExiter with the last exit code.
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(exitErr.ExitCode())
return
}
if multiErr, ok := err.(MultiError); ok {
code := handleMultiError(multiErr)
OsExiter(code)
return
}
if err.Error() != "" {
if _, ok := err.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(1)
}
func handleMultiError(multiErr MultiError) int {
code := 1
for _, merr := range multiErr.Errors {
if multiErr2, ok := merr.(MultiError); ok {
code = handleMultiError(multiErr2)
} else {
fmt.Fprintln(ErrWriter, merr)
if exitErr, ok := merr.(ExitCoder); ok {
code = exitErr.ExitCode()
}
}
}
return code
}

93
vendor/github.com/urfave/cli/flag-types.json generated vendored Normal file
View file

@ -0,0 +1,93 @@
[
{
"name": "Bool",
"type": "bool",
"value": false,
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "BoolT",
"type": "bool",
"value": false,
"doctail": " that is true by default",
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "Duration",
"type": "time.Duration",
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
"context_default": "0",
"parser": "time.ParseDuration(f.Value.String())"
},
{
"name": "Float64",
"type": "float64",
"context_default": "0",
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
},
{
"name": "Generic",
"type": "Generic",
"dest": false,
"context_default": "nil",
"context_type": "interface{}"
},
{
"name": "Int64",
"type": "int64",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
},
{
"name": "Int",
"type": "int",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
"parser_cast": "int(parsed)"
},
{
"name": "IntSlice",
"type": "*IntSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]int",
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
},
{
"name": "Int64Slice",
"type": "*Int64Slice",
"dest": false,
"context_default": "nil",
"context_type": "[]int64",
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
},
{
"name": "String",
"type": "string",
"context_default": "\"\"",
"parser": "f.Value.String(), error(nil)"
},
{
"name": "StringSlice",
"type": "*StringSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]string",
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
},
{
"name": "Uint64",
"type": "uint64",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
},
{
"name": "Uint",
"type": "uint",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
"parser_cast": "uint(parsed)"
}
]

799
vendor/github.com/urfave/cli/flag.go generated vendored Normal file
View file

@ -0,0 +1,799 @@
package cli
import (
"flag"
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"syscall"
"time"
)
const defaultPlaceholder = "value"
// BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
Hidden: true,
}
// VersionFlag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// HelpFlag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// FlagStringer converts a flag definition to a string. This is used by help
// to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName()
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
GetName() string
}
// errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
//TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
}
}
return set, nil
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if err := val.Set(envVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
return nil
}
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
type Int64Slice []int64
// Set parses the value into an integer and appends it to the list of values
func (f *Int64Slice) Set(value string) error {
tmp, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Slice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Int(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Duration(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = float64(envValFloat)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Float64(name, f.Value, f.Usage)
})
return nil
}
func visibleFlags(fl []Flag) []Flag {
visible := []Flag{}
for _, flag := range fl {
if !flagValue(flag).FieldByName("Hidden").Bool() {
visible = append(visible, flag)
}
}
return visible
}
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
// Returns the placeholder, if any, and the unquoted usage string.
func unquoteUsage(usage string) (string, string) {
for i := 0; i < len(usage); i++ {
if usage[i] == '`' {
for j := i + 1; j < len(usage); j++ {
if usage[j] == '`' {
name := usage[i+1 : j]
usage = usage[:i] + name + usage[j+1:]
return name, usage
}
}
break
}
}
return "", usage
}
func prefixedNames(fullName, placeholder string) string {
var prefixed string
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
prefixed += prefixFor(name) + name
if placeholder != "" {
prefixed += " " + placeholder
}
if i < len(parts)-1 {
prefixed += ", "
}
}
return prefixed
}
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
prefix := "$"
suffix := ""
sep := ", $"
if runtime.GOOS == "windows" {
prefix = "%"
suffix = "%"
sep = "%, %"
}
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
}
return str + envText
}
func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr {
fv = reflect.Indirect(fv)
}
return fv
}
func stringifyFlag(f Flag) string {
fv := flagValue(f)
switch f.(type) {
case IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag)))
case Int64SliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
case StringSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag)))
}
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
needsPlaceholder := false
defaultValueString := ""
val := fv.FieldByName("Value")
if val.IsValid() {
needsPlaceholder = true
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
if val.Kind() == reflect.String && val.String() != "" {
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
}
}
if defaultValueString == " (default: )" {
defaultValueString = ""
}
if needsPlaceholder && placeholder == "" {
placeholder = defaultPlaceholder
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
return withEnvHint(fv.FieldByName("EnvVar").String(),
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
}
func stringifyIntSliceFlag(f IntSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifyStringSliceFlag(f StringSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
if len(s) > 0 {
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
}
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifySliceFlag(usage, name string, defaultVals []string) string {
placeholder, usage := unquoteUsage(usage)
if placeholder == "" {
placeholder = defaultPlaceholder
}
defaultVal := ""
if len(defaultVals) > 0 {
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
}

627
vendor/github.com/urfave/cli/flag_generated.go generated vendored Normal file
View file

@ -0,0 +1,627 @@
package cli
import (
"flag"
"strconv"
"time"
)
// WARNING: This file is generated!
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolFlag) GetName() string {
return f.Name
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// BoolTFlag is a flag with type bool that is true by default
type BoolTFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolTFlag) GetName() string {
return f.Name
}
// BoolT looks up the value of a local BoolTFlag, returns
// false if not found
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// GlobalBoolT looks up the value of a global BoolTFlag, returns
// false if not found
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value time.Duration
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f DurationFlag) GetName() string {
return f.Name
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value float64
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Float64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Float64Flag) GetName() string {
return f.Name
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value Generic
}
// String returns a readable representation of this value
// (for usage defaults)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f GenericFlag) GetName() string {
return f.Name
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int64
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64Flag) GetName() string {
return f.Name
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
func (c *Context) GlobalInt64(name string) int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntFlag) GetName() string {
return f.Name
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *IntSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntSliceFlag) GetName() string {
return f.Name
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *Int64Slice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64SliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64SliceFlag) GetName() string {
return f.Name
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
func (c *Context) GlobalInt64Slice(name string) []int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringFlag) GetName() string {
return f.Name
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *StringSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringSliceFlag) GetName() string {
return f.Name
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint64
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Uint64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Uint64Flag) GetName() string {
return f.Name
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
func (c *Context) GlobalUint64(name string) uint64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f UintFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f UintFlag) GetName() string {
return f.Name
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
func (c *Context) GlobalUint(name string) uint {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}

28
vendor/github.com/urfave/cli/funcs.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package cli
// BashCompleteFunc is an action to execute when the bash-completion flag is set
type BashCompleteFunc func(*Context)
// BeforeFunc is an action to execute before any subcommands are run, but after
// the context is ready if a non-nil error is returned, no subcommands are run
type BeforeFunc func(*Context) error
// AfterFunc is an action to execute after any subcommands are run, but after the
// subcommand has finished it is run even if Action() panics
type AfterFunc func(*Context) error
// ActionFunc is the action to execute when no subcommands are specified
type ActionFunc func(*Context) error
// CommandNotFoundFunc is executed if the proper command cannot be found
type CommandNotFoundFunc func(*Context, string)
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
// customized usage error messages. This function is able to replace the
// original error messages. If this function is not set, the "Incorrect usage"
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
// FlagStringFunc is used by the help generation to display a flag, which is
// expected to be a single line.
type FlagStringFunc func(Flag) string

255
vendor/github.com/urfave/cli/generate-flag-types generated vendored Executable file
View file

@ -0,0 +1,255 @@
#!/usr/bin/env python
"""
The flag types that ship with the cli library have many things in common, and
so we can take advantage of the `go generate` command to create much of the
source code from a list of definitions. These definitions attempt to cover
the parts that vary between flag types, and should evolve as needed.
An example of the minimum definition needed is:
{
"name": "SomeType",
"type": "sometype",
"context_default": "nil"
}
In this example, the code generated for the `cli` package will include a type
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
Fetching values by name via `*cli.Context` will default to a value of `nil`.
A more complete, albeit somewhat redundant, example showing all available
definition keys is:
{
"name": "VeryMuchType",
"type": "*VeryMuchType",
"value": true,
"dest": false,
"doctail": " which really only wraps a []float64, oh well!",
"context_type": "[]float64",
"context_default": "nil",
"parser": "parseVeryMuchType(f.Value.String())",
"parser_cast": "[]float64(parsed)"
}
The meaning of each field is as follows:
name (string) - The type "name", which will be suffixed with
`Flag` when generating the type definition
for `cli` and the wrapper type for `altsrc`
type (string) - The type that the generated `Flag` type for `cli`
is expected to "contain" as its `.Value` member
value (bool) - Should the generated `cli` type have a `Value`
member?
dest (bool) - Should the generated `cli` type support a
destination pointer?
doctail (string) - Additional docs for the `cli` flag type comment
context_type (string) - The literal type used in the `*cli.Context`
reader func signature
context_default (string) - The literal value used as the default by the
`*cli.Context` reader funcs when no value is
present
parser (string) - Literal code used to parse the flag `f`,
expected to have a return signature of
(value, error)
parser_cast (string) - Literal code used to cast the `parsed` value
returned from the `parser` code
"""
from __future__ import print_function, unicode_literals
import argparse
import json
import os
import subprocess
import sys
import tempfile
import textwrap
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
def main(sysargs=sys.argv[:]):
parser = argparse.ArgumentParser(
description='Generate flag type code!',
formatter_class=_FancyFormatter)
parser.add_argument(
'package',
type=str, default='cli', choices=_WRITEFUNCS.keys(),
help='Package for which flag types will be generated'
)
parser.add_argument(
'-i', '--in-json',
type=argparse.FileType('r'),
default=sys.stdin,
help='Input JSON file which defines each type to be generated'
)
parser.add_argument(
'-o', '--out-go',
type=argparse.FileType('w'),
default=sys.stdout,
help='Output file/stream to which generated source will be written'
)
parser.epilog = __doc__
args = parser.parse_args(sysargs[1:])
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
return 0
def _generate_flag_types(writefunc, output_go, input_json):
types = json.load(input_json)
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
writefunc(tmp, types)
tmp.close()
new_content = subprocess.check_output(
['goimports', tmp.name]
).decode('utf-8')
print(new_content, file=output_go, end='')
output_go.flush()
os.remove(tmp.name)
def _set_typedef_defaults(typedef):
typedef.setdefault('doctail', '')
typedef.setdefault('context_type', typedef['type'])
typedef.setdefault('dest', True)
typedef.setdefault('value', True)
typedef.setdefault('parser', 'f.Value, error(nil)')
typedef.setdefault('parser_cast', 'parsed')
def _write_cli_flag_types(outfile, types):
_fwrite(outfile, """\
package cli
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is a flag with type {type}{doctail}
type {name}Flag struct {{
Name string
Usage string
EnvVar string
Hidden bool
""".format(**typedef))
if typedef['value']:
_fwrite(outfile, """\
Value {type}
""".format(**typedef))
if typedef['dest']:
_fwrite(outfile, """\
Destination *{type}
""".format(**typedef))
_fwrite(outfile, "\n}\n\n")
_fwrite(outfile, """\
// String returns a readable representation of this value
// (for usage defaults)
func (f {name}Flag) String() string {{
return FlagStringer(f)
}}
// GetName returns the name of the flag
func (f {name}Flag) GetName() string {{
return f.Name
}}
// {name} looks up the value of a local {name}Flag, returns
// {context_default} if not found
func (c *Context) {name}(name string) {context_type} {{
return lookup{name}(name, c.flagSet)
}}
// Global{name} looks up the value of a global {name}Flag, returns
// {context_default} if not found
func (c *Context) Global{name}(name string) {context_type} {{
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
return lookup{name}(name, fs)
}}
return {context_default}
}}
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
f := set.Lookup(name)
if f != nil {{
parsed, err := {parser}
if err != nil {{
return {context_default}
}}
return {parser_cast}
}}
return {context_default}
}}
""".format(**typedef))
def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\
package altsrc
import (
"gopkg.in/urfave/cli.v1"
)
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
// for other values to be specified
type {name}Flag struct {{
cli.{name}Flag
set *flag.FlagSet
}}
// New{name}Flag creates a new {name}Flag
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
return &{name}Flag{{{name}Flag: fl, set: nil}}
}}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.Apply
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
f.set = set
f.{name}Flag.Apply(set)
}}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef))
def _fwrite(outfile, text):
print(textwrap.dedent(text), end='', file=outfile)
_WRITEFUNCS = {
'cli': _write_cli_flag_types,
'altsrc': _write_altsrc_flag_types
}
if __name__ == '__main__':
sys.exit(main())

294
vendor/github.com/urfave/cli/help.go generated vendored Normal file
View file

@ -0,0 +1,294 @@
package cli
import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"text/template"
)
// AppHelpTemplate is the text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) error {
args := c.Args()
if args.Present() {
return ShowCommandHelp(c, args.First())
}
ShowAppHelp(c)
return nil
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) error {
args := c.Args()
if args.Present() {
return ShowCommandHelp(c, args.First())
}
return ShowSubcommandHelp(c)
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
// HelpPrinter is a function that writes the help output. If not set a default
// is used. The function signature is:
// func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// VersionPrinter prints the version for the App
var VersionPrinter = printVersion
// ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) error {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
return nil
}
// DefaultAppComplete prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// ShowCommandHelp prints help for the given command
func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return nil
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return nil
}
}
if ctx.App.CommandNotFound == nil {
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
}
ctx.App.CommandNotFound(ctx, command)
return nil
}
// ShowSubcommandHelp prints help for the given subcommand
func ShowSubcommandHelp(c *Context) error {
return ShowCommandHelp(c, c.Command.Name)
}
// ShowVersion prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// ShowCompletions prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// ShowCommandCompletions prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
// If the writer is closed, t.Execute will fail, and there's nothing
// we can do to recover.
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
}
return
}
w.Flush()
}
func checkVersion(c *Context) bool {
found := false
if VersionFlag.Name != "" {
eachName(VersionFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkHelp(c *Context) bool {
found := false
if HelpFlag.Name != "" {
eachName(HelpFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.Bool("h") || c.Bool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
if !a.EnableBashCompletion {
return false, arguments
}
pos := len(arguments) - 1
lastArg := arguments[pos]
if lastArg != "--"+BashCompletionFlag.Name {
return false, arguments
}
return true, arguments[:pos]
}
func checkCompletions(c *Context) bool {
if !c.shellComplete {
return false
}
if args := c.Args(); args.Present() {
name := args.First()
if cmd := c.App.Command(name); cmd != nil {
// let the command handle the completion
return false
}
}
ShowCompletions(c)
return true
}
func checkCommandCompletions(c *Context, name string) bool {
if !c.shellComplete {
return false
}
ShowCommandCompletions(c, name)
return true
}

122
vendor/github.com/urfave/cli/runtests generated vendored Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import sys
import tempfile
from subprocess import check_call, check_output
PACKAGE_NAME = os.environ.get(
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
)
def main(sysargs=sys.argv[:]):
targets = {
'vet': _vet,
'test': _test,
'gfmrun': _gfmrun,
'toc': _toc,
'gen': _gen,
}
parser = argparse.ArgumentParser()
parser.add_argument(
'target', nargs='?', choices=tuple(targets.keys()), default='test'
)
args = parser.parse_args(sysargs[1:])
targets[args.target]()
return 0
def _test():
if check_output('go version'.split()).split()[2] < 'go1.2':
_run('go test -v .')
return
coverprofiles = []
for subpackage in ['', 'altsrc']:
coverprofile = 'cli.coverprofile'
if subpackage != '':
coverprofile = '{}.coverprofile'.format(subpackage)
coverprofiles.append(coverprofile)
_run('go test -v'.split() + [
'-coverprofile={}'.format(coverprofile),
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
])
combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined_name))
os.remove(combined_name)
def _gfmrun():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
def _vet():
_run('go vet ./...')
def _toc():
_run('node_modules/.bin/markdown-toc -i README.md')
_run('git diff --exit-code')
def _gen():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.5':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run('go generate ./...')
_run('git diff --exit-code')
def _run(command):
if hasattr(command, 'split'):
command = command.split()
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
check_call(command)
def _gfmrun_count():
with open('README.md') as infile:
lines = infile.read().splitlines()
return len(filter(_is_go_runnable, lines))
def _is_go_runnable(line):
return line.startswith('package main')
def _combine_coverprofiles(coverprofiles):
combined = tempfile.NamedTemporaryFile(
suffix='.coverprofile', delete=False
)
combined.write('mode: set\n')
for coverprofile in coverprofiles:
with open(coverprofile, 'r') as infile:
for line in infile.readlines():
if not line.startswith('mode: '):
combined.write(line)
combined.flush()
name = combined.name
combined.close()
return name
if __name__ == '__main__':
sys.exit(main())

View file

@ -1,39 +0,0 @@
atime
==========
[![GoDoc](https://godoc.org/github.com/djherbis/atime?status.svg)](https://godoc.org/github.com/djherbis/atime)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt)
[![Build Status](https://travis-ci.org/djherbis/atime.svg?branch=master)](https://travis-ci.org/djherbis/atime)
[![Coverage Status](https://coveralls.io/repos/djherbis/atime/badge.svg?branch=master)](https://coveralls.io/r/djherbis/atime?branch=master)
Usage
------------
File Access Times for #golang
Looking for ctime or btime? Checkout https://github.com/djherbis/times
Go has a hidden atime function for most platforms, this repo makes it accessible.
```go
package main
import (
"log"
"github.com/djherbis/atime"
)
func main() {
at, err := atime.Stat("myfile")
if err != nil {
log.Fatal(err.Error())
}
log.Println(at)
}
```
Installation
------------
```sh
go get github.com/djherbis/atime
```

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_darwin.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_dragonfly.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_freebsd.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_linux.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

View file

@ -1,22 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_nacl.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(sec, nsec int64) time.Time {
return time.Unix(sec, nsec)
}
func atime(fi os.FileInfo) time.Time {
st := fi.Sys().(*syscall.Stat_t)
return timespecToTime(st.Atime, st.AtimeNsec)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_netbsd.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_openbsd.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

View file

@ -1,16 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_plan9.go
package atime
import (
"os"
"time"
)
func atime(fi os.FileInfo) time.Time {
return time.Unix(int64(fi.Sys().(*syscall.Dir).Atime), 0)
}

View file

@ -1,21 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_solaris.go
package atime
import (
"os"
"syscall"
"time"
)
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func atime(fi os.FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

View file

@ -1,17 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://golang.org/src/os/stat_windows.go
package atime
import (
"os"
"syscall"
"time"
)
func atime(fi os.FileInfo) time.Time {
return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
}

View file

@ -1,21 +0,0 @@
// Package atime provides a platform-independent way to get atimes for files.
package atime
import (
"os"
"time"
)
// Get returns the Last Access Time for the given FileInfo
func Get(fi os.FileInfo) time.Time {
return atime(fi)
}
// Stat returns the Last Access Time for the given filename
func Stat(name string) (time.Time, error) {
fi, err := os.Stat(name)
if err != nil {
return time.Time{}, err
}
return atime(fi), nil
}

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Dustin H
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,80 +0,0 @@
stream
==========
[![GoDoc](https://godoc.org/github.com/djherbis/stream?status.svg)](https://godoc.org/github.com/djherbis/stream)
[![Release](https://img.shields.io/github/release/djherbis/stream.svg)](https://github.com/djherbis/stream/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt)
[![Build Status](https://travis-ci.org/djherbis/stream.svg?branch=master)](https://travis-ci.org/djherbis/stream)
[![Coverage Status](https://coveralls.io/repos/djherbis/stream/badge.svg?branch=master)](https://coveralls.io/r/djherbis/stream?branch=master)
Usage
------------
Write and Read concurrently, and independently.
To explain further, if you need to write to multiple places you can use io.MultiWriter,
if you need multiple Readers on something you can use io.TeeReader. If you want concurrency you can use io.Pipe().
However all of these methods "tie" each Read/Write together, your readers can't read from different places in the stream, each write must be distributed to all readers in sequence.
This package provides a way for multiple Readers to read off the same Writer, without waiting for the others. This is done by writing to a "File" interface which buffers the input so it can be read at any time from many independent readers. Readers can even be created while writing or after the stream is closed. They will all see a consistent view of the stream and will block until the section of the stream they request is written, all while being unaffected by the actions of the other readers.
The use case for this stems from my other project djherbis/fscache. I needed a byte caching mechanism which allowed many independent clients to have access to the data while it was being written, rather than re-generating the byte stream for each of them or waiting for a complete copy of the stream which could be stored and then re-used.
```go
import(
"io"
"log"
"os"
"time"
"github.com/djherbis/stream"
)
func main(){
w, err := stream.New("mystream")
if err != nil {
log.Fatal(err)
}
go func(){
io.WriteString(w, "Hello World!")
<-time.After(time.Second)
io.WriteString(w, "Streaming updates...")
w.Close()
}()
waitForReader := make(chan struct{})
go func(){
// Read from the stream
r, err := w.NextReader()
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, r) // Hello World! (1 second) Streaming updates...
r.Close()
close(waitForReader)
}()
// Full copy of the stream!
r, err := w.NextReader()
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, r) // Hello World! (1 second) Streaming updates...
// r supports io.ReaderAt too.
p := make([]byte, 4)
r.ReadAt(p, 1) // Read "ello" into p
r.Close()
<-waitForReader // don't leave main before go-routine finishes
}
```
Installation
------------
```sh
go get github.com/djherbis/stream
```

View file

@ -1,39 +0,0 @@
package stream
import (
"io"
"os"
)
// File is a backing data-source for a Stream.
type File interface {
Name() string // The name used to Create/Open the File
io.Reader // Reader must continue reading after EOF on subsequent calls after more Writes.
io.ReaderAt // Similarly to Reader
io.Writer // Concurrent reading/writing must be supported.
io.Closer // Close should do any cleanup when done with the File.
}
// FileSystem is used to manage Files
type FileSystem interface {
Create(name string) (File, error) // Create must return a new File for Writing
Open(name string) (File, error) // Open must return an existing File for Reading
Remove(name string) error // Remove deletes an existing File
}
// StdFileSystem is backed by the os package.
var StdFileSystem FileSystem = stdFS{}
type stdFS struct{}
func (fs stdFS) Create(name string) (File, error) {
return os.Create(name)
}
func (fs stdFS) Open(name string) (File, error) {
return os.Open(name)
}
func (fs stdFS) Remove(name string) error {
return os.Remove(name)
}

View file

@ -1,107 +0,0 @@
package stream
import (
"bytes"
"errors"
"io"
"sync"
)
// ErrNotFoundInMem is returned when an in-memory FileSystem cannot find a file.
var ErrNotFoundInMem = errors.New("not found")
type memfs struct {
mu sync.RWMutex
files map[string]*memFile
}
// NewMemFS returns a New in-memory FileSystem
func NewMemFS() FileSystem {
return &memfs{
files: make(map[string]*memFile),
}
}
func (fs *memfs) Create(key string) (File, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
file := &memFile{
name: key,
r: bytes.NewBuffer(nil),
}
file.memReader.memFile = file
fs.files[key] = file
return file, nil
}
func (fs *memfs) Open(key string) (File, error) {
fs.mu.RLock()
defer fs.mu.RUnlock()
if f, ok := fs.files[key]; ok {
return &memReader{memFile: f}, nil
}
return nil, ErrNotFoundInMem
}
func (fs *memfs) Remove(key string) error {
fs.mu.Lock()
defer fs.mu.Unlock()
delete(fs.files, key)
return nil
}
type memFile struct {
mu sync.RWMutex
name string
r *bytes.Buffer
memReader
}
func (f *memFile) Name() string {
return f.name
}
func (f *memFile) Write(p []byte) (int, error) {
if len(p) > 0 {
f.mu.Lock()
defer f.mu.Unlock()
return f.r.Write(p)
}
return len(p), nil
}
func (f *memFile) Bytes() []byte {
f.mu.RLock()
defer f.mu.RUnlock()
return f.r.Bytes()
}
func (f *memFile) Close() error {
return nil
}
type memReader struct {
*memFile
n int
}
func (r *memReader) ReadAt(p []byte, off int64) (n int, err error) {
data := r.Bytes()
if int64(len(data)) < off {
return 0, io.EOF
}
n, err = bytes.NewReader(data[off:]).ReadAt(p, 0)
return n, err
}
func (r *memReader) Read(p []byte) (n int, err error) {
n, err = bytes.NewReader(r.Bytes()[r.n:]).Read(p)
r.n += n
return n, err
}
func (r *memReader) Close() error {
return nil
}

View file

@ -1,82 +0,0 @@
package stream
import "io"
// Reader is a concurrent-safe Stream Reader.
type Reader struct {
s *Stream
file File
}
// Name returns the name of the underlying File in the FileSystem.
func (r *Reader) Name() string { return r.file.Name() }
// ReadAt lets you Read from specific offsets in the Stream.
// ReadAt blocks while waiting for the requested section of the Stream to be written,
// unless the Stream is closed in which case it will always return immediately.
func (r *Reader) ReadAt(p []byte, off int64) (n int, err error) {
r.s.b.RLock()
defer r.s.b.RUnlock()
var m int
for {
m, err = r.file.ReadAt(p[n:], off+int64(n))
n += m
if r.s.b.IsOpen() {
switch {
case n != 0 && err == nil:
return n, err
case err == io.EOF:
r.s.b.Wait()
case err != nil:
return n, err
}
} else {
return n, err
}
}
}
// Read reads from the Stream. If the end of an open Stream is reached, Read
// blocks until more data is written or the Stream is Closed.
func (r *Reader) Read(p []byte) (n int, err error) {
r.s.b.RLock()
defer r.s.b.RUnlock()
var m int
for {
m, err = r.file.Read(p[n:])
n += m
if r.s.b.IsOpen() {
switch {
case n != 0 && err == nil:
return n, err
case err == io.EOF:
r.s.b.Wait()
case err != nil:
return n, err
}
} else {
return n, err
}
}
}
// Close closes this Reader on the Stream. This must be called when done with the
// Reader or else the Stream cannot be Removed.
func (r *Reader) Close() error {
defer r.s.dec()
return r.file.Close()
}

View file

@ -1,92 +0,0 @@
// Package stream provides a way to read and write to a synchronous buffered pipe, with multiple reader support.
package stream
import (
"errors"
"sync"
)
// ErrRemoving is returned when requesting a Reader on a Stream which is being Removed.
var ErrRemoving = errors.New("cannot open a new reader while removing file")
// Stream is used to concurrently Write and Read from a File.
type Stream struct {
grp sync.WaitGroup
b *broadcaster
file File
fs FileSystem
removing chan struct{}
}
// New creates a new Stream from the StdFileSystem with Name "name".
func New(name string) (*Stream, error) {
return NewStream(name, StdFileSystem)
}
// NewStream creates a new Stream with Name "name" in FileSystem fs.
func NewStream(name string, fs FileSystem) (*Stream, error) {
f, err := fs.Create(name)
sf := &Stream{
file: f,
fs: fs,
b: newBroadcaster(),
removing: make(chan struct{}),
}
sf.inc()
return sf, err
}
// Name returns the name of the underlying File in the FileSystem.
func (s *Stream) Name() string { return s.file.Name() }
// Write writes p to the Stream. It's concurrent safe to be called with Stream's other methods.
func (s *Stream) Write(p []byte) (int, error) {
defer s.b.Broadcast()
s.b.Lock()
defer s.b.Unlock()
return s.file.Write(p)
}
// Close will close the active stream. This will cause Readers to return EOF once they have
// read the entire stream.
func (s *Stream) Close() error {
defer s.dec()
defer s.b.Close()
s.b.Lock()
defer s.b.Unlock()
return s.file.Close()
}
// Remove will block until the Stream and all its Readers have been Closed,
// at which point it will delete the underlying file. NextReader() will return
// ErrRemoving if called after Remove.
func (s *Stream) Remove() error {
close(s.removing)
s.grp.Wait()
return s.fs.Remove(s.file.Name())
}
// NextReader will return a concurrent-safe Reader for this stream. Each Reader will
// see a complete and independent view of the stream, and can Read will the stream
// is written to.
func (s *Stream) NextReader() (*Reader, error) {
s.inc()
select {
case <-s.removing:
s.dec()
return nil, ErrRemoving
default:
}
file, err := s.fs.Open(s.file.Name())
if err != nil {
s.dec()
return nil, err
}
return &Reader{file: file, s: s}, nil
}
func (s *Stream) inc() { s.grp.Add(1) }
func (s *Stream) dec() { s.grp.Done() }

View file

@ -1,34 +0,0 @@
package stream
import (
"sync"
"sync/atomic"
)
type broadcaster struct {
sync.RWMutex
closed uint32
*sync.Cond
}
func newBroadcaster() *broadcaster {
var b broadcaster
b.Cond = sync.NewCond(b.RWMutex.RLocker())
return &b
}
func (b *broadcaster) Wait() {
if b.IsOpen() {
b.Cond.Wait()
}
}
func (b *broadcaster) IsOpen() bool {
return atomic.LoadUint32(&b.closed) == 0
}
func (b *broadcaster) Close() error {
atomic.StoreUint32(&b.closed, 1)
b.Cond.Broadcast()
return nil
}

57
vendor/vendor.json vendored
View file

@ -2,11 +2,6 @@
"comment": "",
"ignore": "test github.com/drone/mq/ github.com/tidwall/redlog/ google.golang.org/appengine/ github.com/syndtr/goleveldb/ github.com/drone/drone-ui/",
"package": [
{
"path": "code.google.com/p/go.crypto/ssh",
"revision": "7aa593ce8cea",
"revisionTime": "2014-02-19T20:51:49+01:00"
},
{
"checksumSHA1": "zTn0jzjOiJlScR1px66MvrgrlLs=",
"origin": "github.com/docker/docker/vendor/github.com/Microsoft/go-winio",
@ -108,32 +103,17 @@
"revision": "1ce1ada7160f1eda015a16c1b7f9ea497fa36873",
"revisionTime": "2017-03-03T07:04:55Z"
},
{
"path": "github.com/codegangsta/cli",
"revision": "70e3fa51ebed95df8c0fbe1519c1c1f9bc16bb13",
"revisionTime": "2015-10-29T20:11:27-07:00"
},
{
"origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew",
"path": "github.com/davecgh/go-spew/spew",
"revision": "9f9027faeb0dad515336ed2f28317f9f8f527ab4",
"revisionTime": "2016-01-29T19:31:06Z"
},
{
"path": "github.com/dchest/uniuri",
"revision": "bc4af7603a3e0ce9d58009f82fca481555182e1c",
"revisionTime": "2013-12-23T09:21:49+01:00"
},
{
"path": "github.com/dgrijalva/jwt-go",
"revision": "c1da56349675b292d3200463e2c88b9aa5e02391",
"revisionTime": "2015-09-04T14:24:56-07:00"
},
{
"path": "github.com/djherbis/fscache",
"revision": "ffc728270b01f3906c396bbe796232b87750f24e",
"revisionTime": "2016-03-05T10:30:05-08:00"
},
{
"checksumSHA1": "rSxOx+SnSLAxR4ST8fSz9hhJLdk=",
"origin": "github.com/docker/docker/vendor/github.com/docker/distribution/reference",
@ -316,21 +296,6 @@
"revision": "3e65ae5fd2d944d56fdf52cb3f887247498d50e9",
"revisionTime": "2017-01-18T15:01:55Z"
},
{
"path": "github.com/eknkc/amber",
"revision": "144da19a9994994c069f0693294a66dd310e14a4",
"revisionTime": "2015-08-30T00:43:03+03:00"
},
{
"path": "github.com/eknkc/amber/parser",
"revision": "144da19a9994994c069f0693294a66dd310e14a4",
"revisionTime": "2015-08-30T00:43:03+03:00"
},
{
"path": "github.com/elazarl/go-bindata-assetfs",
"revision": "bea323321994103859d60197d229f1a94699dde3",
"revisionTime": "2015-04-14T21:44:09+03:00"
},
{
"checksumSHA1": "++oBqqmh8bt50iB6/I+BHnrzC80=",
"origin": "github.com/docker/libcompose/vendor/github.com/flynn/go-shlex",
@ -497,12 +462,6 @@
"revision": "308c3d0e5e45f543a2eb6c787cbfe0db3880e220",
"revisionTime": "2013-09-19T15:23:15-06:00"
},
{
"checksumSHA1": "+HvW+k8YkDaPKwF0Lwcz+Tf2A+E=",
"path": "github.com/samalba/dockerclient",
"revision": "91d7393ff85980ba3a8966405871a3d446ca28f2",
"revisionTime": "2016-04-14T17:47:13Z"
},
{
"checksumSHA1": "LMgaBYB2FIEfqEAXkV6a2bEfrVs=",
"path": "github.com/sourcegraph/jsonrpc2",
@ -547,6 +506,12 @@
"revision": "3c25f2fe7cd0ef3eabefce1d90efd69a65d35b12",
"revisionTime": "2016-06-28T10:11:33Z"
},
{
"checksumSHA1": "7cxwOBG3z8bcqlACknqxIV+jB/o=",
"path": "github.com/urfave/cli",
"revision": "f1be59ff3d239f0942b201619030d302bce912cc",
"revisionTime": "2016-11-19T21:37:11Z"
},
{
"path": "golang.org/x/net/context",
"revision": "6acef71eb69611914f7a30939ea9f6e194c78172",
@ -596,16 +561,6 @@
"revision": "f645ffca04abf2dd6c89ac9057a1eb7d2b0ac338",
"revisionTime": "2017-01-24T17:08:27Z"
},
{
"path": "gopkg.in/djherbis/atime.v1",
"revision": "8e47e0e01d08df8b9f840d74299c8ab70a024a30",
"revisionTime": "2015-08-29T00:19:25-07:00"
},
{
"path": "gopkg.in/djherbis/stream.v1",
"revision": "26a761059928627ca84837000dfb33447c66a146",
"revisionTime": "2016-02-03T22:24:40-08:00"
},
{
"path": "gopkg.in/go-playground/validator.v8",
"revision": "014792cf3e266caff1e916876be12282b33059e0",