<commit_before>package recipes

import (
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
	"reflect"
	"time"

	"github.com/elvtechnology/gocqltable"

	r "github.com/elvtechnology/gocqltable/reflect"
)

type CRUD struct {
	gocqltable.TableInterface
}

func (t CRUD) Insert(row interface{}) error {
	return t.insert(row, nil)
}

func (t CRUD) InsertWithTTL(row interface{}, ttl *time.Time) error {
	return t.insert(row, ttl)
}

func (t CRUD) insert(row interface{}, ttl *time.Time) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	// TODO: Test and remove
	// where := []string{}
	// for _, key := range append(rowKeys, rangeKeys...) {
	// 	where = append(where, key+" = ?")
	// }

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	fields := []string{}
	placeholders := []string{}
	vals := []interface{}{}
	for key, value := range m {
		// Check for empty row- or range keys
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				if value == nil {
					return errors.New(fmt.Sprintf("Inserting row failed due to missing key value (for key %q)", rowKey))
				}
				break
			}
		}
		// Append to insertion slices
		fields = append(fields, strings.ToLower(fmt.Sprintf("%q", key)))
		placeholders = append(placeholders, "?")
		vals = append(vals, value)
	}

	options := ""
	if ttl != nil {
		options = "USING TTL ?"
		vals = append(vals, int(ttl.Sub(time.Now().UTC()).Seconds()+.5))
	}

	err := t.Query(fmt.Sprintf(`INSERT INTO %q.%q (%s) VALUES (%s) %s`, t.Keyspace().Name(), t.Name(), strings.Join(fields, ", "), strings.Join(placeholders, ", "), options), vals...).Exec()
	if err != nil {
		for _, v := range vals {
			log.Printf("%T %v", v, v)
		}
		return err
	}

	return nil

}

func (t CRUD) Get(ids ...interface{}) (interface{}, error) {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return nil, errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	row, err := t.Query(fmt.Sprintf(`SELECT * FROM %q.%q WHERE %s LIMIT 1`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).FetchRow()
	if err != nil {
		return nil, err
	}

	return row, nil

}

func (t CRUD) List(ids ...interface{}) (interface{}, error) {
	return t.Range(ids...).Fetch()
}

func (t CRUD) Update(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	set := []string{}
	vals := []interface{}{}
	for key, value := range m {
		isAKey := false
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				isAKey = true
				break
			}
		}

		if !isAKey {
			set = append(set, key+" = ?")
			vals = append(vals, value)
		}
	}

	if len(ids) < len(rowKeys) {
		return errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required minimum of %d)", len(ids), len(rowKeys)))
	}

	err := t.Query(fmt.Sprintf(`UPDATE %q.%q SET %s WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(set, ", "), strings.Join(where, " AND ")), append(vals, ids...)...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Delete(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	for key, value := range m {
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to delete row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`DELETE FROM %q.%q WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Range(ids ...interface{}) Range {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	rangeObj := Range{
		table: t,
	}

	numAppended := 0
	for idx, key := range append(rowKeys, rangeKeys...) {
		if len(ids) == numAppended {
			break
		}
		rangeObj = rangeObj.EqualTo(key, ids[idx])
		numAppended += 1
	}

	return rangeObj
}

type Range struct {
	table gocqltable.TableInterface

	where     []string
	whereVals []interface{}
	order     string
	limit     *int
	filtering bool
}

func (r Range) LessThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" < ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) LessThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" <= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" > ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" >= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) EqualTo(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" = ?")
	r.whereVals = append(r.whereVals, value)
	return r
}

func (r Range) OrderBy(fieldAndDirection string) Range {
	r.order = fieldAndDirection
	return r
}

func (r Range) Limit(l int) Range {
	r.limit = &l
	return r
}

func (r Range) Fetch() (interface{}, error) {

	where := r.where
	whereVals := r.whereVals
	order := r.order
	limit := r.limit
	filtering := r.filtering

	whereString := ""
	if len(where) > 0 {
		whereString = "WHERE " + strings.Join(where, " AND ")
	}

	orderString := ""
	if order != "" {
		orderString = fmt.Sprintf("ORDER BY %v", order)
	}

	limitString := ""
	if limit != nil {
		limitString = "LIMIT " + strconv.Itoa(*limit)
	}

	filteringString := ""
	if filtering {
		filteringString = "ALLOW FILTERING"
	}

	iter := r.table.Query(fmt.Sprintf(`SELECT * FROM %q.%q %s %s %s %s`, r.table.Keyspace().Name(), r.table.Name(), whereString, orderString, limitString, filteringString), whereVals...).Fetch()

	result := reflect.Zero(reflect.SliceOf(reflect.PtrTo(reflect.TypeOf(r.table.Row())))) // Create a zero-value slice of pointers to our model type
	for row := range iter.Range() {
		result = reflect.Append(result, reflect.ValueOf(row)) // Append the rows to our slice
	}

	if err := iter.Close(); err != nil {
		return nil, err
	}

	return result.Interface(), nil

}
<commit_msg>Range looping over maps are sometimes randomized. I fixed a mistake where CQL parameters are randomized.<commit_after>package recipes

import (
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
	"reflect"
	"time"

	"github.com/elvtechnology/gocqltable"

	r "github.com/elvtechnology/gocqltable/reflect"
)

type CRUD struct {
	gocqltable.TableInterface
}

func (t CRUD) Insert(row interface{}) error {
	return t.insert(row, nil)
}

func (t CRUD) InsertWithTTL(row interface{}, ttl *time.Time) error {
	return t.insert(row, ttl)
}

func (t CRUD) insert(row interface{}, ttl *time.Time) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	// TODO: Test and remove
	// where := []string{}
	// for _, key := range append(rowKeys, rangeKeys...) {
	// 	where = append(where, key+" = ?")
	// }

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	fields := []string{}
	placeholders := []string{}
	vals := []interface{}{}
	for key, value := range m {
		// Check for empty row- or range keys
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				if value == nil {
					return errors.New(fmt.Sprintf("Inserting row failed due to missing key value (for key %q)", rowKey))
				}
				break
			}
		}
		// Append to insertion slices
		fields = append(fields, strings.ToLower(fmt.Sprintf("%q", key)))
		placeholders = append(placeholders, "?")
		vals = append(vals, value)
	}

	options := ""
	if ttl != nil {
		options = "USING TTL ?"
		vals = append(vals, int(ttl.Sub(time.Now().UTC()).Seconds()+.5))
	}

	err := t.Query(fmt.Sprintf(`INSERT INTO %q.%q (%s) VALUES (%s) %s`, t.Keyspace().Name(), t.Name(), strings.Join(fields, ", "), strings.Join(placeholders, ", "), options), vals...).Exec()
	if err != nil {
		for _, v := range vals {
			log.Printf("%T %v", v, v)
		}
		return err
	}

	return nil

}

func (t CRUD) Get(ids ...interface{}) (interface{}, error) {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return nil, errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	row, err := t.Query(fmt.Sprintf(`SELECT * FROM %q.%q WHERE %s LIMIT 1`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).FetchRow()
	if err != nil {
		return nil, err
	}

	return row, nil

}

func (t CRUD) List(ids ...interface{}) (interface{}, error) {
	return t.Range(ids...).Fetch()
}

func (t CRUD) Update(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	set := []string{}
	vals := []interface{}{}

	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	for key, value := range m {
		isAKey := false
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				isAKey = true
				break
			}
		}
		if !isAKey {
			set = append(set, strings.ToLower(key)+" = ?")
			vals = append(vals, value)
		}
	}

	if len(ids) < len(rowKeys) {
		return errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required minimum of %d)", len(ids), len(rowKeys)))
	}

	err := t.Query(fmt.Sprintf(`UPDATE %q.%q SET %s WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(set, ", "), strings.Join(where, " AND ")), append(vals, ids...)...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Delete(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, key+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to delete row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`DELETE FROM %q.%q WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Range(ids ...interface{}) Range {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	rangeObj := Range{
		table: t,
	}

	numAppended := 0
	for idx, key := range append(rowKeys, rangeKeys...) {
		if len(ids) == numAppended {
			break
		}
		rangeObj = rangeObj.EqualTo(key, ids[idx])
		numAppended += 1
	}

	return rangeObj
}

type Range struct {
	table gocqltable.TableInterface

	where     []string
	whereVals []interface{}
	order     string
	limit     *int
	filtering bool
}

func (r Range) LessThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" < ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) LessThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" <= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" > ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" >= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) EqualTo(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" = ?")
	r.whereVals = append(r.whereVals, value)
	return r
}

func (r Range) OrderBy(fieldAndDirection string) Range {
	r.order = fieldAndDirection
	return r
}

func (r Range) Limit(l int) Range {
	r.limit = &l
	return r
}

func (r Range) Fetch() (interface{}, error) {

	where := r.where
	whereVals := r.whereVals
	order := r.order
	limit := r.limit
	filtering := r.filtering

	whereString := ""
	if len(where) > 0 {
		whereString = "WHERE " + strings.Join(where, " AND ")
	}

	orderString := ""
	if order != "" {
		orderString = fmt.Sprintf("ORDER BY %v", order)
	}

	limitString := ""
	if limit != nil {
		limitString = "LIMIT " + strconv.Itoa(*limit)
	}

	filteringString := ""
	if filtering {
		filteringString = "ALLOW FILTERING"
	}

	iter := r.table.Query(fmt.Sprintf(`SELECT * FROM %q.%q %s %s %s %s`, r.table.Keyspace().Name(), r.table.Name(), whereString, orderString, limitString, filteringString), whereVals...).Fetch()

	result := reflect.Zero(reflect.SliceOf(reflect.PtrTo(reflect.TypeOf(r.table.Row())))) // Create a zero-value slice of pointers to our model type
	for row := range iter.Range() {
		result = reflect.Append(result, reflect.ValueOf(row)) // Append the rows to our slice
	}

	if err := iter.Close(); err != nil {
		return nil, err
	}

	return result.Interface(), nil

}
<|endoftext|>


<commit_before>package gonx

