<commit_before>package ipfix

import (
	"encoding/binary"
	"fmt"
	"reflect"
	"strings"
)

/*

Template Record - A Template Record defines the structure and interpretation of fields in a Data Record.
Options Template Record - An Options Template Record is a Template Record that defines the structure and interpretation of fields in a Data Record, including defining how to scope the applicability of the Data Record.
Data Record - A Data Record is a record that contains values of the parameters corresponding to a Template Record.

One of the essential elements in the IPFIX record format is the Template Record.
Templates greatly enhance the flexibility of the record format because they allow the Collecting Process to process IPFIX Messages without necessarily knowing the interpretation of all Data Records.
A Template Record contains any combination of IANA-assigned and/or enterprise-specific Information Element identifiers.

The format of the Template Record consists of a Template Record Header and one or more Field Specifiers.

The scope, which is only available in the Options Template Set, gives the context of the reported Information Elements in the Data Records.

The scope is one or more Information Elements, specified in the Options Template Record.  At a minimum, Collecting Processes SHOULD support as scope the observationDomainId, exportingProcessId, meteringProcessId, templateId, lineCardId, exporterIPv4Address, exporterIPv6Address, and ingressInterface Information Elements.
The IPFIX protocol doesn't prevent the use of any Information Elements for scope.  However, some Information Element types don't make sense if specified as scope (for example, the counter Information Elements).

Multiple Scope Fields MAY be present in the Options Template Record, in which case the composite scope is the combination of the scopes.
For example, if the two scopes are meteringProcessId and templateId, the combined scope is this Template for this Metering Process.
If a different order of Scope Fields would result in a Record having a different semantic meaning, then the order of Scope Fields MUST be preserved by the Exporting Process.  For example, in the context of PSAMP [RFC5476], if the first scope defines the filtering function, while the second scope defines the sampling function, the order of the scope is important.
Applying the sampling function first, followed by the filtering function, would lead to potentially different Data Records than applying the filtering function first, followed by the sampling function.

*/

// TemplateRecord defines the structure of a (Options) Template Record
// If no Scope Fields are present then it is a regular Template Record
type TemplateRecord struct {
	TemplateID uint16 //Each of the newly generated Template Records is given a unique Template ID.  This uniqueness is local to the Transport Session and Observation Domain that generated the Template ID. Template IDs 0-255 are reserved for Template Sets, Options Template Sets, and other reserved Sets yet to be created.  Template IDs of Data Sets are numbered from 256 to 65535.  There are no constraints regarding the order of the Template ID allocation.
	//ScopeFieldCount      uint16
	//FieldCount           uint16 //Number of fields in this Template Record.
	ScopeFieldSpecifiers []*FieldSpecifier
	FieldSpecifiers      []*FieldSpecifier
}

// NewTemplateRecord returns a new *TemplateRecord that has the given templateid and 0 Field Specifiers.
// Note that templateid *must* be between a number in the range 256-65535, else an error is returned.
func NewTemplateRecord(templateid uint16) (*TemplateRecord, error) {
	if templateid < 256 {
		return nil, fmt.Errorf("Invalid template id. Must be >=256 but got %d", templateid)
	}
	return &TemplateRecord{
		TemplateID:           templateid,
		ScopeFieldSpecifiers: nil,
		FieldSpecifiers:      make([]*FieldSpecifier, 0, 0),
	}, nil
}

// NewOptionsTemplateRecord returns a new *TemplateRecord that has the given templateid, 0 Scope Field Specifiers and 0 Field Specifiers.
// Note that templateid *must* be between a number in the range 256-65535, else an error is returned.
func NewOptionsTemplateRecord(templateid uint16) (*TemplateRecord, error) {
	templaterecord, err := NewTemplateRecord(templateid)
	if err != nil {
		return nil, err
	}
	templaterecord.ScopeFieldSpecifiers = make([]*FieldSpecifier, 0, 0)
	return templaterecord, nil
}

// IsOptionsTemplateRecord returns true if the template record is an Options Template Record
func (tmplrec *TemplateRecord) IsOptionsTemplateRecord() bool {
	return tmplrec.ScopeFieldSpecifiers == nil
}

//RegisterTemplateRecord creates a new templaterecord from a struct using the tags.
//This will be hell to make for basiclist etc... but hey.... want to be complete
func RegisterTemplateRecord(templateid uint16, val interface{}) (*TemplateRecord, error) {
	templaterecord, err := NewTemplateRecord(templateid)
	if err != nil {
		return nil, err
	}
	//Must check if it is a struct!!
	value := reflect.ValueOf(val)
	for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
		getFieldSpecifierFromValue(value.Field(i), strings.Split(value.Type().Field(i).Tag.Get("ipfix"), ","))
	}

	return templaterecord, nil
}
BOOKMARK
func getFieldSpecifierFromValue(value reflect.Value, tags []string) *FieldSpecifier {
	//enterpriseid := 0
	//fieldid := 0
	//fieldlen := 0

	fmt.Println(value.Type().Kind(), tags)
	if value.Type().Kind() == reflect.Struct {
		fmt.Println("str")
		for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
			getFieldSpecifierFromValue(value.Field(i), strings.Split(value.Type().Field(0).Tag.Get("ipfix"), ","))
		}
	}

	/*	tags := strings.Split(value.Type().Field(0).Tag.Get("ipfix"), ",")
		for _, tag := range tags {
			kv := strings.SplitN(tag, ":", 2)
			if len(kv) > 1 {
				fmt.Println(kv[0], " -> ", kv[1])
			} else {
				fmt.Println("'", kv[0], "'")
			}
		}
	*/
	//		switch tag.Get("check") {
	return nil
}

// String returns the string representation of the Template Record
func (tmplrec *TemplateRecord) String() string {
	retstring := fmt.Sprintf("id=%d, ", tmplrec.TemplateID)
	if tmplrec.ScopeFieldSpecifiers == nil {
		retstring += fmt.Sprintf("type=template, field count=%d, ", len(tmplrec.FieldSpecifiers))

	} else {
		retstring += fmt.Sprintf("type=options template, field count=%d, scope field count=%d", len(tmplrec.FieldSpecifiers), len(tmplrec.ScopeFieldSpecifiers))
		for idx, sfs := range tmplrec.ScopeFieldSpecifiers {
			if idx > 0 {
				retstring += "; "
			} else {
				retstring += ": "
			}
			retstring += sfs.String()
		}
	}
	for idx, fs := range tmplrec.FieldSpecifiers {
		if idx > 0 {
			retstring += "; "
		} else {
			retstring += ": "
		}
		retstring += fs.String()
	}
	return retstring
}

//AddScopeSpecifier adds a Scope Field Specifier to the record
func (tmplrec *TemplateRecord) AddScopeSpecifier(fsp *FieldSpecifier) (*TemplateRecord, error) {
	if tmplrec.ScopeFieldSpecifiers == nil {
		return nil, fmt.Errorf("TemplateRecord is not an options template record.")
	}
	tmplrec.ScopeFieldSpecifiers = append(tmplrec.ScopeFieldSpecifiers, fsp)
	return tmplrec, nil
}

//AddSpecifier adds a Field Specifier to the record
func (tmplrec *TemplateRecord) AddSpecifier(fsp *FieldSpecifier) *TemplateRecord {
	tmplrec.FieldSpecifiers = append(tmplrec.FieldSpecifiers, fsp)
	return tmplrec
}

// Len returns the size in octets of the template record
func (tmplrec *TemplateRecord) Len() uint16 {
	reclen := uint16(4) //basic header is 4 bytes
	if tmplrec.ScopeFieldSpecifiers != nil {
		reclen += 2 //we need some space in the header for the count of the scope fields
		for _, rec := range tmplrec.ScopeFieldSpecifiers {
			reclen += rec.Len()
		}
	}
	for _, rec := range tmplrec.FieldSpecifiers {
		reclen += rec.Len()
	}
	return reclen
}

/*Field Count

     Number of all fields in this Options Template Record, including
     the Scope Fields.

  Scope Field Count

     Number of scope fields in this Options Template Record.  The Scope
     Fields are normal Fields, except that they are interpreted as
     scope at the Collector.  A scope field count of N specifies that
     the first N Field Specifiers in the Template Record are Scope
     Fields.  The Scope Field Count MUST NOT be zero.
*/

// MarshalBinary satisfies the encoding/BinaryMarshaler interface
func (tmplrec *TemplateRecord) MarshalBinary() (data []byte, err error) {
	if len(tmplrec.FieldSpecifiers) < 1 {
		return nil, NewError("Can not marshal record, must have at least one Field Specifier", ErrCritical)
	}

	marshalValue := make([]byte, 0, 0)
	marshalTemplateID, err := marshalBinarySingleValue(tmplrec.TemplateID)
	if err != nil {
		return nil, err
	}
	marshalValue = append(marshalValue, marshalTemplateID...)

	marshalFieldCount, err := marshalBinarySingleValue(uint16(uint16(len(tmplrec.FieldSpecifiers)) + uint16(len(tmplrec.ScopeFieldSpecifiers))))
	if err != nil {
		return nil, err
	}
	marshalValue = append(marshalValue, marshalFieldCount...)

	if tmplrec.ScopeFieldSpecifiers != nil {
		marshalScopeFieldCount, err := marshalBinarySingleValue(uint16(len(tmplrec.ScopeFieldSpecifiers)))
		if err != nil {
			return nil, err
		}
		marshalValue = append(marshalValue, marshalScopeFieldCount...)
		for _, listitem := range tmplrec.ScopeFieldSpecifiers {
			itemdata, err := listitem.MarshalBinary()
			if err != nil {
				return nil, err
			}
			marshalValue = append(marshalValue, itemdata...)
		}
	}
	for _, listitem := range tmplrec.FieldSpecifiers {
		itemdata, err := listitem.MarshalBinary()
		if err != nil {
			return nil, err
		}
		marshalValue = append(marshalValue, itemdata...)
	}

	return marshalValue, nil
}

// UnmarshalBinary satisfies the encoding/BinaryUnmarshaler interface
func (tmplrec *TemplateRecord) UnmarshalBinary(data []byte) (err error) {
	if data == nil || len(data) < 6 {
		return NewError(fmt.Sprintf("Can not unmarshal, invalid data. %#v", data), ErrCritical)
	}
	tmplrec.TemplateID = binary.BigEndian.Uint16(data[0:2])
	totalFieldCount := binary.BigEndian.Uint16(data[2:4])
	scopeFieldCount := uint16(0)
	cursor := uint16(4)
	if tmplrec.ScopeFieldSpecifiers != nil {
		scopeFieldCount = binary.BigEndian.Uint16(data[4:6])
		cursor = 6
	}
	for cnt := uint16(0); cnt < scopeFieldCount; cnt++ {
		scopeField := &FieldSpecifier{}
		if (data[cursor] & 128) != 0 {
			suberr := scopeField.UnmarshalBinary(data[cursor : cursor+8])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(8)
		} else {
			suberr := scopeField.UnmarshalBinary(data[cursor : cursor+4])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(4)
		}
		tmplrec.ScopeFieldSpecifiers = append(tmplrec.ScopeFieldSpecifiers, scopeField)
	}
	for cnt := uint16(0); cnt < (totalFieldCount - scopeFieldCount); cnt++ {
		fieldSpecifier := &FieldSpecifier{}
		if (data[cursor] & 128) != 0 {
			suberr := fieldSpecifier.UnmarshalBinary(data[cursor : cursor+8])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}

			cursor += uint16(8)
		} else {
			suberr := fieldSpecifier.UnmarshalBinary(data[cursor : cursor+4])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(4)
		}
		tmplrec.FieldSpecifiers = append(tmplrec.FieldSpecifiers, fieldSpecifier)
	}
	return err
}
<commit_msg>Started work on (options)template records from Go struct<commit_after>package ipfix

