<commit_before>package validator

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"
	"unicode"

	"github.com/ghodss/yaml"
	"github.com/golang/protobuf/jsonpb"
	"github.com/googleapis/gapic-config-validator/internal/config"
	"github.com/jhump/protoreflect/desc"
	"google.golang.org/genproto/googleapis/api/annotations"
	"google.golang.org/genproto/googleapis/longrunning"
)

var (
	wellKnownPatterns = map[string]bool{
		"projects/{project}":                      true,
		"organizations/{organization}":            true,
		"folders/{folder}":                        true,
		"projects/{project}/locations/{location}": true,
		"billingAccounts/{billing_account_id}":    true,
	}

	wellKnownNames = map[string]bool{
		"project":      true,
		"organization": true,
		"folder":       true,
		"location":     true,
	}

	isIamMethod = map[string]bool{
		"GetIamPolicy":       true,
		"SetIamPolicy":       true,
		"TestIamPermissions": true,
	}
)

func (v *validator) compare() {
	// compare interfaces
	v.compareServices()

	// compare resource references
	v.compareResourceRefs()
}

func (v *validator) compareServices() {
	for _, inter := range v.gapic.GetInterfaces() {
		serv := v.resolveServiceByName(inter.GetName())
		if serv == nil {
			v.addError("Interface %q does not exist", inter.GetName())
			continue
		}

		// compare resources
		v.compareResources(inter)

		// compare methods
		for _, method := range inter.GetMethods() {
			methodDesc := serv.FindMethodByName(method.GetName())
			if methodDesc == nil {
				v.addError("Method %q does not exist", inter.GetName()+"."+method.GetName())
				continue
			}

			v.compareMethod(methodDesc, method)
		}
	}
}

func (v *validator) compareMethod(methodDesc *desc.MethodDescriptor, method *config.MethodConfigProto) {
	fqn := methodDesc.GetFullyQualifiedName()
	mOpts := methodDesc.GetMethodOptions()

	// ignore IAM methods
	if isIamMethod[method.GetName()] {
		return
	}

	// compare method_signatures & flattening groups
	if flattenings := method.GetFlattening(); flattenings != nil {
		eSigs, err := ext(mOpts, annotations.E_MethodSignature)
		if err != nil {
			v.addError("Method %q missing method_signature(s) for flattening(s)", fqn)
			goto LRO
		}
		sigs := eSigs.([]string)

		for _, flat := range flattenings.GetGroups() {
			joined := strings.Join(flat.GetParameters(), ",")
			if !containStr(sigs, joined) {
				v.addError("Method %q missing method_signature for flattening %q", fqn, joined)
			}
		}
	}

LRO:
	// compare operation_info & longrunning config
	if lro := method.GetLongRunning(); lro != nil {
		eLRO, err := ext(mOpts, longrunning.E_OperationInfo)
		if err != nil {
			v.addError("Method %q missing longrunning.operation_info", fqn)
			goto Behavior
		}
		info := eLRO.(*longrunning.OperationInfo)

		// trim to local message name
		protoRes := info.GetResponseType()
		if strings.Contains(protoRes, ".") {
			protoRes = protoRes[strings.LastIndex(protoRes, ".")+1:]
		}
		gapicRes := lro.GetReturnType()
		if strings.Contains(gapicRes, ".") {
			gapicRes = gapicRes[strings.LastIndex(gapicRes, ".")+1:]
		}

		if protoRes != gapicRes {
			v.addError("Method %q operation_info.response_type %q does not match %q",
				fqn,
				protoRes,
				gapicRes)
		}

		// trim to local message name
		protoMeta := info.GetMetadataType()
		if strings.Contains(protoMeta, ".") {
			protoMeta = protoMeta[strings.LastIndex(protoMeta, ".")+1:]
		}
		gapicMeta := lro.GetMetadataType()
		if strings.Contains(gapicMeta, ".") {
			gapicMeta = gapicMeta[strings.LastIndex(gapicMeta, ".")+1:]
		}

		if protoMeta != gapicMeta {
			v.addError("Method %q operation_info.metadata_type %q does not match %q",
				fqn,
				protoMeta,
				gapicMeta)
		}
	}

Behavior:
	// compare input message field_behaviors & required_fields
	if reqs := method.GetRequiredFields(); len(reqs) > 0 {
		input := methodDesc.GetInputType()

		for _, name := range reqs {
			field := input.FindFieldByName(name)
			if field == nil {
				v.addError("Field %q in method %q required_fields does not exist in %q",
					name,
					fqn,
					input.GetFullyQualifiedName())
				continue
			}

			eBehv, err := ext(field.GetFieldOptions(), annotations.E_FieldBehavior)
			if err != nil {
				v.addError("Field %q is missing field_behavior = REQUIRED per required_fields config", field.GetFullyQualifiedName())
				continue
			}
			behavior := eBehv.([]annotations.FieldBehavior)

			if !containBehavior(behavior, annotations.FieldBehavior_REQUIRED) {
				v.addError("Field %q is not annotated as REQUIRED per required_fields config", field.GetFullyQualifiedName())
			}
		}
	}
}

func (v *validator) compareResources(inter *config.InterfaceConfigProto) {
	for _, res := range inter.GetCollections() {
		if wellKnownPatterns[res.GetNamePattern()] || wellKnownNames[res.GetEntityName()] {
			continue
		}

		for _, f := range v.files {
			for _, m := range f.GetMessageTypes() {
				eRes, err := ext(m.GetMessageOptions(), annotations.E_Resource)
				if err != nil {
					continue
				}
				resDesc := eRes.(*annotations.ResourceDescriptor)

				typ := resDesc.GetType()
				typ = typ[strings.Index(typ, "/")+1:]

				entName := snakeToCamel(res.GetEntityName())

				// the pattern is defined in a resource named differently than the
				// name_pattern value, which is OK.
				if containStr(resDesc.GetPattern(), res.GetNamePattern()) {
					goto Next
				}

				if typ == entName {
					if !containStr(resDesc.GetPattern(), res.GetNamePattern()) {
						v.addError("resource definition for %q in %q does not have pattern %q",
							resDesc.GetType(),
							m.GetFullyQualifiedName(),
							res.GetNamePattern())
					}

					goto Next
				}
			}
		}

		v.addError("No corresponding resource definition for %q: %q", res.GetEntityName(), res.GetNamePattern())

	Next:
	}
}

func (v *validator) compareResourceRefs() {
	for _, ref := range v.gapic.GetResourceNameGeneration() {
		// skip IAM messages
		if strings.HasPrefix(ref.GetMessageName(), "google.iam.v1") {
			continue
		}

		msgDesc := v.resolveMsgByLocalName(ref.GetMessageName())
		if msgDesc == nil {
			v.addError("Message %q in resource_name_generation item does not exist", ref.GetMessageName())
			continue
		}

		for fname, ref := range ref.GetFieldEntityMap() {
			// skip nested fields, presumably they are
			// being validated in the origial msg
			if strings.Contains(fname, ".") {
				continue
			}

			field := msgDesc.FindFieldByName(fname)
			if field == nil {
				v.addError("Field %q does not exist on message %q per resource_name_generation item", fname, msgDesc.GetFullyQualifiedName())
				continue
			}

			var typ, child string
			if eResRef, err := ext(field.GetFieldOptions(), annotations.E_ResourceReference); err == nil {
				resRef := eResRef.(*annotations.ResourceReference)

				typ = resRef.GetType()
				child = resRef.GetChildType()
			} else if eRes, err := ext(msgDesc.GetMessageOptions(), annotations.E_Resource); err == nil {
				res := eRes.(*annotations.ResourceDescriptor)
				typ = res.GetType()
			} else {
				v.addError("Field %q missing resource_reference to %q", field.GetFullyQualifiedName(), ref)
				continue
			}

			// use child_type instead
			if typ == "" {
				childMsg := v.resolveResRefMessage(child, msgDesc.GetFile())
				if childMsg == nil {
					v.addError("child_type %q on %q is not a defined resource", child, field.GetFullyQualifiedName())
					continue
				}

				refItem := v.resolveRefFromCollections(ref)
				if refItem == nil {
					v.addError("entity_name %q is not a defined in any available collection", ref)
				}

				if eResDef, err := ext(childMsg.GetMessageOptions(), annotations.E_Resource); err == nil {
					var found bool
					resDef := eResDef.(*annotations.ResourceDescriptor)
					for _, pattern := range resDef.GetPattern() {
						if strings.HasPrefix(pattern, refItem.GetNamePattern()) {
							found = true
							break
						}
					}

					if !found {
						v.addError("Field %q child_type %q isn't a proper child of %q in GAPIC config", field.GetFullyQualifiedName(), child, ref)
					}
				}

				continue
			}

			// compare using upper camel case names
			t := typ[strings.Index(typ, "/")+1:]
			if !wellKnownTypes[typ] && t != snakeToCamel(ref) {
				v.addError("Field %q resource_type_kind %q doesn't match %q in config", field.GetFullyQualifiedName(), typ, ref)
			}
		}
	}
}

func containBehavior(arr []annotations.FieldBehavior, behv annotations.FieldBehavior) bool {
	for _, b := range arr {
		if b == behv {
			return true
		}
	}

	return false
}

func containStr(arr []string, str string) bool {
	for _, s := range arr {
		if s == str {
			return true
		}
	}

	return false
}

func (v *validator) resolveServiceByName(name string) *desc.ServiceDescriptor {
	for _, f := range v.files {
		if s := f.FindService(name); s != nil {
			return s
		}
	}

	return nil
}

func (v *validator) resolveMsgByLocalName(name string) *desc.MessageDescriptor {
	for _, f := range v.files {
		fqn := f.GetPackage() + "." + name

		if m := f.FindMessage(fqn); m != nil {
			return m
		}
	}

	return nil
}

func (v *validator) resolveRefFromCollections(name string) *config.CollectionConfigProto {
	for _, item := range v.gapic.GetCollections() {
		if item.GetEntityName() == name {
			return item
		}
	}

	for _, inter := range v.gapic.GetInterfaces() {
		for _, item := range inter.GetCollections() {
			if item.GetEntityName() == name {
				return item
			}
		}
	}

	return nil
}

func (v *validator) parseParameters(p string) error {
	for _, s := range strings.Split(p, ",") {
		if e := strings.IndexByte(s, '='); e > 0 {
			switch s[:e] {
			case "gapic-yaml":

				f, err := ioutil.ReadFile(s[e+1:])
				if err != nil {
					return fmt.Errorf("error reading gapic config: %v", err)
				}

				// throw away the first line containing
				// "type: com.google.api.codegen.ConfigProto" because
				// that's not in the proto, causing an unmarshal error
				data := bytes.NewBuffer(f)
				data.ReadString('\n')

				j, err := yaml.YAMLToJSON(data.Bytes())
				if err != nil {
					return fmt.Errorf("error decoding gapic config: %v", err)
				}

				v.gapic = &config.ConfigProto{}
				err = jsonpb.Unmarshal(bytes.NewBuffer(j), v.gapic)
				if err != nil {
					return fmt.Errorf("error decoding gapic config: %v", err)
				}
			}
		}
	}

	return nil
}

// converts snake_case and SNAKE_CASE to CamelCase.
//
// copied from github.com/googleapis/gapic-generator-go
func snakeToCamel(s string) string {
	var sb strings.Builder
	up := true
	for _, r := range s {
		if r == '_' {
			up = true
		} else if up {
			sb.WriteRune(unicode.ToUpper(r))
			up = false
		} else {
			sb.WriteRune(unicode.ToLower(r))
		}
	}
	return sb.String()
}
<commit_msg>comparison: check for resource_definitions (#56)<commit_after>package validator

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"
	"unicode"

	"github.com/ghodss/yaml"
	"github.com/golang/protobuf/jsonpb"
	"github.com/googleapis/gapic-config-validator/internal/config"
	"github.com/jhump/protoreflect/desc"
	"google.golang.org/genproto/googleapis/api/annotations"
	"google.golang.org/genproto/googleapis/longrunning"
)