import (
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestReducer(t *testing.T) {
	Convey("Test process input channel with reducers", t, func() {
		input := make(chan *Entry, 10)

		Convey("ReadAll reducer", func() {
			reducer := new(ReadAll)

			// Prepare import channel
			entry := NewEmptyEntry()
			input <- entry
			close(input)

			output := make(chan *Entry, 1) // Make it buffered to avoid deadlock
			reducer.Reduce(input, output)

			// ReadAll reducer writes input channel to the output
			result, ok := <-output
			So(ok, ShouldBeTrue)
			So(result, ShouldEqual, entry)
		})

		Convey("With filled input channel", func() {
			// Prepare import channel
			input <- NewEntry(Fields{
				"uri":  "/asd/fgh",
				"host": "alpha.example.com",
				"foo":  "1",
				"bar":  "2",
				"baz":  "3",
			})
			input <- NewEntry(Fields{
				"uri":  "/zxc/vbn",
				"host": "beta.example.com",
				"foo":  "4",
				"bar":  "5",
				"baz":  "6",
			})
			input <- NewEntry(Fields{
				"uri":  "/ijk/lmn",
				"host": "beta.example.com",
				"foo":  "7",
				"bar":  "8",
				"baz":  "9",
			})
			close(input)
			total := float64(len(input))

			output := make(chan *Entry, 1) // Make it buffered to avoid deadlock

			Convey("Count reducer", func() {
				reducer := new(Count)
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)
				count, err := result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, total)
			})

			Convey("Sum reducer", func() {
				reducer := &Sum{[]string{"foo", "bar"}}
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, 1+4+7)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, 2+5+8)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})

			Convey("Avg reducer", func() {
				reducer := &Avg{[]string{"foo", "bar"}}
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (1.0+4+7)/total)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (2.0+5+8)/total)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})

			Convey("Chain reducer", func() {
				reducer := NewChain(&Avg{[]string{"foo", "bar"}}, &Count{})
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (1.0+4+7)/total)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (2.0+5+8)/total)

				count, err := result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, total)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})
		})
	})
}

func TestGroupByReducer(t *testing.T) {
	reducer := NewGroupBy(
		// Fields to group by
		[]string{"host"},
		// Result reducers
		&Sum{[]string{"foo", "bar"}},
		new(Count),
	)
	assert.Implements(t, (*Reducer)(nil), reducer)

	// Prepare import channel
	input := make(chan *Entry, 10)
	input <- NewEntry(Fields{
		"uri":  "/asd/fgh",
		"host": "alpha.example.com",
		"foo":  "1",
		"bar":  "2",
		"baz":  "3",
	})
	input <- NewEntry(Fields{
		"uri":  "/zxc/vbn",
		"host": "beta.example.com",
		"foo":  "4",
		"bar":  "5",
		"baz":  "6",
	})
	input <- NewEntry(Fields{
		"uri":  "/ijk/lmn",
		"host": "beta.example.com",
		"foo":  "7",
		"bar":  "8",
		"baz":  "9",
	})
	close(input)
	output := make(chan *Entry, 2) // Make it buffered to avoid deadlock
	reducer.Reduce(input, output)

	// Collect result entries from output channel to the map, because reading
	// from channel can be in any order, it depends on each reducer processing
	resultMap := make(map[string]*Entry)
	for result := range output {
		value, err := result.Field("host")
		assert.NoError(t, err)
		resultMap[value] = result
	}
	assert.Equal(t, len(resultMap), 2)

	// Read and assert first group result
	result := resultMap["alpha.example.com"]

	floatVal, err := result.FloatField("foo")
	assert.NoError(t, err)
	assert.Equal(t, floatVal, 1.0)

	floatVal, err = result.FloatField("bar")
	assert.NoError(t, err)
	assert.Equal(t, floatVal, 2.0)

	value, err := result.Field("count")
	assert.NoError(t, err)
	assert.Equal(t, value, "1")

	// Read and assert second group result
	result = resultMap["beta.example.com"]

	floatVal, err = result.FloatField("foo")
	assert.NoError(t, err)
	assert.Equal(t, floatVal, 4.0+7.0)

	floatVal, err = result.FloatField("bar")
	assert.NoError(t, err)
	assert.Equal(t, floatVal, 5.0+8.0)

	value, err = result.Field("count")
	assert.NoError(t, err)
	assert.Equal(t, value, "2")
}
<commit_msg>goconvey test for group reducer<commit_after>package gonx

import (
	. "github.com/smartystreets/goconvey/convey"
	"testing"
)

func TestReducer(t *testing.T) {
	Convey("Test process input channel with reducers", t, func() {
		input := make(chan *Entry, 10)

		Convey("ReadAll reducer", func() {
			reducer := new(ReadAll)

			// Prepare import channel
			entry := NewEmptyEntry()
			input <- entry
			close(input)

			output := make(chan *Entry, 1) // Make it buffered to avoid deadlock
			reducer.Reduce(input, output)

			// ReadAll reducer writes input channel to the output
			result, ok := <-output
			So(ok, ShouldBeTrue)
			So(result, ShouldEqual, entry)
		})

		Convey("With filled input channel", func() {
			// Prepare import channel
			input <- NewEntry(Fields{
				"uri":  "/asd/fgh",
				"host": "alpha.example.com",
				"foo":  "1",
				"bar":  "2",
				"baz":  "3",
			})
			input <- NewEntry(Fields{
				"uri":  "/zxc/vbn",
				"host": "beta.example.com",
				"foo":  "4",
				"bar":  "5",
				"baz":  "6",
			})
			input <- NewEntry(Fields{
				"uri":  "/ijk/lmn",
				"host": "beta.example.com",
				"foo":  "7",
				"bar":  "8",
				"baz":  "9",
			})
			close(input)
			total := float64(len(input))

			output := make(chan *Entry, 10) // Make it buffered to avoid deadlock

			Convey("Count reducer", func() {
				reducer := new(Count)
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)
				count, err := result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, total)
			})

			Convey("Sum reducer", func() {
				reducer := &Sum{[]string{"foo", "bar"}}
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, 1+4+7)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, 2+5+8)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})

			Convey("Avg reducer", func() {
				reducer := &Avg{[]string{"foo", "bar"}}
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (1.0+4+7)/total)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (2.0+5+8)/total)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})

			Convey("Chain reducer", func() {
				reducer := NewChain(&Avg{[]string{"foo", "bar"}}, &Count{})
				reducer.Reduce(input, output)

				result, ok := <-output
				So(ok, ShouldBeTrue)

				value, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (1.0+4+7)/total)

				value, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(value, ShouldEqual, (2.0+5+8)/total)

				count, err := result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, total)

				_, err = result.Field("buz")
				So(err, ShouldNotBeNil)
			})

			Convey("Group reducer", func() {
				reducer := NewGroupBy(
					// Fields to group by
					[]string{"host"},
					// Result reducers
					&Sum{[]string{"foo", "bar"}},
					new(Count),
				)
				reducer.Reduce(input, output)

				// Collect result entries from output channel to the map, because reading
				// from channel can be in any order, it depends on each reducer processing
				resultMap := make(map[string]*Entry)
				for result := range output {
					value, err := result.Field("host")
					So(err, ShouldBeNil)
					resultMap[value] = result
				}
				So(len(resultMap), ShouldEqual, 2)

				// Read and assert first group result
				result := resultMap["alpha.example.com"]

				floatVal, err := result.FloatField("foo")
				So(err, ShouldBeNil)
				So(floatVal, ShouldEqual, 1)

				floatVal, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(floatVal, ShouldEqual, 2)

				count, err := result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)

				// Read and assert second group result
				result = resultMap["beta.example.com"]

				floatVal, err = result.FloatField("foo")
				So(err, ShouldBeNil)
				So(floatVal, ShouldEqual, 4+7)

				floatVal, err = result.FloatField("bar")
				So(err, ShouldBeNil)
				So(floatVal, ShouldEqual, 5+8)

				count, err = result.FloatField("count")
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 2)
			})
		})
	})
}
<|endoftext|>


<commit_before>package runtime

import (
    "fmt"
    "reflect"
    "strings"
    "github.com/cloudson/gitql/parser"
    "github.com/libgit2/git2go"
)

const (
    WALK_COMMITS = 1
    WALK_TREES = 2
    WALK_REFERENCES = 3
)

const (
    REFERENCE_TYPE_BRANCH = "branch"
    REFERENCE_TYPE_REMOTE = "remote"
    REFERENCE_TYPE_TAG = "tag"
)

var repo *git.Repository
var builder *GitBuilder
var boolRegister bool

type GitBuilder struct {
    tables map[string]string
    possibleTables map[string][]string
    repo *git.Repository
    currentWalkType uint8
    currentCommit *git.Commit
    currentReference *git.Reference
    walk *git.RevWalk

}

type RuntimeError struct {
    code uint8
    message string
}

type RuntimeVisitor struct {

}

// =========================== Runtime
func Run(n *parser.NodeProgram) {
    builder = GetGitBuilder(n.Path)
    visitor := new(RuntimeVisitor)
    err := visitor.Visit(n)
    if err != nil {
        panic(err)
    }
    switch findWalkType(n) {
        case WALK_COMMITS:
            walkCommits(n, visitor)
            break
        case WALK_TREES:
            walkTrees(n, visitor)
            break
        case WALK_REFERENCES:
            walkReferences(n, visitor)
    }
}

func findWalkType(n *parser.NodeProgram) uint8 {
    s := n.Child.(*parser.NodeSelect)

    switch s.Tables[0] {
        case "commits" :
            builder.currentWalkType = WALK_COMMITS
        case "trees" :
            builder.currentWalkType = WALK_TREES
        case "refs":
            builder.currentWalkType = WALK_REFERENCES
    }

    return builder.currentWalkType
}

func walkCommits(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    builder.walk, _ = repo.Walk()
    builder.walk.PushHead()
    builder.walk.Sorting(git.SortTime)

    s := n.Child.(*parser.NodeSelect)
    where := s.Where

    counter := 1
    fmt.Println()
    fn := func (object *git.Commit) bool {
        builder.setCommit(object)
        boolRegister = true
        visitor.VisitExpr(where)
        if boolRegister {
            fields := s.Fields
            if s.WildCard {
                fields = builder.possibleTables[s.Tables[0]]
            }
            for _, f := range fields {
                fmt.Printf("%s | ", metadataCommit(f, object))
            }
            fmt.Println()

            counter = counter + 1
        }
        if counter > s.Limit {
            return false
        }
        return true
    }

    err := builder.walk.Iterate(fn)
    if err != nil {
        fmt.Printf(err.Error())
    }
}

func walkTrees(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    // not yet!
}

func walkReferences(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    s := n.Child.(*parser.NodeSelect)
    where := s.Where

    // @TODO make PR with Repository.WalkReference()
    iterator, err := builder.repo.NewReferenceIterator()
    if err != nil {
        panic(err)
    }
    counter := 1
    for object, inTheEnd := iterator.Next(); inTheEnd == nil; object, inTheEnd = iterator.Next() {

        builder.setReference(object)
        boolRegister = true
        visitor.VisitExpr(where)
        if boolRegister {
            fields := s.Fields
            if s.WildCard {
                fields = builder.possibleTables[s.Tables[0]]
            }
            for _, f := range fields {
                fmt.Printf("%s | ", metadataReference(f, object))
            }
            fmt.Println()

            counter = counter + 1
            if counter > s.Limit {
                break
            }
        }
    }

}

func metadata(identifier string) string {
    switch builder.currentWalkType {
        case WALK_COMMITS:
            return metadataCommit(identifier, builder.currentCommit)
        case WALK_REFERENCES:
            return metadataReference(identifier, builder.currentReference)
    }

    panic("GOD!")
}

