Skip to content

Commit

Permalink
Merge pull request #7 from albertrdixon/feature/chan
Browse files Browse the repository at this point in the history
Refactor to use buffered channel and new File interface
  • Loading branch information
albertrdixon committed Mar 18, 2015
2 parents 365d0a8 + 2797070 commit 614a0ef
Show file tree
Hide file tree
Showing 17 changed files with 667 additions and 339 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ Run tmplnator like so: `t2 -template-dir /templates`

And that's it!

*NOTE*: Templates without a described `dir` will use `default-dir` as their output directory.
**NOTE**: Templates without a described `dir` will use `default-dir` as their output directory.

## Template Functions

Access environment variables in the template with `.Env` like so `.Env.VARIABLE`

Access etcd values with `.Var <key>` if key not found will look in ENV
Access etcd values with `.Get <key>` if key not found will look in ENV

`dir "/path/to/destination/dir" <args...>`: Describe destination directory. Accepts printf style formatting in path string. *NOTE*: Templates without a described `dir` will use `default-dir` as their output directory.
`dir "/path/to/destination/dir" <args...>`: Describe destination directory. Accepts printf style formatting in path string. **NOTE**: Templates without a described `dir` will use `default-dir` as their output directory.

`name "name" <args...>`: Describe name of generated file. Accepts printf style formatting of name string.

Expand All @@ -90,6 +90,8 @@ Access etcd values with `.Var <key>` if key not found will look in ENV

`group <gid>`: Describe gid for generated file

`file_info`: Returns a file.Info object for the current file

`to_json <input>`: Marshal JSON string

`from_json <string>`: Unmarshal JSON string
Expand Down
2 changes: 1 addition & 1 deletion config/version.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package config

const (
CodeVersion = "v0.1.1"
CodeVersion = "v1.0.0"
)
52 changes: 52 additions & 0 deletions file/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package file

import (
"bytes"
"os"
"text/template"
)

// Testing is set to true for running file tests
var Testing bool

// File describes a tmplnator template file
type File interface {
Write(*bytes.Buffer, interface{}) error
Read() ([]byte, error)
Template(*template.Template)
Destination() string
Info() Info
Output() string
DeleteTemplate() error
setDir(string, ...interface{}) string
setName(string, ...interface{}) string
setUser(int) string
setGroup(int) string
setMode(os.FileMode) string
setDirMode(os.FileMode) string
setSkip() string
}

// Info objects have all the info for objects that implement File.
type Info struct {
Src string
Name string
Dir string
User int
Group int
Mode os.FileMode
Dirmode os.FileMode
}

// NewFile returns a File object. If Testing is true underlying struct is
// a mockFile, otherwise it is a templateFile
func NewFile(path string, defaultDir string) File {
if Testing {
return newMockFile(path, defaultDir)
}
return newTemplateFile(path, defaultDir)
}

func init() {
Testing = false
}
96 changes: 96 additions & 0 deletions file/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package file

import (
// "io/ioutil"
"bytes"
"os"
"testing"
)

var filetest = []struct {
name string
template string
expectedOutput string
expectedInfo Info
expectError bool
stackSize int
}{
{
name: "bad",
template: `{{ dir "/some/other/path" }{{ mode 0755 "one too many" }}Body Text {{ env "BAD" Something }}`,
expectedOutput: "",
expectedInfo: Info{},
expectError: true,
stackSize: 0,
},
{
name: "change_everything",
template: `{{ dir "/some/path" }}{{ name "name_changed" }}{{ mode 0777 }}{{ user 10000 }}Body Text`,
expectedOutput: "Body Text",
expectedInfo: Info{
Name: "name_changed",
Dir: "/some/path",
Mode: os.FileMode(0777),
User: 10000,
},
expectError: false,
stackSize: 1,
},
}

func TestParseFile(t *testing.T) {
Testing = true
for _, ft := range filetest {
fq := NewFileQueue()
mf := NewFile(ft.template, ft.name)
err := ParseFile(mf, fq)
fq.PopulateQueue()

if !ft.expectError && err != nil {
t.Errorf("ParseFile(%q): Expected no error while parsing, got: %v", ft.name, err)
}
if ft.expectError && err == nil {
t.Errorf("ParseFile(%q): Expected an error while parsing", ft.name)
}
if fq.Len() != ft.stackSize {
t.Errorf("ParseFile(%q): Expected stack size to be %d, got %d", ft.name, ft.stackSize, fq.Len())
}
}
}