import (
	"encoding/binary"
	"fmt"
	"reflect"
	"strings"
)

/*

Template Record - A Template Record defines the structure and interpretation of fields in a Data Record.
Options Template Record - An Options Template Record is a Template Record that defines the structure and interpretation of fields in a Data Record, including defining how to scope the applicability of the Data Record.
Data Record - A Data Record is a record that contains values of the parameters corresponding to a Template Record.

One of the essential elements in the IPFIX record format is the Template Record.
Templates greatly enhance the flexibility of the record format because they allow the Collecting Process to process IPFIX Messages without necessarily knowing the interpretation of all Data Records.
A Template Record contains any combination of IANA-assigned and/or enterprise-specific Information Element identifiers.

The format of the Template Record consists of a Template Record Header and one or more Field Specifiers.

The scope, which is only available in the Options Template Set, gives the context of the reported Information Elements in the Data Records.

The scope is one or more Information Elements, specified in the Options Template Record.  At a minimum, Collecting Processes SHOULD support as scope the observationDomainId, exportingProcessId, meteringProcessId, templateId, lineCardId, exporterIPv4Address, exporterIPv6Address, and ingressInterface Information Elements.
The IPFIX protocol doesn't prevent the use of any Information Elements for scope.  However, some Information Element types don't make sense if specified as scope (for example, the counter Information Elements).

Multiple Scope Fields MAY be present in the Options Template Record, in which case the composite scope is the combination of the scopes.
For example, if the two scopes are meteringProcessId and templateId, the combined scope is this Template for this Metering Process.
If a different order of Scope Fields would result in a Record having a different semantic meaning, then the order of Scope Fields MUST be preserved by the Exporting Process.  For example, in the context of PSAMP [RFC5476], if the first scope defines the filtering function, while the second scope defines the sampling function, the order of the scope is important.
Applying the sampling function first, followed by the filtering function, would lead to potentially different Data Records than applying the filtering function first, followed by the sampling function.

*/

// TemplateRecord defines the structure of a (Options) Template Record
// If no Scope Fields are present then it is a regular Template Record
type TemplateRecord struct {
	TemplateID uint16 //Each of the newly generated Template Records is given a unique Template ID.  This uniqueness is local to the Transport Session and Observation Domain that generated the Template ID. Template IDs 0-255 are reserved for Template Sets, Options Template Sets, and other reserved Sets yet to be created.  Template IDs of Data Sets are numbered from 256 to 65535.  There are no constraints regarding the order of the Template ID allocation.
	//ScopeFieldCount      uint16
	//FieldCount           uint16 //Number of fields in this Template Record.
	ScopeFieldSpecifiers []*FieldSpecifier
	FieldSpecifiers      []*FieldSpecifier
}

// NewTemplateRecord returns a new *TemplateRecord that has the given templateid and 0 Field Specifiers.
// Note that templateid *must* be between a number in the range 256-65535, else an error is returned.
func NewTemplateRecord(templateid uint16) (*TemplateRecord, error) {
	if templateid < 256 {
		return nil, fmt.Errorf("Invalid template id. Must be >=256 but got %d", templateid)
	}
	return &TemplateRecord{
		TemplateID:           templateid,
		ScopeFieldSpecifiers: nil,
		FieldSpecifiers:      make([]*FieldSpecifier, 0, 0),
	}, nil
}

// NewOptionsTemplateRecord returns a new *TemplateRecord that has the given templateid, 0 Scope Field Specifiers and 0 Field Specifiers.
// Note that templateid *must* be between a number in the range 256-65535, else an error is returned.
func NewOptionsTemplateRecord(templateid uint16) (*TemplateRecord, error) {
	templaterecord, err := NewTemplateRecord(templateid)
	if err != nil {
		return nil, err
	}
	templaterecord.ScopeFieldSpecifiers = make([]*FieldSpecifier, 0, 0)
	return templaterecord, nil
}

// IsOptionsTemplateRecord returns true if the template record is an Options Template Record
func (tmplrec *TemplateRecord) IsOptionsTemplateRecord() bool {
	return tmplrec.ScopeFieldSpecifiers == nil
}

//RegisterTemplateRecord creates a new templaterecord from a struct using the tags.
//This will be hell to make for basiclist etc... but hey.... want to be complete
func RegisterTemplateRecord(templateid uint16, val interface{}) (*TemplateRecord, error) {
	templaterecord, err := NewTemplateRecord(templateid)
	if err != nil {
		return nil, err
	}
	//Must check if it is a struct!!
	value := reflect.ValueOf(val)
	for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
		getFieldSpecifierFromValue(value.Field(i), strings.Split(value.Type().Field(i).Tag.Get("ipfix"), ","))
	}

	return templaterecord, nil
}
BOOKMARK
func getFieldSpecifierFromValue(value reflect.Value, tags []string) *FieldSpecifier {
	//enterpriseid := 0
	//fieldid := 0
	//fieldlen := 0

	fmt.Println(value.Type().Kind(), tags)
	if value.Type().Kind() == reflect.Struct {
		fmt.Println("str")
		for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
			getFieldSpecifierFromValue(value.Field(i), strings.Split(value.Type().Field(i).Tag.Get("ipfix"), ","))
		}
	}

	return nil
}

// String returns the string representation of the Template Record
func (tmplrec *TemplateRecord) String() string {
	retstring := fmt.Sprintf("id=%d, ", tmplrec.TemplateID)
	if tmplrec.ScopeFieldSpecifiers == nil {
		retstring += fmt.Sprintf("type=template, field count=%d, ", len(tmplrec.FieldSpecifiers))

	} else {
		retstring += fmt.Sprintf("type=options template, field count=%d, scope field count=%d", len(tmplrec.FieldSpecifiers), len(tmplrec.ScopeFieldSpecifiers))
		for idx, sfs := range tmplrec.ScopeFieldSpecifiers {
			if idx > 0 {
				retstring += "; "
			} else {
				retstring += ": "
			}
			retstring += sfs.String()
		}
	}
	for idx, fs := range tmplrec.FieldSpecifiers {
		if idx > 0 {
			retstring += "; "
		} else {
			retstring += ": "
		}
		retstring += fs.String()
	}
	return retstring
}

//AddScopeSpecifier adds a Scope Field Specifier to the record
func (tmplrec *TemplateRecord) AddScopeSpecifier(fsp *FieldSpecifier) (*TemplateRecord, error) {
	if tmplrec.ScopeFieldSpecifiers == nil {
		return nil, fmt.Errorf("TemplateRecord is not an options template record.")
	}
	tmplrec.ScopeFieldSpecifiers = append(tmplrec.ScopeFieldSpecifiers, fsp)
	return tmplrec, nil
}

//AddSpecifier adds a Field Specifier to the record
func (tmplrec *TemplateRecord) AddSpecifier(fsp *FieldSpecifier) *TemplateRecord {
	tmplrec.FieldSpecifiers = append(tmplrec.FieldSpecifiers, fsp)
	return tmplrec
}

// Len returns the size in octets of the template record
func (tmplrec *TemplateRecord) Len() uint16 {
	reclen := uint16(4) //basic header is 4 bytes
	if tmplrec.ScopeFieldSpecifiers != nil {
		reclen += 2 //we need some space in the header for the count of the scope fields
		for _, rec := range tmplrec.ScopeFieldSpecifiers {
			reclen += rec.Len()
		}
	}
	for _, rec := range tmplrec.FieldSpecifiers {
		reclen += rec.Len()
	}
	return reclen
}

/*Field Count

     Number of all fields in this Options Template Record, including
     the Scope Fields.

  Scope Field Count

     Number of scope fields in this Options Template Record.  The Scope
     Fields are normal Fields, except that they are interpreted as
     scope at the Collector.  A scope field count of N specifies that
     the first N Field Specifiers in the Template Record are Scope
     Fields.  The Scope Field Count MUST NOT be zero.
*/

// MarshalBinary satisfies the encoding/BinaryMarshaler interface
func (tmplrec *TemplateRecord) MarshalBinary() (data []byte, err error) {
	if len(tmplrec.FieldSpecifiers) < 1 {
		return nil, NewError("Can not marshal record, must have at least one Field Specifier", ErrCritical)
	}

	marshalValue := make([]byte, 0, 0)
	marshalTemplateID, err := marshalBinarySingleValue(tmplrec.TemplateID)
	if err != nil {
		return nil, err
	}
	marshalValue = append(marshalValue, marshalTemplateID...)

	marshalFieldCount, err := marshalBinarySingleValue(uint16(uint16(len(tmplrec.FieldSpecifiers)) + uint16(len(tmplrec.ScopeFieldSpecifiers))))
	if err != nil {
		return nil, err
	}
	marshalValue = append(marshalValue, marshalFieldCount...)

	if tmplrec.ScopeFieldSpecifiers != nil {
		marshalScopeFieldCount, err := marshalBinarySingleValue(uint16(len(tmplrec.ScopeFieldSpecifiers)))
		if err != nil {
			return nil, err
		}
		marshalValue = append(marshalValue, marshalScopeFieldCount...)
		for _, listitem := range tmplrec.ScopeFieldSpecifiers {
			itemdata, err := listitem.MarshalBinary()
			if err != nil {
				return nil, err
			}
			marshalValue = append(marshalValue, itemdata...)
		}
	}
	for _, listitem := range tmplrec.FieldSpecifiers {
		itemdata, err := listitem.MarshalBinary()
		if err != nil {
			return nil, err
		}
		marshalValue = append(marshalValue, itemdata...)
	}

	return marshalValue, nil
}