func metadataTree(identifier string, object *git.TreeEntry) string {
    return "" // not yet implemented!
}

func metadataReference(identifier string, object *git.Reference) string {
    switch identifier {
        case "name" :
            return object.Name()
        case "hash" :
            return object.Target().String()
        case "type":
            if object.IsBranch() {
                return REFERENCE_TYPE_BRANCH
            }

            if object.IsRemote() {
                return REFERENCE_TYPE_REMOTE
            }

            if object.IsTag() {
                return REFERENCE_TYPE_TAG
            }
    }

    panic(fmt.Sprintf("Trying select field %s ", identifier))
}

func metadataCommit(identifier string, object *git.Commit) string {
    key := ""
    for key, _ = range builder.tables {
        break
    }
    table := key
    err := builder.UseFieldFromTable(identifier, table)
    if err != nil {
        panic(err)
    }
    switch identifier {
        case "hash" :
            return object.Id().String()
        case "author":
            return object.Author().Name
        case "author_email":
            return object.Author().Email
        case "committer":
            return object.Committer().Name
        case "committer_email":
            return object.Committer().Email
        case "date":
            return object.Committer().When.Format(parser.Time_YMDHIS)
        case "full_message":
            return object.Message()
        case "message":
            message := object.Message()
            r := []rune("\n")
            idx := strings.IndexRune(message, r[0])
            if idx != -1 {
                message = message[0:idx]
            }
            return message

    }

    panic(fmt.Sprintf("Trying select field %s ", identifier))
}

// =========================== Error

func (e *RuntimeError) Error() string{
    return e.message
}

func throwRuntimeError(message string, code uint8) (*RuntimeError) {
    e := new(RuntimeError)
    e.message = message
    e.code = code

    return e
}

// ========================== RuntimeVisitor

func (v *RuntimeVisitor) Visit(n *parser.NodeProgram) (error) {
    return v.VisitSelect(n.Child.(*parser.NodeSelect))
}

func (v *RuntimeVisitor) VisitSelect(n *parser.NodeSelect) (error) {
    table := n.Tables[0]
    fields := n.Fields
    var err error
    for _, f := range fields {
        err = builder.UseFieldFromTable(f, table)
        builder.tables[table] = table
        if err != nil {
            return err
        }
    }
    // Why not visit expression right now ?
    // Because we will, at first, discover the current commit
    return nil
}

func (v *RuntimeVisitor) VisitExpr(n parser.NodeExpr) (error) {
    switch reflect.TypeOf(n) {
        case reflect.TypeOf(new(parser.NodeEqual)) :
            g:= n.(*parser.NodeEqual)
            return v.VisitEqual(g)
        case reflect.TypeOf(new(parser.NodeGreater)) :
            g:= n.(*parser.NodeGreater)
            return v.VisitGreater(g)
        case reflect.TypeOf(new(parser.NodeSmaller)) :
            g:= n.(*parser.NodeSmaller)
            return v.VisitSmaller(g)
        case reflect.TypeOf(new(parser.NodeOr)) :
            g:= n.(*parser.NodeOr)
            return v.VisitOr(g)
        case reflect.TypeOf(new(parser.NodeAnd)) :
            g:= n.(*parser.NodeAnd)
            return v.VisitAnd(g)

    }

    return nil
}