var (
	wellKnownPatterns = map[string]bool{
		"projects/{project}":                      true,
		"organizations/{organization}":            true,
		"folders/{folder}":                        true,
		"projects/{project}/locations/{location}": true,
		"billingAccounts/{billing_account_id}":    true,
	}

	wellKnownNames = map[string]bool{
		"project":      true,
		"organization": true,
		"folder":       true,
		"location":     true,
	}

	isIamMethod = map[string]bool{
		"GetIamPolicy":       true,
		"SetIamPolicy":       true,
		"TestIamPermissions": true,
	}
)

func (v *validator) compare() {
	// compare interfaces
	v.compareServices()

	// compare resource references
	v.compareResourceRefs()
}

func (v *validator) compareServices() {
	for _, inter := range v.gapic.GetInterfaces() {
		serv := v.resolveServiceByName(inter.GetName())
		if serv == nil {
			v.addError("Interface %q does not exist", inter.GetName())
			continue
		}

		// compare resources
		v.compareResources(inter)

		// compare methods
		for _, method := range inter.GetMethods() {
			methodDesc := serv.FindMethodByName(method.GetName())
			if methodDesc == nil {
				v.addError("Method %q does not exist", inter.GetName()+"."+method.GetName())
				continue
			}

			v.compareMethod(methodDesc, method)
		}
	}
}

func (v *validator) compareMethod(methodDesc *desc.MethodDescriptor, method *config.MethodConfigProto) {
	fqn := methodDesc.GetFullyQualifiedName()
	mOpts := methodDesc.GetMethodOptions()

	// ignore IAM methods
	if isIamMethod[method.GetName()] {
		return
	}

	// compare method_signatures & flattening groups
	if flattenings := method.GetFlattening(); flattenings != nil {
		eSigs, err := ext(mOpts, annotations.E_MethodSignature)
		if err != nil {
			v.addError("Method %q missing method_signature(s) for flattening(s)", fqn)
			goto LRO
		}
		sigs := eSigs.([]string)

		for _, flat := range flattenings.GetGroups() {
			joined := strings.Join(flat.GetParameters(), ",")
			if !containStr(sigs, joined) {
				v.addError("Method %q missing method_signature for flattening %q", fqn, joined)
			}
		}
	}

LRO:
	// compare operation_info & longrunning config
	if lro := method.GetLongRunning(); lro != nil {
		eLRO, err := ext(mOpts, longrunning.E_OperationInfo)
		if err != nil {
			v.addError("Method %q missing longrunning.operation_info", fqn)
			goto Behavior
		}
		info := eLRO.(*longrunning.OperationInfo)

		// trim to local message name
		protoRes := info.GetResponseType()
		if strings.Contains(protoRes, ".") {
			protoRes = protoRes[strings.LastIndex(protoRes, ".")+1:]
		}
		gapicRes := lro.GetReturnType()
		if strings.Contains(gapicRes, ".") {
			gapicRes = gapicRes[strings.LastIndex(gapicRes, ".")+1:]
		}

		if protoRes != gapicRes {
			v.addError("Method %q operation_info.response_type %q does not match %q",
				fqn,
				protoRes,
				gapicRes)
		}

		// trim to local message name
		protoMeta := info.GetMetadataType()
		if strings.Contains(protoMeta, ".") {
			protoMeta = protoMeta[strings.LastIndex(protoMeta, ".")+1:]
		}
		gapicMeta := lro.GetMetadataType()
		if strings.Contains(gapicMeta, ".") {
			gapicMeta = gapicMeta[strings.LastIndex(gapicMeta, ".")+1:]
		}

		if protoMeta != gapicMeta {
			v.addError("Method %q operation_info.metadata_type %q does not match %q",
				fqn,
				protoMeta,
				gapicMeta)
		}
	}

Behavior:
	// compare input message field_behaviors & required_fields
	if reqs := method.GetRequiredFields(); len(reqs) > 0 {
		input := methodDesc.GetInputType()

		for _, name := range reqs {
			field := input.FindFieldByName(name)
			if field == nil {
				v.addError("Field %q in method %q required_fields does not exist in %q",
					name,
					fqn,
					input.GetFullyQualifiedName())
				continue
			}

			eBehv, err := ext(field.GetFieldOptions(), annotations.E_FieldBehavior)
			if err != nil {
				v.addError("Field %q is missing field_behavior = REQUIRED per required_fields config", field.GetFullyQualifiedName())
				continue
			}
			behavior := eBehv.([]annotations.FieldBehavior)

			if !containBehavior(behavior, annotations.FieldBehavior_REQUIRED) {
				v.addError("Field %q is not annotated as REQUIRED per required_fields config", field.GetFullyQualifiedName())
			}
		}
	}
}

func (v *validator) compareResources(inter *config.InterfaceConfigProto) {
	for _, res := range inter.GetCollections() {
		ent := snakeToCamel(res.GetEntityName())
		pat := res.GetNamePattern()
		if wellKnownPatterns[pat] || wellKnownNames[ent] {
			continue
		}

		for _, f := range v.files {
			// check file for resource_definition annotations
			eResDef, err := ext(f.GetFileOptions(), annotations.E_ResourceDefinition)
			if err == nil {
				resDefs := eResDef.([]*annotations.ResourceDescriptor)
				for _, res := range resDefs {
					typ := res.GetType()
					typ = typ[strings.Index(typ, "/")+1:]

					if ent == typ {
						if !containStr(res.GetPattern(), pat) {
							v.addError("resource definition for %q in %q does not have pattern %q",
								res.GetType(),
								f.GetFullyQualifiedName(),
								pat)
						}
						goto NextCollectionItem
					}
				}
			}

			for _, m := range f.GetMessageTypes() {
				eRes, err := ext(m.GetMessageOptions(), annotations.E_Resource)
				if err != nil {
					continue
				}
				resDesc := eRes.(*annotations.ResourceDescriptor)

				typ := resDesc.GetType()
				typ = typ[strings.Index(typ, "/")+1:]

				// the pattern is defined in a resource named differently than the
				// name_pattern value, which is OK.
				if containStr(resDesc.GetPattern(), pat) {
					goto NextCollectionItem
				}

				if typ == ent {
					if !containStr(resDesc.GetPattern(), pat) {
						v.addError("resource definition for %q in %q does not have pattern %q",
							resDesc.GetType(),
							m.GetFullyQualifiedName(),
							pat)
					}

					goto NextCollectionItem
				}
			}
		}

		v.addError("No corresponding resource definition for %q: %q", res.GetEntityName(), res.GetNamePattern())

	NextCollectionItem:
	}
}

func (v *validator) compareResourceRefs() {
	for _, ref := range v.gapic.GetResourceNameGeneration() {
		// skip IAM messages
		if strings.HasPrefix(ref.GetMessageName(), "google.iam.v1") {
			continue
		}

		msgDesc := v.resolveMsgByLocalName(ref.GetMessageName())
		if msgDesc == nil {
			v.addError("Message %q in resource_name_generation item does not exist", ref.GetMessageName())
			continue
		}

		for fname, ref := range ref.GetFieldEntityMap() {
			// skip nested fields, presumably they are
			// being validated in the origial msg
			if strings.Contains(fname, ".") {
				continue
			}

			field := msgDesc.FindFieldByName(fname)
			if field == nil {
				v.addError("Field %q does not exist on message %q per resource_name_generation item", fname, msgDesc.GetFullyQualifiedName())
				continue
			}

			var typ, child string
			if eResRef, err := ext(field.GetFieldOptions(), annotations.E_ResourceReference); err == nil {
				resRef := eResRef.(*annotations.ResourceReference)

				typ = resRef.GetType()
				child = resRef.GetChildType()
			} else if eRes, err := ext(msgDesc.GetMessageOptions(), annotations.E_Resource); err == nil {
				res := eRes.(*annotations.ResourceDescriptor)
				typ = res.GetType()
			} else {
				v.addError("Field %q missing resource_reference to %q", field.GetFullyQualifiedName(), ref)
				continue
			}

			// use child_type instead
			if typ == "" {
				childMsg := v.resolveResRefMessage(child, msgDesc.GetFile())
				if childMsg == nil {
					v.addError("child_type %q on %q is not a defined resource", child, field.GetFullyQualifiedName())
					continue
				}

				refItem := v.resolveRefFromCollections(ref)
				if refItem == nil {
					v.addError("entity_name %q is not a defined in any available collection", ref)
				}

				if eResDef, err := ext(childMsg.GetMessageOptions(), annotations.E_Resource); err == nil {
					var found bool
					resDef := eResDef.(*annotations.ResourceDescriptor)
					for _, pattern := range resDef.GetPattern() {
						if strings.HasPrefix(pattern, refItem.GetNamePattern()) {
							found = true
							break
						}
					}

					if !found {
						v.addError("Field %q child_type %q isn't a proper child of %q in GAPIC config", field.GetFullyQualifiedName(), child, ref)
					}
				}

				continue
			}

			// compare using upper camel case names
			t := typ[strings.Index(typ, "/")+1:]
			if !wellKnownTypes[typ] && t != snakeToCamel(ref) {
				v.addError("Field %q resource_type_kind %q doesn't match %q in config", field.GetFullyQualifiedName(), typ, ref)
			}
		}
	}
}

func containBehavior(arr []annotations.FieldBehavior, behv annotations.FieldBehavior) bool {
	for _, b := range arr {
		if b == behv {
			return true
		}
	}

	return false
}

func containStr(arr []string, str string) bool {
	for _, s := range arr {
		if s == str {
			return true
		}
	}

	return false
}

func (v *validator) resolveServiceByName(name string) *desc.ServiceDescriptor {
	for _, f := range v.files {
		if s := f.FindService(name); s != nil {
			return s
		}
	}

	return nil
}

func (v *validator) resolveMsgByLocalName(name string) *desc.MessageDescriptor {
	for _, f := range v.files {
		fqn := f.GetPackage() + "." + name

		if m := f.FindMessage(fqn); m != nil {
			return m
		}
	}

	return nil
}

func (v *validator) resolveRefFromCollections(name string) *config.CollectionConfigProto {
	for _, item := range v.gapic.GetCollections() {
		if item.GetEntityName() == name {
			return item
		}
	}

	for _, inter := range v.gapic.GetInterfaces() {
		for _, item := range inter.GetCollections() {
			if item.GetEntityName() == name {
				return item
			}
		}
	}

	return nil
}

func (v *validator) parseParameters(p string) error {
	for _, s := range strings.Split(p, ",") {
		if e := strings.IndexByte(s, '='); e > 0 {
			switch s[:e] {
			case "gapic-yaml":

				f, err := ioutil.ReadFile(s[e+1:])
				if err != nil {
					return fmt.Errorf("error reading gapic config: %v", err)
				}

				// throw away the first line containing
				// "type: com.google.api.codegen.ConfigProto" because
				// that's not in the proto, causing an unmarshal error
				data := bytes.NewBuffer(f)
				data.ReadString('\n')

				j, err := yaml.YAMLToJSON(data.Bytes())
				if err != nil {
					return fmt.Errorf("error decoding gapic config: %v", err)
				}

				v.gapic = &config.ConfigProto{}
				err = jsonpb.Unmarshal(bytes.NewBuffer(j), v.gapic)
				if err != nil {
					return fmt.Errorf("error decoding gapic config: %v", err)
				}
			}
		}
	}

	return nil
}

// converts snake_case and SNAKE_CASE to CamelCase.
//
// copied from github.com/googleapis/gapic-generator-go
func snakeToCamel(s string) string {
	var sb strings.Builder
	up := true
	for _, r := range s {
		if r == '_' {
			up = true
		} else if up {
			sb.WriteRune(unicode.ToUpper(r))
			up = false
		} else {
			sb.WriteRune(unicode.ToLower(r))
		}
	}
	return sb.String()
}
<|endoftext|>


<commit_before>package fire

import (
	"errors"
	"reflect"

	"github.com/asaskevich/govalidator"
	"github.com/manyminds/api2go/jsonapi"
	"gopkg.in/mgo.v2/bson"
)

// Model is the main interface implemented by every fire model embedding Base.
type Model interface {
	ID() bson.ObjectId
	Get(string) interface{}
	Set(string, interface{})
	Validate(bool) error
	Meta() *Meta

	initialize(Model)
}

// Init initializes the internals of a model and should be called first.
func Init(model Model) Model {
	model.initialize(model)
	return model
}