// UnmarshalBinary satisfies the encoding/BinaryUnmarshaler interface
func (tmplrec *TemplateRecord) UnmarshalBinary(data []byte) (err error) {
	if data == nil || len(data) < 6 {
		return NewError(fmt.Sprintf("Can not unmarshal, invalid data. %#v", data), ErrCritical)
	}
	tmplrec.TemplateID = binary.BigEndian.Uint16(data[0:2])
	totalFieldCount := binary.BigEndian.Uint16(data[2:4])
	scopeFieldCount := uint16(0)
	cursor := uint16(4)
	if tmplrec.ScopeFieldSpecifiers != nil {
		scopeFieldCount = binary.BigEndian.Uint16(data[4:6])
		cursor = 6
	}
	for cnt := uint16(0); cnt < scopeFieldCount; cnt++ {
		scopeField := &FieldSpecifier{}
		if (data[cursor] & 128) != 0 {
			suberr := scopeField.UnmarshalBinary(data[cursor : cursor+8])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(8)
		} else {
			suberr := scopeField.UnmarshalBinary(data[cursor : cursor+4])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(4)
		}
		tmplrec.ScopeFieldSpecifiers = append(tmplrec.ScopeFieldSpecifiers, scopeField)
	}
	for cnt := uint16(0); cnt < (totalFieldCount - scopeFieldCount); cnt++ {
		fieldSpecifier := &FieldSpecifier{}
		if (data[cursor] & 128) != 0 {
			suberr := fieldSpecifier.UnmarshalBinary(data[cursor : cursor+8])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}

			cursor += uint16(8)
		} else {
			suberr := fieldSpecifier.UnmarshalBinary(data[cursor : cursor+4])
			if suberr != nil {
				if err == nil {
					err = NewError("Sub errors unmarshalling record.", ErrFailure)
				}
				err.(*ProtocolError).Stack(suberr)
			}
			cursor += uint16(4)
		}
		tmplrec.FieldSpecifiers = append(tmplrec.FieldSpecifiers, fieldSpecifier)
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/google/shlex"
	"gopkg.in/alecthomas/kingpin.v3-unstable"
)

type Vars map[string]string

func (v Vars) Copy() Vars {
	out := Vars{}
	for k, v := range v {
		out[k] = v
	}
	return out
}

func (v Vars) Replace(s string) string {
	for k, v := range v {
		prefix := regexp.MustCompile(fmt.Sprintf("{%s=([^}]*)}", k))
		if v != "" {
			s = prefix.ReplaceAllString(s, "$1")
		} else {
			s = prefix.ReplaceAllString(s, "")
		}
		s = strings.Replace(s, fmt.Sprintf("{%s}", k), v, -1)
	}
	return s
}

// Severity of linter message.
type Severity string

// Linter message severity levels.
const ( // nolint: deadcode
	Error   Severity = "error"
	Warning Severity = "warning"
)

type Issue struct {
	Linter   *Linter  `json:"linter"`
	Severity Severity `json:"severity"`
	Path     string   `json:"path"`
	Line     int      `json:"line"`
	Col      int      `json:"col"`
	Message  string   `json:"message"`
}

func (i *Issue) String() string {
	buf := new(bytes.Buffer)
	err := formatTemplate.Execute(buf, i)
	kingpin.FatalIfError(err, "Invalid output format")
	return buf.String()
}

type linterState struct {
	*Linter
	paths    []string
	issues   chan *Issue
	vars     Vars
	exclude  *regexp.Regexp
	include  *regexp.Regexp
	deadline <-chan time.Time
}

func (l *linterState) Partitions() ([][]string, error) {
	command := l.vars.Replace(l.Command)
	cmdArgs, err := parseCommand(command)
	if err != nil {
		return nil, err
	}
	parts := l.Linter.partitionStrategy(cmdArgs, l.paths)
	if len(parts) == 0 {
		return nil, fmt.Errorf("%s: no files to lint", l.Name)
	}
	return parts, nil
}

func runLinters(linters map[string]*Linter, paths []string, concurrency int, exclude, include *regexp.Regexp) (chan *Issue, chan error) {
	errch := make(chan error)
	concurrencych := make(chan bool, concurrency)
	incomingIssues := make(chan *Issue, 1000000)
	processedIssues := filterIssuesViaDirectives(
		newDirectiveParser(),
		maybeSortIssues(maybeAggregateIssues(incomingIssues)))

	vars := Vars{
		"duplthreshold":    fmt.Sprintf("%d", config.DuplThreshold),
		"mincyclo":         fmt.Sprintf("%d", config.Cyclo),
		"maxlinelength":    fmt.Sprintf("%d", config.LineLength),
		"min_confidence":   fmt.Sprintf("%f", config.MinConfidence),
		"min_occurrences":  fmt.Sprintf("%d", config.MinOccurrences),
		"min_const_length": fmt.Sprintf("%d", config.MinConstLength),
		"tests":            "",
	}
	if config.Test {
		vars["tests"] = "-t"
	}

	wg := &sync.WaitGroup{}
	for _, linter := range linters {
		deadline := time.After(config.Deadline)
		state := &linterState{
			Linter:   linter,
			issues:   incomingIssues,
			paths:    paths,
			vars:     vars,
			exclude:  exclude,
			include:  include,
			deadline: deadline,
		}

		partitions, err := state.Partitions()
		if err != nil {
			errch <- err
			continue
		}
		for _, args := range partitions {
			wg.Add(1)
			// Call the goroutine with a copy of the args array so that the
			// contents of the array are not modified by the next iteration of
			// the above for loop
			go func(args []string) {
				concurrencych <- true
				err := executeLinter(state, args)
				if err != nil {
					errch <- err
				}
				<-concurrencych
				wg.Done()
			}(append(args))
		}
	}

	go func() {
		wg.Wait()
		close(incomingIssues)
		close(errch)
	}()
	return processedIssues, errch
}

func executeLinter(state *linterState, args []string) error {
	if len(args) == 0 {
		return fmt.Errorf("missing linter command")
	}

	start := time.Now()
	debug("executing %s", strings.Join(args, " "))
	buf := bytes.NewBuffer(nil)
	command := args[0]
	cmd := exec.Command(command, args[1:]...) // nolint: gas
	cmd.Stdout = buf
	cmd.Stderr = buf
	err := cmd.Start()
	if err != nil {
		return fmt.Errorf("failed to execute linter %s: %s", command, err)
	}

	done := make(chan bool)
	go func() {
		err = cmd.Wait()
		done <- true
	}()

	// Wait for process to complete or deadline to expire.
	select {
	case <-done:

	case <-state.deadline:
		err = fmt.Errorf("deadline exceeded by linter %s (try increasing --deadline)",
			state.Name)
		kerr := cmd.Process.Kill()
		if kerr != nil {
			warning("failed to kill %s: %s", state.Name, kerr)
		}
		return err
	}

	if err != nil {
		debug("warning: %s returned %s: %s", command, err, buf.String())
	}

	processOutput(state, buf.Bytes())
	elapsed := time.Since(start)
	debug("%s linter took %s", state.Name, elapsed)
	return nil
}

func parseCommand(command string) ([]string, error) {
	args, err := shlex.Split(command)
	if err != nil {
		return nil, err
	}
	if len(args) == 0 {
		return nil, fmt.Errorf("invalid command %q", command)
	}
	exe, err := exec.LookPath(args[0])
	if err != nil {
		return nil, err
	}
	return append([]string{exe}, args[1:]...), nil
}

// nolint: gocyclo
func processOutput(state *linterState, out []byte) {
	re := state.regex
	all := re.FindAllSubmatchIndex(out, -1)
	debug("%s hits %d: %s", state.Name, len(all), state.Pattern)

	cwd, err := os.Getwd()
	if err != nil {
		warning("failed to get working directory %s", err)
	}

	// Create a local copy of vars so they can be modified by the linter output
	vars := state.vars.Copy()

	for _, indices := range all {
		group := [][]byte{}
		for i := 0; i < len(indices); i += 2 {
			var fragment []byte
			if indices[i] != -1 {
				fragment = out[indices[i]:indices[i+1]]
			}
			group = append(group, fragment)
		}

		issue := &Issue{Line: 1, Linter: state.Linter}
		for i, name := range re.SubexpNames() {
			if group[i] == nil {
				continue
			}
			part := string(group[i])
			if name != "" {
				vars[name] = part
			}
			switch name {
			case "path":
				issue.Path = relativePath(cwd, part)

			case "line":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "line matched invalid integer")
				issue.Line = int(n)

			case "col":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "col matched invalid integer")
				issue.Col = int(n)

			case "message":
				issue.Message = part

			case "":
			}
		}
		if m, ok := config.MessageOverride[state.Name]; ok {
			issue.Message = vars.Replace(m)
		}
		if sev, ok := config.Severity[state.Name]; ok {
			issue.Severity = Severity(sev)
		} else {
			issue.Severity = "warning"
		}
		if state.exclude != nil && state.exclude.MatchString(issue.String()) {
			continue
		}
		if state.include != nil && !state.include.MatchString(issue.String()) {
			continue
		}
		state.issues <- issue
	}
	return
}

func relativePath(root, path string) string {
	if !filepath.IsAbs(path) {
		return path
	}
	relative, err := filepath.Rel(root, path)
	if err != nil {
		warning("failed to make %s a relative path: %s", path, err)
		return path
	}
	return relative
}

type sortedIssues struct {
	issues []*Issue
	order  []string
}

func (s *sortedIssues) Len() int      { return len(s.issues) }
func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] }

// nolint: gocyclo
func (s *sortedIssues) Less(i, j int) bool {
	l, r := s.issues[i], s.issues[j]
	for _, key := range s.order {
		switch key {
		case "path":
			if l.Path > r.Path {
				return false
			}
		case "line":
			if l.Line > r.Line {
				return false
			}
		case "column":
			if l.Col > r.Col {
				return false
			}
		case "severity":
			if l.Severity > r.Severity {
				return false
			}
		case "message":
			if l.Message > r.Message {
				return false
			}
		case "linter":
			if l.Linter.Name > r.Linter.Name {
				return false
			}
		}
	}
	return true
}

func maybeSortIssues(issues chan *Issue) chan *Issue {
	if reflect.DeepEqual([]string{"none"}, config.Sort) {
		return issues
	}
	out := make(chan *Issue, 1000000)
	sorted := &sortedIssues{
		issues: []*Issue{},
		order:  config.Sort,
	}
	go func() {
		for issue := range issues {
			sorted.issues = append(sorted.issues, issue)
		}
		sort.Sort(sorted)
		for _, issue := range sorted.issues {
			out <- issue
		}
		close(out)
	}()
	return out
}
<commit_msg>Increase errch buffer size.<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	kingpin "gopkg.in/alecthomas/kingpin.v2"

	"github.com/google/shlex"
)

type Vars map[string]string

func (v Vars) Copy() Vars {
	out := Vars{}
	for k, v := range v {
		out[k] = v
	}
	return out
}

func (v Vars) Replace(s string) string {
	for k, v := range v {
		prefix := regexp.MustCompile(fmt.Sprintf("{%s=([^}]*)}", k))
		if v != "" {
			s = prefix.ReplaceAllString(s, "$1")
		} else {
			s = prefix.ReplaceAllString(s, "")
		}
		s = strings.Replace(s, fmt.Sprintf("{%s}", k), v, -1)
	}
	return s
}

// Severity of linter message.
type Severity string

// Linter message severity levels.
const ( // nolint: deadcode
	Error   Severity = "error"
	Warning Severity = "warning"
)

type Issue struct {
	Linter   *Linter  `json:"linter"`
	Severity Severity `json:"severity"`
	Path     string   `json:"path"`
	Line     int      `json:"line"`
	Col      int      `json:"col"`
	Message  string   `json:"message"`
}