func TestWriteFile(t *testing.T) {
Testing = true
for _, ft := range filetest {
fq := NewFileQueue()
mf := NewFile(ft.template, ft.name)
err := ParseFile(mf, fq)
if err != nil {
if !ft.expectError {
t.Errorf("WriteFile(%q): Parsing failed, please fix it.", ft.name)
}
} else {
err = mf.Write(new(bytes.Buffer), nil)
if err != nil {
t.Errorf("WriteFile(%q): Did not expect error in write: %v", ft.name, err)
}

out, info := mf.Output(), mf.Info()
if out != ft.expectedOutput {
t.Errorf("WriteFile(%q): Expected output=%q, got output=%q", ft.name, ft.expectedOutput, out)
}

if info.Name != ft.expectedInfo.Name {
t.Errorf("WriteFile(%q): Expected filename=%q, got filename=%q", ft.name, ft.expectedInfo.Name, info.Name)
}
if info.Dir != ft.expectedInfo.Dir {
t.Errorf("WriteFile(%q): Expected dir=%q, got dir=%q", ft.name, ft.expectedInfo.Dir, info.Dir)
}
if info.Mode != ft.expectedInfo.Mode {
t.Errorf("WriteFile(%q): Expected mode=%q, got mode=%q", ft.name, ft.expectedInfo.Mode, info.Mode)
}
if info.User != ft.expectedInfo.User {
t.Errorf("WriteFile(%q): Expected user=%d, got user=%d", ft.name, ft.expectedInfo.User, info.User)
}
}
}
}
109 changes: 109 additions & 0 deletions file/mock_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package file

import (
"bytes"
"fmt"
"os"
"path/filepath"
tmpl "text/template"
)

type mockFile struct {
dir string
dirmode os.FileMode
example string
group int
mode os.FileMode
name string
output string
template *tmpl.Template
user int
}

func (mf *mockFile) Write(b *bytes.Buffer, data interface{}) (err error) {
err = mf.template.Execute(b, data)
mf.output = b.String()
return
}

func (mf *mockFile) Read() ([]byte, error) {
return []byte(mf.example), nil
}

func (mf *mockFile) Template(t *tmpl.Template) {
mf.template = t
}

func (mf *mockFile) Destination() string {
return filepath.Join(mf.dir, mf.name)
}

func (mf *mockFile) Info() Info {
return Info{
Name: mf.name,
Dir: mf.dir,
User: mf.user,
Group: mf.group,
Mode: mf.mode,
Dirmode: mf.dirmode,
}
}

func (mf *mockFile) Output() string {
return mf.output
}

func (mf *mockFile) DeleteTemplate() error {
return nil
}

func (mf *mockFile) setDir(d string, args ...interface{}) string {
for i, a := range args {
if a == nil {
args[i] = ""
}
}
mf.dir = fmt.Sprintf(d, args...)
return ""
}

func (mf *mockFile) setName(n string, args ...interface{}) string {
for i, a := range args {
if a == nil {
args[i] = ""
}
}
mf.name = fmt.Sprintf(n, args...)
return ""
}

func (mf *mockFile) setUser(uid int) string {
mf.user = uid
return ""
}

func (mf *mockFile) setGroup(gid int) string {
mf.group = gid
return ""
}

func (mf *mockFile) setMode(fm os.FileMode) string {
mf.mode = fm
return ""
}

func (mf *mockFile) setDirMode(dm os.FileMode) string {
mf.dirmode = dm
return ""
}

func (mf *mockFile) setSkip() string {
return ""
}

func newMockFile(e string, n string) File {
return &mockFile{
example: e,
name: n,
}
}
53 changes: 53 additions & 0 deletions file/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package file

import (
l "github.com/Sirupsen/logrus"
"os"
"path/filepath"
"text/template"
)

// ParseFiles will recursively parse all the files under dir, returning
// a Queue object with all the files loaded in.
func ParseFiles(dir string, def string) (fq *Queue, err error) {
l.WithField("directory", dir).Info("Parsing files")
fq = NewFileQueue()
err = filepath.Walk(dir, walkfunc(def, fq))
fq.PopulateQueue()
return
}

func walkfunc(def string, fq *Queue) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
ext := filepath.Ext(path)
if info.Mode().IsRegular() && ext != ".skip" && ext != ".ignore" {
f := NewFile(path, def)
return ParseFile(f, fq)
}
l.WithField("path", path).Debug("Skipping")
return nil
}
}

// ParseFile will parse an individual file and put it in the
// Queue
func ParseFile(f File, fq *Queue) (err error) {
l.WithField("path", f.Info().Src).Debug("Parsing file")

contents, err := f.Read()
if err != nil {
return
}

t, err := newTemplate(f).Parse(string(contents))
if err != nil {
return
}
f.Template(t)
fq.add(f)
return
}

func newTemplate(f File) *template.Template {
return template.New(f.Info().Src).Funcs(newFuncMap(f))
}
44 changes: 44 additions & 0 deletions file/queue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package file

import l "github.com/Sirupsen/logrus"

// Queue describes a queue of files ofr the generator workers
type Queue struct {
files []File
queue chan File
}

// NewFileQueue returns an initialized file.Queue
func NewFileQueue() *Queue {
return &Queue{files: []File{}}
}

func (fq *Queue) add(f File) {
l.WithField("file", f).Debug("Adding file to queue")
fq.files = append(fq.files, f)
l.WithField("file", f).Debug("File added")
}

// PopulateQueue feeds parsed files into the underlying channel
func (fq *Queue) PopulateQueue() {
fq.queue = make(chan File, len(fq.files))
for _, f := range fq.files {
fq.queue <- f
}
close(fq.queue)
}

// Queue returns the File channel
func (fq *Queue) Queue() chan File {
return fq.queue
}

// Len returns the length of the queue
func (fq *Queue) Len() int {
return len(fq.queue)
}

// Files returns the file slice
func (f *Queue) Files() []File {
return f.files
}
Loading

0 comments on commit 614a0ef

Please sign in to comment.