func (v *RuntimeVisitor) VisitEqual(n *parser.NodeEqual) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()
    boolRegister = n.Assertion(metadata(lvalue), rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitGreater(n *parser.NodeGreater) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    lvalue = metadata(lvalue)
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()

    boolRegister = n.Assertion(lvalue, rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitSmaller(n *parser.NodeSmaller) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    lvalue = metadata(lvalue)
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()

    boolRegister = n.Assertion(lvalue, rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitOr(n *parser.NodeOr) (error) {
    v.VisitExpr(n.LeftValue())
    boolLeft := boolRegister
    v.VisitExpr(n.RightValue())
    boolRight := boolRegister

    boolRegister = boolLeft || boolRight
    return nil
}

func (v *RuntimeVisitor) VisitAnd(n *parser.NodeAnd) (error) {
    v.VisitExpr(n.LeftValue())
    boolLeft := boolRegister
    v.VisitExpr(n.RightValue())
    boolRight := boolRegister

    boolRegister = boolLeft && boolRight
    return nil
}

func (v *RuntimeVisitor) Builder() (*GitBuilder){
    return nil
}

// =================== GitBuilder

func GetGitBuilder(path *string) (*GitBuilder) {

    gb := new(GitBuilder)
    gb.tables = make(map[string]string)
    possibleTables := map[string][]string {
        "commits": {
            "hash",
            "date",
            "author",
            "author_email",
            "committer",
            "committer_email",
            "message",
            "full_message",
        },
        "trees": {
            "hash",
            "name",
            "id",
            "type",
            "filemode",
        },
        "refs": {
            "name",
            "type",
            "hash",
        },
    }
    gb.possibleTables = possibleTables

    openRepository(path)

    gb.repo = repo

    return gb
}

func openRepository(path *string) {
    _repo, err := git.OpenRepository(*path)
    if err != nil {
        panic(err)
    }
    repo = _repo
}

func (g *GitBuilder) setCommit(object *git.Commit) {
    g.currentCommit = object
}

func (g *GitBuilder) setReference(object *git.Reference) {
    g.currentReference = object
}

func (g *GitBuilder) WithTable(tableName string, alias string) error {
    err := g.isValidTable(tableName)
    if err != nil {
        return err
    }

    if g.possibleTables[tableName] == nil {
        return throwRuntimeError(fmt.Sprintf("Table '%s' not found", tableName), 0)
    }

    if alias == "" {
        alias = tableName
    }

    g.tables[alias] = tableName

    return nil
}

func (g *GitBuilder) isValidTable(tableName string) error {
    if g.possibleTables[tableName] == nil {
        return throwRuntimeError(fmt.Sprintf("Table '%s' not found", tableName), 0)
    }

    return nil
}

func (g *GitBuilder) UseFieldFromTable(field string, tableName string) error {
    if field == "*" {
        return nil
    }

    err := g.isValidTable(tableName)
    if err != nil {
        return err
    }

    table := g.possibleTables[tableName]
    for _, t := range table {
        if t == field {
            return nil
        }
    }

    return throwRuntimeError(fmt.Sprintf("Table '%s' has not field '%s'", tableName, field), 0)
}

// Criar varias funcoes de asserção, a closure usará elas para saber se um certo objeto
// pode ser mostrado ou não.<commit_msg>Add new field for short name on refs table<commit_after>package runtime

import (
    "fmt"
    "reflect"
    "strings"
    "github.com/cloudson/gitql/parser"
    "github.com/libgit2/git2go"
)

const (
    WALK_COMMITS = 1
    WALK_TREES = 2
    WALK_REFERENCES = 3
)

const (
    REFERENCE_TYPE_BRANCH = "branch"
    REFERENCE_TYPE_REMOTE = "remote"
    REFERENCE_TYPE_TAG = "tag"
)

var repo *git.Repository
var builder *GitBuilder
var boolRegister bool

type GitBuilder struct {
    tables map[string]string
    possibleTables map[string][]string
    repo *git.Repository
    currentWalkType uint8
    currentCommit *git.Commit
    currentReference *git.Reference
    walk *git.RevWalk

}

type RuntimeError struct {
    code uint8
    message string
}

type RuntimeVisitor struct {

}

// =========================== Runtime
func Run(n *parser.NodeProgram) {
    builder = GetGitBuilder(n.Path)
    visitor := new(RuntimeVisitor)
    err := visitor.Visit(n)
    if err != nil {
        panic(err)
    }
    switch findWalkType(n) {
        case WALK_COMMITS:
            walkCommits(n, visitor)
            break
        case WALK_TREES:
            walkTrees(n, visitor)
            break
        case WALK_REFERENCES:
            walkReferences(n, visitor)
    }
}

func findWalkType(n *parser.NodeProgram) uint8 {
    s := n.Child.(*parser.NodeSelect)

    switch s.Tables[0] {
        case "commits" :
            builder.currentWalkType = WALK_COMMITS
        case "trees" :
            builder.currentWalkType = WALK_TREES
        case "refs":
            builder.currentWalkType = WALK_REFERENCES
    }

    return builder.currentWalkType
}

func walkCommits(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    builder.walk, _ = repo.Walk()
    builder.walk.PushHead()
    builder.walk.Sorting(git.SortTime)

    s := n.Child.(*parser.NodeSelect)
    where := s.Where

    counter := 1
    fmt.Println()
    fn := func (object *git.Commit) bool {
        builder.setCommit(object)
        boolRegister = true
        visitor.VisitExpr(where)
        if boolRegister {
            fields := s.Fields
            if s.WildCard {
                fields = builder.possibleTables[s.Tables[0]]
            }
            for _, f := range fields {
                fmt.Printf("%s | ", metadataCommit(f, object))
            }
            fmt.Println()

            counter = counter + 1
        }
        if counter > s.Limit {
            return false
        }
        return true
    }

    err := builder.walk.Iterate(fn)
    if err != nil {
        fmt.Printf(err.Error())
    }
}

func walkTrees(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    // not yet!
}

func walkReferences(n *parser.NodeProgram, visitor *RuntimeVisitor) {
    s := n.Child.(*parser.NodeSelect)
    where := s.Where

    // @TODO make PR with Repository.WalkReference()
    iterator, err := builder.repo.NewReferenceIterator()
    if err != nil {
        panic(err)
    }
    counter := 1
    for object, inTheEnd := iterator.Next(); inTheEnd == nil; object, inTheEnd = iterator.Next() {

        builder.setReference(object)
        boolRegister = true
        visitor.VisitExpr(where)
        if boolRegister {
            fields := s.Fields
            if s.WildCard {
                fields = builder.possibleTables[s.Tables[0]]
            }
            for _, f := range fields {
                fmt.Printf("%s | ", metadataReference(f, object))
            }
            fmt.Println()

            counter = counter + 1
            if counter > s.Limit {
                break
            }
        }
    }

}

func metadata(identifier string) string {
    switch builder.currentWalkType {
        case WALK_COMMITS:
            return metadataCommit(identifier, builder.currentCommit)
        case WALK_REFERENCES:
            return metadataReference(identifier, builder.currentReference)
    }

    panic("GOD!")
}

func metadataTree(identifier string, object *git.TreeEntry) string {
    return "" // not yet implemented!
}

func metadataReference(identifier string, object *git.Reference) string {
    switch identifier {
        case "name":
            return object.Shorthand()
        case "full_name" :
            return object.Name()
        case "hash" :
            return object.Target().String()
        case "type":
            if object.IsBranch() {
                return REFERENCE_TYPE_BRANCH
            }

            if object.IsRemote() {
                return REFERENCE_TYPE_REMOTE
            }

            if object.IsTag() {
                return REFERENCE_TYPE_TAG
            }
    }

    panic(fmt.Sprintf("Trying select field %s ", identifier))
}

func metadataCommit(identifier string, object *git.Commit) string {
    key := ""
    for key, _ = range builder.tables {
        break
    }
    table := key
    err := builder.UseFieldFromTable(identifier, table)
    if err != nil {
        panic(err)
    }
    switch identifier {
        case "hash" :
            return object.Id().String()
        case "author":
            return object.Author().Name
        case "author_email":
            return object.Author().Email
        case "committer":
            return object.Committer().Name
        case "committer_email":
            return object.Committer().Email
        case "date":
            return object.Committer().When.Format(parser.Time_YMDHIS)
        case "full_message":
            return object.Message()
        case "message":
            message := object.Message()
            r := []rune("\n")
            idx := strings.IndexRune(message, r[0])
            if idx != -1 {
                message = message[0:idx]
            }
            return message

    }

    panic(fmt.Sprintf("Trying select field %s ", identifier))
}

// =========================== Error

func (e *RuntimeError) Error() string{
    return e.message
}

func throwRuntimeError(message string, code uint8) (*RuntimeError) {
    e := new(RuntimeError)
    e.message = message
    e.code = code

    return e
}

// ========================== RuntimeVisitor

func (v *RuntimeVisitor) Visit(n *parser.NodeProgram) (error) {
    return v.VisitSelect(n.Child.(*parser.NodeSelect))
}

func (v *RuntimeVisitor) VisitSelect(n *parser.NodeSelect) (error) {
    table := n.Tables[0]
    fields := n.Fields
    var err error
    for _, f := range fields {
        err = builder.UseFieldFromTable(f, table)
        builder.tables[table] = table
        if err != nil {
            return err
        }
    }
    // Why not visit expression right now ?
    // Because we will, at first, discover the current commit
    return nil
}

func (v *RuntimeVisitor) VisitExpr(n parser.NodeExpr) (error) {
    switch reflect.TypeOf(n) {
        case reflect.TypeOf(new(parser.NodeEqual)) :
            g:= n.(*parser.NodeEqual)
            return v.VisitEqual(g)
        case reflect.TypeOf(new(parser.NodeGreater)) :
            g:= n.(*parser.NodeGreater)
            return v.VisitGreater(g)
        case reflect.TypeOf(new(parser.NodeSmaller)) :
            g:= n.(*parser.NodeSmaller)
            return v.VisitSmaller(g)
        case reflect.TypeOf(new(parser.NodeOr)) :
            g:= n.(*parser.NodeOr)
            return v.VisitOr(g)
        case reflect.TypeOf(new(parser.NodeAnd)) :
            g:= n.(*parser.NodeAnd)
            return v.VisitAnd(g)

    }

    return nil
}

func (v *RuntimeVisitor) VisitEqual(n *parser.NodeEqual) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()
    boolRegister = n.Assertion(metadata(lvalue), rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitGreater(n *parser.NodeGreater) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    lvalue = metadata(lvalue)
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()

    boolRegister = n.Assertion(lvalue, rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitSmaller(n *parser.NodeSmaller) (error) {
    lvalue := n.LeftValue().(*parser.NodeId).Value()
    lvalue = metadata(lvalue)
    rvalue := n.RightValue().(*parser.NodeLiteral).Value()

    boolRegister = n.Assertion(lvalue, rvalue)

    return nil
}

func (v *RuntimeVisitor) VisitOr(n *parser.NodeOr) (error) {
    v.VisitExpr(n.LeftValue())
    boolLeft := boolRegister
    v.VisitExpr(n.RightValue())
    boolRight := boolRegister

    boolRegister = boolLeft || boolRight
    return nil
}

func (v *RuntimeVisitor) VisitAnd(n *parser.NodeAnd) (error) {
    v.VisitExpr(n.LeftValue())
    boolLeft := boolRegister
    v.VisitExpr(n.RightValue())
    boolRight := boolRegister

    boolRegister = boolLeft && boolRight
    return nil
}

func (v *RuntimeVisitor) Builder() (*GitBuilder){
    return nil
}

// =================== GitBuilder

func GetGitBuilder(path *string) (*GitBuilder) {

    gb := new(GitBuilder)
    gb.tables = make(map[string]string)
    possibleTables := map[string][]string {
        "commits": {
            "hash",
            "date",
            "author",
            "author_email",
            "committer",
            "committer_email",
            "message",
            "full_message",
        },
        "trees": {
            "hash",
            "name",
            "id",
            "type",
            "filemode",
        },
        "refs": {
            "name",
            "full_name",
            "type",
            "hash",
        },
    }
    gb.possibleTables = possibleTables

    openRepository(path)

    gb.repo = repo

    return gb
}

func openRepository(path *string) {
    _repo, err := git.OpenRepository(*path)
    if err != nil {
        panic(err)
    }
    repo = _repo
}

func (g *GitBuilder) setCommit(object *git.Commit) {
    g.currentCommit = object
}

func (g *GitBuilder) setReference(object *git.Reference) {
    g.currentReference = object
}

func (g *GitBuilder) WithTable(tableName string, alias string) error {
    err := g.isValidTable(tableName)
    if err != nil {
        return err
    }

    if g.possibleTables[tableName] == nil {
        return throwRuntimeError(fmt.Sprintf("Table '%s' not found", tableName), 0)
    }

    if alias == "" {
        alias = tableName
    }

    g.tables[alias] = tableName

    return nil
}

func (g *GitBuilder) isValidTable(tableName string) error {
    if g.possibleTables[tableName] == nil {
        return throwRuntimeError(fmt.Sprintf("Table '%s' not found", tableName), 0)
    }

    return nil
}

func (g *GitBuilder) UseFieldFromTable(field string, tableName string) error {
    if field == "*" {
        return nil
    }

    err := g.isValidTable(tableName)
    if err != nil {
        return err
    }

    table := g.possibleTables[tableName]
    for _, t := range table {
        if t == field {
            return nil
        }
    }

    return throwRuntimeError(fmt.Sprintf("Table '%s' has not field '%s'", tableName, field), 0)
}

// Criar varias funcoes de asserção, a closure usará elas para saber se um certo objeto
// pode ser mostrado ou não.<|endoftext|>


<commit_before>package yext

// TODO
// * Need better custom field accessors and helpers
// * The API will accept some things and return them in a different format - this makes diff'ing difficult:
// ** Phone: Send in 540-444-4444, get back 5404444444
// ** Custom Field Multi-Option: Send in options ["3", "2", "1"], get back ["1", "2", "3"]

import (
	"encoding/json"
	"fmt"
)

// Location is the representation of a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm
type Location struct {
	Id                     *string                `json:"id,omitempty"`
	Name                   *string                `json:"locationName,omitempty"`
	Lists                  []ECL                  `json:"lists,omitempty"`
	Keywords               []string               `json:"keywords,omitempty"`
	Associations           []string               `json:"associations,omitempty"`
	CustomFields           map[string]interface{} `json:"customFields,omitempty"`
	CustomerId             *string                `json:"customerId,omitempty"`
	Address                *string                `json:"address,omitempty"`
	Address2               *string                `json:"address2,omitempty"`
	SuppressAddress        *bool                  `json:"suppressAddress,omitempty"`
	DisplayAddress         *string                `json:"displayAddress,omitempty"`
	City                   *string                `json:"city,omitempty"`
	State                  *string                `json:"state,omitempty"`
	Zip                    *string                `json:"zip,omitempty"`
	CountryCode            *string                `json:"countryCode,omitempty"`
	Phone                  *string                `json:"phone,omitempty"`
	IsPhoneTracked         *bool                  `json:"isPhoneTracked,omitempty"`
	LocalPhone             *string                `json:"localPhone,omitempty"`
	AlternatePhone         *string                `json:"alternatePhone,omitempty"`
	FaxPhone               *string                `json:"faxPhone,omitempty"`
	MobilePhone            *string                `json:"mobilePhone,omitempty"`
	TollFreePhone          *string                `json:"tollFreePhone,omitempty"`
	TtyPhone               *string                `json:"ttyPhone,omitempty"`
	SpecialOffer           *string                `json:"specialOffer,omitempty"`
	SpecialOfferUrl        *string                `json:"specialOfferUrl,omitempty"`
	WebsiteUrl             *string                `json:"websiteUrl,omitempty"`
	DisplayWebsiteUrl      *string                `json:"displayWebsiteUrl,omitempty"`
	ReservationUrl         *string                `json:"reservationUrl,omitempty"`
	Hours                  *string                `json:"hours,omitempty"` // A concrete type would be nice for this
	AdditionalHoursText    *string                `json:"additionalHoursText,omitempty"`
	Description            *string                `json:"description,omitempty"`
	PaymentOptions         []string               `json:"paymentOptions,omitempty"`
	VideoUrls              []string               `json:"videoUrls,omitempty"`
	TwitterHandle          *string                `json:"twitterHandle,omitempty"`
	FacebookPageUrl        *string                `json:"facebookPageUrl,omitempty"`
	YearEstablished        *string                `json:"yearEstablished,omitempty"`
	DisplayLat             *float64               `json:"displayLat,omitempty"`
	DisplayLng             *float64               `json:"displayLng,omitempty"`
	RoutableLat            *float64               `json:"routableLat,omitempty"`
	RoutableLng            *float64               `json:"routableLng,omitempty"`
	Emails                 []string               `json:"emails,omitempty"`
	Specialties            []string               `json:"specialties,omitempty"`
	Services               []string               `json:"services,omitempty"`
	Brands                 []string               `json:"brands,omitempty"`
	Languages              []string               `json:"languages,omitempty"`
	FolderId               *string                `json:"folderId,omitempty"`
	FacebookCoverPhoto     *LocationPhoto         `json:"facebookCoverPhoto,omitempty"`
	FacebookProfilePicture *LocationPhoto         `json:"facebookProfilePicture,omitempty"`
	Logo                   *LocationPhoto         `json:"logo,omitempty"`
	Photos                 []LocationPhoto        `json:"photos,omitempty"`
	Closed                 *LocationClosed        `json:"closed,omitempty"`
	CategoryIds            []string               `json:"categoryIds,omitempty"`
	HolidayHours           []HolidayHours         `json:"holidayHours,omitempty"`
	hydrated               bool
}

func (y Location) GetId() string {
	if y.Id != nil {
		return *y.Id
	}
	return ""
}

func (y Location) GetName() string {
	if y.Name != nil {
		return *y.Name
	}
	return ""
}

func (y Location) GetCustomerId() string {
	if y.CustomerId != nil {
		return *y.CustomerId
	}
	return ""
}

func (y Location) GetAddress() string {
	if y.Address != nil {
		return *y.Address
	}
	return ""
}

func (y Location) GetAddress2() string {
	if y.Address2 != nil {
		return *y.Address2
	}
	return ""
}

func (y Location) GetSuppressAddress() bool {
	if y.SuppressAddress != nil {
		return *y.SuppressAddress
	}
	return false
}

func (y Location) GetDisplayAddress() string {
	if y.DisplayAddress != nil {
		return *y.DisplayAddress
	}
	return ""
}

func (y Location) GetCity() string {
	if y.City != nil {
		return *y.City
	}
	return ""
}

func (y Location) GetState() string {
	if y.State != nil {
		return *y.State
	}
	return ""
}

func (y Location) GetZip() string {
	if y.Zip != nil {
		return *y.Zip
	}
	return ""
}

func (y Location) GetCountryCode() string {
	if y.CountryCode != nil {
		return *y.CountryCode
	}
	return ""
}

func (y Location) GetPhone() string {
	if y.Phone != nil {
		return *y.Phone
	}
	return ""
}

func (y Location) GetIsPhoneTracked() bool {
	if y.IsPhoneTracked != nil {
		return *y.IsPhoneTracked
	}
	return false
}

func (y Location) GetLocalPhone() string {
	if y.LocalPhone != nil {
		return *y.LocalPhone
	}
	return ""
}

func (y Location) GetAlternatePhone() string {
	if y.AlternatePhone != nil {
		return *y.AlternatePhone
	}
	return ""
}

func (y Location) GetFaxPhone() string {
	if y.FaxPhone != nil {
		return *y.FaxPhone
	}
	return ""
}

func (y Location) GetMobilePhone() string {
	if y.MobilePhone != nil {
		return *y.MobilePhone
	}
	return ""
}

func (y Location) GetTollFreePhone() string {
	if y.TollFreePhone != nil {
		return *y.TollFreePhone
	}
	return ""
}

func (y Location) GetTtyPhone() string {
	if y.TtyPhone != nil {
		return *y.TtyPhone
	}
	return ""
}

func (y Location) GetSpecialOffer() string {
	if y.SpecialOffer != nil {
		return *y.SpecialOffer
	}
	return ""
}

func (y Location) GetSpecialOfferUrl() string {
	if y.SpecialOfferUrl != nil {
		return *y.SpecialOfferUrl
	}
	return ""
}

func (y Location) GetWebsiteUrl() string {
	if y.WebsiteUrl != nil {
		return *y.WebsiteUrl
	}
	return ""
}

func (y Location) GetDisplayWebsiteUrl() string {
	if y.DisplayWebsiteUrl != nil {
		return *y.DisplayWebsiteUrl
	}
	return ""
}

func (y Location) GetReservationUrl() string {
	if y.ReservationUrl != nil {
		return *y.ReservationUrl
	}
	return ""
}

func (y Location) GetHours() string {
	if y.Hours != nil {
		return *y.Hours
	}
	return ""
}

func (y Location) GetAdditionalHoursText() string {
	if y.AdditionalHoursText != nil {
		return *y.AdditionalHoursText
	}
	return ""
}

func (y Location) GetDescription() string {
	if y.Description != nil {
		return *y.Description
	}
	return ""
}

func (y Location) GetTwitterHandle() string {
	if y.TwitterHandle != nil {
		return *y.TwitterHandle
	}
	return ""
}

func (y Location) GetFacebookPageUrl() string {
	if y.FacebookPageUrl != nil {
		return *y.FacebookPageUrl
	}
	return ""
}

func (y Location) GetYearEstablished() string {
	if y.YearEstablished != nil {
		return *y.YearEstablished
	}
	return ""
}

func (y Location) GetDisplayLat() float64 {
	if y.DisplayLat != nil {
		return *y.DisplayLat
	}
	return 0
}

func (y Location) GetDisplayLng() float64 {
	if y.DisplayLng != nil {
		return *y.DisplayLng
	}
	return 0
}

func (y Location) GetRoutableLat() float64 {
	if y.RoutableLat != nil {
		return *y.RoutableLat
	}
	return 0
}

func (y Location) GetRoutableLng() float64 {
	if y.RoutableLng != nil {
		return *y.RoutableLng
	}
	return 0
}

func (y Location) GetFolderId() string {
	if y.FolderId != nil {
		return *y.FolderId
	}
	return ""
}

func (y Location) String() string {
	b, _ := json.Marshal(y)
	return string(b)
}

// Photo represents a photo associated with a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Photo
type LocationPhoto struct {
	Url             string `json:"url,omitempty"`
	Description     string `json:"description,omitempty"`
	ClickThroughURL string `json:"clickthroughUrl,omitempty"`
}

func (l Photo) String() string {
	return fmt.Sprintf("Url: '%v', Description: '%v'", l.Url, l.Description)
}

// LocationClosed represents the 'closed' state of a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Closed
type LocationClosed struct {
	IsClosed   string `json:"isClosed"`
	ClosedDate string `json:"closedDate,omitempty"`
}

func (l LocationClosed) String() string {
	return fmt.Sprintf("isClosed: %v, closedDate: '%v'", l.IsClosed, l.ClosedDate)
}

// HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager.
// For details see
type HolidayHours struct {
	Date  string `json:"date"`
	Hours string `json:"hours"`
}
<commit_msg>Add labels to Location<commit_after>package yext

// TODO
// * Need better custom field accessors and helpers
// * The API will accept some things and return them in a different format - this makes diff'ing difficult:
// ** Phone: Send in 540-444-4444, get back 5404444444
// ** Custom Field Multi-Option: Send in options ["3", "2", "1"], get back ["1", "2", "3"]

import (
	"encoding/json"
	"fmt"
)

// Location is the representation of a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm
type Location struct {
	Id                     *string                `json:"id,omitempty"`
	Name                   *string                `json:"locationName,omitempty"`
	Lists                  []ECL                  `json:"lists,omitempty"`
	Keywords               []string               `json:"keywords,omitempty"`
	Associations           []string               `json:"associations,omitempty"`
	CustomFields           map[string]interface{} `json:"customFields,omitempty"`
	CustomerId             *string                `json:"customerId,omitempty"`
	Address                *string                `json:"address,omitempty"`
	Address2               *string                `json:"address2,omitempty"`
	SuppressAddress        *bool                  `json:"suppressAddress,omitempty"`
	DisplayAddress         *string                `json:"displayAddress,omitempty"`
	City                   *string                `json:"city,omitempty"`
	State                  *string                `json:"state,omitempty"`
	Zip                    *string                `json:"zip,omitempty"`
	CountryCode            *string                `json:"countryCode,omitempty"`
	Phone                  *string                `json:"phone,omitempty"`
	IsPhoneTracked         *bool                  `json:"isPhoneTracked,omitempty"`
	LocalPhone             *string                `json:"localPhone,omitempty"`
	AlternatePhone         *string                `json:"alternatePhone,omitempty"`
	FaxPhone               *string                `json:"faxPhone,omitempty"`
	MobilePhone            *string                `json:"mobilePhone,omitempty"`
	TollFreePhone          *string                `json:"tollFreePhone,omitempty"`
	TtyPhone               *string                `json:"ttyPhone,omitempty"`
	SpecialOffer           *string                `json:"specialOffer,omitempty"`
	SpecialOfferUrl        *string                `json:"specialOfferUrl,omitempty"`
	WebsiteUrl             *string                `json:"websiteUrl,omitempty"`
	DisplayWebsiteUrl      *string                `json:"displayWebsiteUrl,omitempty"`
	ReservationUrl         *string                `json:"reservationUrl,omitempty"`
	Hours                  *string                `json:"hours,omitempty"` // A concrete type would be nice for this
	AdditionalHoursText    *string                `json:"additionalHoursText,omitempty"`
	Description            *string                `json:"description,omitempty"`
	PaymentOptions         []string               `json:"paymentOptions,omitempty"`
	VideoUrls              []string               `json:"videoUrls,omitempty"`
	TwitterHandle          *string                `json:"twitterHandle,omitempty"`
	FacebookPageUrl        *string                `json:"facebookPageUrl,omitempty"`
	YearEstablished        *string                `json:"yearEstablished,omitempty"`
	DisplayLat             *float64               `json:"displayLat,omitempty"`
	DisplayLng             *float64               `json:"displayLng,omitempty"`
	RoutableLat            *float64               `json:"routableLat,omitempty"`
	RoutableLng            *float64               `json:"routableLng,omitempty"`
	Emails                 []string               `json:"emails,omitempty"`
	Specialties            []string               `json:"specialties,omitempty"`
	Services               []string               `json:"services,omitempty"`
	Brands                 []string               `json:"brands,omitempty"`
	Languages              []string               `json:"languages,omitempty"`
	FolderId               *string                `json:"folderId,omitempty"`
	LabelIds               []string               `json:"labelIds,omitempty"`
	FacebookCoverPhoto     *LocationPhoto         `json:"facebookCoverPhoto,omitempty"`
	FacebookProfilePicture *LocationPhoto         `json:"facebookProfilePicture,omitempty"`
	Logo                   *LocationPhoto         `json:"logo,omitempty"`
	Photos                 []LocationPhoto        `json:"photos,omitempty"`
	Closed                 *LocationClosed        `json:"closed,omitempty"`
	CategoryIds            []string               `json:"categoryIds,omitempty"`
	HolidayHours           []HolidayHours         `json:"holidayHours,omitempty"`
	hydrated               bool
}

func (y Location) GetId() string {
	if y.Id != nil {
		return *y.Id
	}
	return ""
}

func (y Location) GetName() string {
	if y.Name != nil {
		return *y.Name
	}
	return ""
}

func (y Location) GetCustomerId() string {
	if y.CustomerId != nil {
		return *y.CustomerId
	}
	return ""
}

func (y Location) GetAddress() string {
	if y.Address != nil {
		return *y.Address
	}
	return ""
}

func (y Location) GetAddress2() string {
	if y.Address2 != nil {
		return *y.Address2
	}
	return ""
}

func (y Location) GetSuppressAddress() bool {
	if y.SuppressAddress != nil {
		return *y.SuppressAddress
	}
	return false
}

func (y Location) GetDisplayAddress() string {
	if y.DisplayAddress != nil {
		return *y.DisplayAddress
	}
	return ""
}

func (y Location) GetCity() string {
	if y.City != nil {
		return *y.City
	}
	return ""
}

func (y Location) GetState() string {
	if y.State != nil {
		return *y.State
	}
	return ""
}

func (y Location) GetZip() string {
	if y.Zip != nil {
		return *y.Zip
	}
	return ""
}

func (y Location) GetCountryCode() string {
	if y.CountryCode != nil {
		return *y.CountryCode
	}
	return ""
}

func (y Location) GetPhone() string {
	if y.Phone != nil {
		return *y.Phone
	}
	return ""
}

func (y Location) GetIsPhoneTracked() bool {
	if y.IsPhoneTracked != nil {
		return *y.IsPhoneTracked
	}
	return false
}

func (y Location) GetLocalPhone() string {
	if y.LocalPhone != nil {
		return *y.LocalPhone
	}
	return ""
}

func (y Location) GetAlternatePhone() string {
	if y.AlternatePhone != nil {
		return *y.AlternatePhone
	}
	return ""
}

func (y Location) GetFaxPhone() string {
	if y.FaxPhone != nil {
		return *y.FaxPhone
	}
	return ""
}

func (y Location) GetMobilePhone() string {
	if y.MobilePhone != nil {
		return *y.MobilePhone
	}
	return ""
}

func (y Location) GetTollFreePhone() string {
	if y.TollFreePhone != nil {
		return *y.TollFreePhone
	}
	return ""
}

func (y Location) GetTtyPhone() string {
	if y.TtyPhone != nil {
		return *y.TtyPhone
	}
	return ""
}

func (y Location) GetSpecialOffer() string {
	if y.SpecialOffer != nil {
		return *y.SpecialOffer
	}
	return ""
}

func (y Location) GetSpecialOfferUrl() string {
	if y.SpecialOfferUrl != nil {
		return *y.SpecialOfferUrl
	}
	return ""
}

func (y Location) GetWebsiteUrl() string {
	if y.WebsiteUrl != nil {
		return *y.WebsiteUrl
	}
	return ""
}

func (y Location) GetDisplayWebsiteUrl() string {
	if y.DisplayWebsiteUrl != nil {
		return *y.DisplayWebsiteUrl
	}
	return ""
}

func (y Location) GetReservationUrl() string {
	if y.ReservationUrl != nil {
		return *y.ReservationUrl
	}
	return ""
}

func (y Location) GetHours() string {
	if y.Hours != nil {
		return *y.Hours
	}
	return ""
}

func (y Location) GetAdditionalHoursText() string {
	if y.AdditionalHoursText != nil {
		return *y.AdditionalHoursText
	}
	return ""
}

func (y Location) GetDescription() string {
	if y.Description != nil {
		return *y.Description
	}
	return ""
}

func (y Location) GetTwitterHandle() string {
	if y.TwitterHandle != nil {
		return *y.TwitterHandle
	}
	return ""
}

func (y Location) GetFacebookPageUrl() string {
	if y.FacebookPageUrl != nil {
		return *y.FacebookPageUrl
	}
	return ""
}

func (y Location) GetYearEstablished() string {
	if y.YearEstablished != nil {
		return *y.YearEstablished
	}
	return ""
}

func (y Location) GetDisplayLat() float64 {
	if y.DisplayLat != nil {
		return *y.DisplayLat
	}
	return 0
}

func (y Location) GetDisplayLng() float64 {
	if y.DisplayLng != nil {
		return *y.DisplayLng
	}
	return 0
}

func (y Location) GetRoutableLat() float64 {
	if y.RoutableLat != nil {
		return *y.RoutableLat
	}
	return 0
}

func (y Location) GetRoutableLng() float64 {
	if y.RoutableLng != nil {
		return *y.RoutableLng
	}
	return 0
}

func (y Location) GetFolderId() string {
	if y.FolderId != nil {
		return *y.FolderId
	}
	return ""
}

func (y Location) String() string {
	b, _ := json.Marshal(y)
	return string(b)
}

// Photo represents a photo associated with a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Photo
type LocationPhoto struct {
	Url             string `json:"url,omitempty"`
	Description     string `json:"description,omitempty"`
	ClickThroughURL string `json:"clickthroughUrl,omitempty"`
}

func (l Photo) String() string {
	return fmt.Sprintf("Url: '%v', Description: '%v'", l.Url, l.Description)
}

// LocationClosed represents the 'closed' state of a Location in Yext Location Manager.
// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Closed
type LocationClosed struct {
	IsClosed   string `json:"isClosed"`
	ClosedDate string `json:"closedDate,omitempty"`
}

func (l LocationClosed) String() string {
	return fmt.Sprintf("isClosed: %v, closedDate: '%v'", l.IsClosed, l.ClosedDate)
}

// HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager.
// For details see
type HolidayHours struct {
	Date  string `json:"date"`
	Hours string `json:"hours"`
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/chai2010/gettext-go/gettext"
	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
)

type ByName [][]string

func (a ByName) Len() int {
	return len(a)
}

func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a ByName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct{}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return gettext.Gettext(
		`Lists the available resources.

lxc list [resource] [filters]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same`)
}

func (c *listCmd) flags() {}

// This seems a little excessive.
func dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func shouldShow(filters []string, state *shared.ContainerState) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if dotPrefixMatch(key, configKey) {
					if value == configValue {
						found = true
						break
					} else {
						// the property was found but didn't match
						return false
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func listContainers(cinfos []shared.ContainerInfo, filters []string, listsnaps bool) error {
	data := [][]string{}

	for _, cinfo := range cinfos {
		cstate := cinfo.State
		d := []string{cstate.Name, strings.ToUpper(cstate.Status.Status)}

		if !shouldShow(filters, &cstate) {
			continue
		}

		if cstate.Status.StatusCode == shared.Running || cstate.Status.StatusCode == shared.Frozen {
			ipv4s := []string{}
			ipv6s := []string{}
			for _, ip := range cstate.Status.Ips {
				if ip.Interface == "lo" {
					continue
				}

				if ip.Protocol == "IPV6" {
					ipv6s = append(ipv6s, ip.Address)
				} else {
					ipv4s = append(ipv4s, ip.Address)
				}
			}
			ipv4 := strings.Join(ipv4s, ", ")
			ipv6 := strings.Join(ipv6s, ", ")
			d = append(d, ipv4)
			d = append(d, ipv6)
		} else {
			d = append(d, "")
			d = append(d, "")
		}
		if cstate.Ephemeral {
			d = append(d, gettext.Gettext("YES"))
		} else {
			d = append(d, gettext.Gettext("NO"))
		}
		// List snapshots
		csnaps := cinfo.Snaps
		d = append(d, fmt.Sprintf("%d", len(csnaps)))

		data = append(data, d)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{
		gettext.Gettext("NAME"),
		gettext.Gettext("STATE"),
		gettext.Gettext("IPV4"),
		gettext.Gettext("IPV6"),
		gettext.Gettext("EPHEMERAL"),
		gettext.Gettext("SNAPSHOTS")})
	sort.Sort(ByName(data))
	table.AppendBulk(data)
	table.Render()

	if listsnaps && len(cinfos) == 1 {
		csnaps := cinfos[0].Snaps
		first_snapshot := true
		for _, snap := range csnaps {
			if first_snapshot {
				fmt.Println(gettext.Gettext("Snapshots:"))
			}
			fmt.Printf("  %s\n", snap)
			first_snapshot = false
		}
	}
	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.State.Name) >= len(name) && cinfo.State.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	return listContainers(cts, filters, len(cts) == 1)
}
<commit_msg>Improve rendering of lxc list wrt IPs<commit_after>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/chai2010/gettext-go/gettext"
	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
)

type ByName [][]string

func (a ByName) Len() int {
	return len(a)
}

func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a ByName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct{}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return gettext.Gettext(
		`Lists the available resources.

lxc list [resource] [filters]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same`)
}

func (c *listCmd) flags() {}

// This seems a little excessive.
func dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func shouldShow(filters []string, state *shared.ContainerState) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if dotPrefixMatch(key, configKey) {
					if value == configValue {
						found = true
						break
					} else {
						// the property was found but didn't match
						return false
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func listContainers(cinfos []shared.ContainerInfo, filters []string, listsnaps bool) error {
	data := [][]string{}

	for _, cinfo := range cinfos {
		cstate := cinfo.State
		d := []string{cstate.Name, strings.ToUpper(cstate.Status.Status)}

		if !shouldShow(filters, &cstate) {
			continue
		}

		if cstate.Status.StatusCode == shared.Running || cstate.Status.StatusCode == shared.Frozen {
			ipv4s := []string{}
			ipv6s := []string{}
			for _, ip := range cstate.Status.Ips {
				if ip.Interface == "lo" {
					continue
				}

				if ip.Protocol == "IPV6" {
					ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
				} else {
					ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
				}
			}
			ipv4 := strings.Join(ipv4s, "\n")
			ipv6 := strings.Join(ipv6s, "\n")
			d = append(d, ipv4)
			d = append(d, ipv6)
		} else {
			d = append(d, "")
			d = append(d, "")
		}
		if cstate.Ephemeral {
			d = append(d, gettext.Gettext("YES"))
		} else {
			d = append(d, gettext.Gettext("NO"))
		}
		// List snapshots
		csnaps := cinfo.Snaps
		d = append(d, fmt.Sprintf("%d", len(csnaps)))

		data = append(data, d)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetRowLine(true)
	table.SetHeader([]string{
		gettext.Gettext("NAME"),
		gettext.Gettext("STATE"),
		gettext.Gettext("IPV4"),
		gettext.Gettext("IPV6"),
		gettext.Gettext("EPHEMERAL"),
		gettext.Gettext("SNAPSHOTS")})
	sort.Sort(ByName(data))
	table.AppendBulk(data)
	table.Render()

	if listsnaps && len(cinfos) == 1 {
		csnaps := cinfos[0].Snaps
		first_snapshot := true
		for _, snap := range csnaps {
			if first_snapshot {
				fmt.Println(gettext.Gettext("Snapshots:"))
			}
			fmt.Printf("  %s\n", snap)
			first_snapshot = false
		}
	}
	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.State.Name) >= len(name) && cinfo.State.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	return listContainers(cts, filters, len(cts) == 1)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/lxc/lxd"
	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared/i18n"

	"github.com/lxc/lxd/shared/gnuflag"
)

type moveCmd struct {
	containerOnly bool
}

func (c *moveCmd) showByDefault() bool {
	return true
}

func (c *moveCmd) usage() string {
	return i18n.G(
		`Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]] [--container-only]

Move containers within or in between LXD instances.

lxc move [<remote>:]<source container> [<remote>:][<destination container>] [--container-only]
    Move a container between two hosts, renaming it if destination name differs.

lxc move <old name> <new name> [--container-only]
    Rename a local container.

lxc move <container>/<old snapshot name> <container>/<new snapshot name>
    Rename a snapshot.`)
}

func (c *moveCmd) flags() {
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Move the container without its snapshots"))
}

func (c *moveCmd) run(conf *config.Config, args []string) error {
	if len(args) != 2 {
		return errArgs
	}

	sourceRemote, sourceName, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	destRemote, destName, err := conf.ParseRemote(args[1])
	if err != nil {
		return err
	}

	// As an optimization, if the source an destination are the same, do
	// this via a simple rename. This only works for containers that aren't
	// running, containers that are running should be live migrated (of
	// course, this changing of hostname isn't supported right now, so this
	// simply won't work).
	if sourceRemote == destRemote {
		source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
		if err != nil {
			return err
		}

		rename, err := source.Rename(sourceName, destName)
		if err != nil {
			return err
		}

		return source.WaitForSuccess(rename.Operation)
	}

	cpy := copyCmd{}

	// A move is just a copy followed by a delete; however, we want to
	// keep the volatile entries around since we are moving the container.
	err = cpy.copyContainer(conf, args[0], args[1], true, -1, true, c.containerOnly)
	if err != nil {
		return err
	}

	return commands["delete"].run(conf, args[:1])
}
<commit_msg>lxc/move: Port to new client library<commit_after>package main

import (
	"strings"

	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
)

type moveCmd struct {
	containerOnly bool
}

func (c *moveCmd) showByDefault() bool {
	return true
}

func (c *moveCmd) usage() string {
	return i18n.G(
		`Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]] [--container-only]

Move containers within or in between LXD instances.

lxc move [<remote>:]<source container> [<remote>:][<destination container>] [--container-only]
    Move a container between two hosts, renaming it if destination name differs.

lxc move <old name> <new name> [--container-only]
    Rename a local container.

lxc move <container>/<old snapshot name> <container>/<new snapshot name>
    Rename a snapshot.`)
}

func (c *moveCmd) flags() {
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Move the container without its snapshots"))
}

func (c *moveCmd) run(conf *config.Config, args []string) error {
	if len(args) != 2 {
		return errArgs
	}

	sourceRemote, sourceName, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	destRemote, destName, err := conf.ParseRemote(args[1])
	if err != nil {
		return err
	}

	// As an optimization, if the source an destination are the same, do
	// this via a simple rename. This only works for containers that aren't
	// running, containers that are running should be live migrated (of
	// course, this changing of hostname isn't supported right now, so this
	// simply won't work).
	if sourceRemote == destRemote {
		source, err := conf.GetContainerServer(sourceRemote)
		if err != nil {
			return err
		}

		if shared.IsSnapshot(sourceName) {
			// Snapshot rename
			srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
			dstFields := strings.SplitN(destName, shared.SnapshotDelimiter, 2)

			op, err := source.RenameContainerSnapshot(srcFields[0], srcFields[1], api.ContainerSnapshotPost{Name: dstFields[1]})
			if err != nil {
				return err
			}

			return op.Wait()
		}

		// Container rename
		op, err := source.RenameContainer(sourceName, api.ContainerPost{Name: destName})
		if err != nil {
			return err
		}

		return op.Wait()
	}

	cpy := copyCmd{}

	// A move is just a copy followed by a delete; however, we want to
	// keep the volatile entries around since we are moving the container.
	err = cpy.copyContainer(conf, args[0], args[1], true, -1, true, c.containerOnly)
	if err != nil {
		return err
	}

	return commands["delete"].run(conf, args[:1])
}
<|endoftext|>


<commit_before>// +build mage

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps
	"github.com/magefile/mage/sh" // mg contains helpful utility functions, like Deps
)

const localWorkDir = "./sparta"

var header = strings.Repeat("-", 80)

var ignoreSubdirectoryPaths = []string{
	".vendor",
	".sparta",
	".vscode",
	"/resources/describe",
}

// Default target to run when none is specified
// If not set, running mage will list available targets
// var Default = Build

func mageScript(commands [][]string) error {
	for _, eachCommand := range commands {
		var commandErr error
		if len(eachCommand) <= 1 {
			commandErr = sh.Run(eachCommand[0])
		} else {
			commandErr = sh.Run(eachCommand[0], eachCommand[1:]...)
		}
		if commandErr != nil {
			return commandErr
		}
	}
	return nil
}
func mageLog(formatSpecifier string, args ...interface{}) {
	if mg.Verbose() {
		if len(args) != 0 {
			log.Printf(formatSpecifier, args...)
		} else {
			log.Printf(formatSpecifier)
		}
	}
}

func sourceFilesOfType(extension string) ([]string, error) {
	testExtension := strings.TrimPrefix(extension, ".")
	testExtension = fmt.Sprintf(".%s", testExtension)

	files := make([]string, 0)
	walker := func(path string, info os.FileInfo, err error) error {
		contains := false
		for _, eachComponent := range ignoreSubdirectoryPaths {
			contains = strings.Contains(path, eachComponent)
			if contains {
				break
			}
		}
		if !contains && (filepath.Ext(path) == testExtension) {
			files = append(files, path)
		}
		return nil
	}
	goSourceFilesErr := filepath.Walk(".", walker)
	return files, goSourceFilesErr
}

func applyToSource(fileExtension string, commandParts ...string) error {
	if len(commandParts) <= 0 {
		return errors.New("applyToSource requires a command to apply to source files")
	}
	eligibleSourceFiles, eligibleSourceFilesErr := sourceFilesOfType(fileExtension)
	if eligibleSourceFilesErr != nil {
		return eligibleSourceFilesErr
	}

	mageLog(header)
	mageLog("Applying `%s` to %d `*.%s` source files", commandParts[0], len(eligibleSourceFiles), fileExtension)
	mageLog(header)

	commandArgs := []string{}
	if len(commandParts) > 1 {
		for _, eachPart := range commandParts[1:] {
			commandArgs = append(commandArgs, eachPart)
		}
	}
	for _, eachFile := range eligibleSourceFiles {
		applyArgs := append(commandArgs, eachFile)
		applyErr := sh.Run(commandParts[0], applyArgs...)
		if applyErr != nil {
			return applyErr
		}
	}
	return nil
}

func markdownSourceApply(commandParts ...string) error {
	return applyToSource("md", commandParts...)
}
func goSourceApply(commandParts ...string) error {
	return applyToSource("go", commandParts...)
}

// GenerateBuildInfo creates the automatic buildinfo.go file so that we can
// stamp the SHA into the binaries we build...
func GenerateBuildInfo() error {
	// The first thing we need is the `git` SHA
	cmd := exec.Command("git", "rev-parse", "HEAD")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		return err
	}
	stdOutResult := strings.TrimSpace(string(stdout.Bytes()))

	// Super = update the buildinfo data
	buildInfoTemplate := `package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: %s

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "%s"
`
	updatedInfo := fmt.Sprintf(buildInfoTemplate, time.Now().UTC(), stdOutResult)
	// Write it to the output location...
	return ioutil.WriteFile("./buildinfo.go", []byte(updatedInfo), os.ModePerm)
}

// GenerateConstants runs the set of commands that update the embedded CONSTANTS
// for both local and AWS Lambda execution
func GenerateConstants() error {
	generateCommands := [][]string{
		// Create the embedded version
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS.go", "-private", "-pkg", "sparta", "./resources"},
		//Create a secondary CONSTANTS_AWSBINARY.go file with empty content.
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS_AWSBINARY.go", "-private", "-pkg", "sparta", "./resources/awsbinary/README.md"},
		//The next step will insert the
		// build tags at the head of each file so that they are mutually exclusive
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS", "!lambdabinary"},
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS_AWSBINARY", "lambdabinary"},
		{"git", "commit", "-a", "-m", "Autogenerated constants"},
	}
	return mageScript(generateCommands)
}

// InstallBuildRequirements installs or updates the dependent
// packages that aren't referenced by the source, but are needed
// to build the Sparta source
func InstallBuildRequirements() error {
	mageLog("`go get` update flags (env.GO_GET_FLAG): %s", os.Getenv("GO_GET_FLAG"))

	requirements := []string{
		"github.com/golang/dep/...",
		"honnef.co/go/tools/cmd/megacheck",
		"honnef.co/go/tools/cmd/gosimple",
		"honnef.co/go/tools/cmd/unused",
		"honnef.co/go/tools/cmd/staticcheck",
		"golang.org/x/tools/cmd/goimports",
		"github.com/fzipp/gocyclo",
		"github.com/golang/lint/golint",
		"github.com/mjibson/esc",
		"github.com/securego/gosec/cmd/gosec/...",
		"github.com/client9/misspell/cmd/misspell",
	}
	for _, eachDep := range requirements {
		cmdErr := sh.Run("go",
			"get",
			os.Getenv("GO_GET_FLAG"),
			eachDep)

		// cmdErr := exec.Command(.Run()
		if cmdErr != nil {
			return cmdErr
		}
	}
	return nil
}

// EnsureSpelling ensures that there are no misspellings in the source
func EnsureSpelling() error {
	goSpelling := func() error {
		return goSourceApply("misspell", "-error")
	}
	mdSpelling := func() error {
		return markdownSourceApply("misspell", "-error")
	}
	mg.SerialDeps(
		goSpelling,
		mdSpelling)
	return nil
}

// EnsureVet ensures that the source has been `go vet`ted
func EnsureVet() error {
	return goSourceApply("go", "tool", "vet")
}

// EnsureLint ensures that the source is `golint`ed
func EnsureLint() error {
	return goSourceApply("golint")
}

// EnsureFormatted ensures that the source code is formatted with goimports
func EnsureFormatted() error {
	return goSourceApply("goimports", "-d")
}

// EnsureStaticChecks ensures that the source code passes static code checks
func EnsureStaticChecks() error {
	// Megacheck
	megacheckErr := sh.Run("megacheck",
		"-ignore",
		"github.com/mweagle/Sparta/CONSTANTS.go:*")
	if megacheckErr != nil {
		return megacheckErr
	}
	// Gosec
	return sh.Run("gosec",
		"-exclude=G204,G505,G401",
		"./...")
}

// EnsureAllPreconditions ensures that the source passes *ALL* static `ensure*`
// precondition steps
func EnsureAllPreconditions() error {
	mg.SerialDeps(
		InstallBuildRequirements,
		EnsureVet,
		EnsureLint,
		EnsureFormatted,
		EnsureStaticChecks,
		EnsureSpelling,
	)
	return nil
}

// EnsureTravisBuildEnvironment is the command that sets up the Travis
// environment to run the build.
func EnsureTravisBuildEnvironment() error {
	mg.SerialDeps(InstallBuildRequirements)

	// Super run some commands
	travisComands := [][]string{
		[]string{"dep", "version"},
		[]string{"dep", "ensure"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(travisComands)
}

// Build the application
func Build() error {
	mg.Deps(EnsureAllPreconditions)
	return sh.Run("go", "build", ".")
}

// Clean the working directory
func Clean() error {
	cleanCommands := [][]string{
		[]string{"go", "clean", "."},
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(cleanCommands)
}

// Describe runs the `TestDescribe` test to generate a describe HTML output
// file at graph.html
func Describe() error {
	describeCommands := [][]string{
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"go", "test", "-v", "-run", "TestDescribe"},
	}
	return mageScript(describeCommands)
}

// Publish the latest source
func Publish() error {
	mg.SerialDeps(GenerateBuildInfo)

	describeCommands := [][]string{
		[]string{"echo", "Checking `git` tree status"},
		[]string{"git", "diff", "--exit-code"},
		// TODO - migrate to Go
		[]string{"./buildinfo.sh"},
		[]string{"git", "commit", "-a", "-m", "Tagging Sparta commit"},
		[]string{"git", "push", "origin"},
	}
	return mageScript(describeCommands)
}

// Test runs the Sparta tests
func Test() {
	testCommand := func() error {
		return sh.Run("go",
			"test",
			"-cover",
			"-race",
			"./...")
	}
	mg.SerialDeps(
		EnsureAllPreconditions,
		testCommand,
	)
}

// TestCover runs the test and opens up the resulting report
func TestCover() error {
	// mg.SerialDeps(
	// 	EnsureAllPreconditions,
	// )
	coverageReport := fmt.Sprintf("%s/cover.out", localWorkDir)
	testCoverCommands := [][]string{
		[]string{"go", "test", fmt.Sprintf("-coverprofile=%s", coverageReport), "."},
		[]string{"go", "tool", "cover", fmt.Sprintf("-html=%s", coverageReport)},
		[]string{"rm", coverageReport},
		[]string{"open", fmt.Sprintf("%s/cover.html", localWorkDir)},
	}
	return mageScript(testCoverCommands)
}

// TravisBuild is the task to build in the context of a Travis CI pipeline
func TravisBuild() error {
	mg.SerialDeps(EnsureTravisBuildEnvironment,
		Build,
		Test)
	return nil
}
<commit_msg>Log dependencies<commit_after>// +build mage

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps
	"github.com/magefile/mage/sh" // mg contains helpful utility functions, like Deps
)

const localWorkDir = "./sparta"

var header = strings.Repeat("-", 80)

var ignoreSubdirectoryPaths = []string{
	".vendor",
	".sparta",
	".vscode",
	"/resources/describe",
}

// Default target to run when none is specified
// If not set, running mage will list available targets
// var Default = Build

func mageScript(commands [][]string) error {
	for _, eachCommand := range commands {
		var commandErr error
		if len(eachCommand) <= 1 {
			commandErr = sh.Run(eachCommand[0])
		} else {
			commandErr = sh.Run(eachCommand[0], eachCommand[1:]...)
		}
		if commandErr != nil {
			return commandErr
		}
	}
	return nil
}
func mageLog(formatSpecifier string, args ...interface{}) {
	if mg.Verbose() {
		if len(args) != 0 {
			log.Printf(formatSpecifier, args...)
		} else {
			log.Printf(formatSpecifier)
		}
	}
}

func sourceFilesOfType(extension string) ([]string, error) {
	testExtension := strings.TrimPrefix(extension, ".")
	testExtension = fmt.Sprintf(".%s", testExtension)

	files := make([]string, 0)
	walker := func(path string, info os.FileInfo, err error) error {
		contains := false
		for _, eachComponent := range ignoreSubdirectoryPaths {
			contains = strings.Contains(path, eachComponent)
			if contains {
				break
			}
		}
		if !contains && (filepath.Ext(path) == testExtension) {
			files = append(files, path)
		}
		return nil
	}
	goSourceFilesErr := filepath.Walk(".", walker)
	return files, goSourceFilesErr
}

func applyToSource(fileExtension string, commandParts ...string) error {
	if len(commandParts) <= 0 {
		return errors.New("applyToSource requires a command to apply to source files")
	}
	eligibleSourceFiles, eligibleSourceFilesErr := sourceFilesOfType(fileExtension)
	if eligibleSourceFilesErr != nil {
		return eligibleSourceFilesErr
	}

	mageLog(header)
	mageLog("Applying `%s` to %d `*.%s` source files", commandParts[0], len(eligibleSourceFiles), fileExtension)
	mageLog(header)

	commandArgs := []string{}
	if len(commandParts) > 1 {
		for _, eachPart := range commandParts[1:] {
			commandArgs = append(commandArgs, eachPart)
		}
	}
	for _, eachFile := range eligibleSourceFiles {
		applyArgs := append(commandArgs, eachFile)
		applyErr := sh.Run(commandParts[0], applyArgs...)
		if applyErr != nil {
			return applyErr
		}
	}
	return nil
}

func markdownSourceApply(commandParts ...string) error {
	return applyToSource("md", commandParts...)
}
func goSourceApply(commandParts ...string) error {
	return applyToSource("go", commandParts...)
}

// GenerateBuildInfo creates the automatic buildinfo.go file so that we can
// stamp the SHA into the binaries we build...
func GenerateBuildInfo() error {
	// The first thing we need is the `git` SHA
	cmd := exec.Command("git", "rev-parse", "HEAD")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		return err
	}
	stdOutResult := strings.TrimSpace(string(stdout.Bytes()))

	// Super = update the buildinfo data
	buildInfoTemplate := `package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: %s

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "%s"
`
	updatedInfo := fmt.Sprintf(buildInfoTemplate, time.Now().UTC(), stdOutResult)
	// Write it to the output location...
	return ioutil.WriteFile("./buildinfo.go", []byte(updatedInfo), os.ModePerm)
}

// GenerateConstants runs the set of commands that update the embedded CONSTANTS
// for both local and AWS Lambda execution
func GenerateConstants() error {
	generateCommands := [][]string{
		// Create the embedded version
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS.go", "-private", "-pkg", "sparta", "./resources"},
		//Create a secondary CONSTANTS_AWSBINARY.go file with empty content.
		{"go", "run", "$GOPATH/src/github.com/mjibson/esc/main.go", "-o", "./CONSTANTS_AWSBINARY.go", "-private", "-pkg", "sparta", "./resources/awsbinary/README.md"},
		//The next step will insert the
		// build tags at the head of each file so that they are mutually exclusive
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS", "!lambdabinary"},
		{"go", "run", "./cmd/insertTags/main.go", "./CONSTANTS_AWSBINARY", "lambdabinary"},
		{"git", "commit", "-a", "-m", "Autogenerated constants"},
	}
	return mageScript(generateCommands)
}

// InstallBuildRequirements installs or updates the dependent
// packages that aren't referenced by the source, but are needed
// to build the Sparta source
func InstallBuildRequirements() error {
	mageLog("`go get` update flags (env.GO_GET_FLAG): %s", os.Getenv("GO_GET_FLAG"))

	requirements := []string{
		"github.com/golang/dep/...",
		"honnef.co/go/tools/cmd/megacheck",
		"honnef.co/go/tools/cmd/gosimple",
		"honnef.co/go/tools/cmd/unused",
		"honnef.co/go/tools/cmd/staticcheck",
		"golang.org/x/tools/cmd/goimports",
		"github.com/fzipp/gocyclo",
		"github.com/golang/lint/golint",
		"github.com/mjibson/esc",
		"github.com/securego/gosec/cmd/gosec/...",
		"github.com/client9/misspell/cmd/misspell",
	}
	for _, eachDep := range requirements {
		cmdErr := sh.Run("go",
			"get",
			os.Getenv("GO_GET_FLAG"),
			eachDep)

		// cmdErr := exec.Command(.Run()
		if cmdErr != nil {
			return cmdErr
		}
	}
	return nil
}

// EnsureSpelling ensures that there are no misspellings in the source
func EnsureSpelling() error {
	goSpelling := func() error {
		return goSourceApply("misspell", "-error")
	}
	mdSpelling := func() error {
		return markdownSourceApply("misspell", "-error")
	}
	mg.SerialDeps(
		goSpelling,
		mdSpelling)
	return nil
}

// EnsureVet ensures that the source has been `go vet`ted
func EnsureVet() error {
	return goSourceApply("go", "tool", "vet")
}

// EnsureLint ensures that the source is `golint`ed
func EnsureLint() error {
	return goSourceApply("golint")
}

// EnsureFormatted ensures that the source code is formatted with goimports
func EnsureFormatted() error {
	return goSourceApply("goimports", "-d")
}

// EnsureStaticChecks ensures that the source code passes static code checks
func EnsureStaticChecks() error {
	// Megacheck
	megacheckErr := sh.Run("megacheck",
		"-ignore",
		"github.com/mweagle/Sparta/CONSTANTS.go:*")
	if megacheckErr != nil {
		return megacheckErr
	}
	// Gosec
	return sh.Run("gosec",
		"-exclude=G204,G505,G401",
		"./...")
}

// EnsureAllPreconditions ensures that the source passes *ALL* static `ensure*`
// precondition steps
func EnsureAllPreconditions() error {
	mg.SerialDeps(
		InstallBuildRequirements,
		EnsureVet,
		EnsureLint,
		EnsureFormatted,
		EnsureStaticChecks,
		EnsureSpelling,
	)
	return nil
}

// EnsureTravisBuildEnvironment is the command that sets up the Travis
// environment to run the build.
func EnsureTravisBuildEnvironment() error {
	mg.SerialDeps(InstallBuildRequirements)

	// Super run some commands
	travisComands := [][]string{
		[]string{"dep", "version"},
		[]string{"dep", "ensure", "-v"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(travisComands)
}

// Build the application
func Build() error {
	mg.Deps(EnsureAllPreconditions)
	return sh.Run("go", "build", ".")
}

// Clean the working directory
func Clean() error {
	cleanCommands := [][]string{
		[]string{"go", "clean", "."},
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"rsync", "-a", "--quiet", "--remove-source-files", "./vendor/", "$GOPATH/src"},
	}
	return mageScript(cleanCommands)
}

// Describe runs the `TestDescribe` test to generate a describe HTML output
// file at graph.html
func Describe() error {
	describeCommands := [][]string{
		[]string{"rm", "-rf", "./graph.html"},
		[]string{"go", "test", "-v", "-run", "TestDescribe"},
	}
	return mageScript(describeCommands)
}

// Publish the latest source
func Publish() error {
	mg.SerialDeps(GenerateBuildInfo)

	describeCommands := [][]string{
		[]string{"echo", "Checking `git` tree status"},
		[]string{"git", "diff", "--exit-code"},
		// TODO - migrate to Go
		[]string{"./buildinfo.sh"},
		[]string{"git", "commit", "-a", "-m", "Tagging Sparta commit"},
		[]string{"git", "push", "origin"},
	}
	return mageScript(describeCommands)
}

// Test runs the Sparta tests
func Test() {
	testCommand := func() error {
		return sh.Run("go",
			"test",
			"-cover",
			"-race",
			"./...")
	}
	mg.SerialDeps(
		EnsureAllPreconditions,
		testCommand,
	)
}

// TestCover runs the test and opens up the resulting report
func TestCover() error {
	// mg.SerialDeps(
	// 	EnsureAllPreconditions,
	// )
	coverageReport := fmt.Sprintf("%s/cover.out", localWorkDir)
	testCoverCommands := [][]string{
		[]string{"go", "test", fmt.Sprintf("-coverprofile=%s", coverageReport), "."},
		[]string{"go", "tool", "cover", fmt.Sprintf("-html=%s", coverageReport)},
		[]string{"rm", coverageReport},
		[]string{"open", fmt.Sprintf("%s/cover.html", localWorkDir)},
	}
	return mageScript(testCoverCommands)
}

// TravisBuild is the task to build in the context of a Travis CI pipeline
func TravisBuild() error {
	mg.SerialDeps(EnsureTravisBuildEnvironment,
		Build,
		Test)
	return nil
}
<|endoftext|>