func (i *Issue) String() string {
	buf := new(bytes.Buffer)
	err := formatTemplate.Execute(buf, i)
	kingpin.FatalIfError(err, "Invalid output format")
	return buf.String()
}

type linterState struct {
	*Linter
	paths    []string
	issues   chan *Issue
	vars     Vars
	exclude  *regexp.Regexp
	include  *regexp.Regexp
	deadline <-chan time.Time
}

func (l *linterState) Partitions() ([][]string, error) {
	command := l.vars.Replace(l.Command)
	cmdArgs, err := parseCommand(command)
	if err != nil {
		return nil, err
	}
	parts := l.Linter.partitionStrategy(cmdArgs, l.paths)
	if len(parts) == 0 {
		return nil, fmt.Errorf("%s: no files to lint", l.Name)
	}
	return parts, nil
}

func runLinters(linters map[string]*Linter, paths []string, concurrency int, exclude, include *regexp.Regexp) (chan *Issue, chan error) {
	errch := make(chan error, len(linters))
	concurrencych := make(chan bool, concurrency)
	incomingIssues := make(chan *Issue, 1000000)
	processedIssues := filterIssuesViaDirectives(
		newDirectiveParser(),
		maybeSortIssues(maybeAggregateIssues(incomingIssues)))

	vars := Vars{
		"duplthreshold":    fmt.Sprintf("%d", config.DuplThreshold),
		"mincyclo":         fmt.Sprintf("%d", config.Cyclo),
		"maxlinelength":    fmt.Sprintf("%d", config.LineLength),
		"min_confidence":   fmt.Sprintf("%f", config.MinConfidence),
		"min_occurrences":  fmt.Sprintf("%d", config.MinOccurrences),
		"min_const_length": fmt.Sprintf("%d", config.MinConstLength),
		"tests":            "",
	}
	if config.Test {
		vars["tests"] = "-t"
	}

	wg := &sync.WaitGroup{}
	for _, linter := range linters {
		deadline := time.After(config.Deadline)
		state := &linterState{
			Linter:   linter,
			issues:   incomingIssues,
			paths:    paths,
			vars:     vars,
			exclude:  exclude,
			include:  include,
			deadline: deadline,
		}

		partitions, err := state.Partitions()
		if err != nil {
			errch <- err
			continue
		}
		for _, args := range partitions {
			wg.Add(1)
			// Call the goroutine with a copy of the args array so that the
			// contents of the array are not modified by the next iteration of
			// the above for loop
			go func(args []string) {
				concurrencych <- true
				err := executeLinter(state, args)
				if err != nil {
					errch <- err
				}
				<-concurrencych
				wg.Done()
			}(append(args))
		}
	}

	go func() {
		wg.Wait()
		close(incomingIssues)
		close(errch)
	}()
	return processedIssues, errch
}

func executeLinter(state *linterState, args []string) error {
	if len(args) == 0 {
		return fmt.Errorf("missing linter command")
	}

	start := time.Now()
	debug("executing %s", strings.Join(args, " "))
	buf := bytes.NewBuffer(nil)
	command := args[0]
	cmd := exec.Command(command, args[1:]...) // nolint: gas
	cmd.Stdout = buf
	cmd.Stderr = buf
	err := cmd.Start()
	if err != nil {
		return fmt.Errorf("failed to execute linter %s: %s", command, err)
	}

	done := make(chan bool)
	go func() {
		err = cmd.Wait()
		done <- true
	}()

	// Wait for process to complete or deadline to expire.
	select {
	case <-done:

	case <-state.deadline:
		err = fmt.Errorf("deadline exceeded by linter %s (try increasing --deadline)",
			state.Name)
		kerr := cmd.Process.Kill()
		if kerr != nil {
			warning("failed to kill %s: %s", state.Name, kerr)
		}
		return err
	}

	if err != nil {
		debug("warning: %s returned %s: %s", command, err, buf.String())
	}

	processOutput(state, buf.Bytes())
	elapsed := time.Since(start)
	debug("%s linter took %s", state.Name, elapsed)
	return nil
}

func parseCommand(command string) ([]string, error) {
	args, err := shlex.Split(command)
	if err != nil {
		return nil, err
	}
	if len(args) == 0 {
		return nil, fmt.Errorf("invalid command %q", command)
	}
	exe, err := exec.LookPath(args[0])
	if err != nil {
		return nil, err
	}
	return append([]string{exe}, args[1:]...), nil
}

// nolint: gocyclo
func processOutput(state *linterState, out []byte) {
	re := state.regex
	all := re.FindAllSubmatchIndex(out, -1)
	debug("%s hits %d: %s", state.Name, len(all), state.Pattern)

	cwd, err := os.Getwd()
	if err != nil {
		warning("failed to get working directory %s", err)
	}

	// Create a local copy of vars so they can be modified by the linter output
	vars := state.vars.Copy()

	for _, indices := range all {
		group := [][]byte{}
		for i := 0; i < len(indices); i += 2 {
			var fragment []byte
			if indices[i] != -1 {
				fragment = out[indices[i]:indices[i+1]]
			}
			group = append(group, fragment)
		}

		issue := &Issue{Line: 1, Linter: state.Linter}
		for i, name := range re.SubexpNames() {
			if group[i] == nil {
				continue
			}
			part := string(group[i])
			if name != "" {
				vars[name] = part
			}
			switch name {
			case "path":
				issue.Path = relativePath(cwd, part)

			case "line":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "line matched invalid integer")
				issue.Line = int(n)

			case "col":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "col matched invalid integer")
				issue.Col = int(n)

			case "message":
				issue.Message = part

			case "":
			}
		}
		if m, ok := config.MessageOverride[state.Name]; ok {
			issue.Message = vars.Replace(m)
		}
		if sev, ok := config.Severity[state.Name]; ok {
			issue.Severity = Severity(sev)
		} else {
			issue.Severity = "warning"
		}
		if state.exclude != nil && state.exclude.MatchString(issue.String()) {
			continue
		}
		if state.include != nil && !state.include.MatchString(issue.String()) {
			continue
		}
		state.issues <- issue
	}
	return
}

func relativePath(root, path string) string {
	if !filepath.IsAbs(path) {
		return path
	}
	relative, err := filepath.Rel(root, path)
	if err != nil {
		warning("failed to make %s a relative path: %s", path, err)
		return path
	}
	return relative
}

type sortedIssues struct {
	issues []*Issue
	order  []string
}

func (s *sortedIssues) Len() int      { return len(s.issues) }
func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] }

// nolint: gocyclo
func (s *sortedIssues) Less(i, j int) bool {
	l, r := s.issues[i], s.issues[j]
	for _, key := range s.order {
		switch key {
		case "path":
			if l.Path > r.Path {
				return false
			}
		case "line":
			if l.Line > r.Line {
				return false
			}
		case "column":
			if l.Col > r.Col {
				return false
			}
		case "severity":
			if l.Severity > r.Severity {
				return false
			}
		case "message":
			if l.Message > r.Message {
				return false
			}
		case "linter":
			if l.Linter.Name > r.Linter.Name {
				return false
			}
		}
	}
	return true
}

func maybeSortIssues(issues chan *Issue) chan *Issue {
	if reflect.DeepEqual([]string{"none"}, config.Sort) {
		return issues
	}
	out := make(chan *Issue, 1000000)
	sorted := &sortedIssues{
		issues: []*Issue{},
		order:  config.Sort,
	}
	go func() {
		for issue := range issues {
			sorted.issues = append(sorted.issues, issue)
		}
		sort.Sort(sorted)
		for _, issue := range sorted.issues {
			out <- issue
		}
		close(out)
	}()
	return out
}
<|endoftext|>


<commit_before>// exp 包中是用于 SQL 语句生成的各种组件
//
// exp.go 中保存了 exp 包的一些未分类组件
//
// TODO
//
// 普通的表达式没有处理字符串转义，需要接受外部传入的数据的话，请一定参数化
//
// 目前还没有对纯文本文法中的命名做映射，要享受转换的能力，请使用 table 和 field 类型
package exp

import (
	"fmt"
	"time"
	"strings"
	"regexp"

	"github.com/lib/pq"
)

var argFinder,_ = regexp.Compile("([^\\$]\\$\\d)|(^\\$d)")
var dollar,_ = regexp.Compile("\\$\\$")

type Env interface {
	// Type Name to Table Name
	// NOTE: 需要注意的是当前使用type的 Name()，其中包含 packages 名
	TynaToTana(typename string) string
	// Struct Field Name to Table Column Name
	FinaToCona(typename string, fieldname string) string
	Scope() Exp
	SetScope(Exp)
}

type Exp interface {
	Eval(Env) string
}

type equal struct{
	x, y Exp
}
func Equal(x, y Exp) Exp{
	return &equal{x, y}
}
func (e equal)Eval(env Env)string{
	return fmt.Sprintf("%s=%s", e.x.Eval(env), e.y.Eval(env))
}

type notequal struct{
	x, y Exp
}
func NotEqual(x, y Exp) Exp{
	return &notequal{x, y}
}
func (e notequal)Eval(env Env)string{
	return fmt.Sprintf("%s!=%s", e.x.Eval(env), e.y.Eval(env))
}

type great struct{
	x, y Exp
}
func Great(x, y Exp) Exp{
	return &great{x, y}
}
func (e great)Eval(env Env)string{
	return fmt.Sprintf("%s>%s", e.x.Eval(env), e.y.Eval(env))
}

type less struct{
	x, y Exp
}
func Less(x, y Exp) Exp{
	return &great{x, y}
}
func (e less)Eval(env Env)string{
	return fmt.Sprintf("%s<%s", e.x.Eval(env), e.y.Eval(env))
}

type and struct{
	x, y Exp
}
func And(x, y Exp) Exp{
	return &and{x, y}
}
func (a and)Eval(env Env)string{
	return fmt.Sprintf("(%s) and (%s)", a.x.Eval(env), a.y.Eval(env))
}

type or struct{
	x, y Exp
}
func Or(x, y Exp) Exp{
	return &or{x, y}
}
func (o or)Eval(env Env)string{
	return fmt.Sprintf("(%s) or (%s)", o.x.Eval(env), o.y.Eval(env))
}

type text struct{
	data string
}
// Text 函数用于生成text类型的表达式。
// text 就是 PostgreSQL 的 text 类型。由于作者太懒，先用这个代替所有的文本和字符串类型用吧，
// 在 PG 里其实一般的规模好像也问题不大……
func Text(data string) Exp{
	return &text{data}
}

// 从 text 对象到 SQL 片段的解析函数实现
func(t *text)Eval(env Env)string{
	return fmt.Sprintf("'%s'", t.data)
}

type integer struct{
	data int
}
// Integer 函数生成 integer 类型表达式，这个 integer 指 PostgreSQL 中的 integer 类型
func Integer(data int) Exp{
	return &integer{data}
}
// 从 integer 对象到 SQL 片段的解析函数实现
func(i *integer)Eval(env Env)string{
	return fmt.Sprintf("%d", i.data)
}