// Base is the base for every fire model.
type Base struct {
	DocID bson.ObjectId `json:"-" bson:"_id,omitempty"`

	model interface{}
	meta  *Meta
}

// ID returns the models id.
func (b *Base) ID() bson.ObjectId {
	return b.DocID
}

// Get returns the value of the given field.
//
// Note: Get will return the value of the first field that has a matching Name,
// JSONName, or BSONName and will panic if no field can be found.
func (b *Base) Get(name string) interface{} {
	for _, field := range b.meta.Fields {
		if field.JSONName == name || field.BSONName == name || field.Name == name {
			// read value from model struct
			field := reflect.ValueOf(b.model).Elem().Field(field.index)
			return field.Interface()
		}
	}

	panic("Missing field " + name + " on " + b.meta.SingularName)
}

// Set will set given field to the the passed valued.
//
// Note: Set will set the value of the first field that has a matching Name,
// JSONName, or BSONName and will panic if no field can been found. The method
// will also panic if the type of the field and the passed value do not match.
func (b *Base) Set(name string, value interface{}) {
	for _, field := range b.meta.Fields {
		if field.JSONName == name || field.BSONName == name || field.Name == name {
			// set the value on model struct
			reflect.ValueOf(b.model).Elem().Field(field.index).Set(reflect.ValueOf(value))
			return
		}
	}

	panic("Missing field " + name + " on " + b.meta.SingularName)
}

// Validate validates the model based on the `valid:""` struct tags.
func (b *Base) Validate(fresh bool) error {
	// validate id
	if !b.DocID.Valid() {
		return errors.New("invalid id")
	}

	// validate parent model
	ok, err := govalidator.ValidateStruct(b.model)
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("validation failed")
	}

	return nil
}

// Meta returns the models Meta structure.
func (b *Base) Meta() *Meta {
	return b.meta
}

func (b *Base) initialize(model Model) {
	b.model = model

	// set id if missing
	if !b.DocID.Valid() {
		b.DocID = bson.NewObjectId()
	}

	// assign meta
	b.meta = NewMeta(model)
}

/* api2go.jsonapi interface */

// GetName returns the plural name of the Model.
//
// This methods is required by the jsonapi.EntityNamer interface.
func (b *Base) GetName() string {
	return b.meta.PluralName
}

// GetID returns the id of the Model.
//
// This methods is required by the jsonapi.MarshalIdentifier interface.
func (b *Base) GetID() string {
	return b.DocID.Hex()
}

// SetID sets the id of the Model.
//
// This methods is required by the jsonapi.UnmarshalIdentifier interface.
func (b *Base) SetID(id string) error {
	if len(id) == 0 {
		b.DocID = bson.NewObjectId()
		return nil
	}

	if !bson.IsObjectIdHex(id) {
		return errors.New("invalid id")
	}

	b.DocID = bson.ObjectIdHex(id)
	return nil
}

// GetReferences returns a list of the available references.
//
// This methods is required by the jsonapi.MarshalReferences interface.
func (b *Base) GetReferences() []jsonapi.Reference {
	// prepare result
	var refs []jsonapi.Reference

	// add to one and has many relationships
	for _, field := range b.meta.Fields {
		if field.ToOne || field.HasMany {
			refs = append(refs, jsonapi.Reference{
				Type:        field.RelType,
				Name:        field.RelName,
				IsNotLoaded: true,
			})
		}
	}

	return refs
}

// GetReferencesIDs returns list of references ids.
//
// This methods is required by the jsonapi.MarshalLinkedRelations interface.
func (b *Base) GetReferencedIDs() []jsonapi.ReferenceID {
	// prepare result
	var ids []jsonapi.ReferenceID

	// add to one relationships
	for _, field := range b.meta.Fields {
		if field.ToOne {
			// get struct field
			structField := reflect.ValueOf(b.model).Elem().Field(field.index)

			// prepare id
			var id string

			// check if field is optional
			if field.Optional {
				// continue if id is not set
				if structField.IsNil() {
					continue
				}

				// get id
				id = structField.Interface().(*bson.ObjectId).Hex()
			} else {
				// get id
				id = structField.Interface().(bson.ObjectId).Hex()
			}

			// append reference id
			ids = append(ids, jsonapi.ReferenceID{
				ID:   id,
				Type: field.RelType,
				Name: field.RelName,
			})
		}
	}

	return ids
}

// SetToOneReferenceID sets a reference to the passed id.
//
// This methods is required by the jsonapi.UnmarshalToOneRelations interface.
func (b *Base) SetToOneReferenceID(name, id string) error {
	// check object id
	if !bson.IsObjectIdHex(id) {
		return errors.New("invalid id")
	}

	for _, field := range b.meta.Fields {
		if field.ToOne && field.RelName == name {
			// get struct field
			structField := reflect.ValueOf(b.model).Elem().Field(field.index)

			// create id
			oid := bson.ObjectIdHex(id)

			// check if optional
			if field.Optional {
				structField.Set(reflect.ValueOf(&oid))
			} else {
				structField.Set(reflect.ValueOf(oid))
			}

			return nil
		}
	}

	return errors.New("missing relationship " + name)
}
<commit_msg>updated wording and added links<commit_after>package fire

import (
	"errors"
	"reflect"

	"github.com/asaskevich/govalidator"
	"github.com/manyminds/api2go/jsonapi"
	"gopkg.in/mgo.v2/bson"
)

// Model is the main interface implemented by every fire model embedding Base.
type Model interface {
	ID() bson.ObjectId
	Get(string) interface{}
	Set(string, interface{})
	Validate(bool) error
	Meta() *Meta

	initialize(Model)
}

// Init initializes the internals of a model and should be called before using
// a newly created Model.
func Init(model Model) Model {
	model.initialize(model)
	return model
}

// Base is the base for every fire model.
type Base struct {
	DocID bson.ObjectId `json:"-" bson:"_id,omitempty"`

	model interface{}
	meta  *Meta
}

// ID returns the models id.
func (b *Base) ID() bson.ObjectId {
	return b.DocID
}

// Get returns the value of the given field.
//
// Note: Get will return the value of the first field that has a matching Name,
// JSONName, or BSONName and will panic if no field can be found.
func (b *Base) Get(name string) interface{} {
	for _, field := range b.meta.Fields {
		if field.JSONName == name || field.BSONName == name || field.Name == name {
			// read value from model struct
			field := reflect.ValueOf(b.model).Elem().Field(field.index)
			return field.Interface()
		}
	}

	panic("Missing field " + name + " on " + b.meta.SingularName)
}

// Set will set given field to the the passed valued.
//
// Note: Set will set the value of the first field that has a matching Name,
// JSONName, or BSONName and will panic if no field can been found. The method
// will also panic if the type of the field and the passed value do not match.
func (b *Base) Set(name string, value interface{}) {
	for _, field := range b.meta.Fields {
		if field.JSONName == name || field.BSONName == name || field.Name == name {
			// set the value on model struct
			reflect.ValueOf(b.model).Elem().Field(field.index).Set(reflect.ValueOf(value))
			return
		}
	}

	panic("Missing field " + name + " on " + b.meta.SingularName)
}

// Validate validates the model based on the `valid:""` struct tags.
func (b *Base) Validate(fresh bool) error {
	// validate id
	if !b.DocID.Valid() {
		return errors.New("invalid id")
	}

	// validate parent model
	ok, err := govalidator.ValidateStruct(b.model)
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("validation failed")
	}

	return nil
}

// Meta returns the models Meta structure.
func (b *Base) Meta() *Meta {
	return b.meta
}

func (b *Base) initialize(model Model) {
	b.model = model

	// set id if missing
	if !b.DocID.Valid() {
		b.DocID = bson.NewObjectId()
	}

	// assign meta
	b.meta = NewMeta(model)
}

/* api2go.jsonapi interface */

// GetName returns the plural name of the Model.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#EntityNamer.
func (b *Base) GetName() string {
	return b.meta.PluralName
}

// GetID returns the id of the Model.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#MarshalIdentifier.
func (b *Base) GetID() string {
	return b.DocID.Hex()
}

// SetID sets the id of the Model.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#UnmarshalIdentifier.
func (b *Base) SetID(id string) error {
	if len(id) == 0 {
		b.DocID = bson.NewObjectId()
		return nil
	}

	if !bson.IsObjectIdHex(id) {
		return errors.New("invalid id")
	}

	b.DocID = bson.ObjectIdHex(id)
	return nil
}

// GetReferences returns a list of the available references.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#MarshalReferences.
func (b *Base) GetReferences() []jsonapi.Reference {
	// prepare result
	var refs []jsonapi.Reference

	// add to one and has many relationships
	for _, field := range b.meta.Fields {
		if field.ToOne || field.HasMany {
			refs = append(refs, jsonapi.Reference{
				Type:        field.RelType,
				Name:        field.RelName,
				IsNotLoaded: true,
			})
		}
	}

	return refs
}

// GetReferencesIDs returns list of references ids.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#MarshalLinkedRelations.
func (b *Base) GetReferencedIDs() []jsonapi.ReferenceID {
	// prepare result
	var ids []jsonapi.ReferenceID

	// add to one relationships
	for _, field := range b.meta.Fields {
		if field.ToOne {
			// get struct field
			structField := reflect.ValueOf(b.model).Elem().Field(field.index)

			// prepare id
			var id string

			// check if field is optional
			if field.Optional {
				// continue if id is not set
				if structField.IsNil() {
					continue
				}

				// get id
				id = structField.Interface().(*bson.ObjectId).Hex()
			} else {
				// get id
				id = structField.Interface().(bson.ObjectId).Hex()
			}

			// append reference id
			ids = append(ids, jsonapi.ReferenceID{
				ID:   id,
				Type: field.RelType,
				Name: field.RelName,
			})
		}
	}

	return ids
}

// SetToOneReferenceID sets a reference to the passed id.
//
// This methods is required by https://godoc.org/github.com/manyminds/api2go/jsonapi#UnmarshalToOneRelations.
func (b *Base) SetToOneReferenceID(name, id string) error {
	// check object id
	if !bson.IsObjectIdHex(id) {
		return errors.New("invalid id")
	}

	for _, field := range b.meta.Fields {
		if field.ToOne && field.RelName == name {
			// get struct field
			structField := reflect.ValueOf(b.model).Elem().Field(field.index)

			// create id
			oid := bson.ObjectIdHex(id)

			// check if optional
			if field.Optional {
				structField.Set(reflect.ValueOf(&oid))
			} else {
				structField.Set(reflect.ValueOf(oid))
			}

			return nil
		}
	}

	return errors.New("missing relationship " + name)
}
<|endoftext|>


<commit_before>// Package controller contains web-api resource controller container
package controller

import (
	"github.com/MG-RAST/Shock/shock-server/controller/node"
	"github.com/MG-RAST/Shock/shock-server/controller/node/acl"
	"github.com/MG-RAST/Shock/shock-server/controller/node/index"
	"github.com/MG-RAST/Shock/shock-server/controller/preauth"
	"github.com/MG-RAST/golib/goweb"
)

type Controller struct {
	Node    *node.Controller
	Index   goweb.ControllerFunc
	Acl     map[string]goweb.ControllerFunc
	Preauth func(*goweb.Context)
}

// New configures and returns web-api controller that will be used in routing
func New() *Controller {
	return &Controller{
		Node:    new(node.Controller),
		Index:   index.Controller,
		Acl:     map[string]goweb.ControllerFunc{"base": acl.Controller, "typed": acl.ControllerTyped},
		Preauth: preauth.PreAuthRequest,
	}
}
<commit_msg>No longer need the goweb version 1 controller.<commit_after><|endoftext|>


<commit_before>package digests

import (
	"fmt"
	"sort"
	"strconv"

	"github.com/ion-channel/ionic/scanner"
	"github.com/ion-channel/ionic/scans"
)

const (
	scoreVersion2 = "2.0"
	scoreHigh     = 7.0
	scoreCritical = 9.0
)

// ByScore sort interface impl for sorting vulnerabilities by score
type ByScore []scans.VulnerabilityResultsVulnerability

