diff --git a/.travis.yml b/.travis.yml index b9daa71..dc22711 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,22 @@ os: linux language: go go: 1.4.2 -install: go get github.com/tools/godep +install: + - go get github.com/tools/godep + - go get github.com/mitchellh/gox +before_script: make dep-restore script: make test-verbose -after_success: make package +after_success: + - "cd $GOROOT/src && GOOS=darwin GOARCH=amd64 ./make.bash --no-clean && cd -" + - make package deploy: provider: releases api_key: secure: gE+O8G3DK0jhYc7lIIz9wlRLSZTXau7WdWz3dQqG/DAPvkJbjcGAjA3WmW81KcEs4Ob0ODIdChSGJY5+px/w0aFTdAAP27NvoPRwute2VcLRevpjCglroqz3girj3KUVJmACtya4h/nJZ3Eo20/6/lTJTE6y6W9dVhbwx1DpKlM= - file: tnator-linux-amd64.tar.gz + file: + - tnator-linux-amd64.tar.gz + - tnator-darwin-amd64.tar.gz on: repo: albertrdixon/tmplnator tags: true diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 040de48..62fa954 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -5,6 +5,11 @@ "./..." ], "Deps": [ + { + "ImportPath": "code.google.com/p/go-uuid/uuid", + "Comment": "null-12", + "Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9" + }, { "ImportPath": "github.com/Sirupsen/logrus", "Comment": "v0.6.6-2-g2cea0f0", diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/LICENSE b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/LICENSE new file mode 100644 index 0000000..ab6b011 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 Google Inc. 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. diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go new file mode 100644 index 0000000..50a0f2d --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go new file mode 100644 index 0000000..d8bd013 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go @@ -0,0 +1,8 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The uuid package generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services. +package uuid diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go new file mode 100644 index 0000000..cdd4192 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known Name Space IDs and UUIDs +var ( + NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + NIL = Parse("00000000-0000-0000-0000-000000000000") +) + +// NewHash returns a new UUID dervied from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space) + h.Write([]byte(data)) + s := h.Sum(nil) + uuid := make([]byte, 16) + copy(uuid, s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go new file mode 100644 index 0000000..dd0a8ac --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go @@ -0,0 +1,101 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "net" + +var ( + interfaces []net.Interface // cached list of interfaces + ifname string // name of interface being used + nodeID []byte // hardware for version 1 UUIDs +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil && name != "" { + return false + } + } + + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + if setNodeID(ifs.HardwareAddr) { + ifname = ifs.Name + return true + } + } + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + if nodeID == nil { + nodeID = make([]byte, 6) + } + randomBits(nodeID) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + if nodeID == nil { + SetNodeInterface("") + } + nid := make([]byte, 6) + copy(nid, nodeID) + return nid +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if setNodeID(id) { + ifname = "user" + return true + } + return false +} + +func setNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + if nodeID == nil { + nodeID = make([]byte, 6) + } + copy(nodeID, id) + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + if len(uuid) != 16 { + return nil + } + node := make([]byte, 6) + copy(node, uuid[10:]) + return node +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go new file mode 100644 index 0000000..b9369c2 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go @@ -0,0 +1,132 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + mu sync.Mutex + lasttime uint64 // last time we returned + clock_seq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// adjusts the clock sequence as needed. An error is returned if the current +// time cannot be determined. +func GetTime() (Time, error) { + defer mu.Unlock() + mu.Lock() + return getTime() +} + +func getTime() (Time, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clock_seq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence a new random +// clock sequence is generated the first time a clock sequence is requested by +// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated +// for +func ClockSequence() int { + defer mu.Unlock() + mu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clock_seq == 0 { + setClockSequence(-1) + } + return int(clock_seq & 0x3fff) +} + +// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer mu.Unlock() + mu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + old_seq := clock_seq + clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if old_seq != clock_seq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. It returns false if uuid is not valid. The time is only well defined +// for version 1 and 2 UUIDs. +func (uuid UUID) Time() (Time, bool) { + if len(uuid) != 16 { + return 0, false + } + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time), true +} + +// ClockSequence returns the clock sequence encoded in uuid. It returns false +// if uuid is not valid. The clock sequence is only well defined for version 1 +// and 2 UUIDs. +func (uuid UUID) ClockSequence() (int, bool) { + if len(uuid) != 16 { + return 0, false + } + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go new file mode 100644 index 0000000..de40b10 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts the the first two hex bytes of x into a byte. +func xtob(x string) (byte, bool) { + b1 := xvalues[x[0]] + b2 := xvalues[x[1]] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go new file mode 100644 index 0000000..2920fae --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go @@ -0,0 +1,163 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID []byte + +// A Version represents a UUIDs version. +type Version byte + +// A Variant represents a UUIDs variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// New returns a new random (version 4) UUID as a string. It is a convenience +// function for NewRandom().String(). +func New() string { + return NewRandom().String() +} + +// Parse decodes s into a UUID or returns nil. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) UUID { + if len(s) == 36+9 { + if strings.ToLower(s[:9]) != "urn:uuid:" { + return nil + } + s = s[9:] + } else if len(s) != 36 { + return nil + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return nil + } + uuid := make([]byte, 16) + for i, x := range []int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + if v, ok := xtob(s[x:]); !ok { + return nil + } else { + uuid[i] = v + } + } + return uuid +} + +// Equal returns true if uuid1 and uuid2 are equal. +func Equal(uuid1, uuid2 UUID) bool { + return bytes.Equal(uuid1, uuid2) +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + if uuid == nil || len(uuid) != 16 { + return "" + } + b := []byte(uuid) + return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", + b[:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + if uuid == nil || len(uuid) != 16 { + return "" + } + b := []byte(uuid) + return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x", + b[:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +// Variant returns the variant encoded in uuid. It returns Invalid if +// uuid is invalid. +func (uuid UUID) Variant() Variant { + if len(uuid) != 16 { + return Invalid + } + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } + panic("unreachable") +} + +// Version returns the verison of uuid. It returns false if uuid is not +// valid. +func (uuid UUID) Version() (Version, bool) { + if len(uuid) != 16 { + return 0, false + } + return Version(uuid[6] >> 4), true +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implents io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go new file mode 100644 index 0000000..417ebeb --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go @@ -0,0 +1,390 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" + "time" +) + +type test struct { + in string + version Version + variant Variant + isuuid bool +} + +var tests = []test{ + {"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true}, + {"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true}, + {"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true}, + {"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true}, + {"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true}, + {"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true}, + {"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true}, + {"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true}, + {"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true}, + {"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true}, + {"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true}, + {"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true}, + {"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true}, + + {"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true}, + {"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true}, + + {"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false}, + {"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false}, +} + +var constants = []struct { + c interface{} + name string +}{ + {Person, "Person"}, + {Group, "Group"}, + {Org, "Org"}, + {Invalid, "Invalid"}, + {RFC4122, "RFC4122"}, + {Reserved, "Reserved"}, + {Microsoft, "Microsoft"}, + {Future, "Future"}, + {Domain(17), "Domain17"}, + {Variant(42), "BadVariant42"}, +} + +func testTest(t *testing.T, in string, tt test) { + uuid := Parse(in) + if ok := (uuid != nil); ok != tt.isuuid { + t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if uuid == nil { + return + } + + if v := uuid.Variant(); v != tt.variant { + t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant) + } + if v, _ := uuid.Version(); v != tt.version { + t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version) + } +} + +func TestUUID(t *testing.T) { + for _, tt := range tests { + testTest(t, tt.in, tt) + testTest(t, strings.ToUpper(tt.in), tt) + } +} + +func TestConstants(t *testing.T) { + for x, tt := range constants { + v, ok := tt.c.(fmt.Stringer) + if !ok { + t.Errorf("%x: %v: not a stringer", x, v) + } else if s := v.String(); s != tt.name { + v, _ := tt.c.(int) + t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name) + } + } +} + +func TestRandomUUID(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + uuid := NewRandom() + s := uuid.String() + if m[s] { + t.Errorf("NewRandom returned duplicated UUID %s\n", s) + } + m[s] = true + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s\n", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d\n", uuid.Variant()) + } + } +} + +func TestNew(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + s := New() + if m[s] { + t.Errorf("New returned duplicated UUID %s\n", s) + } + m[s] = true + uuid := Parse(s) + if uuid == nil { + t.Errorf("New returned %q which does not decode\n", s) + continue + } + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s\n", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d\n", uuid.Variant()) + } + } +} + +func clockSeq(t *testing.T, uuid UUID) int { + seq, ok := uuid.ClockSequence() + if !ok { + t.Fatalf("%s: invalid clock sequence\n", uuid) + } + return seq +} + +func TestClockSeq(t *testing.T) { + // Fake time.Now for this test to return a monotonically advancing time; restore it at end. + defer func(orig func() time.Time) { timeNow = orig }(timeNow) + monTime := time.Now() + timeNow = func() time.Time { + monTime = monTime.Add(1 * time.Second) + return monTime + } + + SetClockSequence(-1) + uuid1 := NewUUID() + uuid2 := NewUUID() + + if clockSeq(t, uuid1) != clockSeq(t, uuid2) { + t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2)) + } + + SetClockSequence(-1) + uuid2 = NewUUID() + + // Just on the very off chance we generated the same sequence + // two times we try again. + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + SetClockSequence(-1) + uuid2 = NewUUID() + } + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1)) + } + + SetClockSequence(0x1234) + uuid1 = NewUUID() + if seq := clockSeq(t, uuid1); seq != 0x1234 { + t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq) + } +} + +func TestCoding(t *testing.T) { + text := "7d444840-9dc0-11d1-b245-5ffdce74fad2" + urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2" + data := UUID{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + if v := data.String(); v != text { + t.Errorf("%x: encoded to %s, expected %s\n", data, v, text) + } + if v := data.URN(); v != urn { + t.Errorf("%x: urn is %s, expected %s\n", data, v, urn) + } + + uuid := Parse(text) + if !Equal(uuid, data) { + t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data) + } +} + +func TestVersion1(t *testing.T) { + uuid1 := NewUUID() + uuid2 := NewUUID() + + if Equal(uuid1, uuid2) { + t.Errorf("%s:duplicate uuid\n", uuid1) + } + if v, _ := uuid1.Version(); v != 1 { + t.Errorf("%s: version %s expected 1\n", uuid1, v) + } + if v, _ := uuid2.Version(); v != 1 { + t.Errorf("%s: version %s expected 1\n", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x\n", n1, n2) + } + t1, ok := uuid1.Time() + if !ok { + t.Errorf("%s: invalid time\n", uuid1) + } + t2, ok := uuid2.Time() + if !ok { + t.Errorf("%s: invalid time\n", uuid2) + } + q1, ok := uuid1.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence\n", uuid1) + } + q2, ok := uuid2.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid2) + } + + switch { + case t1 == t2 && q1 == q2: + t.Errorf("time stopped\n") + case t1 > t2 && q1 == q2: + t.Errorf("time reversed\n") + case t1 < t2 && q1 != q2: + t.Errorf("clock sequence chaned unexpectedly\n") + } +} + +func TestNodeAndTime(t *testing.T) { + // Time is February 5, 1998 12:30:23.136364800 AM GMT + + uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2") + node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2} + + ts, ok := uuid.Time() + if ok { + c := time.Unix(ts.UnixTime()) + want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC) + if !c.Equal(want) { + t.Errorf("Got time %v, want %v", c, want) + } + } else { + t.Errorf("%s: bad time\n", uuid) + } + if !bytes.Equal(node, uuid.NodeID()) { + t.Errorf("Expected node %v got %v\n", node, uuid.NodeID()) + } +} + +func TestMD5(t *testing.T) { + uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String() + want := "6fa459ea-ee8a-3ca4-894e-db77e160355e" + if uuid != want { + t.Errorf("MD5: got %q expected %q\n", uuid, want) + } +} + +func TestSHA1(t *testing.T) { + uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String() + want := "886313e1-3b8a-5372-9b90-0c9aee199e5d" + if uuid != want { + t.Errorf("SHA1: got %q expected %q\n", uuid, want) + } +} + +func TestNodeID(t *testing.T) { + nid := []byte{1, 2, 3, 4, 5, 6} + SetNodeInterface("") + s := NodeInterface() + if s == "" || s == "user" { + t.Errorf("NodeInterface %q after SetInteface\n", s) + } + node1 := NodeID() + if node1 == nil { + t.Errorf("NodeID nil after SetNodeInterface\n", s) + } + SetNodeID(nid) + s = NodeInterface() + if s != "user" { + t.Errorf("Expected NodeInterface %q got %q\n", "user", s) + } + node2 := NodeID() + if node2 == nil { + t.Errorf("NodeID nil after SetNodeID\n", s) + } + if bytes.Equal(node1, node2) { + t.Errorf("NodeID not changed after SetNodeID\n", s) + } else if !bytes.Equal(nid, node2) { + t.Errorf("NodeID is %x, expected %x\n", node2, nid) + } +} + +func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) { + if uuid == nil { + t.Errorf("%s failed\n", name) + return + } + if v, _ := uuid.Version(); v != 2 { + t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v) + return + } + if v, ok := uuid.Domain(); !ok || v != domain { + if !ok { + t.Errorf("%s: %d: Domain failed\n", name, uuid) + } else { + t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v) + } + } + if v, ok := uuid.Id(); !ok || v != id { + if !ok { + t.Errorf("%s: %d: Id failed\n", name, uuid) + } else { + t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v) + } + } +} + +func TestDCE(t *testing.T) { + testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678) + testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid())) + testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid())) +} + +type badRand struct{} + +func (r badRand) Read(buf []byte) (int, error) { + for i, _ := range buf { + buf[i] = byte(i) + } + return len(buf), nil +} + +func TestBadRand(t *testing.T) { + SetRand(badRand{}) + uuid1 := New() + uuid2 := New() + if uuid1 != uuid2 { + t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2) + } + SetRand(nil) + uuid1 = New() + uuid2 = New() + if uuid1 == uuid2 { + t.Errorf("unexecpted duplicates, got %q\n", uuid1) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go new file mode 100644 index 0000000..6358004 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go @@ -0,0 +1,41 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil. +func NewUUID() UUID { + if nodeID == nil { + SetNodeInterface("") + } + + now, err := GetTime() + if err != nil { + return nil + } + + uuid := make([]byte, 16) + + time_low := uint32(now & 0xffffffff) + time_mid := uint16((now >> 32) & 0xffff) + time_hi := uint16((now >> 48) & 0x0fff) + time_hi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], time_low) + binary.BigEndian.PutUint16(uuid[4:], time_mid) + binary.BigEndian.PutUint16(uuid[6:], time_hi) + binary.BigEndian.PutUint16(uuid[8:], clock_seq) + copy(uuid[10:], nodeID) + + return uuid +} diff --git a/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go new file mode 100644 index 0000000..b3d4a36 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go @@ -0,0 +1,25 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// Random returns a Random (Version 4) UUID or panics. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() UUID { + uuid := make([]byte, 16) + randomBits([]byte(uuid)) + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid +} diff --git a/Makefile b/Makefile index 8184767..a7164d8 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,9 @@ PROJECT = github.com/albertrdixon/tmplnator -INSTALL = $(PROJECT)/cmd/t2 -EXECUTABLE = "t2" -LDFLAGS = "-X $(PROJECT)/config.Build $$(git rev-parse --short HEAD) -s" -BINARY = "cmd/t2/t2.go" -TEST_COMMAND = TNATOR_DIR=$(shell pwd)/fixtures godep go test -PLATFORM = "$$(echo "$$(uname)" | tr '[A-Z]' '[a-z]')" -VERSION = "$$(./t2 -v)" +TEST_COMMAND = godep go test +PLATFORMS = linux darwin BUILD_ARGS = "" -.PHONY: dep-save dep-restore test test-verbose test-integration vet lint build install clean +.PHONY: dep-save dep-restore test test-verbose build install clean all: test @@ -28,10 +23,12 @@ help: @echo " clean" dep-save: - godep save ./... + @echo "==> Saving dependencies..." + @godep save ./... dep-restore: - godep restore + @echo "==> Restoring dependencies..." + @godep restore test: @echo "==> Running all tests" @@ -43,27 +40,22 @@ test-verbose: @ echo "" @$(TEST_COMMAND) -test.v ./... -test-integration: - $(TEST_COMMAND) ./... -tags integration - -vet: - go vet ./... - -lint: - golint ./... - build: - @echo "==> Building $(EXECUTABLE) with ldflags '$(LDFLAGS)'" - @godep go build -ldflags $(LDFLAGS) $(BINARY) + @echo "==> Building executables" + @gox -osarch="linux/amd64 darwin/amd64" -output="{{.Dir}}-{{.OS}}-{{.Arch}}" ./... install: - @echo "==> Installing $(EXECUTABLE) with ldflags $(LDFLAGS)" - @godep go install -ldflags $(LDFLAGS) $(INSTALL) + @echo "==> Installing..." + @godep go install ./... package: build - @echo "==> Tar'ing up the binary" - @test -f t2 && tar czf tnator-$(PLATFORM)-amd64.tar.gz t2 + @for p in $(PLATFORMS) ; do \ + echo "==> Tar'ing up $$p/amd64 binary" ; \ + test -f t2-$$p-amd64 && mv t2-$$p-amd64 t2 && tar czf tnator-$$p-amd64.tar.gz t2 ; \ + rm -f t2 ; \ + done clean: - go clean ./... - rm -rf t2 + @echo "==> Cleaning up workspace..." + @go clean ./... + @rm -rf t2* tnator*.tar.gz diff --git a/cmd/t2/t2.go b/cmd/t2/t2.go index 18170b5..2a17723 100644 --- a/cmd/t2/t2.go +++ b/cmd/t2/t2.go @@ -37,7 +37,8 @@ func main() { tmplnator.TmpDir = *tmpDir } - tmplnator.NewGenerator(false, false).Generate(*rootDir) + tmplnator.InitFs(false, false) + tmplnator.Generate(*rootDir) if *printTmpDir { println(tmplnator.TmpDir) } diff --git a/doc.go b/doc.go index edc2912..c9fcabe 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ // Package tmplnator generates files from templates under a specified directory package tmplnator -const Version = "v2.0.0" +const Version = "v2.1.0" diff --git a/errors.go b/errors.go index 5a981cb..9af97a6 100644 --- a/errors.go +++ b/errors.go @@ -1,39 +1,17 @@ package tmplnator -import ( - "fmt" +import "fmt" - l "github.com/Sirupsen/logrus" -) - -// TemplateError are template related errors -type TemplateError struct { - template string - msg string -} - -// GeneratorError are generator related errors -type GeneratorError struct { - thread string - msg string -} - -func (t TemplateError) Error() string { - return fmt.Sprintf("%s: %s", t.template, t.msg) -} - -func (g GeneratorError) Error() string { - return fmt.Sprintf("%s: %s", g.thread, g.msg) +type FileError struct { + File string + Template string + Msg string } -func newGeneratorError(thread string, format string, items ...interface{}) GeneratorError { - ge := GeneratorError{thread, fmt.Sprintf(format, items...)} - l.Error(ge.Error()) - return ge +func (f FileError) Error() string { + return fmt.Sprintf("%s: %s", f.File, f.Msg) } -func newTemplateError(template string, format string, items ...interface{}) TemplateError { - te := TemplateError{template, fmt.Sprintf(format, items...)} - l.Error(te.Error()) - return te +func NewFileError(f *File, format string, items ...interface{}) FileError { + return FileError{f.info.fileName, f.template.Name(), fmt.Sprintf(format, items...)} } diff --git a/file.go b/file.go new file mode 100644 index 0000000..ef04371 --- /dev/null +++ b/file.go @@ -0,0 +1,210 @@ +package tmplnator + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + l "github.com/Sirupsen/logrus" + "github.com/spf13/afero" +) + +var extensions = []string{".tpl", ".tmpl", ".templ", ".template"} + +// Reader is a convenience interface +type Reader interface { + Read(p []byte) (n int, err error) +} + +// File type wraps text/tsemplate so we can update the info before generatingthe file +type File struct { + template *template.Template + info *FileInfo + errs []error +} + +type FileInfo struct { + template string + fileName string + fullPath string + mode os.FileMode + dirmode os.FileMode + modtime time.Time + size int64 + skip bool +} + +func NewFile(path string) *File { + var filename string + var fullpath string + + filename = filepath.Base(path) + for _, suffix := range extensions { + filename = strings.TrimSuffix(filename, suffix) + } + + if TmpDir == "" { + fullpath = filepath.Join(filepath.Dir(path), filename) + } else { + fullpath = filepath.Join(TmpDir, filename) + } + + f := &File{ + template: template.New(path), + info: &FileInfo{ + fileName: filename, + fullPath: fullpath, + mode: os.FileMode(0644), + dirmode: os.FileMode(0755), + modtime: time.Time{}, + size: 0, + skip: false, + }, + } + + return f.RegisterFuncs(newFuncMap(f.Info())) +} + +func (f *FileInfo) Name() string { return f.fileName } +func (f *FileInfo) Mode() os.FileMode { return f.mode } +func (f *FileInfo) ModTime() time.Time { return f.modtime } +func (f *FileInfo) IsDir() bool { return false } +func (f *FileInfo) Sys() interface{} { return nil } +func (f *FileInfo) Size() int64 { return f.size } +func (f *FileInfo) String() string { return f.Name() } +func (f *FileInfo) Source() string { return f.template } + +func (f *File) TemplateName() string { return f.template.Name() } +func (f *File) Name() string { return f.info.Name() } +func (f *File) FullPath() string { return f.info.fullPath } +func (f *File) Dir() string { return filepath.Dir(f.FullPath()) } +func (f *File) Skip() bool { return f.info.skip } +func (f *File) Info() *FileInfo { return f.info } +func (f *File) String() string { + return fmt.Sprintf("(template)%s (file)%s", f.TemplateName(), f.FullPath()) +} +func (f *File) HasErrs() bool { + if len(f.errs) > 0 { + return true + } + return false +} + +func (f *File) RegisterFuncs(funcMap map[string]interface{}) *File { + f.template = f.template.Funcs(funcMap) + return f +} + +func (f *FileInfo) SetFilename(format string, items ...interface{}) string { + path := fmt.Sprintf(format, items...) + l.Debugf("%v: Changing name to %s", f, filepath.Base(path)) + f.fileName = filepath.Base(path) + return f.SetFullpath(filepath.Dir(path)) +} + +func (f *FileInfo) SetFullpath(format string, items ...interface{}) string { + path := fmt.Sprintf(format, items...) + l.Debugf("%v: Changing path to %s", f, path) + fullpath := filepath.Join(path, f.Name()) + if ForceTemp { + fullpath = filepath.Join(TmpDir, f.Name()) + } + f.fullPath = filepath.Clean(fullpath) + return "" +} + +func (f *FileInfo) SetMode(mode os.FileMode) string { + l.Debugf("%v: Changing filemode to %v", f, mode) + f.mode = mode + return "" +} + +func (f *FileInfo) SetDirmode(mode os.FileMode) string { + l.Debugf("%v: Changing dirmode to %v", f, mode) + f.dirmode = mode + return "" +} + +func (f *FileInfo) SetSkip() string { + l.Debugf("%v: Setting skip to true") + f.skip = true + return "" +} + +func (f *File) Open() (afero.File, error) { + return destFs.Open(f.FullPath()) +} + +func ParseTemplate(f *File, fs afero.Fs) error { + defer func() { + if r := recover(); r != nil { + f.errs = append(f.errs, NewFileError(f, "panic: %v", r)) + } + }() + + fh, err := fs.Open(f.template.Name()) + if err != nil { + f.errs = append(f.errs, err) + return err + } + defer fh.Close() + + var b bytes.Buffer + if _, err := b.ReadFrom(fh); err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + + t, err := f.template.Parse(b.String()) + if err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + + f.template = t + return nil +} + +func WriteFile(f *File, fs afero.Fs) error { + b := new(bytes.Buffer) + if err := f.template.Execute(b, data); err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + + if f.Skip() { + return nil + } + + if _, err := fs.Stat(f.Dir()); err != nil { + if err = fs.MkdirAll(f.Dir(), f.info.dirmode); err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + } + + gf, err := fs.Create(f.FullPath()) + if err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + + n, err := b.WriteTo(gf) + if err != nil { + e := NewFileError(f, err.Error()) + f.errs = append(f.errs, e) + return e + } + f.info.size = n + fs.Chmod(f.FullPath(), f.info.Mode()) + return nil +} diff --git a/example/file1.conf b/fixtures/test/file1.conf similarity index 54% rename from example/file1.conf rename to fixtures/test/file1.conf index 686802a..4564126 100644 --- a/example/file1.conf +++ b/fixtures/test/file1.conf @@ -1,3 +1,3 @@ -{{ dir "%s/out" .Env.PWD }} +{{ file "%s/out/file1.conf" .Env.PWD }} Generated from {{ source }} at {{ timestamp }} diff --git a/example/file2.conf b/fixtures/test/file2.conf similarity index 55% rename from example/file2.conf rename to fixtures/test/file2.conf index b89bf01..ef4cb71 100644 --- a/example/file2.conf +++ b/fixtures/test/file2.conf @@ -1,2 +1,3 @@ -{{ dir "%s/out" .Env.PWD }} +{{ path "%s/out" .Env.PWD }} Generated from {{ source }} at {{ timestamp }} +{{ bad } diff --git a/example/file3.conf b/fixtures/test/file3.conf similarity index 50% rename from example/file3.conf rename to fixtures/test/file3.conf index b89bf01..8f3f28a 100644 --- a/example/file3.conf +++ b/fixtures/test/file3.conf @@ -1,2 +1,2 @@ -{{ dir "%s/out" .Env.PWD }} +{{ file "%s/out/awesome-name.conf" .Env.PWD }} Generated from {{ source }} at {{ timestamp }} diff --git a/fixtures/test/skip.conf.tmpl b/fixtures/test/skip.conf.tmpl new file mode 100644 index 0000000..4da5ba8 --- /dev/null +++ b/fixtures/test/skip.conf.tmpl @@ -0,0 +1,2 @@ +{{ skip }} +Will be skipped diff --git a/fixtures/test/subdir/more.conf.tmpl b/fixtures/test/subdir/more.conf.tmpl new file mode 100644 index 0000000..6979fce --- /dev/null +++ b/fixtures/test/subdir/more.conf.tmpl @@ -0,0 +1,2 @@ +{{ file "simple.conf" }} +Get "Foo" from backend: {{ .Get "Foo" }} diff --git a/fixtures/test/subdir/some.conf.tmpl b/fixtures/test/subdir/some.conf.tmpl new file mode 100644 index 0000000..d013ac5 --- /dev/null +++ b/fixtures/test/subdir/some.conf.tmpl @@ -0,0 +1,2 @@ +{{ file "/a/really/long/path/name/conf1.conf" }} +{{ uuid }} diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..af087ae --- /dev/null +++ b/generate.go @@ -0,0 +1,147 @@ +package tmplnator + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + l "github.com/Sirupsen/logrus" + "github.com/albertrdixon/tmplnator/backend" + "github.com/spf13/afero" +) + +var ( + errChan chan error + data *Data + srcFs afero.Fs + destFs afero.Fs + + // Backend is the Key/Value store for Data + Backend backend.Backend + + // ForceTemp will force all templates to be written to TmpDir if true + ForceTemp = false + + // TmpDir is the tmpdir we will use + TmpDir = filepath.Join(os.TempDir(), "T2") + + cpus = runtime.NumCPU() + pkg = "tmplnator" +) + +func InitFs(srcInMem, destInMem bool) { + mem := &afero.MemMapFs{} + os := &afero.OsFs{} + + if srcFs == nil { + srcFs = os + if srcInMem { + srcFs = mem + } + } + + if destFs == nil { + destFs = os + if destInMem { + destFs = mem + } + } +} + +func ClearFs() { + srcFs, destFs = nil, nil +} + +// Generate is the main entrypoint. It will parse any templates found under dir. +func Generate(dir string) []*File { + l.Infof("Starting generator. Template directory: %q", dir) + var rawFiles []*File + var finishedFiles []*File + data = NewData(Backend) + defer func() { data = nil }() + + if err := destFs.MkdirAll(TmpDir, os.FileMode(0777)); err != nil { + l.Warnf("Could not create tmpdir %q: %v", TmpDir, err) + TmpDir = "" + } + + rawFiles = dirRead(dir, srcFs) + if len(rawFiles) < 1 { + l.Errorf("Found no templates under %q!", dir) + return rawFiles + } + + l.Infof("Found %d files to parse.", len(rawFiles)) + p := FileParser(0) + pfc := p.parseTemplates(srcFs, rawFiles...) + var wfc []<-chan *File + for i := 0; i < cpus; i++ { + w := FileWriter(i) + wfc = append(wfc, w.writeFiles(pfc, destFs)) + } + + for file := range merge(wfc...) { + finishedFiles = append(finishedFiles, file) + } + + if len(finishedFiles) < len(rawFiles) { + n := len(rawFiles) - len(finishedFiles) + l.Errorf("%d files failed to be parsed or written!", n) + } + l.Infof("Generated %d files.", len(finishedFiles)) + + return finishedFiles +} + +func dirRead(root string, fs afero.Fs) []*File { + var files []*File + l.Debugf("Reading dir %s", root) + + items, err := afero.ReadDir(root, fs) + if err != nil { + l.Errorf("Unable to read dir %q: %v", root, err) + return files + } + + for _, item := range items { + path := filepath.Join(root, item.Name()) + l.Debugf("Reading item %q", path) + if item.IsDir() { + files = append(files, dirRead(path, fs)...) + continue + } + if !strings.Contains(item.Name(), "ignore") && !strings.Contains(item.Name(), "skip") { + l.Debugf("Adding %q to file queue.", item.Name()) + files = append(files, NewFile(path)) + } + } + return files +} + +func merge(cs ...<-chan *File) <-chan *File { + var wg sync.WaitGroup + out := make(chan *File) + + output := func(n int, c <-chan *File) { + for f := range c { + l.Debugf("gatherer (%d): Got Output: %v", n, f) + out <- f + } + l.Debug("Done") + wg.Done() + } + l.Debugf("WaitGroup: %d", len(cs)) + wg.Add(len(cs)) + for i, c := range cs { + l.Debugf("START: Result Gatherer (%d)", i) + go output(i, c) + } + + go func() { + wg.Wait() + close(out) + }() + return out +} diff --git a/generate_test.go b/generate_test.go new file mode 100644 index 0000000..06327af --- /dev/null +++ b/generate_test.go @@ -0,0 +1,17 @@ +package tmplnator + +import "testing" + +var testDir = "fixtures/test/" + +func TestGenerate(t *testing.T) { + InitFs(false, true) + for _, file := range Generate(testDir) { + if file.HasErrs() { + t.Errorf("%q: Got errors!", file.TemplateName()) + for _, err := range file.errs { + t.Errorf("%v", err) + } + } + } +} diff --git a/generator.go b/generator.go deleted file mode 100644 index c0262d4..0000000 --- a/generator.go +++ /dev/null @@ -1,221 +0,0 @@ -package tmplnator - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - - l "github.com/Sirupsen/logrus" - "github.com/albertrdixon/tmplnator/backend" - "github.com/spf13/afero" -) - -var ( - errChan chan error - data *Data - - // Backend is the Key/Value store for Data - Backend backend.Backend - - // ForceTemp will force all templates to be written to TmpDir if true - ForceTemp = false - - // TmpDir is the tmpdir we will use - TmpDir = filepath.Join(os.TempDir(), "T2") - - cpus = runtime.NumCPU() - pkg = "tmplnator" -) - -// Generator holds references to the filesystem. It will parse templates -// found in srcFs and write generated files to destFs. -type Generator struct { - srcFs afero.Fs - destFs afero.Fs -} - -func NewGenerator(srcInMem, destInMem bool) *Generator { - mem := &afero.MemMapFs{} - os := &afero.OsFs{} - - var src afero.Fs - var dest afero.Fs - src, dest = os, os - if srcInMem { - src = mem - } - if destInMem { - dest = mem - } - return &Generator{src, dest} -} - -// Generate is the main entrypoint. It will parse any templates found under dir. -func (g *Generator) Generate(dir string) ([]string, []error) { - if err := g.destFs.MkdirAll(TmpDir, os.FileMode(0777)); err != nil { - TmpDir = "" - } - - errChan = make(chan error, 10) - data = NewData(Backend) - - l.Infof("Starting generator. Template directory: %q", dir) - paths := dirRead(dir, g.srcFs) - l.Infof("Found %d files to parse.", len(paths)) - templates := parseTemplates(g.srcFs, paths...) - var fcs []<-chan string - for i := 0; i < cpus; i++ { - fcs = append(fcs, writeFiles(i, templates, g.destFs)) - } - - var files []string - var errs []error - for file := range merge(fcs...) { - files = append(files, file) - } - close(errChan) - if len(errChan) > 0 { - for err := range errChan { - errs = append(errs, err) - } - } - - if len(errs) > 0 { - l.Warnf("Saw %d errors!", len(errs)) - } - l.Infof("Wrote %d files.", len(files)) - return files, errs -} - -func parseTemplates(fs afero.Fs, paths ...string) <-chan *Template { - out := make(chan *Template) - - l.Debug("START: Parser") - go func() { - defer close(out) - for _, path := range paths { - l.Debugf("Parsing %s", path) - file, err := fs.Open(path) - if err != nil { - l.Errorf("Could not read template %s: %v", path, err) - continue - } - t := NewTemplate(path) - if err := t.Parse(file); err == nil { - out <- t - } else { - errChan <- newGeneratorError("parser", "%v", err) - } - file.Close() - } - }() - return out -} - -func writeFiles(n int, templates <-chan *Template, fs afero.Fs) <-chan string { - thread := fmt.Sprintf("writer (%d)", n) - defer func() { - if r := recover(); r != nil { - l.WithFields(l.Fields{ - "writer": n, - "package": pkg, - }).Errorf("Recover from panic: %v", r) - errChan <- newGeneratorError(thread, "%v", r) - } - }() - - out := make(chan string) - l.Debugf("START: Writer (%d)", n) - go func() { - defer close(out) - for t := range templates { - if t.template == nil { - continue - } - l.Debugf("%s: Working on %s", thread, t.Info.Name) - buf := new(bytes.Buffer) - if err := t.template.Execute(buf, data); err != nil { - errChan <- newGeneratorError(thread, "Could not exec template %s: %v", t.Info.Source, err) - continue - } - - if t.Skip() { - continue - } - - if _, err := fs.Stat(t.Info.Dir); err != nil { - if err := fs.MkdirAll(t.Info.Dir, t.Info.Dirmode); err != nil { - errChan <- newGeneratorError(thread, "Could not mkdir %s: %v", t.Info.Dir, err) - continue - } - } - - file, err := fs.Create(t.Output()) - if err != nil { - errChan <- newGeneratorError(thread, "Could not open %s: %v", t.Output(), err) - continue - } - - if _, err = buf.WriteTo(file); err != nil { - errChan <- newGeneratorError(thread, "Could not write to %s: %v", t.Output(), err) - continue - } - fs.Chmod(t.Output(), t.Info.Mode) - out <- file.Name() - } - }() - return out -} - -func dirRead(root string, fs afero.Fs) []string { - var files []string - l.Debugf("Reading dir %s", root) - - items, err := afero.ReadDir(root, fs) - if err != nil { - errChan <- newGeneratorError("main", "%v", err) - return files - } - - for _, item := range items { - l.Debugf("Reading item %q", item.Name()) - if item.IsDir() { - files = append(files, dirRead(filepath.Join(root, item.Name()), fs)...) - continue - } - if !strings.Contains(item.Name(), "ignore") && !strings.Contains(item.Name(), "skip") { - files = append(files, filepath.Join(root, item.Name())) - } - } - return files -} - -func merge(cs ...<-chan string) <-chan string { - var wg sync.WaitGroup - out := make(chan string) - - output := func(n int, c <-chan string) { - for f := range c { - l.Debugf("gatherer (%d): Got Output: %v", n, f) - out <- f - } - l.Debug("Done") - wg.Done() - } - l.Debugf("WaitGroup: %d", len(cs)) - wg.Add(len(cs)) - for i, c := range cs { - l.Debugf("START: Result Gatherer (%d)", i) - go output(i, c) - } - - go func() { - wg.Wait() - close(out) - }() - return out -} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..1471d70 --- /dev/null +++ b/parse.go @@ -0,0 +1,32 @@ +package tmplnator + +import ( + "fmt" + + l "github.com/Sirupsen/logrus" + "github.com/spf13/afero" +) + +type FileParser int + +func (f FileParser) String() string { + return fmt.Sprintf("Parser (%d)", f) +} + +func (f FileParser) parseTemplates(fs afero.Fs, files ...*File) <-chan *File { + out := make(chan *File) + + l.Debugf("START: %v", f) + go func() { + defer close(out) + for _, file := range files { + l.Debugf("%v: Parsing %q.", f, file.TemplateName()) + if err := ParseTemplate(file, srcFs); err != nil { + l.Errorf("%v: Failed to parse %q: %v", f, file.TemplateName(), err) + continue + } + out <- file + } + }() + return out +} diff --git a/template.go b/template.go deleted file mode 100644 index 36fa52a..0000000 --- a/template.go +++ /dev/null @@ -1,157 +0,0 @@ -package tmplnator - -import ( - "bytes" - "fmt" - "os" - "os/user" - "path/filepath" - "strconv" - "strings" - "text/template" - - l "github.com/Sirupsen/logrus" -) - -var extensions = []string{".tpl", ".tmpl", ".templ", ".template"} - -// Reader is a convenience interface -type Reader interface { - Read(p []byte) (n int, err error) -} - -// Template type wraps text/template so we can update the info before generatingthe file -type Template struct { - template *template.Template - Name string - Info *FileInfo -} - -type FileInfo struct { - skip bool - Source string - Name string - Dir string - Owner int - Group int - Mode os.FileMode - Dirmode os.FileMode -} - -func NewTemplate(path string) *Template { - name := filepath.Base(path) - for _, suffix := range extensions { - name = strings.TrimSuffix(name, suffix) - } - - dir := TmpDir - if dir == "" { - dir = filepath.Dir(path) - } - return &Template{ - Name: filepath.Base(path), - Info: &FileInfo{ - skip: false, - Source: path, - Name: name, - Dir: dir, - Owner: os.Geteuid(), - Group: os.Getegid(), - Mode: os.FileMode(0644), - Dirmode: os.FileMode(0755), - }, - } -} - -func (f *Template) Output() string { - return filepath.Join(f.Info.Dir, f.Info.Name) -} - -func (fi *FileInfo) SetName(format string, items ...interface{}) string { - fi.Name = fmt.Sprintf(format, items...) - return "" -} - -func (fi *FileInfo) SetDir(format string, items ...interface{}) string { - if ForceTemp { - fi.Dir = TmpDir - } else { - fi.Dir = fmt.Sprintf(format, items...) - } - return "" -} - -func (fi *FileInfo) SetOwner(owner string) string { - u, err := user.Lookup(owner) - if err != nil { - errChan <- err - fi.Owner = os.Geteuid() - return "" - } - uid, err := strconv.Atoi(u.Uid) - if err != nil { - errChan <- err - fi.Owner = os.Geteuid() - return "" - } - fi.Owner = uid - return "" -} - -func (fi *FileInfo) SetUID(owner int) string { - fi.Owner = owner - return "" -} - -func (fi *FileInfo) SetGID(group int) string { - fi.Group = group - return "" -} - -func (fi *FileInfo) SetMode(mode os.FileMode) string { - fi.Mode = mode - return "" -} - -func (fi *FileInfo) SetDirmode(mode os.FileMode) string { - fi.Dirmode = mode - return "" -} - -func (fi *FileInfo) Skip() string { - fi.skip = true - return "" -} - -func (fi *FileInfo) Src() string { - return fi.Source -} - -func (t *Template) Skip() bool { - return t.Info.skip -} - -func (t *Template) Parse(r Reader) error { - defer func() { - if r := recover(); r != nil { - l.WithFields(l.Fields{ - "package": pkg, - }).Errorf("Recover from panic: %s", r) - errChan <- newTemplateError(t.Name, "%v", r) - } - }() - - buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(r); err != nil { - return TemplateError{t.Name, err.Error()} - } - - tmpl := template.New(t.Name).Funcs(newFuncMap(t.Info)) - tmpl, err := tmpl.Parse(buf.String()) - if err != nil { - return TemplateError{t.Name, err.Error()} - } - - t.template = tmpl - return nil -} diff --git a/template_funcs.go b/template_funcs.go index 1b28c57..11ffb9e 100644 --- a/template_funcs.go +++ b/template_funcs.go @@ -11,19 +11,18 @@ import ( "reflect" "strings" "time" + + "code.google.com/p/go-uuid/uuid" ) -func newFuncMap(fi *FileInfo) map[string]interface{} { +func newFuncMap(f *FileInfo) map[string]interface{} { return map[string]interface{}{ - "dir": fi.SetDir, - "name": fi.SetName, - "mode": fi.SetMode, - "dir_mode": fi.SetDirmode, - "user": fi.SetOwner, - "uid": fi.SetUID, - "gid": fi.SetGID, - "skip": fi.Skip, - "source": fi.Src, + "file": f.SetFilename, + "path": f.SetFullpath, + "mode": f.SetMode, + "dir_mode": f.SetDirmode, + "skip": f.SetSkip, + "source": f.Source, "timestamp": timestamp, "to_json": marshalJSON, "from_json": UnmarshalJSON, @@ -46,6 +45,7 @@ func newFuncMap(fi *FileInfo) map[string]interface{} { "upcase": strings.ToUpper, "trim_suffix": strings.TrimSuffix, "trim_space": strings.TrimSpace, + "uuid": uuid.New, } } diff --git a/write.go b/write.go new file mode 100644 index 0000000..64e6014 --- /dev/null +++ b/write.go @@ -0,0 +1,36 @@ +package tmplnator + +import ( + "fmt" + + l "github.com/Sirupsen/logrus" + "github.com/spf13/afero" +) + +type FileWriter int + +func (f FileWriter) String() string { + return fmt.Sprintf("Writer (%d)", f) +} + +func (f FileWriter) writeFiles(files <-chan *File, fs afero.Fs) <-chan *File { + out := make(chan *File) + l.Debugf("START: %v", f) + + go func() { + defer close(out) + for file := range files { + l.Debugf("%v: Working on %v.", f, file) + if file.HasErrs() { + l.Warnf("%v: %q has parse errors, skipping!", f, file.TemplateName()) + continue + } + if err := WriteFile(file, fs); err != nil { + l.Errorf("%v: Failed to write %q: %v", f, file.Name(), err) + continue + } + out <- file + } + }() + return out +}