type timestamp struct{
	data time.Time
}
// Timestamp 函数用于生成 timestamp 类型的表达式。
// timestamp 类型即 PostgreSQL 的 timestamp 时间戳。由于作者太懒，目前还没有 Date
// 和 Datetime 类型的支持。
func Timestamp(data time.Time) Exp {
	return &timestamp{data}
}
// 从 时间戳对象到 SQL 片段的解析函数实现
func(ts *timestamp)Eval(env Env) string{
	dbtime := pq.NullTime{ts.data, true}
	val, err := dbtime.Value()
	if err != nil {
		panic(err)
	} else {
		if ret,ok := val.([]byte);ok{
			return string(ret)
		} else {
			panic(ts.data)
		}
	}
}

// 之所以用 arg 而不是 parameter ， 完全是为了少写几个字母……
type arg struct{
	Order int
}
// 我也觉得自动生成序列号比较省力气啊。不过想了半天， $%d 就是为了可以指定参数插入位置啊，
// 自动生成顺序就白瞎了啊……
// 将来可能会把这个变成支持命名的，order虽然更省事儿，但是命名相对来说更省心
func Arg(order int) *arg {
	return &arg{order}
}
func (a arg)Eval(env Env)string{
	return fmt.Sprintf("$%d", a.Order)
}

// IncOrder 其实在 pgears 之外应该不太有机会用到，这个是内部生成表达式的时候偶尔
// 需要调整表达式中参数的 order，这个就是起这种作用的，其实如果全命名化了也就没这个问题了。
// 之所以公开是因为 Engine 会用到。
func IncOrder(e Exp, step int){
	switch ex := e.(type) {
	case *equal:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *notequal:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *and:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *or:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *great:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *less:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *not:
		IncOrder((*ex).exp, step)
	case *arg:
		ex.Order += step
	}
}

type function struct{
	name string
	args []Exp
}
func Func(name string, args... Exp) Exp{
	return &function{name, args}
}
func (f *function)Eval(env Env)string{
	var args = make([]string, len(f.args))
	for _, arg := range f.args{
		args = append(args, arg.Eval(env))
	}
	return fmt.Sprintf("%s(\"%s\")", f.name, strings.Join(args, ","))
}

type not struct{
	exp Exp
}
func Not(exp Exp) *not{
	return &not{exp}
}
func (n not)Eval(env Env) string{
	return fmt.Sprintf("not (%s)", n.exp.Eval(env))
}

type fieldIsNull struct{
	exp Exp
}
func FieldIsNull(field Exp) fieldIsNull{
	return fieldIsNull{field}
}
func (isnull *fieldIsNull)Eval(env Env) string{
	return fmt.Sprintf("%s is NULL", isnull.exp.Eval(env))
}

type isNullFunc struct{
	field Exp
	exp Exp
}
func IsNullFunc(field Exp, exp Exp) Exp{
	return isNullFunc{field, exp}
}
func (isnull isNullFunc)Eval(env Env) string{
	return fmt.Sprintf("isNull(%s, %s)", isnull.field.Eval(env),
		isnull.exp.Eval(env))
}

type nullif struct{
	field Exp
	exp Exp
}
func NullIf(field Exp, exp Exp) Exp{
	return nullif{field, exp}
}
func (nullif nullif)Eval(env Env) string{
	return fmt.Sprintf("nullif(%s, %s)", nullif.field.Eval(env),
		nullif.exp.Eval(env))
}

type fullTextSearch struct{
	field Exp
	query Exp
}
func FTS(field Exp, query Exp) *fullTextSearch{
	return &fullTextSearch{field, query}
}
func (fts fullTextSearch)Eval(env Env)string{
	return fmt.Sprintf("%s @@ %s", fts.field.Eval(env),
		fts.query.Eval(env))
}

// snippet 就是直接插入一段纯文本咯，不做任何检测咯，玩脱了是你活该咯……
type snippet struct {
	str *string
}
func Snippet(s string) *snippet{
	return &snippet{&s}
}
func (s *snippet)Eval(env Env) string {
	return *s.str
}

// 虽然理论上说这个内涵的 field 肯定会是field，但是这里姑且还是假定为 exp.Exp
type desc struct {
	field Exp
}
// Desc 生成一个用于orderby desc的表达式
func Desc(field Exp)Exp{
	return &desc{field}
}

func (d desc)Eval(env Env) string{
	return d.field.Eval(env)+" desc"
}

type count struct {
	fields []Exp
	isAll bool
}

// TODO: postgresql 是不是允许count多个字段列啊……
func Count(fields... Exp)Exp{
	return &count{fields, false}
}
// count(*)
func Counts()Exp{
	return &count{[]Exp{}, true}
}
func (d count)Eval(env Env) string{
	if d.isAll {
		return "count(*)"
	}else{
		fs := make([]string, 0, len(d.fields))
		for _, f := range d.fields {
			fs = append(fs, f.Eval(env))
		}
		fields := strings.Join(fs, ",")
		return fmt.Sprintf("count(%s)", fields)
	}
}
<commit_msg>fix bug<commit_after>// exp 包中是用于 SQL 语句生成的各种组件
//
// exp.go 中保存了 exp 包的一些未分类组件
//
// TODO
//
// 普通的表达式没有处理字符串转义，需要接受外部传入的数据的话，请一定参数化
//
// 目前还没有对纯文本文法中的命名做映射，要享受转换的能力，请使用 table 和 field 类型
package exp

import (
	"fmt"
	"time"
	"strings"
	"regexp"

	"github.com/lib/pq"
)

var argFinder,_ = regexp.Compile("([^\\$]\\$\\d)|(^\\$d)")
var dollar,_ = regexp.Compile("\\$\\$")

type Env interface {
	// Type Name to Table Name
	// NOTE: 需要注意的是当前使用type的 Name()，其中包含 packages 名
	TynaToTana(typename string) string
	// Struct Field Name to Table Column Name
	FinaToCona(typename string, fieldname string) string
	Scope() Exp
	SetScope(Exp)
}

type Exp interface {
	Eval(Env) string
}

type equal struct{
	x, y Exp
}
func Equal(x, y Exp) Exp{
	return &equal{x, y}
}
func (e equal)Eval(env Env)string{
	return fmt.Sprintf("%s=%s", e.x.Eval(env), e.y.Eval(env))
}

type notequal struct{
	x, y Exp
}
func NotEqual(x, y Exp) Exp{
	return &notequal{x, y}
}
func (e notequal)Eval(env Env)string{
	return fmt.Sprintf("%s!=%s", e.x.Eval(env), e.y.Eval(env))
}

type great struct{
	x, y Exp
}
func Great(x, y Exp) Exp{
	return &great{x, y}
}
func (e great)Eval(env Env)string{
	return fmt.Sprintf("%s>%s", e.x.Eval(env), e.y.Eval(env))
}

type less struct{
	x, y Exp
}
func Less(x, y Exp) Exp{
	return &great{x, y}
}
func (e less)Eval(env Env)string{
	return fmt.Sprintf("%s<%s", e.x.Eval(env), e.y.Eval(env))
}

type and struct{
	x, y Exp
}
func And(x, y Exp) Exp{
	return &and{x, y}
}
func (a and)Eval(env Env)string{
	return fmt.Sprintf("(%s) and (%s)", a.x.Eval(env), a.y.Eval(env))
}

type or struct{
	x, y Exp
}
func Or(x, y Exp) Exp{
	return &or{x, y}
}
func (o or)Eval(env Env)string{
	return fmt.Sprintf("(%s) or (%s)", o.x.Eval(env), o.y.Eval(env))
}

type text struct{
	data string
}
// Text 函数用于生成text类型的表达式。
// text 就是 PostgreSQL 的 text 类型。由于作者太懒，先用这个代替所有的文本和字符串类型用吧，
// 在 PG 里其实一般的规模好像也问题不大……
func Text(data string) Exp{
	return &text{data}
}

// 从 text 对象到 SQL 片段的解析函数实现
func(t *text)Eval(env Env)string{
	return fmt.Sprintf("'%s'", t.data)
}

type integer struct{
	data int
}
// Integer 函数生成 integer 类型表达式，这个 integer 指 PostgreSQL 中的 integer 类型
func Integer(data int) Exp{
	return &integer{data}
}
// 从 integer 对象到 SQL 片段的解析函数实现
func(i *integer)Eval(env Env)string{
	return fmt.Sprintf("%d", i.data)
}

type timestamp struct{
	data time.Time
}
// Timestamp 函数用于生成 timestamp 类型的表达式。
// timestamp 类型即 PostgreSQL 的 timestamp 时间戳。由于作者太懒，目前还没有 Date
// 和 Datetime 类型的支持。
func Timestamp(data time.Time) Exp {
	return &timestamp{data}
}
// 从 时间戳对象到 SQL 片段的解析函数实现
func(ts *timestamp)Eval(env Env) string{
	dbtime := pq.NullTime{ts.data, true}
	val, err := dbtime.Value()
	if err != nil {
		panic(err)
	} else {
		if ret,ok := val.([]byte);ok{
			return string(ret)
		} else {
			panic(ts.data)
		}
	}
}

// 之所以用 arg 而不是 parameter ， 完全是为了少写几个字母……
type arg struct{
	Order int
}
// 我也觉得自动生成序列号比较省力气啊。不过想了半天， $%d 就是为了可以指定参数插入位置啊，
// 自动生成顺序就白瞎了啊……
// 将来可能会把这个变成支持命名的，order虽然更省事儿，但是命名相对来说更省心
func Arg(order int) *arg {
	return &arg{order}
}
func (a arg)Eval(env Env)string{
	return fmt.Sprintf("$%d", a.Order)
}

// IncOrder 其实在 pgears 之外应该不太有机会用到，这个是内部生成表达式的时候偶尔
// 需要调整表达式中参数的 order，这个就是起这种作用的，其实如果全命名化了也就没这个问题了。
// 之所以公开是因为 Engine 会用到。
func IncOrder(e Exp, step int){
	switch ex := e.(type) {
	case *equal:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *notequal:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *and:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *or:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *great:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *less:
		IncOrder((*ex).x, step)
		IncOrder((*ex).y, step)
	case *not:
		IncOrder((*ex).exp, step)
	case *arg:
		ex.Order += step
	}
}

type function struct{
	name string
	args []Exp
}
func Func(name string, args... Exp) Exp{
	return &function{name, args}
}
func (f *function)Eval(env Env)string{
	var args = make([]string, len(f.args))
	for idx, arg := range f.args{
		args[idx] = arg.Eval(env)
	}
	return fmt.Sprintf("%s(%s)", f.name, strings.Join(args, ","))
}

type not struct{
	exp Exp
}
func Not(exp Exp) *not{
	return &not{exp}
}
func (n not)Eval(env Env) string{
	return fmt.Sprintf("not (%s)", n.exp.Eval(env))
}

type fieldIsNull struct{
	exp Exp
}
func FieldIsNull(field Exp) fieldIsNull{
	return fieldIsNull{field}
}
func (isnull *fieldIsNull)Eval(env Env) string{
	return fmt.Sprintf("%s is NULL", isnull.exp.Eval(env))
}