func (v ByScore) Len() int      { return len(v) }
func (v ByScore) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (v ByScore) Less(i, j int) bool {
	iscore, err := strconv.ParseFloat(v[i].Score, 32)
	if err != nil {
		return false
	}

	jscore, err := strconv.ParseFloat(v[j].Score, 32)
	if err != nil {
		return true
	}
	return iscore > jscore
}

type vfilter func(*scans.VulnerabilityResultsVulnerability) bool

func all(v *scans.VulnerabilityResultsVulnerability) bool {
	return true
}

func high(v *scans.VulnerabilityResultsVulnerability) bool {
	score, err := strconv.ParseFloat(v.Score, 32)
	if err != nil {
		return false
	}
	if v.ScoreVersion == scoreVersion2 && score >= scoreHigh {
		return true
	}
	if v.ScoreVersion != scoreVersion2 && score >= scoreHigh && score < scoreCritical {
		return true
	}
	return false
}

func critical(v *scans.VulnerabilityResultsVulnerability) bool {
	score, err := strconv.ParseFloat(v.Score, 32)
	if err != nil {
		return false
	}
	if v.ScoreVersion != scoreVersion2 && score >= scoreCritical {
		return true
	}
	return false
}

func pivotToVulnerabilities(data interface{}, f vfilter) ([]scans.VulnerabilityResultsVulnerability, error) {
	b, ok := data.(scans.VulnerabilityResults)
	if !ok {
		return nil, fmt.Errorf("error coercing evaluation translated results into vuln")
	}

	var values []scans.VulnerabilityResultsVulnerability
	uu := map[string]*scans.VulnerabilityResultsVulnerability{}
	for _, p := range b.Vulnerabilities {
		values = []scans.VulnerabilityResultsVulnerability{}
		pp := p
		pp.Vulnerabilities = []scans.VulnerabilityResultsVulnerability{}
		for _, v := range p.Vulnerabilities {
			if f(&v) {
				key := v.ExternalID
				if uu[key] == nil {
					v.Dependencies = append(v.Dependencies, pp)
					values = append(values, v)
					uu[key] = &v
				}
			}
		}
	}

	sort.Sort(ByScore(values))
	return values, nil
}

func vulnerabilityDigests(status *scanner.ScanStatus, eval *scans.Evaluation) ([]Digest, error) {
	digests := make([]Digest, 4)

	var vulnCount int
	var uniqVulnCount int
	var highs int
	var crits int
	var data interface{}
	var results scans.VulnerabilityResults
	if eval != nil {
		eval.Translate()
		data = eval.TranslatedResults.Data
		b, ok := data.(scans.VulnerabilityResults)
		results = b
		if !ok {
			return nil, fmt.Errorf("error coercing evaluation translated results into vuln")
		}

		vulnCount = results.Meta.VulnerabilityCount

		ids := make(map[int]bool, 0)

		for i := range results.Vulnerabilities {
			for j := range results.Vulnerabilities[i].Vulnerabilities {
				v := results.Vulnerabilities[i].Vulnerabilities[j]
				ids[v.ID] = true

				if v.ScoreSystem == "NPM" {
					if npmScore, err := strconv.ParseFloat(v.Score, 32); err == nil {
						if npmScore > 7 { // 10, 9, 8
							crits++
						} else if npmScore > 5 { // 7, 6
							highs++
						}
					}
				} else {
					ver, _ := strconv.ParseFloat(v.ScoreVersion, 32)
					switch int(ver) {
					case 3:
						if v.ScoreDetails.CVSSv3 != nil && v.ScoreDetails.CVSSv3.BaseScore >= 9.0 {
							crits++
						} else if v.ScoreDetails.CVSSv3 != nil && v.ScoreDetails.CVSSv3.BaseScore >= 7.0 {
							highs++
						}
					case 2:
						if v.ScoreDetails.CVSSv2 != nil && v.ScoreDetails.CVSSv2.BaseScore >= 7.0 {
							highs++
						}
					default:
					}
				}

			}
		}

		uniqVulnCount = len(ids)
	}

	// wg := new(sync.WaitGroup)
	// wg.Add(4)
	//
	// // total vulns
	// go func() {
	// 	defer wg.Done()
	d := NewDigest(status, TotalVulnerabilitiesIndex, "total vulnerability", "total vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, all)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")
		err = d.AppendEval(eval, "count", vulnCount)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to total vulnerabilities digest: %v", err.Error())
		}

		if vulnCount > 0 {
			d.Warning = true
			d.WarningMessage = "vulnerabilities found"

			if vulnCount == 1 {
				d.WarningMessage = "vulnerability found"
			}
		}

		d.Evaluated = false // As of now there's no rule to evaluate this against so it's set to not evaluated.
	}

	digests[0] = *d
	// }()

	// go func() {
	// 	defer wg.Done()
	// 	// unique vulns
	d = NewDigest(status, UniqueVulnerabilitiesIndex, "unique vulnerability", "unique vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, all)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")
		err = d.AppendEval(eval, "count", uniqVulnCount)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if uniqVulnCount > 0 {
			d.Warning = true
			d.WarningMessage = "vulnerabilities found"

			if uniqVulnCount == 1 {
				d.WarningMessage = "vulnerability found"
			}
		}

		d.Evaluated = false // As of now there's no rule to evaluate this against so it's set to not evaluated.
	}

	digests[1] = *d
	// }()

	// go func() {
	// 	defer wg.Done()
	// 	// high vulns
	d = NewDigest(status, HighVulnerabilitiesIndex, "high vulnerability", "high vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, high)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")

		err = d.AppendEval(eval, "count", highs)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if highs == 0 {
			d.Passed = true
		}
	}

	digests[2] = *d
	// }()

	// go func() {
	// 	defer wg.Done()
	// critical vulns
	d = NewDigest(status, CriticalVulnerabilitiesIndex, "critical vulnerability", "critical vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, critical)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")

		err = d.AppendEval(eval, "count", crits)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if crits == 0 {
			d.Passed = true
		}
	}

	digests[3] = *d
	// }()

	// wg.Wait()
	return digests, nil
}
<commit_msg>Revert to append<commit_after>package digests

import (
	"fmt"
	"sort"
	"strconv"

	"github.com/ion-channel/ionic/scanner"
	"github.com/ion-channel/ionic/scans"
)

const (
	scoreVersion2 = "2.0"
	scoreHigh     = 7.0
	scoreCritical = 9.0
)

// ByScore sort interface impl for sorting vulnerabilities by score
type ByScore []scans.VulnerabilityResultsVulnerability

func (v ByScore) Len() int      { return len(v) }
func (v ByScore) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (v ByScore) Less(i, j int) bool {
	iscore, err := strconv.ParseFloat(v[i].Score, 32)
	if err != nil {
		return false
	}

	jscore, err := strconv.ParseFloat(v[j].Score, 32)
	if err != nil {
		return true
	}
	return iscore > jscore
}

type vfilter func(*scans.VulnerabilityResultsVulnerability) bool

func all(v *scans.VulnerabilityResultsVulnerability) bool {
	return true
}

func high(v *scans.VulnerabilityResultsVulnerability) bool {
	score, err := strconv.ParseFloat(v.Score, 32)
	if err != nil {
		return false
	}
	if v.ScoreVersion == scoreVersion2 && score >= scoreHigh {
		return true
	}
	if v.ScoreVersion != scoreVersion2 && score >= scoreHigh && score < scoreCritical {
		return true
	}
	return false
}

func critical(v *scans.VulnerabilityResultsVulnerability) bool {
	score, err := strconv.ParseFloat(v.Score, 32)
	if err != nil {
		return false
	}
	if v.ScoreVersion != scoreVersion2 && score >= scoreCritical {
		return true
	}
	return false
}

func pivotToVulnerabilities(data interface{}, f vfilter) ([]scans.VulnerabilityResultsVulnerability, error) {
	b, ok := data.(scans.VulnerabilityResults)
	if !ok {
		return nil, fmt.Errorf("error coercing evaluation translated results into vuln")
	}

	var values []scans.VulnerabilityResultsVulnerability
	uu := map[string]*scans.VulnerabilityResultsVulnerability{}
	for _, p := range b.Vulnerabilities {
		values = []scans.VulnerabilityResultsVulnerability{}
		pp := p
		pp.Vulnerabilities = []scans.VulnerabilityResultsVulnerability{}
		for _, v := range p.Vulnerabilities {
			if f(&v) {
				key := v.ExternalID
				if uu[key] == nil {
					v.Dependencies = append(v.Dependencies, pp)
					values = append(values, v)
					uu[key] = &v
				}
			}
		}
	}

	sort.Sort(ByScore(values))
	return values, nil
}

func vulnerabilityDigests(status *scanner.ScanStatus, eval *scans.Evaluation) ([]Digest, error) {
	digests := []Digest{}

	var vulnCount int
	var uniqVulnCount int
	var highs int
	var crits int
	var data interface{}
	var results scans.VulnerabilityResults
	if eval != nil {
		eval.Translate()
		data = eval.TranslatedResults.Data
		b, ok := data.(scans.VulnerabilityResults)
		results = b
		if !ok {
			return nil, fmt.Errorf("error coercing evaluation translated results into vuln")
		}

		vulnCount = results.Meta.VulnerabilityCount

		ids := make(map[int]bool, 0)

		for i := range results.Vulnerabilities {
			for j := range results.Vulnerabilities[i].Vulnerabilities {
				v := results.Vulnerabilities[i].Vulnerabilities[j]
				ids[v.ID] = true

				if v.ScoreSystem == "NPM" {
					if npmScore, err := strconv.ParseFloat(v.Score, 32); err == nil {
						if npmScore > 7 { // 10, 9, 8
							crits++
						} else if npmScore > 5 { // 7, 6
							highs++
						}
					}
				} else {
					ver, _ := strconv.ParseFloat(v.ScoreVersion, 32)
					switch int(ver) {
					case 3:
						if v.ScoreDetails.CVSSv3 != nil && v.ScoreDetails.CVSSv3.BaseScore >= 9.0 {
							crits++
						} else if v.ScoreDetails.CVSSv3 != nil && v.ScoreDetails.CVSSv3.BaseScore >= 7.0 {
							highs++
						}
					case 2:
						if v.ScoreDetails.CVSSv2 != nil && v.ScoreDetails.CVSSv2.BaseScore >= 7.0 {
							highs++
						}
					default:
					}
				}

			}
		}

		uniqVulnCount = len(ids)
	}

	d := NewDigest(status, TotalVulnerabilitiesIndex, "total vulnerability", "total vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, all)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")
		err = d.AppendEval(eval, "count", vulnCount)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to total vulnerabilities digest: %v", err.Error())
		}

		if vulnCount > 0 {
			d.Warning = true
			d.WarningMessage = "vulnerabilities found"

			if vulnCount == 1 {
				d.WarningMessage = "vulnerability found"
			}
		}

		d.Evaluated = false // As of now there's no rule to evaluate this against so it's set to not evaluated.
	}

	digests = append(digests, *d)

	// unique vulns
	d = NewDigest(status, UniqueVulnerabilitiesIndex, "unique vulnerability", "unique vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, all)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")
		err = d.AppendEval(eval, "count", uniqVulnCount)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if uniqVulnCount > 0 {
			d.Warning = true
			d.WarningMessage = "vulnerabilities found"

			if uniqVulnCount == 1 {
				d.WarningMessage = "vulnerability found"
			}
		}

		d.Evaluated = false // As of now there's no rule to evaluate this against so it's set to not evaluated.
	}

	digests = append(digests, *d)

	// high vulns
	d = NewDigest(status, HighVulnerabilitiesIndex, "high vulnerability", "high vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, high)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")

		err = d.AppendEval(eval, "count", highs)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if highs == 0 {
			d.Passed = true
		}
	}

	digests = append(digests, *d)

	// critical vulns
	d = NewDigest(status, CriticalVulnerabilitiesIndex, "critical vulnerability", "critical vulnerabilities")

	if eval != nil && !status.Errored() {
		pivoted, err := pivotToVulnerabilities(data, critical)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}
		d.MarshalSourceData(pivoted, "vulnerability")

		err = d.AppendEval(eval, "count", crits)
		if err != nil {
			return nil, fmt.Errorf("failed to add evaluation data to unique vulnerabilities digest: %v", err.Error())
		}

		if crits == 0 {
			d.Passed = true
		}
	}

	digests = append(digests, *d)

	return digests, nil
}
<|endoftext|>


<commit_before>package engi

import (
	"github.com/paked/engi/ecs"
)

// MouseComponent is the location for the MouseSystem to store its results;
// to be used / viewed by other Systems
type MouseComponent struct {
	// Clicked is true whenever the Mouse was clicked over
	// the entity space in this frame
	Clicked bool
	// Released is true whenever the left mouse button is released over the
	// entity space in this frame
	Released bool
	// Hovered is true whenever the Mouse is hovering
	// the entity space in this frame. This does not necessarily imply that
	// the mouse button was pressed down in your entity space.
	Hovered bool
	// Dragged is true whenever the entity space was clicked,
	// and then the mouse started moving (while holding)
	Dragged bool
	// RightClicked is true whenever the entity space was right-clicked
	// in this frame
	RightClicked bool
	// RightReleased is true whenever the right mouse button is released over
	// the entity space in this frame. This does not necessarily imply that
	// the mouse button was pressed down in your entity space.
	RightReleased bool
	// Enter is true whenever the Mouse entered the entity space in that frame,
	// but wasn't in that space during the previous frame
	Enter bool
	// Leave is true whenever the Mouse was in the space on the previous frame,
	// but now isn't
	Leave bool
	// Position of the mouse at any moment this is generally used
	// in conjunction with Track = true
	MouseX float32
	MouseY float32
	// Set manually this to true and your mouse component will track the mouse
	// and your entity will always be able to receive an updated mouse
	// component even if its space is not under the mouse cursor
	// WARNING: you MUST know why you want to use this because it will
	// have serious performance impacts if you have many entities with
	// a MouseComponent in tracking mode.
	// This is ideally used for a really small number of entities
	// that must really be aware of the mouse details event when the
	// mouse is not hovering them
	Track bool
}

// Type returns the string representation of the MouseComponent type
func (*MouseComponent) Type() string {
	return "MouseComponent"
}

// MouseSystem listens for mouse events, and changes value for MouseComponent accordingly
type MouseSystem struct {
	*ecs.System

	mouseX    float32
	mouseY    float32
	mouseDown bool
}

// Type returns the string representation of the MouseSystem type
func (*MouseSystem) Type() string {
	return "MouseSystem"
}

// New initializes the MouseSystem
func (m *MouseSystem) New(*ecs.World) {
	m.System = ecs.NewSystem()
}

// Priority returns a priority of 10 (higher than most) to ensure that this System runs before all others
func (m *MouseSystem) Priority() int {
	return 10
}

// Pre is called before all Update calls, and is used to compute internal values
func (m *MouseSystem) Pre() {
	// Translate Mouse.X and Mouse.Y into "game coordinates"
	m.mouseX = Mouse.X*cam.z*(gameWidth/windowWidth) + cam.x - (gameWidth/2)*cam.z
	m.mouseY = Mouse.Y*cam.z*(gameHeight/windowHeight) + cam.y - (gameHeight/2)*cam.z
}

// Update sets the MouseComponent values for each Entity
func (m *MouseSystem) Update(entity *ecs.Entity, dt float32) {
	var (
		mc     *MouseComponent
		space  *SpaceComponent
		render *RenderComponent
		ok     bool
	)

	// We need MouseComponent to save our findings
	if mc, ok = entity.ComponentFast(mc).(*MouseComponent); !ok {
		return
	}

	// We need SpaceComponent for the location
	if space, ok = entity.ComponentFast(space).(*SpaceComponent); !ok {
		return
	}

	// We need RenderComponent for the Priority
	if render, ok = entity.ComponentFast(render).(*RenderComponent); !ok {
		return
	}

	// Reset some values
	mc.Leave = false

	mx := m.mouseX
	my := m.mouseY

	// Special case: HUD
	if render.priority >= HUDGround {
		mx = Mouse.X
		my = Mouse.Y
	}

	// if the Mouse component is a tracker we always update it
	// Check if the X-value is within range
	// and if the Y-value is within range
	if mc.Track || mx > space.Position.X && mx < (space.Position.X+space.Width) &&
		my > space.Position.Y && my < (space.Position.Y+space.Height) {

		mc.Enter = !mc.Hovered
		mc.Hovered = true

		mc.Released = false
		// track mouse position so that systems that need to stay on the mouse
		// position can do it (think an RTS when placing a new building and
		// you get a ghost building following your mouse until you click to
		// place it somewhere in your world.
		mc.MouseX = mx
		mc.MouseY = my

		switch Mouse.Action {
		case PRESS:
			switch Mouse.Button {
			case MouseButtonLeft:
				mc.Clicked = true
			case MouseButtonRight:
				mc.RightClicked = true
			}
			m.mouseDown = true
		case RELEASE:
			switch Mouse.Button {
			case MouseButtonLeft:
				mc.Released = true
			case MouseButtonRight:
				mc.RightReleased = true
			}
			// dragging stops as soon as one of the currently pressed buttons
			// is released
			mc.Dragged = false
			// mouseDown goes false as soon as one of the pressed buttons is
			// released. Effectively ending any dragging
			m.mouseDown = false
		case MOVE:
			if m.mouseDown {
				mc.Dragged = true
			}
		}
	} else {
		if mc.Hovered {
			mc.Leave = true
		}
		mc.Hovered = false
	}
}

// Post is called after all Update calls, and is used to compute internal values
func (m *MouseSystem) Post() {
	// reset mouse.Action value to something meaningless to avoid
	// catching the same "signal" twice
	Mouse.Action = NEUTRAL
}
<commit_msg>Propagate Mouse.Modifier to mouse components<commit_after>package engi

import (
	"github.com/paked/engi/ecs"
)

// MouseComponent is the location for the MouseSystem to store its results;
// to be used / viewed by other Systems
type MouseComponent struct {
	// Clicked is true whenever the Mouse was clicked over
	// the entity space in this frame
	Clicked bool
	// Released is true whenever the left mouse button is released over the
	// entity space in this frame
	Released bool
	// Hovered is true whenever the Mouse is hovering
	// the entity space in this frame. This does not necessarily imply that
	// the mouse button was pressed down in your entity space.
	Hovered bool
	// Dragged is true whenever the entity space was clicked,
	// and then the mouse started moving (while holding)
	Dragged bool
	// RightClicked is true whenever the entity space was right-clicked
	// in this frame
	RightClicked bool
	// RightReleased is true whenever the right mouse button is released over
	// the entity space in this frame. This does not necessarily imply that
	// the mouse button was pressed down in your entity space.
	RightReleased bool
	// Enter is true whenever the Mouse entered the entity space in that frame,
	// but wasn't in that space during the previous frame
	Enter bool
	// Leave is true whenever the Mouse was in the space on the previous frame,
	// but now isn't
	Leave bool
	// Position of the mouse at any moment this is generally used
	// in conjunction with Track = true
	MouseX float32
	MouseY float32
	// Set manually this to true and your mouse component will track the mouse
	// and your entity will always be able to receive an updated mouse
	// component even if its space is not under the mouse cursor
	// WARNING: you MUST know why you want to use this because it will
	// have serious performance impacts if you have many entities with
	// a MouseComponent in tracking mode.
	// This is ideally used for a really small number of entities
	// that must really be aware of the mouse details event when the
	// mouse is not hovering them
	Track bool
	// Modifier is used to store the eventual modifiers that were pressed during
	// the same time the different click events occurred
	Modifier Modifier
}

// Type returns the string representation of the MouseComponent type
func (*MouseComponent) Type() string {
	return "MouseComponent"
}

// MouseSystem listens for mouse events, and changes value for MouseComponent accordingly
type MouseSystem struct {
	*ecs.System

	mouseX    float32
	mouseY    float32
	mouseDown bool
}

// Type returns the string representation of the MouseSystem type
func (*MouseSystem) Type() string {
	return "MouseSystem"
}

// New initializes the MouseSystem
func (m *MouseSystem) New(*ecs.World) {
	m.System = ecs.NewSystem()
}

// Priority returns a priority of 10 (higher than most) to ensure that this System runs before all others
func (m *MouseSystem) Priority() int {
	return 10
}

// Pre is called before all Update calls, and is used to compute internal values
func (m *MouseSystem) Pre() {
	// Translate Mouse.X and Mouse.Y into "game coordinates"
	m.mouseX = Mouse.X*cam.z*(gameWidth/windowWidth) + cam.x - (gameWidth/2)*cam.z
	m.mouseY = Mouse.Y*cam.z*(gameHeight/windowHeight) + cam.y - (gameHeight/2)*cam.z
}

// Update sets the MouseComponent values for each Entity
func (m *MouseSystem) Update(entity *ecs.Entity, dt float32) {
	var (
		mc     *MouseComponent
		space  *SpaceComponent
		render *RenderComponent
		ok     bool
	)

	// We need MouseComponent to save our findings
	if mc, ok = entity.ComponentFast(mc).(*MouseComponent); !ok {
		return
	}

	// We need SpaceComponent for the location
	if space, ok = entity.ComponentFast(space).(*SpaceComponent); !ok {
		return
	}

	// We need RenderComponent for the Priority
	if render, ok = entity.ComponentFast(render).(*RenderComponent); !ok {
		return
	}

	// Reset some values
	mc.Leave = false

	mx := m.mouseX
	my := m.mouseY

	// Special case: HUD
	if render.priority >= HUDGround {
		mx = Mouse.X
		my = Mouse.Y
	}

	// if the Mouse component is a tracker we always update it
	// Check if the X-value is within range
	// and if the Y-value is within range
	if mc.Track || mx > space.Position.X && mx < (space.Position.X+space.Width) &&
		my > space.Position.Y && my < (space.Position.Y+space.Height) {

		mc.Enter = !mc.Hovered
		mc.Hovered = true

		mc.Released = false
		// track mouse position so that systems that need to stay on the mouse
		// position can do it (think an RTS when placing a new building and
		// you get a ghost building following your mouse until you click to
		// place it somewhere in your world.
		mc.MouseX = mx
		mc.MouseY = my

		// propagate the modifiers to the mouse component so that game
		// implementers can take different decisions based on those
		mc.Modifier = Mouse.Modifer

		switch Mouse.Action {
		case PRESS:
			switch Mouse.Button {
			case MouseButtonLeft:
				mc.Clicked = true
			case MouseButtonRight:
				mc.RightClicked = true
			}
			m.mouseDown = true
		case RELEASE:
			switch Mouse.Button {
			case MouseButtonLeft:
				mc.Released = true
			case MouseButtonRight:
				mc.RightReleased = true
			}
			// dragging stops as soon as one of the currently pressed buttons
			// is released
			mc.Dragged = false
			// mouseDown goes false as soon as one of the pressed buttons is
			// released. Effectively ending any dragging
			m.mouseDown = false
		case MOVE:
			if m.mouseDown {
				mc.Dragged = true
			}
		}
	} else {
		if mc.Hovered {
			mc.Leave = true
			// propagate the modifiers to the mouse component so that game
			// implementers can take different decisions based on those
			mc.Modifier = Mouse.Modifer
		}
		mc.Hovered = false
	}
}

// Post is called after all Update calls, and is used to compute internal values
// NOTE: we do not reset modifiers here because we always set them to meaningful
// values when a mouse event is propagated to the mouse components
func (m *MouseSystem) Post() {
	// reset mouse.Action value to something meaningless to avoid
	// catching the same "signal" twice
	Mouse.Action = NEUTRAL
}
<|endoftext|>


<commit_before>// IronMQ (elastic message queue) client library
package mq