type isNullFunc struct{
	field Exp
	exp Exp
}
func IsNullFunc(field Exp, exp Exp) Exp{
	return isNullFunc{field, exp}
}
func (isnull isNullFunc)Eval(env Env) string{
	return fmt.Sprintf("isNull(%s, %s)", isnull.field.Eval(env),
		isnull.exp.Eval(env))
}

type nullif struct{
	field Exp
	exp Exp
}
func NullIf(field Exp, exp Exp) Exp{
	return nullif{field, exp}
}
func (nullif nullif)Eval(env Env) string{
	return fmt.Sprintf("nullif(%s, %s)", nullif.field.Eval(env),
		nullif.exp.Eval(env))
}

type fullTextSearch struct{
	field Exp
	query Exp
}
func FTS(field Exp, query Exp) *fullTextSearch{
	return &fullTextSearch{field, query}
}
func (fts fullTextSearch)Eval(env Env)string{
	return fmt.Sprintf("%s @@ %s", fts.field.Eval(env),
		fts.query.Eval(env))
}

// snippet 就是直接插入一段纯文本咯，不做任何检测咯，玩脱了是你活该咯……
type snippet struct {
	str *string
}
func Snippet(s string) *snippet{
	return &snippet{&s}
}
func (s *snippet)Eval(env Env) string {
	return *s.str
}

// 虽然理论上说这个内涵的 field 肯定会是field，但是这里姑且还是假定为 exp.Exp
type desc struct {
	field Exp
}
// Desc 生成一个用于orderby desc的表达式
func Desc(field Exp)Exp{
	return &desc{field}
}

func (d desc)Eval(env Env) string{
	return d.field.Eval(env)+" desc"
}

type count struct {
	fields []Exp
	isAll bool
}

// TODO: postgresql 是不是允许count多个字段列啊……
func Count(fields... Exp)Exp{
	return &count{fields, false}
}
// count(*)
func Counts()Exp{
	return &count{[]Exp{}, true}
}
func (d count)Eval(env Env) string{
	if d.isAll {
		return "count(*)"
	}else{
		fs := make([]string, 0, len(d.fields))
		for _, f := range d.fields {
			fs = append(fs, f.Eval(env))
		}
		fields := strings.Join(fs, ",")
		return fmt.Sprintf("count(%s)", fields)
	}
}
<|endoftext|>


<commit_before>package test

import (
	"testing"

	"github.com/nbusy/devastator/neptulon/jsonrpc"
)

func TestAuth(t *testing.T) {
	// t.Fatal("Unauthorized clients cannot call any function other than method.auth and method.close") // call to randomized and all registered routes here
	// t.Fatal("Anonymous calls to method.auth and method.close should be allowed")
}

func TestGoogleAuth(t *testing.T) {
	// t.Fatal("Google+ first sign-in (registration) failed with valid credentials")
	// t.Fatal("Google+ second sign-in (regular) failed with valid credentials")
	// t.Fatal("Google+ sign-in passed with invalid credentials")
	// t.Fatal("Authentication was not ACKed")
}

func TestClientCertAuth(t *testing.T) {
	s := getServer(t)
	c := getClientConnWithClientCert(t)

	writeMsg(t, c, jsonrpc.Request{ID: "123", Method: "auth.cert"}) // should be a variadic fn(method, params...)
	m := readMsg(t, c)

	if m.Result != "ACK" {
		t.Fatal("Authentication failed with a valid client certificate. Got server response:", m)
	}

	closeClientConn(t, c)
	stopServer(t, s)

	// t.Fatal("Authenticated with invalid/expired client certificate")
	// t.Fatal("Authentication was not ACKed")
}

func TestTokenAuth(t *testing.T) {
	// t.Fatal("Authentication failed with a valid token")
	// t.Fatal("Authenticated with invalid/expired token")
	// t.Fatal("Authentication was not ACKed")
}
<commit_msg>add id assertion to TestClientCertAuth<commit_after>package test

import (
	"testing"

	"github.com/nbusy/devastator/neptulon/jsonrpc"
)

func TestAuth(t *testing.T) {
	// t.Fatal("Unauthorized clients cannot call any function other than method.auth and method.close") // call to randomized and all registered routes here
	// t.Fatal("Anonymous calls to method.auth and method.close should be allowed")
}

func TestGoogleAuth(t *testing.T) {
	// t.Fatal("Google+ first sign-in (registration) failed with valid credentials")
	// t.Fatal("Google+ second sign-in (regular) failed with valid credentials")
	// t.Fatal("Google+ sign-in passed with invalid credentials")
	// t.Fatal("Authentication was not ACKed")
}

func TestClientCertAuth(t *testing.T) {
	s := getServer(t)
	c := getClientConnWithClientCert(t)

	writeMsg(t, c, jsonrpc.Request{ID: "123", Method: "auth.cert"}) // should be a variadic fn(method, params...)
	m := readMsg(t, c)

	if m.ID != "123" && m.Result != "ACK" {
		t.Fatal("Authentication failed with a valid client certificate. Got server response:", m)
	}

	closeClientConn(t, c)
	stopServer(t, s)

	// t.Fatal("Authenticated with invalid/expired client certificate")
	// t.Fatal("Authentication was not ACKed")
}

func TestTokenAuth(t *testing.T) {
	// t.Fatal("Authentication failed with a valid token")
	// t.Fatal("Authenticated with invalid/expired token")
	// t.Fatal("Authentication was not ACKed")
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"

	"github.com/nytlabs/st-core/core"
	"github.com/thejerf/suture"
)

// BlockLedger is the information the API keeps about the block
// TODO Token and ID should probably be the same thing
type BlockLedger struct {
	Name      string
	BlockType string
	Token     suture.ServiceToken
	ID        int
	Block     *core.Block
}

// GetID returns the block's ID
func (b BlockLedger) GetID() int {
	return b.ID
}

// GetName returns the block's Name
func (b BlockLedger) GetName() string {
	return b.Name
}

// MarshalJSON returns the JSON representation of the block ledger
func (b BlockLedger) MarshalJSON() ([]byte, error) {
	out := map[string]interface{}{
		"id":        strconv.Itoa(b.ID),
		"name":      b.Name,
		"blockType": b.BlockType,
	}
	return json.Marshal(out)
}

func (s *Server) ListBlocks() []BlockLedger {
	var blocks []BlockLedger
	s.Lock()
	for _, b := range s.blocks {
		blocks = append(blocks, *b)
	}
	s.Unlock()
	return blocks
}

func (s *Server) BlockIndex(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(s.ListBlocks()); err != nil {
		panic(err)
	}
}

// CreateBlockHandler responds to a POST request to instantiate a new block and add it to the Server.
// TODO currently all blocks start off life in the root group, which may be a bit limiting.
func (s *Server) BlockCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(body)
	}
	var m BlockLedger
	err = json.Unmarshal(body, &m)
	if err != nil {
		log.Panic(err)
	}
	blockSpec, ok := s.library[m.BlockType]
	if !ok {
		log.Fatal("unknown block type")
	}
	b := core.NewBlock(blockSpec)
	m.ID = s.GetNextID()
	m.Block = b
	// we need to introduce the block to our running supervisor
	m.Token = s.supervisor.Add(b)
	// and we need to assign it to a group
	s.groups[0].AddNode(&m)
	s.blocks[m.ID] = &m
	//broadcast for state update
	out, _ := json.Marshal(m)
	s.broadcast <- out
	// write HTTP response
	w.WriteHeader(200)
	w.Write([]byte("OK"))
}
func (s *Server) BlockDelete(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) BlockModify(w http.ResponseWriter, r *http.Request) {
}
<commit_msg>adding block deletion<commit_after>package server

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/nytlabs/st-core/core"
	"github.com/thejerf/suture"
)

// BlockLedger is the information the API keeps about the block
// TODO Token and ID should probably be the same thing
type BlockLedger struct {
	Name      string
	BlockType string
	Token     suture.ServiceToken
	ID        int
	Block     *core.Block
}

// GetID returns the block's ID
func (b BlockLedger) GetID() int {
	return b.ID
}

// GetName returns the block's Name
func (b BlockLedger) GetName() string {
	return b.Name
}

// MarshalJSON returns the JSON representation of the block ledger
func (b BlockLedger) MarshalJSON() ([]byte, error) {
	out := map[string]interface{}{
		"id":        strconv.Itoa(b.ID),
		"name":      b.Name,
		"blockType": b.BlockType,
	}
	return json.Marshal(out)
}

func (s *Server) ListBlocks() []BlockLedger {
	var blocks []BlockLedger
	s.Lock()
	for _, b := range s.blocks {
		blocks = append(blocks, *b)
	}
	s.Unlock()
	return blocks
}

func (s *Server) BlockIndex(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(s.ListBlocks()); err != nil {
		panic(err)
	}
}

// CreateBlockHandler responds to a POST request to instantiate a new block and add it to the Server.
// TODO currently all blocks start off life in the root group, which may be a bit limiting.
func (s *Server) BlockCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(body)
	}
	var m BlockLedger
	err = json.Unmarshal(body, &m)
	if err != nil {
		log.Panic(err)
	}
	blockSpec, ok := s.library[m.BlockType]
	if !ok {
		log.Fatal("unknown block type")
	}
	b := core.NewBlock(blockSpec)
	m.ID = s.GetNextID()
	m.Block = b
	// we need to introduce the block to our running supervisor
	m.Token = s.supervisor.Add(b)
	// and we need to assign it to a group
	s.groups[0].AddNode(&m)
	s.blocks[m.ID] = &m
	//broadcast for state update
	out, _ := json.Marshal(m)
	s.broadcast <- out
	// write HTTP response
	w.WriteHeader(200)
	w.Write([]byte("OK"))
}

func (s *Server) BlockDelete(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	ids, ok := vars["id"]
	if !ok {
		w.WriteHeader(http.StatusBadRequest)
		writeJSON(w, Error{"no ID supplied"})
		return
	}

	id, err := strconv.Atoi(ids)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		writeJSON(w, Error{err.Error()})
		return
	}

	s.Lock()
	defer s.Unlock()

	b, ok := s.blocks[id]
	if !ok {
		w.WriteHeader(http.StatusBadRequest)
		writeJSON(w, Error{"connection not found"})
		return
	}

	deleteSet := make(map[int]struct{})

	// build a set of connections that we may need to delete
	// we need to panic here because if any error is thrown we are in huge trouble
	// any panic indicates that our server connection ledger is no longer true
	for _, c := range s.connections {
		if c.Target.Id == id {
			route, err := b.Block.GetRoute(core.RouteID(c.Target.Route))
			if err != nil {
				panic(err)
			}
			err = s.blocks[c.Source.Id].Block.Disconnect(core.RouteID(c.Source.Route), route.C)
			if err != nil {
				panic(err)
			}
			deleteSet[c.Id] = struct{}{}
		}
		if c.Source.Id == id {
			route, err := s.blocks[c.Target.Id].Block.GetRoute(core.RouteID(c.Target.Route))
			if err != nil {
				panic(err)
			}
			err = b.Block.Disconnect(core.RouteID(c.Source.Route), route.C)
			if err != nil {
				panic(err)
			}
			deleteSet[c.Id] = struct{}{}
		}
	}

	// delete the connections that involve this block
	for k, _ := range deleteSet {
		s.websocketBroadcast(Update{Action: DELETE, Type: CONNECTION, Data: s.connections[k]})
		delete(s.connections, k)
	}

	// stop and delete the block
	s.supervisor.Remove(b.Token)
	s.websocketBroadcast(Update{Action: DELETE, Type: BLOCK, Data: s.blocks[id]})
	delete(s.blocks, id)

	w.WriteHeader(http.StatusNoContent)

}
func (s *Server) BlockModify(w http.ResponseWriter, r *http.Request) {
}
<|endoftext|>