import (
	"errors"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

type Queue struct {
	Settings config.Settings
	Name     string
}

type QueueInfo struct {
	Id              string   `json:"id,omitempty"`
	Name            string   `json:"name,omitempty"`
	Size            int      `json:"size,omitempty"`
	Reserved        int      `json:"reserved,omitempty"`
	TotalMessages   int      `json:"total_messages,omitempty"`
	MaxReqPerMinute int      `json:"max_req_per_minute,omitempty"`
	Subscriptions   []string `json:"subscriptions,omitempty"`
	PushType        string   `json:"push_type,omitempty"`
}

type Message struct {
	Id   string `json:"id,omitempty"`
	Body string `json:"body"`
	// Timeout is the amount of time in seconds allowed for processing the
	// message.
	Timeout int64 `json:"timeout,omitempty"`
	// Delay is the amount of time in seconds to wait before adding the message
	// to the queue.
	Delay int64 `json:"delay,omitempty"`
	// one of "worker" or "pub/sub"
	PushType string `json:"push_type,omitempty"`
	// list of urls to push the message to.
	Subscriptions []string `json:"subscriptions,omitempty"`
	q             Queue
}

func New(queueName string) *Queue {
	return &Queue{Settings: config.Config("iron_mq"), Name: queueName}
}

func (q Queue) queues(s ...string) *api.URL { return api.Action(q.Settings, "queues", s...) }

func (q Queue) Subscribe(pushType string, subscriptions ...string) (err error) {
	in := QueueInfo{
		Name:          q.Name,
		PushType:      pushType,
		Subscriptions: subscriptions,
	}
	return q.queues().Req("POST", &in, nil)
}

func (q Queue) ListQueues(page, perPage int) (queues []Queue, err error) {
	out := []struct {
		Id         string
		Project_id string
		Name       string
	}{}

	err = q.queues().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	queues = make([]Queue, 0, len(out))
	for _, item := range out {
		queues = append(queues, Queue{
			Settings: q.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (q Queue) Info() (QueueInfo, error) {
	qi := QueueInfo{}
	err := q.queues(q.Name).Req("GET", nil, &qi)
	return qi, err
}

func (q Queue) PushString(body string) (id string, err error) {
	ids, err := q.PushStrings(body)
	if err != nil {
		return
	}
	return ids[0], nil
}

// Push adds one or more messages to the end of the queue using IronMQ's defaults:
//	timeout - 60 seconds
//	delay - none
//
// Identical to PushMessages with Message{Timeout: 60, Delay: 0}
func (q Queue) PushStrings(bodies ...string) (ids []string, err error) {
	msgs := make([]*Message, 0, len(bodies))
	for _, body := range bodies {
		msgs = append(msgs, &Message{Body: body})
	}

	return q.PushMessages(msgs...)
}

func (q Queue) PushMessage(msg *Message) (id string, err error) {
	ids, err := q.PushMessages(msg)
	if err != nil {
		return
	}
	return ids[0], nil
}

func (q Queue) PushMessages(msgs ...*Message) (ids []string, err error) {
	in := struct {
		Messages []*Message `json:"messages"`
	}{Messages: msgs}

	out := struct {
		IDs []string `json:"ids"`
		Msg string   `json:"msg"`
	}{}

	err = q.queues(q.Name, "messages").Req("POST", &in, &out)
	return out.IDs, err
}

// Get reserves a message from the queue.
// The message will not be deleted, but will be reserved until the timeout
// expires. If the timeout expires before the message is deleted, the message
// will be placed back onto the queue.
// As a result, be sure to Delete a message after you're done with it.
func (q Queue) Get() (msg *Message, err error) {
	msgs, err := q.GetN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// get N messages
func (q Queue) GetN(n int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages").
		QueryAdd("n", "%d", n).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

// Delete all messages in the queue
func (q Queue) Clear() (err error) {
	return q.queues(q.Name, "clear").Req("POST", nil, nil)
}

// Delete message from queue
func (q Queue) DeleteMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId).Req("DELETE", nil, nil)
}

// Reset timeout of message to keep it reserved
func (q Queue) TouchMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId, "touch").Req("POST", nil, nil)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (q Queue) ReleaseMessage(msgId string, delay int64) (err error) {
	in := struct {
		Delay int64 `json:"messages"`
	}{Delay: delay}
	return q.queues(q.Name, "messages", msgId, "release").Req("POST", &in, nil)
}

// Delete message from queue
func (m Message) Delete() (err error) {
	return m.q.DeleteMessage(m.Id)
}

// Reset timeout of message to keep it reserved
func (m Message) Touch() (err error) {
	return m.q.TouchMessage(m.Id)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (m Message) Release(delay int64) (err error) {
	return m.q.ReleaseMessage(m.Id, delay)
}
<commit_msg>Support new push queue api<commit_after>// IronMQ (elastic message queue) client library
package mq

import (
	"errors"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

type Queue struct {
	Settings config.Settings
	Name     string
}

type QueueInfo struct {
	Id              string   `json:"id,omitempty"`
	Name            string   `json:"name,omitempty"`
	Size            int      `json:"size,omitempty"`
	Reserved        int      `json:"reserved,omitempty"`
	TotalMessages   int      `json:"total_messages,omitempty"`
	MaxReqPerMinute int      `json:"max_req_per_minute,omitempty"`
	Subscriptions   []string `json:"subscriptions,omitempty"`
	PushType        string   `json:"push_type,omitempty"`
}

type Message struct {
	Id   string `json:"id,omitempty"`
	Body string `json:"body"`
	// Timeout is the amount of time in seconds allowed for processing the
	// message.
	Timeout int64 `json:"timeout,omitempty"`
	// Delay is the amount of time in seconds to wait before adding the message
	// to the queue.
	Delay int64 `json:"delay,omitempty"`
	// one of "worker" or "pub/sub"
	PushType string `json:"push_type,omitempty"`
	// list of urls to push the message to.
	Subscriptions []string `json:"subscriptions,omitempty"`
	q             Queue
}

func New(queueName string) *Queue {
	return &Queue{Settings: config.Config("iron_mq"), Name: queueName}
}

func (q Queue) queues(s ...string) *api.URL { return api.Action(q.Settings, "queues", s...) }

func (q Queue) ListQueues(page, perPage int) (queues []Queue, err error) {
	out := []struct {
		Id         string
		Project_id string
		Name       string
	}{}

	err = q.queues().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	queues = make([]Queue, 0, len(out))
	for _, item := range out {
		queues = append(queues, Queue{
			Settings: q.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (q Queue) Info() (QueueInfo, error) {
	qi := QueueInfo{}
	err := q.queues(q.Name).Req("GET", nil, &qi)
	return qi, err
}

func (q Queue) Subscribe(pushType string, subscriptions ...string) (err error) {
	in := QueueInfo{
		PushType:      pushType,
		Subscriptions: subscriptions,
	}
	return q.queues(q.Name).Req("POST", &in, nil)
}

func (q Queue) PushString(body string) (id string, err error) {
	ids, err := q.PushStrings(body)
	if err != nil {
		return
	}
	return ids[0], nil
}

// Push adds one or more messages to the end of the queue using IronMQ's defaults:
//	timeout - 60 seconds
//	delay - none
//
// Identical to PushMessages with Message{Timeout: 60, Delay: 0}
func (q Queue) PushStrings(bodies ...string) (ids []string, err error) {
	msgs := make([]*Message, 0, len(bodies))
	for _, body := range bodies {
		msgs = append(msgs, &Message{Body: body})
	}

	return q.PushMessages(msgs...)
}

func (q Queue) PushMessage(msg *Message) (id string, err error) {
	ids, err := q.PushMessages(msg)
	if err != nil {
		return
	}
	return ids[0], nil
}

func (q Queue) PushMessages(msgs ...*Message) (ids []string, err error) {
	in := struct {
		Messages []*Message `json:"messages"`
	}{Messages: msgs}

	out := struct {
		IDs []string `json:"ids"`
		Msg string   `json:"msg"`
	}{}

	err = q.queues(q.Name, "messages").Req("POST", &in, &out)
	return out.IDs, err
}

// Get reserves a message from the queue.
// The message will not be deleted, but will be reserved until the timeout
// expires. If the timeout expires before the message is deleted, the message
// will be placed back onto the queue.
// As a result, be sure to Delete a message after you're done with it.
func (q Queue) Get() (msg *Message, err error) {
	msgs, err := q.GetN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// get N messages
func (q Queue) GetN(n int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages").
		QueryAdd("n", "%d", n).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

// Delete all messages in the queue
func (q Queue) Clear() (err error) {
	return q.queues(q.Name, "clear").Req("POST", nil, nil)
}

// Delete message from queue
func (q Queue) DeleteMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId).Req("DELETE", nil, nil)
}

// Reset timeout of message to keep it reserved
func (q Queue) TouchMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId, "touch").Req("POST", nil, nil)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (q Queue) ReleaseMessage(msgId string, delay int64) (err error) {
	in := struct {
		Delay int64 `json:"messages"`
	}{Delay: delay}
	return q.queues(q.Name, "messages", msgId, "release").Req("POST", &in, nil)
}

// Delete message from queue
func (m Message) Delete() (err error) {
	return m.q.DeleteMessage(m.Id)
}

// Reset timeout of message to keep it reserved
func (m Message) Touch() (err error) {
	return m.q.TouchMessage(m.Id)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (m Message) Release(delay int64) (err error) {
	return m.q.ReleaseMessage(m.Id, delay)
}
<|endoftext|>


<commit_before>// IronMQ (elastic message queue) client library
package mq

import (
	"errors"
	"time"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

type Queue struct {
	Settings config.Settings
	Name     string
}

type QueueSubscriber struct {
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers,omitempty"`
}

type QueueInfo struct {
	Id            string            `json:"id,omitempty"`
	Name          string            `json:"name,omitempty"`
	PushType      string            `json:"push_type,omitempty"`
	Reserved      int               `json:"reserved,omitempty"`
	RetriesDelay  int               `json:"retries,omitempty"`
	Retries       int               `json:"retries_delay,omitempty"`
	Size          int               `json:"size,omitempty"`
	Subscribers   []QueueSubscriber `json:"subscribers,omitempty"`
	Alerts        []Alert           `json:"alerts,omitempty"`
	TotalMessages int               `json:"total_messages,omitempty"`
	ErrorQueue    string            `json:"error_queue,omitempty"`
}

type Message struct {
	Id   string `json:"id,omitempty"`
	Body string `json:"body"`
	// Timeout is the amount of time in seconds allowed for processing the
	// message.
	Timeout int64 `json:"timeout,omitempty"`
	// Delay is the amount of time in seconds to wait before adding the message
	// to the queue.
	Delay int64 `json:"delay,omitempty"`
	q     Queue
}

type PushStatus struct {
	Retried    int    `json:"retried"`
	StatusCode int    `json:"status_code"`
	Status     string `json:"status"`
}

type Subscriber struct {
	Retried    int    `json:"retried"`
	StatusCode int    `json:"status_code"`
	Status     string `json:"status"`
	URL        string `json:"url"`
}

type Alert struct {
	Type       string `json:"type"`
	Direction  string `json:direction`
	Trigger    int    `json:trigger`
	Queue      string `queue`
}

func New(queueName string) *Queue {
	return &Queue{Settings: config.Config("iron_mq"), Name: queueName}
}

func ListQueues(page, perPage int) (queues []Queue, err error) {
	out := []struct {
		Id         string
		Project_id string
		Name       string
	}{}

	q := New("");
	err = q.queues().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	queues = make([]Queue, 0, len(out))
	for _, item := range out {
		queues = append(queues, Queue{
			Settings: q.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (q Queue) queues(s ...string) *api.URL { return api.Action(q.Settings, "queues", s...) }

// This method is left to support backward compatibility.
// This method is replaced by func ListQueues(page, perPage int) (queues []Queue, err error)
func (q Queue) ListQueues(page, perPage int) (queues []Queue, err error) {
	return ListQueues(page, perPage)
}

func (q Queue) Info() (QueueInfo, error) {
	qi := QueueInfo{}
	err := q.queues(q.Name).Req("GET", nil, &qi)
	return qi, err
}

func (q Queue) Update(qi QueueInfo) (QueueInfo, error) {
	out := QueueInfo{}
	err := q.queues(q.Name).Req("POST", qi, &out)
	return out, err
}

func (q Queue) Delete() (bool, error) {
	out := QueueInfo{}
	err := q.queues(q.Name).Req("DELETE", nil, &out)
	success := err == nil
	return success, err
}

type Subscription struct {
	PushType     string
	Retries      int
	RetriesDelay int
}

func (q Queue) Subscribe(subscription Subscription, subscribers ...string) (err error) {
	in := QueueInfo{
		PushType:     subscription.PushType,
		Retries:      subscription.Retries,
		RetriesDelay: subscription.RetriesDelay,
		Subscribers:  make([]QueueSubscriber, len(subscribers)),
	}
	for i, subscriber := range subscribers {
		in.Subscribers[i].URL = subscriber
	}
	return q.queues(q.Name).Req("POST", &in, nil)
}

func (q Queue) PushString(body string) (id string, err error) {
	ids, err := q.PushStrings(body)
	if err != nil {
		return
	}
	return ids[0], nil
}

// Push adds one or more messages to the end of the queue using IronMQ's defaults:
//	timeout - 60 seconds
//	delay - none
//
// Identical to PushMessages with Message{Timeout: 60, Delay: 0}
func (q Queue) PushStrings(bodies ...string) (ids []string, err error) {
	msgs := make([]*Message, 0, len(bodies))
	for _, body := range bodies {
		msgs = append(msgs, &Message{Body: body})
	}

	return q.PushMessages(msgs...)
}

func (q Queue) PushMessage(msg *Message) (id string, err error) {
	ids, err := q.PushMessages(msg)
	if err != nil {
		return
	}
	return ids[0], nil
}

func (q Queue) PushMessages(msgs ...*Message) (ids []string, err error) {
	in := struct {
		Messages []*Message `json:"messages"`
	}{Messages: msgs}

	out := struct {
		IDs []string `json:"ids"`
		Msg string   `json:"msg"`
	}{}

	err = q.queues(q.Name, "messages").Req("POST", &in, &out)
	return out.IDs, err
}

// Get reserves a message from the queue.
// The message will not be deleted, but will be reserved until the timeout
// expires. If the timeout expires before the message is deleted, the message
// will be placed back onto the queue.
// As a result, be sure to Delete a message after you're done with it.
func (q Queue) Get() (msg *Message, err error) {
	msgs, err := q.GetN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// get N messages
func (q Queue) GetN(n int) (msgs []*Message, err error) {
	msgs, err = q.GetNWithTimeout(n, 0)

	return
}

func (q Queue) GetNWithTimeout(n, timeout int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages").
		QueryAdd("n", "%d", n).
		QueryAdd("timeout", "%d", timeout).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

func (q Queue) Peek() (msg *Message, err error) {
	msgs, err := q.PeekN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// peek N messages
func (q Queue) PeekN(n int) (msgs []*Message, err error) {
	msgs, err = q.PeekNWithTimeout(n, 0)

	return
}

func (q Queue) PeekNWithTimeout(n, timeout int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages", "peek").
		QueryAdd("n", "%d", n).
		QueryAdd("timeout", "%d", timeout).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

// Delete all messages in the queue
func (q Queue) Clear() (err error) {
	return q.queues(q.Name, "clear").Req("POST", nil, nil)
}

// Delete message from queue
func (q Queue) DeleteMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId).Req("DELETE", nil, nil)
}

// Reset timeout of message to keep it reserved
func (q Queue) TouchMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId, "touch").Req("POST", nil, nil)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (q Queue) ReleaseMessage(msgId string, delay int64) (err error) {
	in := struct {
		Delay int64 `json:"delay"`
	}{Delay: delay}
	return q.queues(q.Name, "messages", msgId, "release").Req("POST", &in, nil)
}

func (q Queue) MessageSubscribers(msgId string) ([]*Subscriber, error) {
	out := struct {
		Subscribers []*Subscriber `json:"subscribers"`
	}{}
	err := q.queues(q.Name, "messages", msgId, "subscribers").Req("GET", nil, &out)
	return out.Subscribers, err
}

func (q Queue) MessageSubscribersPollN(msgId string, n int) ([]*Subscriber, error) {
	subs, err := q.MessageSubscribers(msgId)
	for {
		time.Sleep(100 * time.Millisecond)
		subs, err = q.MessageSubscribers(msgId)
		if err != nil {
			return subs, err
		}
		if len(subs) >= n && actualPushStatus(subs) {
			return subs, nil
		}
	}
	return subs, err
}

func actualPushStatus(subs []*Subscriber) bool {
	for _, sub := range subs {
		if sub.Status == "queued" {
			return false
		}
	}

	return true
}

func (q Queue) AddAlerts(alerts ...*Alert) (err error) {
	in := struct {
		Alerts []*Alert `json:"alerts"`
	}{Alerts: alerts}
	return q.queues(q.Name, "alerts").Req("POST", &in, nil)
}

func (q Queue) UpdateAlerts(alerts ...*Alert) (err error) {
	in := struct {
		Alerts []*Alert `json:"alerts"`
	}{Alerts: alerts}
	return q.queues(q.Name, "alerts").Req("PUT", &in, nil)
}

func (q Queue) RemoveAllAlerts() (err error) {
	return q.queues(q.Name, "alerts").Req("DELETE", nil, nil)
}

type AlertInfo struct {
	Id         string `json:"id"`
}

func (q Queue) RemoveAlerts(alertIds ...string) (err error) {
	in := struct {
		Alerts []AlertInfo `json:"alerts"`
	}{Alerts: make([]AlertInfo, len(alertIds))}
	for i, alertId := range alertIds {
		(in.Alerts[i]).Id = alertId
	}
	return q.queues(q.Name, "alerts").Req("DELETE", &in, nil)
}

func (q Queue) RemoveAlert(alertId string) (err error) {
	return q.queues(q.Name, "alerts", alertId).Req("DELETE", nil, nil)
}

// Delete message from queue
func (m Message) Delete() (err error) {
	return m.q.DeleteMessage(m.Id)
}

// Reset timeout of message to keep it reserved
func (m Message) Touch() (err error) {
	return m.q.TouchMessage(m.Id)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (m Message) Release(delay int64) (err error) {
	return m.q.ReleaseMessage(m.Id, delay)
}

func (m Message) Subscribers() (interface{}, error) {
	return m.q.MessageSubscribers(m.Id)
}
<commit_msg>remove unnecessary line from delete method<commit_after>// IronMQ (elastic message queue) client library
package mq

import (
	"errors"
	"time"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

type Queue struct {
	Settings config.Settings
	Name     string
}

type QueueSubscriber struct {
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers,omitempty"`
}

type QueueInfo struct {
	Id            string            `json:"id,omitempty"`
	Name          string            `json:"name,omitempty"`
	PushType      string            `json:"push_type,omitempty"`
	Reserved      int               `json:"reserved,omitempty"`
	RetriesDelay  int               `json:"retries,omitempty"`
	Retries       int               `json:"retries_delay,omitempty"`
	Size          int               `json:"size,omitempty"`
	Subscribers   []QueueSubscriber `json:"subscribers,omitempty"`
	Alerts        []Alert           `json:"alerts,omitempty"`
	TotalMessages int               `json:"total_messages,omitempty"`
	ErrorQueue    string            `json:"error_queue,omitempty"`
}

type Message struct {
	Id   string `json:"id,omitempty"`
	Body string `json:"body"`
	// Timeout is the amount of time in seconds allowed for processing the
	// message.
	Timeout int64 `json:"timeout,omitempty"`
	// Delay is the amount of time in seconds to wait before adding the message
	// to the queue.
	Delay int64 `json:"delay,omitempty"`
	q     Queue
}

type PushStatus struct {
	Retried    int    `json:"retried"`
	StatusCode int    `json:"status_code"`
	Status     string `json:"status"`
}

type Subscriber struct {
	Retried    int    `json:"retried"`
	StatusCode int    `json:"status_code"`
	Status     string `json:"status"`
	URL        string `json:"url"`
}

type Alert struct {
	Type       string `json:"type"`
	Direction  string `json:direction`
	Trigger    int    `json:trigger`
	Queue      string `queue`
}

func New(queueName string) *Queue {
	return &Queue{Settings: config.Config("iron_mq"), Name: queueName}
}

func ListQueues(page, perPage int) (queues []Queue, err error) {
	out := []struct {
		Id         string
		Project_id string
		Name       string
	}{}

	q := New("");
	err = q.queues().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	queues = make([]Queue, 0, len(out))
	for _, item := range out {
		queues = append(queues, Queue{
			Settings: q.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (q Queue) queues(s ...string) *api.URL { return api.Action(q.Settings, "queues", s...) }

// This method is left to support backward compatibility.
// This method is replaced by func ListQueues(page, perPage int) (queues []Queue, err error)
func (q Queue) ListQueues(page, perPage int) (queues []Queue, err error) {
	return ListQueues(page, perPage)
}

func (q Queue) Info() (QueueInfo, error) {
	qi := QueueInfo{}
	err := q.queues(q.Name).Req("GET", nil, &qi)
	return qi, err
}

func (q Queue) Update(qi QueueInfo) (QueueInfo, error) {
	out := QueueInfo{}
	err := q.queues(q.Name).Req("POST", qi, &out)
	return out, err
}

func (q Queue) Delete() (bool, error) {
	err := q.queues(q.Name).Req("DELETE", nil, nil)
	success := err == nil
	return success, err
}

type Subscription struct {
	PushType     string
	Retries      int
	RetriesDelay int
}

func (q Queue) Subscribe(subscription Subscription, subscribers ...string) (err error) {
	in := QueueInfo{
		PushType:     subscription.PushType,
		Retries:      subscription.Retries,
		RetriesDelay: subscription.RetriesDelay,
		Subscribers:  make([]QueueSubscriber, len(subscribers)),
	}
	for i, subscriber := range subscribers {
		in.Subscribers[i].URL = subscriber
	}
	return q.queues(q.Name).Req("POST", &in, nil)
}

func (q Queue) PushString(body string) (id string, err error) {
	ids, err := q.PushStrings(body)
	if err != nil {
		return
	}
	return ids[0], nil
}

// Push adds one or more messages to the end of the queue using IronMQ's defaults:
//	timeout - 60 seconds
//	delay - none
//
// Identical to PushMessages with Message{Timeout: 60, Delay: 0}
func (q Queue) PushStrings(bodies ...string) (ids []string, err error) {
	msgs := make([]*Message, 0, len(bodies))
	for _, body := range bodies {
		msgs = append(msgs, &Message{Body: body})
	}

	return q.PushMessages(msgs...)
}

func (q Queue) PushMessage(msg *Message) (id string, err error) {
	ids, err := q.PushMessages(msg)
	if err != nil {
		return
	}
	return ids[0], nil
}

func (q Queue) PushMessages(msgs ...*Message) (ids []string, err error) {
	in := struct {
		Messages []*Message `json:"messages"`
	}{Messages: msgs}

	out := struct {
		IDs []string `json:"ids"`
		Msg string   `json:"msg"`
	}{}

	err = q.queues(q.Name, "messages").Req("POST", &in, &out)
	return out.IDs, err
}

// Get reserves a message from the queue.
// The message will not be deleted, but will be reserved until the timeout
// expires. If the timeout expires before the message is deleted, the message
// will be placed back onto the queue.
// As a result, be sure to Delete a message after you're done with it.
func (q Queue) Get() (msg *Message, err error) {
	msgs, err := q.GetN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// get N messages
func (q Queue) GetN(n int) (msgs []*Message, err error) {
	msgs, err = q.GetNWithTimeout(n, 0)

	return
}

func (q Queue) GetNWithTimeout(n, timeout int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages").
		QueryAdd("n", "%d", n).
		QueryAdd("timeout", "%d", timeout).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

func (q Queue) Peek() (msg *Message, err error) {
	msgs, err := q.PeekN(1)
	if err != nil {
		return
	}

	if len(msgs) > 0 {
		msg = msgs[0]
	} else {
		err = errors.New("Couldn't get a single message")
	}

	return
}

// peek N messages
func (q Queue) PeekN(n int) (msgs []*Message, err error) {
	msgs, err = q.PeekNWithTimeout(n, 0)

	return
}

func (q Queue) PeekNWithTimeout(n, timeout int) (msgs []*Message, err error) {
	out := struct {
		Messages []*Message `json:"messages"`
	}{}

	err = q.queues(q.Name, "messages", "peek").
		QueryAdd("n", "%d", n).
		QueryAdd("timeout", "%d", timeout).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	for _, msg := range out.Messages {
		msg.q = q
	}

	return out.Messages, nil
}

// Delete all messages in the queue
func (q Queue) Clear() (err error) {
	return q.queues(q.Name, "clear").Req("POST", nil, nil)
}

// Delete message from queue
func (q Queue) DeleteMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId).Req("DELETE", nil, nil)
}

// Reset timeout of message to keep it reserved
func (q Queue) TouchMessage(msgId string) (err error) {
	return q.queues(q.Name, "messages", msgId, "touch").Req("POST", nil, nil)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (q Queue) ReleaseMessage(msgId string, delay int64) (err error) {
	in := struct {
		Delay int64 `json:"delay"`
	}{Delay: delay}
	return q.queues(q.Name, "messages", msgId, "release").Req("POST", &in, nil)
}

func (q Queue) MessageSubscribers(msgId string) ([]*Subscriber, error) {
	out := struct {
		Subscribers []*Subscriber `json:"subscribers"`
	}{}
	err := q.queues(q.Name, "messages", msgId, "subscribers").Req("GET", nil, &out)
	return out.Subscribers, err
}

func (q Queue) MessageSubscribersPollN(msgId string, n int) ([]*Subscriber, error) {
	subs, err := q.MessageSubscribers(msgId)
	for {
		time.Sleep(100 * time.Millisecond)
		subs, err = q.MessageSubscribers(msgId)
		if err != nil {
			return subs, err
		}
		if len(subs) >= n && actualPushStatus(subs) {
			return subs, nil
		}
	}
	return subs, err
}

func actualPushStatus(subs []*Subscriber) bool {
	for _, sub := range subs {
		if sub.Status == "queued" {
			return false
		}
	}

	return true
}

func (q Queue) AddAlerts(alerts ...*Alert) (err error) {
	in := struct {
		Alerts []*Alert `json:"alerts"`
	}{Alerts: alerts}
	return q.queues(q.Name, "alerts").Req("POST", &in, nil)
}

func (q Queue) UpdateAlerts(alerts ...*Alert) (err error) {
	in := struct {
		Alerts []*Alert `json:"alerts"`
	}{Alerts: alerts}
	return q.queues(q.Name, "alerts").Req("PUT", &in, nil)
}

func (q Queue) RemoveAllAlerts() (err error) {
	return q.queues(q.Name, "alerts").Req("DELETE", nil, nil)
}

type AlertInfo struct {
	Id         string `json:"id"`
}

func (q Queue) RemoveAlerts(alertIds ...string) (err error) {
	in := struct {
		Alerts []AlertInfo `json:"alerts"`
	}{Alerts: make([]AlertInfo, len(alertIds))}
	for i, alertId := range alertIds {
		(in.Alerts[i]).Id = alertId
	}
	return q.queues(q.Name, "alerts").Req("DELETE", &in, nil)
}

func (q Queue) RemoveAlert(alertId string) (err error) {
	return q.queues(q.Name, "alerts", alertId).Req("DELETE", nil, nil)
}

// Delete message from queue
func (m Message) Delete() (err error) {
	return m.q.DeleteMessage(m.Id)
}

// Reset timeout of message to keep it reserved
func (m Message) Touch() (err error) {
	return m.q.TouchMessage(m.Id)
}

// Put message back in the queue, message will be available after +delay+ seconds.
func (m Message) Release(delay int64) (err error) {
	return m.q.ReleaseMessage(m.Id, delay)
}

func (m Message) Subscribers() (interface{}, error) {
	return m.q.MessageSubscribers(m.Id)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"path"
	"strings"
	"text/template"
	"time"
)

func generateInformation(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	tourName := c.String("tour")
	if tourName == "" {
		cli.ShowSubcommandHelp(c)
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	tourfile := c.String("tour-file")
	if tourfile != "" {
		fileInfo, tourfileClean := getFileOfType(tourfile, false, "tour-file")
		if fileInfo == nil {
			return
		}
		tourfile = tourfileClean
		fmt.Println("Processing tours from:", tourfile)
	}

	fmt.Println("The current tour is:", tourName)
	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	tour := new(Tour)
	tour.Name = tourName
	if tourfile != "" { // tourFile is only for reading "alternate vocalists" into tracks map
		if err := getTourFromTourFile(tourfile, tour); err != nil {
			fmt.Println("[Error]", err)
			if !shouldContinue(c) {
				return
			}
		}
	}

	// Stupid windows
	informationTemplate = strings.Replace(informationTemplate, "\n", "\r\n", -1)

	if mode == "single" {
		generateFile(filepath, fileInfo.Name(), *tour, c.GlobalBool("delete"))
		return
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			generateFile(path.Join(filepath, name), name, *tour, c.GlobalBool("delete"))
		}
	}
}

func generateFile(filepath string, name string, tour Tour, deleteMode bool) {
	outputFilename := path.Join(filepath, name+".txt")
	if deleteMode {
		removeFile(outputFilename, true)
		return
	}

	album := new(AlbumData)
	album.Tour = tour.Name

	useCDNames := false
	folders := make([]string, 0)
	extraFolders := make([]string, 0)
	files := make([]string, 0)

	directoryContents, _ := ioutil.ReadDir(filepath)
	for _, fileinfo := range directoryContents {
		filename := fileinfo.Name()
		isDir := fileinfo.IsDir()
		if isDir {
			if strings.HasPrefix(filename, "CD") {
				folders = append(folders, filename)
				useCDNames = true
			} else {
				extraFolders = append(extraFolders, filename)
			}
		} else if (path.Ext(filename) == ".flac") && !isDir {
			files = append(files, filename)
		}
	}

	iterating := files
	if useCDNames {

		if len(files) > 0 {
			// Contains extra files not in a specific CD
			// Do something!
			fmt.Println("Warning! Files outside CD folders in", filepath)
		}

		files := make([]string, 0)
		subfolders := make([]string, 0)
		for _, dirName := range folders {
			subdirectory, _ := ioutil.ReadDir(path.Join(filepath, dirName))
			for _, fileinfo := range subdirectory {
				subdirPath := path.Join(dirName, fileinfo.Name())
				if isDir := fileinfo.IsDir(); isDir {
					subfolders = append(subfolders, subdirPath)
				} else if (path.Ext(fileinfo.Name()) == ".flac") && !isDir {
					files = append(files, subdirPath)
				}
			}
		}

		if len(subfolders) > 0 {
			fmt.Printf("Skipping! Filepath has depth=3 folders (%s)\n", filepath)
			return
		}

		iterating = files // set it to the new files
		// this means old files won't be iterated
	}

	if len(extraFolders) > 0 {
		// Contains extra folders, do something!
		// There's probably a folder like "Bonus"
		fmt.Println("Warning! Extra non CD folders inside", filepath)
	}

	albumDuration := time.Duration(0) // duration incrementer for the album
	for _, file := range iterating {
		track := getTagsFromFile(path.Join(filepath, file), album, &albumDuration)

		if tour.Tracks != nil {
			_, containsAlternateLeadVocalist := tour.Tracks[track.Title]
			track.HasAlternateLeadVocalist = containsAlternateLeadVocalist
		}

		if useCDNames {
			track.Prefix = strings.TrimPrefix(path.Dir(file), "CD") + "."
		}

		// Finally, add the new track to the album
		album.Tracks = append(album.Tracks, track)
	}

	if len(album.Tracks) == 0 {
		fmt.Println("Could not create album - aborting creation of", outputFilename)
		return
	}

	album.Duration = formatDuration(albumDuration)

	funcMap := template.FuncMap{"wikiescape": wikiescape}
	t := template.Must(template.New("generate").Funcs(funcMap).Parse(informationTemplate))

	fmt.Println("Creating", outputFilename+"...")
	infoFile := createFile(outputFilename)
	defer infoFile.Close()

	if infoFile != nil {
		err := t.Execute(infoFile, album)
		if err != nil {
			panic(err)
		}
	}
}
<commit_msg>address golint for generate_generators.go<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"path"
	"strings"
	"text/template"
	"time"
)

func generateInformation(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	tourName := c.String("tour")
	if tourName == "" {
		cli.ShowSubcommandHelp(c)
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	tourfile := c.String("tour-file")
	if tourfile != "" {
		fileInfo, tourfileClean := getFileOfType(tourfile, false, "tour-file")
		if fileInfo == nil {
			return
		}
		tourfile = tourfileClean
		fmt.Println("Processing tours from:", tourfile)
	}

	fmt.Println("The current tour is:", tourName)
	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	tour := new(Tour)
	tour.Name = tourName
	if tourfile != "" { // tourFile is only for reading "alternate vocalists" into tracks map
		if err := getTourFromTourFile(tourfile, tour); err != nil {
			fmt.Println("[Error]", err)
			if !shouldContinue(c) {
				return
			}
		}
	}

	// Stupid windows
	informationTemplate = strings.Replace(informationTemplate, "\n", "\r\n", -1)

	if mode == "single" {
		generateFile(filepath, fileInfo.Name(), *tour, c.GlobalBool("delete"))
		return
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			generateFile(path.Join(filepath, name), name, *tour, c.GlobalBool("delete"))
		}
	}
}

func generateFile(filepath string, name string, tour Tour, deleteMode bool) {
	outputFilename := path.Join(filepath, name+".txt")
	if deleteMode {
		removeFile(outputFilename, true)
		return
	}

	album := new(AlbumData)
	album.Tour = tour.Name

	var useCDNames bool
	var folders []string
	var extraFolders []string
	var files []string

	directoryContents, _ := ioutil.ReadDir(filepath)
	for _, fileinfo := range directoryContents {
		filename := fileinfo.Name()
		isDir := fileinfo.IsDir()
		if isDir {
			if strings.HasPrefix(filename, "CD") {
				folders = append(folders, filename)
				useCDNames = true
			} else {
				extraFolders = append(extraFolders, filename)
			}
		} else if (path.Ext(filename) == ".flac") && !isDir {
			files = append(files, filename)
		}
	}

	iterating := files
	if useCDNames {

		if len(files) > 0 {
			// Contains extra files not in a specific CD
			// Do something!
			fmt.Println("Warning! Files outside CD folders in", filepath)
		}

		var files []string
		var subfolders []string
		for _, dirName := range folders {
			subdirectory, _ := ioutil.ReadDir(path.Join(filepath, dirName))
			for _, fileinfo := range subdirectory {
				subdirPath := path.Join(dirName, fileinfo.Name())
				if isDir := fileinfo.IsDir(); isDir {
					subfolders = append(subfolders, subdirPath)
				} else if (path.Ext(fileinfo.Name()) == ".flac") && !isDir {
					files = append(files, subdirPath)
				}
			}
		}

		if len(subfolders) > 0 {
			fmt.Printf("Skipping! Filepath has depth=3 folders (%s)\n", filepath)
			return
		}

		iterating = files // set it to the new files
		// this means old files won't be iterated
	}

	if len(extraFolders) > 0 {
		// Contains extra folders, do something!
		// There's probably a folder like "Bonus"
		fmt.Println("Warning! Extra non CD folders inside", filepath)
	}

	albumDuration := time.Duration(0) // duration incrementer for the album
	for _, file := range iterating {
		track := getTagsFromFile(path.Join(filepath, file), album, &albumDuration)

		if tour.Tracks != nil {
			_, containsAlternateLeadVocalist := tour.Tracks[track.Title]
			track.HasAlternateLeadVocalist = containsAlternateLeadVocalist
		}

		if useCDNames {
			track.Prefix = strings.TrimPrefix(path.Dir(file), "CD") + "."
		}

		// Finally, add the new track to the album
		album.Tracks = append(album.Tracks, track)
	}

	if len(album.Tracks) == 0 {
		fmt.Println("Could not create album - aborting creation of", outputFilename)
		return
	}

	album.Duration = formatDuration(albumDuration)

	funcMap := template.FuncMap{"wikiescape": wikiescape}
	t := template.Must(template.New("generate").Funcs(funcMap).Parse(informationTemplate))

	fmt.Println("Creating", outputFilename+"...")
	infoFile := createFile(outputFilename)
	defer infoFile.Close()

	if infoFile != nil {
		err := t.Execute(infoFile, album)
		if err != nil {
			panic(err)
		}
	}
}
<|endoftext|>