<commit_before>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"errors"
	"fmt"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

// command represents a drawing command.
//
// A command for drawing that is created when Image functions are called like DrawImage,
// or Fill.
// A command is not immediately executed after created. Instaed, it is queued after created,
// and executed only when necessary.
type command interface {
	Exec(indexOffsetInBytes int) error
}

// commandQueue is a command queue for drawing commands.
type commandQueue struct {
	// commands is a queue of drawing commands.
	commands []command

	// vertices represents a vertices data in OpenGL's array buffer.
	vertices []float32

	// verticesNum represents the current length of vertices.
	// verticesNum must <= len(vertices).
	// vertices is never shrunk since re-extending a vertices buffer is heavy.
	verticesNum int

	m sync.Mutex
}

// theCommandQueue is the command queue for the current process.
var theCommandQueue = &commandQueue{}

// appendVertices appends vertices to the queue.
func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	// for-loop might be faster than copy:
	// On GopherJS, copy might cause subarray calls.
	for i := 0; i < len(vertices); i++ {
		q.vertices[q.verticesNum+i] = vertices[i]
	}
	q.verticesNum += len(vertices)
}

// EnqueueDrawImageCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) {
	// Avoid defer for performance
	q.m.Lock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.canMerge(dst, src, clr, mode, filter) {
				c.verticesNum += len(vertices)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       clr,
		mode:        mode,
		filter:      filter,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

// Enqueue enqueues a drawing command other than a draw-image command.
//
// For a draw-image command, use EnqueueDrawImageCommand.
func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	q.commands = append(q.commands, command)
	q.m.Unlock()
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	var gs [][]command
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

// Flush flushes the command queue.
func (q *commandQueue) Flush() error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	opengl.GetContext().ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			opengl.GetContext().BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			opengl.GetContext().Flush()
		}
		lastN = n
	}
	q.commands = nil
	q.verticesNum = 0
	return nil
}

// FlushCommands flushes the command queue.
func FlushCommands() error {
	return theCommandQueue.Flush()
}

// drawImageCommand represents a drawing command to draw an image on another image.
type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       *affine.ColorM
	mode        opengl.CompositeMode
	filter      Filter
}

// QuadVertexSizeInBytes returns the size in bytes of vertices for a quadrangle.
func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

// Exec executes the drawImageCommand.
func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	f.setAsViewport()

	opengl.GetContext().BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	proj := f.projectionMatrix()
	theOpenGLState.useProgram(proj, c.src.texture.native, c.dst, c.src, c.color, c.filter)
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)

	// glFlush() might be necessary at least on MacBook Pro (a smilar problem at #419),
	// but basically this pass the tests (esp. TestImageTooManyFill).
	// As glFlush() causes performance problems, this should be avoided as much as possible.
	// Let's wait and see, and file a new issue when this problem is newly found.
	return nil
}

// split splits the drawImageCommand c into two drawImageCommands.
//
// split is called when the number of vertices reaches of the maximum and
// a command is needed to be executed as another draw call.
func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

// canMerge returns a boolean value indicating whether the other drawImageCommand can be merged
// with the drawImageCommand c.
func (c *drawImageCommand) canMerge(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	if c.filter != filter {
		return false
	}
	return true
}

// quadsNum returns the number of quadrangles.
func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

// replacePixelsCommand represents a command to replace pixels of an image.
type replacePixelsCommand struct {
	dst    *Image
	pixels []byte
}

// Exec executes the replacePixelsCommand.
func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	f.setAsViewport()

	// This is necessary on Android.
	//
	// glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
	// This also happens when a fillCommand precedes a replacePixelsCommand.
	// TODO: Can we have a better way like optimizing commands?
	opengl.GetContext().Flush()
	opengl.GetContext().BindTexture(c.dst.texture.native)
	opengl.GetContext().TexSubImage2D(c.pixels, emath.NextPowerOf2Int(c.dst.width), emath.NextPowerOf2Int(c.dst.height))
	return nil
}

// disposeCommand represents a command to dispose an image.
type disposeCommand struct {
	target *Image
}

// Exec executes the disposeCommand.
func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
	if c.target.framebuffer != nil &&
		c.target.framebuffer.native != opengl.GetContext().ScreenFramebuffer() {
		opengl.GetContext().DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		opengl.GetContext().DeleteTexture(c.target.texture.native)
	}
	return nil
}

// newImageCommand represents a command to create an empty image with given width and height.
type newImageCommand struct {
	result *Image
	width  int
	height int
}

// Exec executes a newImageCommand.
func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
	w := emath.NextPowerOf2Int(c.width)
	h := emath.NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := opengl.GetContext().NewTexture(w, h)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

// newScreenFramebufferImageCommand is a command to create a special image for the screen.
type newScreenFramebufferImageCommand struct {
	result *Image
	width  int
	height int
}

// Exec executes a newScreenFramebufferImageCommand.
func (c *newScreenFramebufferImageCommand) Exec(indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	// The (default) framebuffer size can't be converted to a power of 2.
	// On browsers, c.width and c.height are used as viewport size and
	// Edge can't treat a bigger viewport than the drawing area (#71).
	c.result.framebuffer = newScreenFramebuffer(c.width, c.height)
	return nil
}
<commit_msg>graphics: Fix comments<commit_after>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"errors"
	"fmt"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

// command represents a drawing command.
//
// A command for drawing that is created when Image functions are called like DrawImage,
// or Fill.
// A command is not immediately executed after created. Instaed, it is queued after created,
// and executed only when necessary.
type command interface {
	Exec(indexOffsetInBytes int) error
}

// commandQueue is a command queue for drawing commands.
type commandQueue struct {
	// commands is a queue of drawing commands.
	commands []command

	// vertices represents a vertices data in OpenGL's array buffer.
	vertices []float32

	// verticesNum represents the current length of vertices.
	// verticesNum must <= len(vertices).
	// vertices is never shrunk since re-extending a vertices buffer is heavy.
	verticesNum int

	m sync.Mutex
}

// theCommandQueue is the command queue for the current process.
var theCommandQueue = &commandQueue{}

// appendVertices appends vertices to the queue.
func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	// for-loop might be faster than copy:
	// On GopherJS, copy might cause subarray calls.
	for i := 0; i < len(vertices); i++ {
		q.vertices[q.verticesNum+i] = vertices[i]
	}
	q.verticesNum += len(vertices)
}

// EnqueueDrawImageCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) {
	// Avoid defer for performance
	q.m.Lock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.canMerge(dst, src, clr, mode, filter) {
				c.verticesNum += len(vertices)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       clr,
		mode:        mode,
		filter:      filter,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

// Enqueue enqueues a drawing command other than a draw-image command.
//
// For a draw-image command, use EnqueueDrawImageCommand.
func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	q.commands = append(q.commands, command)
	q.m.Unlock()
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	var gs [][]command
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

// Flush flushes the command queue.
func (q *commandQueue) Flush() error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	opengl.GetContext().ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			opengl.GetContext().BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			opengl.GetContext().Flush()
		}
		lastN = n
	}
	q.commands = nil
	q.verticesNum = 0
	return nil
}

// FlushCommands flushes the command queue.
func FlushCommands() error {
	return theCommandQueue.Flush()
}

// drawImageCommand represents a drawing command to draw an image on another image.
type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       *affine.ColorM
	mode        opengl.CompositeMode
	filter      Filter
}

// QuadVertexSizeInBytes returns the size in bytes of vertices for a quadrangle.
func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

// Exec executes the drawImageCommand.
func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	f.setAsViewport()

	opengl.GetContext().BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	proj := f.projectionMatrix()
	theOpenGLState.useProgram(proj, c.src.texture.native, c.dst, c.src, c.color, c.filter)
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)

	// glFlush() might be necessary at least on MacBook Pro (a smilar problem at #419),
	// but basically this pass the tests (esp. TestImageTooManyFill).
	// As glFlush() causes performance problems, this should be avoided as much as possible.
	// Let's wait and see, and file a new issue when this problem is newly found.
	return nil
}

// split splits the drawImageCommand c into two drawImageCommands.
//
// split is called when the number of vertices reaches of the maximum and
// a command is needed to be executed as another draw call.
func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

// canMerge returns a boolean value indicating whether the other drawImageCommand can be merged
// with the drawImageCommand c.
func (c *drawImageCommand) canMerge(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	if c.filter != filter {
		return false
	}
	return true
}

// quadsNum returns the number of quadrangles.
func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

// replacePixelsCommand represents a command to replace pixels of an image.
type replacePixelsCommand struct {
	dst    *Image
	pixels []byte
}

// Exec executes the replacePixelsCommand.
func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	f.setAsViewport()

	// glFlush is necessary on Android.
	// glTexSubImage2D didn't work without this hack at least on Nexus 5x and NuAns NEO [Reloaded] (#211).
	opengl.GetContext().Flush()
	opengl.GetContext().BindTexture(c.dst.texture.native)
	opengl.GetContext().TexSubImage2D(c.pixels, emath.NextPowerOf2Int(c.dst.width), emath.NextPowerOf2Int(c.dst.height))
	return nil
}

// disposeCommand represents a command to dispose an image.
type disposeCommand struct {
	target *Image
}

// Exec executes the disposeCommand.
func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
	if c.target.framebuffer != nil &&
		c.target.framebuffer.native != opengl.GetContext().ScreenFramebuffer() {
		opengl.GetContext().DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		opengl.GetContext().DeleteTexture(c.target.texture.native)
	}
	return nil
}

// newImageCommand represents a command to create an empty image with given width and height.
type newImageCommand struct {
	result *Image
	width  int
	height int
}

// Exec executes a newImageCommand.
func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
	w := emath.NextPowerOf2Int(c.width)
	h := emath.NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := opengl.GetContext().NewTexture(w, h)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

// newScreenFramebufferImageCommand is a command to create a special image for the screen.
type newScreenFramebufferImageCommand struct {
	result *Image
	width  int
	height int
}

// Exec executes a newScreenFramebufferImageCommand.
func (c *newScreenFramebufferImageCommand) Exec(indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	// The (default) framebuffer size can't be converted to a power of 2.
	// On browsers, c.width and c.height are used as viewport size and
	// Edge can't treat a bigger viewport than the drawing area (#71).
	c.result.framebuffer = newScreenFramebuffer(c.width, c.height)
	return nil
}
<|endoftext|>


<commit_before>package trafo

import (
	. "github.com/101loops/bdd"
	"github.com/101loops/structor"
)

var _ = Describe("Codec", func() {

	It("should return simple codec", func() {
		entity := &SimpleModel{}
		err := CodecSet.Add(entity)
		Check(err, IsNil)

		var codec *structor.Codec
		codec, err = getCodec(entity)
		Check(err, IsNil)
		Check(codec, NotNil)
		Check(codec.Complete(), IsTrue)

		fieldNames := codec.FieldNames()
		Check(fieldNames, HasLen, 7)
		Check(fieldNames, Equals, []string{"NumID", "CreatedTime", "UpdatedTime", "Num", "Data", "Text", "Time"})

		fields := codec.Fields()
		Check(fields, HasLen, 7)
	})

	It("should return complex codec", func() {
		entity := &ComplexModel{}

		err := CodecSet.Add(entity)
		Check(err, IsNil)

		var codec *structor.Codec
		codec, err = getCodec(entity)
		Check(err, IsNil)
		Check(codec, NotNil)
	})

	// ==== ERRORS

	It("should return error for invalid codec", func() {
		codec, err := getCodec("invalid-type")

		Check(codec, IsNil)
		Check(err, ErrorContains, `value is not a struct, struct pointer or reflect.Type - but "string"`)
	})

	It("should reject field beginning with invalid character", func() {
		type InvalidModel struct {
			InvalidName string `datastore:"$invalid-name"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "InvalidName" begins with invalid character '$'`)

		// as sub-field:
		type Wrapper struct {
			Inner InvalidModel
		}
		err = CodecSet.Add(Wrapper{})
		Check(err, ErrorContains, `field "InvalidName" begins with invalid character '$'`)
	})

	It("should reject fields containing invalid character", func() {
		type InvalidModel struct {
			InvalidName string `datastore:"invalid@name"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "InvalidName" contains invalid character '@'`)

		// as sub-field:
		type Wrapper struct {
			Inner InvalidModel
		}
		err = CodecSet.Add(Wrapper{})
		Check(err, ErrorContains, `field "InvalidName" contains invalid character '@'`)
	})

	It("should reject duplicate field names", func() {
		type InvalidModel struct {
			ID1 string `datastore:"id"`
			ID2 string `datastore:"id"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `duplicate field name "id"`)
	})

	It("should reject invalid field type", func() {
		type InvalidModel struct {
			Ptr *string
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "Ptr" has invalid type 'pointer'`)
	})

	It("should reject invalid map key type", func() {
		type InvalidModel struct {
			Map map[int]string
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "Map" has invalid map key type 'int' - only 'string' is allowed`)
	})

	It("should reject recursive struct", func() {
		type InvalidModel struct {
			Recursive []InvalidModel
		}

		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `recursive struct at field "Recursive"`)
	})

	It("should reject slice of slices", func() {
		type Model struct {
			InnerSlice []string
		}
		type InvalidModel struct {
			OuterSlide []Model
		}

		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "OuterSlide" leads to a slice of slices`)
	})
})
<commit_msg>add test for duplicate, inlined field<commit_after>package trafo

import (
	. "github.com/101loops/bdd"
	"github.com/101loops/structor"
)

var _ = Describe("Codec", func() {

	It("should return simple codec", func() {
		entity := &SimpleModel{}
		err := CodecSet.Add(entity)
		Check(err, IsNil)

		var codec *structor.Codec
		codec, err = getCodec(entity)
		Check(err, IsNil)
		Check(codec, NotNil)
		Check(codec.Complete(), IsTrue)

		fieldNames := codec.FieldNames()
		Check(fieldNames, HasLen, 7)
		Check(fieldNames, Equals, []string{"NumID", "CreatedTime", "UpdatedTime", "Num", "Data", "Text", "Time"})

		fields := codec.Fields()
		Check(fields, HasLen, 7)
	})

	It("should return complex codec", func() {
		entity := &ComplexModel{}

		err := CodecSet.Add(entity)
		Check(err, IsNil)

		var codec *structor.Codec
		codec, err = getCodec(entity)
		Check(err, IsNil)
		Check(codec, NotNil)
	})

	// ==== ERRORS

	It("should return error for invalid codec", func() {
		codec, err := getCodec("invalid-type")

		Check(codec, IsNil)
		Check(err, ErrorContains, `value is not a struct, struct pointer or reflect.Type - but "string"`)
	})

	It("should reject field beginning with invalid character", func() {
		type InvalidModel struct {
			InvalidName string `datastore:"$invalid-name"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "InvalidName" begins with invalid character '$'`)

		// as sub-field:
		type Wrapper struct {
			Inner InvalidModel
		}
		err = CodecSet.Add(Wrapper{})
		Check(err, ErrorContains, `field "InvalidName" begins with invalid character '$'`)
	})

	It("should reject fields containing invalid character", func() {
		type InvalidModel struct {
			InvalidName string `datastore:"invalid@name"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "InvalidName" contains invalid character '@'`)

		// as sub-field:
		type Wrapper struct {
			Inner InvalidModel
		}
		err = CodecSet.Add(Wrapper{})
		Check(err, ErrorContains, `field "InvalidName" contains invalid character '@'`)
	})

	It("should reject duplicate field names", func() {
		type InvalidModel struct {
			ID1 string `datastore:"id"`
			ID2 string `datastore:"id"`
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `duplicate field name "id"`)

		// from sub-field:
		type InnerModel struct {
			ID string `datastore:"id,inline"`
		}
		type MyModel struct {
			ID         string `datastore:"id"`
			InnerModel `datastore:"inner"`
		}
		err = CodecSet.Add(MyModel{})
		Check(err, ErrorContains, `duplicate field name "id"`)
	})

	It("should reject invalid field type", func() {
		type InvalidModel struct {
			Ptr *string
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "Ptr" has invalid type 'pointer'`)
	})

	It("should reject invalid map key type", func() {
		type InvalidModel struct {
			Map map[int]string
		}
		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "Map" has invalid map key type 'int' - only 'string' is allowed`)
	})

	It("should reject recursive struct", func() {
		type InvalidModel struct {
			Recursive []InvalidModel
		}

		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `recursive struct at field "Recursive"`)
	})

	It("should reject slice of slices", func() {
		type Model struct {
			InnerSlice []string
		}
		type InvalidModel struct {
			OuterSlide []Model
		}

		err := CodecSet.Add(InvalidModel{})
		Check(err, ErrorContains, `field "OuterSlide" leads to a slice of slices`)
	})
})
<|endoftext|>


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

package cmd

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/markbates/inflect"
	"github.com/spf13/cobra"
)

var force bool
var verbose bool
var skipPop bool
var skipWebpack bool
var dbType = "postgres"

var newCmd = &cobra.Command{
	Use:   "new [name]",
	Short: "Creates a new Buffalo application",
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 {
			return errors.New("You must enter a name for your new application.")
		}
		name := args[0]
		rootPath, err := rootPath(name)
		if err != nil {
			return err
		}

		s, _ := os.Stat(rootPath)
		if s != nil {
			if force {
				os.RemoveAll(rootPath)
			} else {
				return fmt.Errorf("%s already exists! Either delete it or use the -f flag to force.\n", name)
			}
		}

		return genNewFiles(name, rootPath)
	},
}

func rootPath(name string) (string, error) {
	pwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	rootPath := filepath.Join(pwd, name)
	return rootPath, nil
}

func packagePath(rootPath string) string {
	return strings.Replace(rootPath, filepath.Join(os.Getenv("GOPATH"), "src")+"/", "", 1)
}

func genNewFiles(name, rootPath string) error {
	packagePath := packagePath(rootPath)

	data := map[string]interface{}{
		"name":        name,
		"titleName":   inflect.Titleize(name),
		"packagePath": packagePath,
		"actionsPath": filepath.Join(packagePath, "actions"),
		"modelsPath":  filepath.Join(packagePath, "models"),
		"withPop":     !skipPop,
		"withWebpack": !skipWebpack,
		"dbType":      dbType,
		"version":     Version,
	}

	g := newAppGenerator(data)
	return g.Run(rootPath, data)
}

func init() {
	RootCmd.AddCommand(newCmd)
	newCmd.Flags().BoolVarP(&force, "force", "f", false, "delete and remake if the app already exists")
	newCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbosely print out the go get/install commands")
	newCmd.Flags().BoolVar(&skipPop, "skip-pop", false, "skips adding pop/soda to your app")
	newCmd.Flags().BoolVar(&skipWebpack, "skip-webpack", false, "skips adding Webpack to your app")
	newCmd.Flags().StringVar(&dbType, "db-type", "postgres", "specify the type of database you want to use [postgres, mysql, sqlite3]")
}
<commit_msg>Update new.go<commit_after>// Copyright © 2016 Mark Bates <mark@markbates.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/markbates/inflect"
	"github.com/spf13/cobra"
)

var force bool
var verbose bool
var skipPop bool
var skipWebpack bool
var dbType = "postgres"

var newCmd = &cobra.Command{
	Use:   "new [name]",
	Short: "Creates a new Buffalo application",
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 {
			return errors.New("You must enter a name for your new application.")
		}
		name := args[0]
		rootPath, err := rootPath(name)
		if err != nil {
			return err
		}

		s, _ := os.Stat(rootPath)
		if s != nil {
			if force {
				os.RemoveAll(rootPath)
			} else {
				return fmt.Errorf("%s already exists! Either delete it or use the -f flag to force.\n", name)
			}
		}

		return genNewFiles(name, rootPath)
	},
}

func rootPath(name string) (string, error) {
	pwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	rootPath := filepath.Join(pwd, name)
	return rootPath, nil
}

func packagePath(rootPath string) string {
	gosrcpath := strings.Replace(filepath.Join(os.Getenv("GOPATH"), "src"), "\\", "/", -1)
	rootPath = strings.Replace(rootPath, "\\", "/", -1)
	return strings.Replace(rootPath, gosrcpath+"/", "", 1)
}

func genNewFiles(name, rootPath string) error {
	packagePath := packagePath(rootPath)

	data := map[string]interface{}{
		"name":        name,
		"titleName":   inflect.Titleize(name),
		"packagePath": packagePath,
		"actionsPath": packagePath + "/actions",
		"modelsPath":  packagePath + "/models",
		"withPop":     !skipPop,
		"withWebpack": !skipWebpack,
		"dbType":      dbType,
		"version":     Version,
	}

	g := newAppGenerator(data)
	return g.Run(rootPath, data)
}

func init() {
	RootCmd.AddCommand(newCmd)
	newCmd.Flags().BoolVarP(&force, "force", "f", false, "delete and remake if the app already exists")
	newCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbosely print out the go get/install commands")
	newCmd.Flags().BoolVar(&skipPop, "skip-pop", false, "skips adding pop/soda to your app")
	newCmd.Flags().BoolVar(&skipWebpack, "skip-webpack", false, "skips adding Webpack to your app")
	newCmd.Flags().StringVar(&dbType, "db-type", "postgres", "specify the type of database you want to use [postgres, mysql, sqlite3]")
}
<|endoftext|>