<commit_before>package nut

import (
	"testing"
	"time"
)

var stringTests = []struct {
	in  string
	out string
	ok  bool
}{
	// @todo: tests go here
}

var intTests = []struct {
	in  string
	out int64
	ok  bool
}{
	// decimal
	{ "0", 0, true },
	{ "10", 10, true },
	{ "123456789", 123456789, true },

	// hexadecimal
	{ "0x02", 2, true },
	{ "0xff", 255, true },
	{ "0xc", 12, true },

	// octal
	{ "010", 8, true },
	{ "01234567", 342391, true },
	{ "012345678", 0, false },

	// signs
	{ "+0", 0, true },
	{ "-0", 0, true },
	{ "+10", 10, true },
	{ "-0x00", 0, true },
	{ "-0x10", -16, true },
	{ "+01", 1, true },
	{ "-010", -8, true },

	// limits
	{ "9223372036854775807", 1<<63 - 1, true },
	{ "9223372036854775808", 0, false },
	{ "9223372036854775809", 0, false },
	{ "-9223372036854775807", -(1<<63 - 1), true },
	{ "-9223372036854775808", -1 << 63, true },
	{ "-9223372036854775809", 0, false },

	{ "0x7FFFFFFFFFFFFFFF", 1<<63 - 1, true },
	{ "0X8000000000000000", 0, false },
	{ "0X8000000000000001", 0, false },
	{ "-0x7FFFFFFFFFFFFFFF", -(1<<63 - 1), true },
	{ "-0X8000000000000000", -1 << 63, true },
	{ "-0X8000000000000001", 0, false },

	{ "0777777777777777777777", 1<<63 - 1, true },
	{ "01000000000000000000000", 0, false },
	{ "01000000000000000000001", 0, false },
	{ "-0777777777777777777777", -(1<<63 - 1), true },
	{ "-01000000000000000000000", -1 << 63, true },
	{ "-01000000000000000000001", 0, false },

	// invalid
	{ "", 0, false },
	{ "abc", 0, false },
	{ "100 blue", 0, false },
	{ "-0-", 0, false },
	{ "++0", 0, false },
}

var boolTests = []struct {
	in  string
	out bool
	ok  bool
}{
	// truthy
	{ "true", true, true },
	{ "yes", true, true },
	{ "on", true, true },

	// falsy
	{ "false", false, true },
	{ "no", false, true },
	{ "off", false, true },

	// invalid
	{ "", false, false },
	{ "y", false, false },
	{ "foo", false, false },
}

var durationTests = []struct {
	in  string
	out time.Duration
	ok  bool
}{
	// @todo: tests go here
}

var timeTests = []struct {
	in  string
	out *time.Time
	ok  bool
}{
	// @todo: tests go here
}

func TestParseString(t *testing.T) {
	for _, test := range stringTests {
		t.Logf("> parseString(%#v)\n", test.in)
		out, ok := parseString(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test.out)
		}
	}
}

func TestParseInt(t *testing.T) {
	for _, test := range intTests {
		t.Logf("> parseInt(%#v)\n", test.in)
		out, ok := parseInt(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test.out)
		}
	}
}

func TestParseBool(t *testing.T) {
	for _, test := range boolTests {
		t.Logf("> parseBool(%#v)\n", test.in)
		out, ok := parseBool(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test)
		}
	}
}

func TestParseDuration(t *testing.T) {
	for _, test := range durationTests {
		t.Logf("> parseDuration(%#v)\n", test.in)
		out, ok := parseDuration(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out.String(), test.out.String())
		}
	}
}

func TestParseTime(t *testing.T) {
	for _, test := range timeTests {
		t.Logf("> parseTime(%#v)\n", test.in)
		out, ok := parseTime(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out == nil {
			if test.out != nil {
				t.Fatalf("returned nil\n")
			}
			return
		}

		if !out.Equal(*test.out) {
			t.Fatalf("%#v != %#v\n", out.String(), test.out.String())
		}
	}
}

// Convenience function for creating a `time.Time` struct of a certain value,
// then returning a pointer to it.
func t(value string) *time.Time {
	t, err := time.Parse("2006-01-02 15:04:05 -07:00", value)
	if err != nil {
		panic(err)
	}

	return &t
}
<commit_msg>go fmt<commit_after>package nut

import (
	"testing"
	"time"
)

var stringTests = []struct {
	in  string
	out string
	ok  bool
}{
	// @todo: tests go here
}

var intTests = []struct {
	in  string
	out int64
	ok  bool
}{
	// decimal
	{"0", 0, true},
	{"10", 10, true},
	{"123456789", 123456789, true},

	// hexadecimal
	{"0x02", 2, true},
	{"0xff", 255, true},
	{"0xc", 12, true},

	// octal
	{"010", 8, true},
	{"01234567", 342391, true},
	{"012345678", 0, false},

	// signs
	{"+0", 0, true},
	{"-0", 0, true},
	{"+10", 10, true},
	{"-0x00", 0, true},
	{"-0x10", -16, true},
	{"+01", 1, true},
	{"-010", -8, true},

	// limits
	{"9223372036854775807", 1<<63 - 1, true},
	{"9223372036854775808", 0, false},
	{"9223372036854775809", 0, false},
	{"-9223372036854775807", -(1<<63 - 1), true},
	{"-9223372036854775808", -1 << 63, true},
	{"-9223372036854775809", 0, false},

	{"0x7FFFFFFFFFFFFFFF", 1<<63 - 1, true},
	{"0X8000000000000000", 0, false},
	{"0X8000000000000001", 0, false},
	{"-0x7FFFFFFFFFFFFFFF", -(1<<63 - 1), true},
	{"-0X8000000000000000", -1 << 63, true},
	{"-0X8000000000000001", 0, false},

	{"0777777777777777777777", 1<<63 - 1, true},
	{"01000000000000000000000", 0, false},
	{"01000000000000000000001", 0, false},
	{"-0777777777777777777777", -(1<<63 - 1), true},
	{"-01000000000000000000000", -1 << 63, true},
	{"-01000000000000000000001", 0, false},

	// invalid
	{"", 0, false},
	{"abc", 0, false},
	{"100 blue", 0, false},
	{"-0-", 0, false},
	{"++0", 0, false},
}

var boolTests = []struct {
	in  string
	out bool
	ok  bool
}{
	// truthy
	{"true", true, true},
	{"yes", true, true},
	{"on", true, true},

	// falsy
	{"false", false, true},
	{"no", false, true},
	{"off", false, true},

	// invalid
	{"", false, false},
	{"y", false, false},
	{"foo", false, false},
}

var durationTests = []struct {
	in  string
	out time.Duration
	ok  bool
}{
	// @todo: tests go here
}

var timeTests = []struct {
	in  string
	out *time.Time
	ok  bool
}{
	// @todo: tests go here
}

func TestParseString(t *testing.T) {
	for _, test := range stringTests {
		t.Logf("> parseString(%#v)\n", test.in)
		out, ok := parseString(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test.out)
		}
	}
}

func TestParseInt(t *testing.T) {
	for _, test := range intTests {
		t.Logf("> parseInt(%#v)\n", test.in)
		out, ok := parseInt(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test.out)
		}
	}
}

func TestParseBool(t *testing.T) {
	for _, test := range boolTests {
		t.Logf("> parseBool(%#v)\n", test.in)
		out, ok := parseBool(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out, test)
		}
	}
}

func TestParseDuration(t *testing.T) {
	for _, test := range durationTests {
		t.Logf("> parseDuration(%#v)\n", test.in)
		out, ok := parseDuration(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out != test.out {
			t.Fatalf("%#v != %#v\n", out.String(), test.out.String())
		}
	}
}

func TestParseTime(t *testing.T) {
	for _, test := range timeTests {
		t.Logf("> parseTime(%#v)\n", test.in)
		out, ok := parseTime(test.in)

		if ok != test.ok {
			if test.ok {
				t.Fatalf("parsing failed unexpectedly\n")
			} else {
				t.Fatalf("parsing should not succeed\n")
			}
		}

		if out == nil {
			if test.out != nil {
				t.Fatalf("returned nil\n")
			}
			return
		}

		if !out.Equal(*test.out) {
			t.Fatalf("%#v != %#v\n", out.String(), test.out.String())
		}
	}
}

// Convenience function for creating a `time.Time` struct of a certain value,
// then returning a pointer to it.
func t(value string) *time.Time {
	t, err := time.Parse("2006-01-02 15:04:05 -07:00", value)
	if err != nil {
		panic(err)
	}

	return &t
}
<|endoftext|>


<commit_before>// Package forms provides support for HTML forms and its input elements.
package forms

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/ChristianSiegert/go-packages/html/elements"
	"github.com/ChristianSiegert/go-packages/validation"
)

// Form represents an HTML form.
type Form struct {
	request *http.Request

	// ValidationFields is a map of field names and their corresponding
	// validation.Item. Used to get information about the items’ validation
	// rules.
	ValidationFields validation.Items

	// ValidationMessages contains a validation error message for each form
	// field that has an invalid value.
	ValidationMessages validation.Messages
}

// New returns a new instance of Form.
func New(request *http.Request) (*Form, error) {
	return &Form{
		request:            request,
		ValidationMessages: validation.Messages{},
	}, nil
}

// Error returns a
 element that contains the
// validation error message as text.
func (f *Form) Error(fieldName string) *elements.Element {
	validationMessage, ok := f.ValidationMessages[fieldName]
	if !ok || validationMessage == "" {
		return nil
	}

	element := &elements.Element{
		Attributes: map[string]string{
			"class": "validation-error",
		},
		HasEndTag: true,
		TagName:   "div",
		Text:      validationMessage,
	}
	return element
}

// HasError returns whether the field’s value is invalid.
func (f *Form) HasError(fieldName string) bool {
	_, ok := f.ValidationMessages[fieldName]
	return ok
}

// Input returns an <input> element.
func (f *Form) Input(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		TagName: "input",
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	if placeholder != "" {
		element.Attributes["placeholder"] = placeholder
	}

	if value := f.request.FormValue(fieldName); value != "" {
		element.Attributes["value"] = strings.TrimSpace(value)
	}

	for i, length := 0, len(attributes); i < length; i += 2 {
		if i+1 < length {
			element.AddAttributeValue(attributes[i], attributes[i+1])
		} else {
			element.AddAttributeValue(attributes[i], "")
		}
	}

	if f.ValidationFields == nil {
		return element
	}

	if field, ok := f.ValidationFields[fieldName]; ok {
		for _, rule := range field.Rules {
			if rule.Type == validation.RuleTypeRequired {
				element.Attributes["required"] = ""
			} else if rule.Type == validation.RuleTypeMaxLength {
				if maxLength, ok := rule.Args[0].(int); ok && maxLength > 0 {
					element.Attributes["maxlength"] = strconv.FormatUint(uint64(maxLength), 10)
				}
			} else if rule.Type == validation.RuleTypeMinLength {
				if minLength, ok := rule.Args[0].(int); ok && minLength > 0 {
					element.Attributes["minlength"] = strconv.FormatUint(uint64(minLength), 10)
				}
			}
		}
	}

	return element
}

// Checkbox returns an <input type="checkbox"> element.
func (f *Form) Checkbox(fieldName, value string) *elements.Element {
	element := f.Input(fieldName, "")
	element.Attributes["id"] = fieldName + "-" + value
	element.Attributes["type"] = "checkbox"
	element.Attributes["value"] = value

	if values, ok := f.request.Form[fieldName]; ok {
		for _, v := range values {
			if v == value {
				element.Attributes["checked"] = ""
				break
			}
		}
	}

	return element
}

// Email returns an <input type="email"> element.
func (f *Form) Email(fieldName, placeholder string) *elements.Element {
	element := f.Input(fieldName, placeholder)
	element.Attributes["type"] = "email"

	if _, ok := element.Attributes["maxlength"]; !ok {
		element.Attributes["maxlength"] = "254"
	}
	return element
}

// Number returns an <input type="number"> element.
func (f *Form) Number(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "number"
	return element
}

// Radio returns an <input type="radio"> element.
func (f *Form) Radio(fieldName, value string) *elements.Element {
	element := f.Input(fieldName, "")
	element.Attributes["id"] = fieldName + "-" + value
	element.Attributes["type"] = "radio"
	element.Attributes["value"] = value

	if f.request.FormValue(fieldName) == value {
		element.Attributes["checked"] = ""
	}
	return element
}

// Password returns an <input type="password"> element.
func (f *Form) Password(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "password"
	return element
}

// Search returns an <input type="search"> element.
func (f *Form) Search(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "search"
	return element
}

// Text returns an <input type="text"> element.
func (f *Form) Text(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "text"
	return element
}

// Select returns a <select> element.
func (f *Form) Select(fieldName string, options []*Option) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		HasEndTag: true,
		TagName:   "select",
	}

	if len(options) > 0 {
		element.Children = make([]*elements.Element, 0, len(options))

		for _, option := range options {
			optionElement := f.Option(option.Value, option.Label)
			element.Children = append(element.Children, optionElement)

			if postedValue := f.request.FormValue(fieldName); postedValue == option.Value {
				optionElement.SetAttributeValue("selected", "")
			}
		}
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	return element
}

// Option returns an <option> element.
func (f *Form) Option(value, label string) *elements.Element {
	return &elements.Element{
		Attributes: map[string]string{
			"value": value,
		},
		HasEndTag: true,
		TagName:   "option",
		Text:      label,
	}
}

// Textarea returns a <textarea> element.
func (f *Form) Textarea(fieldName, placeholder string) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		HasEndTag: true,
		TagName:   "textarea",
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	if placeholder != "" {
		element.Attributes["placeholder"] = placeholder
	}

	if value := f.request.FormValue(fieldName); value != "" {
		element.Text = strings.TrimSpace(value)
	}

	if f.ValidationFields == nil {
		return element
	}

	if field, ok := f.ValidationFields[fieldName]; ok {
		for _, rule := range field.Rules {
			if rule.Type == validation.RuleTypeRequired {
				element.Attributes["required"] = ""
			} else if rule.Type == validation.RuleTypeMaxLength {
				if maxLength, ok := rule.Args[0].(int); ok && maxLength > 0 {
					element.Attributes["maxlength"] = strconv.FormatUint(uint64(maxLength), 10)
				}
			} else if rule.Type == validation.RuleTypeMinLength {
				if minLength, ok := rule.Args[0].(int); ok && minLength > 0 {
					element.Attributes["minlength"] = strconv.FormatUint(uint64(minLength), 10)
				}
			}
		}
	}

	return element
}
<commit_msg>Added variadic argument “attributes” to forms.Form.Textarea.<commit_after>// Package forms provides support for HTML forms and its input elements.
package forms

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/ChristianSiegert/go-packages/html/elements"
	"github.com/ChristianSiegert/go-packages/validation"
)

// Form represents an HTML form.
type Form struct {
	request *http.Request

	// ValidationFields is a map of field names and their corresponding
	// validation.Item. Used to get information about the items’ validation
	// rules.
	ValidationFields validation.Items

	// ValidationMessages contains a validation error message for each form
	// field that has an invalid value.
	ValidationMessages validation.Messages
}

// New returns a new instance of Form.
func New(request *http.Request) (*Form, error) {
	return &Form{
		request:            request,
		ValidationMessages: validation.Messages{},
	}, nil
}

// Error returns a
 element that contains the
// validation error message as text.
func (f *Form) Error(fieldName string) *elements.Element {
	validationMessage, ok := f.ValidationMessages[fieldName]
	if !ok || validationMessage == "" {
		return nil
	}

	element := &elements.Element{
		Attributes: map[string]string{
			"class": "validation-error",
		},
		HasEndTag: true,
		TagName:   "div",
		Text:      validationMessage,
	}
	return element
}

// HasError returns whether the field’s value is invalid.
func (f *Form) HasError(fieldName string) bool {
	_, ok := f.ValidationMessages[fieldName]
	return ok
}

// Input returns an <input> element.
func (f *Form) Input(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		TagName: "input",
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	if placeholder != "" {
		element.Attributes["placeholder"] = placeholder
	}

	if value := f.request.FormValue(fieldName); value != "" {
		element.Attributes["value"] = strings.TrimSpace(value)
	}

	for i, length := 0, len(attributes); i < length; i += 2 {
		if i+1 < length {
			element.AddAttributeValue(attributes[i], attributes[i+1])
		} else {
			element.AddAttributeValue(attributes[i], "")
		}
	}

	if f.ValidationFields == nil {
		return element
	}

	if field, ok := f.ValidationFields[fieldName]; ok {
		for _, rule := range field.Rules {
			if rule.Type == validation.RuleTypeRequired {
				element.Attributes["required"] = ""
			} else if rule.Type == validation.RuleTypeMaxLength {
				if maxLength, ok := rule.Args[0].(int); ok && maxLength > 0 {
					element.Attributes["maxlength"] = strconv.FormatUint(uint64(maxLength), 10)
				}
			} else if rule.Type == validation.RuleTypeMinLength {
				if minLength, ok := rule.Args[0].(int); ok && minLength > 0 {
					element.Attributes["minlength"] = strconv.FormatUint(uint64(minLength), 10)
				}
			}
		}
	}

	return element
}

// Checkbox returns an <input type="checkbox"> element.
func (f *Form) Checkbox(fieldName, value string) *elements.Element {
	element := f.Input(fieldName, "")
	element.Attributes["id"] = fieldName + "-" + value
	element.Attributes["type"] = "checkbox"
	element.Attributes["value"] = value

	if values, ok := f.request.Form[fieldName]; ok {
		for _, v := range values {
			if v == value {
				element.Attributes["checked"] = ""
				break
			}
		}
	}

	return element
}

// Email returns an <input type="email"> element.
func (f *Form) Email(fieldName, placeholder string) *elements.Element {
	element := f.Input(fieldName, placeholder)
	element.Attributes["type"] = "email"

	if _, ok := element.Attributes["maxlength"]; !ok {
		element.Attributes["maxlength"] = "254"
	}
	return element
}

// Number returns an <input type="number"> element.
func (f *Form) Number(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "number"
	return element
}

// Radio returns an <input type="radio"> element.
func (f *Form) Radio(fieldName, value string) *elements.Element {
	element := f.Input(fieldName, "")
	element.Attributes["id"] = fieldName + "-" + value
	element.Attributes["type"] = "radio"
	element.Attributes["value"] = value

	if f.request.FormValue(fieldName) == value {
		element.Attributes["checked"] = ""
	}
	return element
}

// Password returns an <input type="password"> element.
func (f *Form) Password(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "password"
	return element
}

// Search returns an <input type="search"> element.
func (f *Form) Search(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "search"
	return element
}

// Text returns an <input type="text"> element.
func (f *Form) Text(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := f.Input(fieldName, placeholder, attributes...)
	element.Attributes["type"] = "text"
	return element
}

// Select returns a <select> element.
func (f *Form) Select(fieldName string, options []*Option) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		HasEndTag: true,
		TagName:   "select",
	}

	if len(options) > 0 {
		element.Children = make([]*elements.Element, 0, len(options))

		for _, option := range options {
			optionElement := f.Option(option.Value, option.Label)
			element.Children = append(element.Children, optionElement)

			if postedValue := f.request.FormValue(fieldName); postedValue == option.Value {
				optionElement.SetAttributeValue("selected", "")
			}
		}
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	return element
}

// Option returns an <option> element.
func (f *Form) Option(value, label string) *elements.Element {
	return &elements.Element{
		Attributes: map[string]string{
			"value": value,
		},
		HasEndTag: true,
		TagName:   "option",
		Text:      label,
	}
}

// Textarea returns a <textarea> element.
func (f *Form) Textarea(fieldName, placeholder string, attributes ...string) *elements.Element {
	element := &elements.Element{
		Attributes: map[string]string{
			"id":   fieldName,
			"name": fieldName,
		},
		HasEndTag: true,
		TagName:   "textarea",
	}

	if f.HasError(fieldName) {
		element.Attributes["class"] = "error"
	}

	if placeholder != "" {
		element.Attributes["placeholder"] = placeholder
	}

	if value := f.request.FormValue(fieldName); value != "" {
		element.Text = strings.TrimSpace(value)
	}

	for i, length := 0, len(attributes); i < length; i += 2 {
		if i+1 < length {
			element.AddAttributeValue(attributes[i], attributes[i+1])
		} else {
			element.AddAttributeValue(attributes[i], "")
		}
	}

	if f.ValidationFields == nil {
		return element
	}

	if field, ok := f.ValidationFields[fieldName]; ok {
		for _, rule := range field.Rules {
			if rule.Type == validation.RuleTypeRequired {
				element.Attributes["required"] = ""
			} else if rule.Type == validation.RuleTypeMaxLength {
				if maxLength, ok := rule.Args[0].(int); ok && maxLength > 0 {
					element.Attributes["maxlength"] = strconv.FormatUint(uint64(maxLength), 10)
				}
			} else if rule.Type == validation.RuleTypeMinLength {
				if minLength, ok := rule.Args[0].(int); ok && minLength > 0 {
					element.Attributes["minlength"] = strconv.FormatUint(uint64(minLength), 10)
				}
			}
		}
	}

	return element
}
<|endoftext|>


<commit_before>package network

import (
	"fmt"
	"net"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/validate"
)

// physical represents a LXD physical network.
type physical struct {
	common
}

// Type returns the network type.
func (n *physical) Type() string {
	return "physical"
}

// DBType returns the network type DB ID.
func (n *physical) DBType() db.NetworkType {
	return db.NetworkTypePhysical
}

// Validate network config.
func (n *physical) Validate(config map[string]string) error {
	rules := map[string]func(value string) error{
		"parent":                      validate.Required(validate.IsNotEmpty, validInterfaceName),
		"mtu":                         validate.Optional(validate.IsNetworkMTU),
		"vlan":                        validate.Optional(validate.IsNetworkVLAN),
		"maas.subnet.ipv4":            validate.IsAny,
		"maas.subnet.ipv6":            validate.IsAny,
		"ipv4.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV4),
		"ipv6.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV6),
		"ipv4.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV4List),
		"ipv6.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV6List),
		"dns.nameservers":             validate.Optional(validate.IsNetworkAddressList),
		"volatile.last_state.created": validate.Optional(validate.IsBool),
	}

	err := n.validate(config, rules)
	if err != nil {
		return err
	}

	return nil
}

// checkParentUse checks if parent is already in use by another network or instance device.
func (n *physical) checkParentUse(ourConfig map[string]string) error {
	// Get all managed networks across all projects.
	var err error
	var projectNetworks map[string]map[int64]api.Network

	err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		projectNetworks, err = tx.GetNonPendingNetworks()
		return err
	})
	if err != nil {
		return errors.Wrapf(err, "Failed to load all networks")
	}

	for projectName, networks := range projectNetworks {
		if projectName != project.Default {
			continue // Only default project networks can possible reference a physical interface.
		}

		for _, network := range networks {
			if network.Name == n.name {
				continue // Ignore our own DB record.
			}

			// Check if another network is using our parent.
			if network.Config["parent"] == ourConfig["parent"] {
				// If either network doesn't specify a vlan, or both specify same vlan,
				// then we can't use this parent.
				if (network.Config["vlan"] == "" || ourConfig["vlan"] == "") || network.Config["vlan"] == ourConfig["vlan"] {
					return fmt.Errorf("Parent interface %q in use by another network", ourConfig["parent"])
				}
			}
		}
	}

	return nil
}

// Create checks whether the referenced parent interface is used by other networks or instance devices, as we
// need to have exclusive access to the interface.
func (n *physical) Create(clientType cluster.ClientType) error {
	n.logger.Debug("Create", log.Ctx{"clientType": clientType, "config": n.config})

	// We only need to check in the database once, not on every clustered node.
	if clientType == cluster.ClientTypeNormal {
		err := n.checkParentUse(n.config)
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete deletes a network.
func (n *physical) Delete(clientType cluster.ClientType) error {
	n.logger.Debug("Delete", log.Ctx{"clientType": clientType})

	err := n.Stop()
	if err != nil {
		return err
	}

	return n.common.delete(clientType)
}

// Rename renames a network.
func (n *physical) Rename(newName string) error {
	n.logger.Debug("Rename", log.Ctx{"newName": newName})

	// Rename common steps.
	err := n.common.rename(newName)
	if err != nil {
		return err
	}

	return nil
}

// Start starts is a no-op.
func (n *physical) Start() error {
	n.logger.Debug("Start")

	if n.status == api.NetworkStatusPending {
		return fmt.Errorf("Cannot start pending network")
	}

	revert := revert.New()
	defer revert.Fail()

	hostName := GetHostDevice(n.config["parent"], n.config["vlan"])
	created, err := VLANInterfaceCreate(n.config["parent"], hostName, n.config["vlan"])
	if err != nil {
		return err
	}
	if created {
		revert.Add(func() { InterfaceRemove(hostName) })
	}

	// Set the MTU.
	if n.config["mtu"] != "" {
		err = InterfaceSetMTU(hostName, n.config["mtu"])
		if err != nil {
			return err
		}
	}

	// Record if we created this device or not (if we have not already recorded that we created it previously),
	// so it can be removed on stop. This way we won't overwrite the setting on LXD restart.
	if !shared.IsTrue(n.config["volatile.last_state.created"]) {
		n.config["volatile.last_state.created"] = fmt.Sprintf("%t", created)
		err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
			return tx.UpdateNetwork(n.id, n.description, n.config)
		})
		if err != nil {
			return errors.Wrapf(err, "Failed saving volatile config")
		}
	}

	revert.Success()
	return nil
}

// Stop stops is a no-op.
func (n *physical) Stop() error {
	n.logger.Debug("Stop")

	hostName := GetHostDevice(n.config["parent"], n.config["vlan"])

	// Only try and remove created VLAN interfaces.
	if n.config["vlan"] != "" && shared.IsTrue(n.config["volatile.last_state.created"]) && InterfaceExists(hostName) {
		err := InterfaceRemove(hostName)
		if err != nil {
			return err
		}
	}

	// Reset MTU back to 1500 if overridden in config.
	if n.config["mtu"] != "" && InterfaceExists(hostName) {
		err := InterfaceSetMTU(hostName, "1500")
		if err != nil {
			return err
		}
	}

	// Remove last state config.
	delete(n.config, "volatile.last_state.created")
	err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		return tx.UpdateNetwork(n.id, n.description, n.config)
	})
	if err != nil {
		return errors.Wrapf(err, "Failed removing volatile config")
	}

	return nil
}

// Update updates the network. Accepts notification boolean indicating if this update request is coming from a
// cluster notification, in which case do not update the database, just apply local changes needed.
func (n *physical) Update(newNetwork api.NetworkPut, targetNode string, clientType cluster.ClientType) error {
	n.logger.Debug("Update", log.Ctx{"clientType": clientType, "newNetwork": newNetwork})

	dbUpdateNeeeded, changedKeys, oldNetwork, err := n.common.configChanged(newNetwork)
	if err != nil {
		return err
	}

	if !dbUpdateNeeeded {
		return nil // Nothing changed.
	}

	revert := revert.New()
	defer revert.Fail()

	hostNameChanged := shared.StringInSlice("vlan", changedKeys) || shared.StringInSlice("parent", changedKeys)

	// We only need to check in the database once, not on every clustered node.
	if clientType == cluster.ClientTypeNormal {
		if hostNameChanged {
			isUsed, err := n.IsUsed()
			if isUsed || err != nil {
				return fmt.Errorf("Cannot update network host name when in use")
			}

			err = n.checkParentUse(newNetwork.Config)
			if err != nil {
				return err
			}
		}
	}

	if hostNameChanged {
		err = n.Stop()
		if err != nil {
			return err
		}

		// Remove the volatile last state from submitted new config if present.
		delete(newNetwork.Config, "volatile.last_state.created")
	}

	// Define a function which reverts everything.
	revert.Add(func() {
		// Reset changes to all nodes and database.
		n.common.update(oldNetwork, targetNode, clientType)
	})

	// Apply changes to all nodes and databse.
	err = n.common.update(newNetwork, targetNode, clientType)
	if err != nil {
		return err
	}

	err = n.Start()
	if err != nil {
		return err
	}

	revert.Success()
	return nil
}

// DHCPv4Subnet returns the DHCPv4 subnet (if DHCP is enabled on network).
func (n *physical) DHCPv4Subnet() *net.IPNet {
	_, subnet, err := net.ParseCIDR(n.config["ipv4.gateway"])
	if err != nil {
		return nil
	}

	return subnet
}

// DHCPv6Subnet returns the DHCPv6 subnet (if DHCP or SLAAC is enabled on network).
func (n *physical) DHCPv6Subnet() *net.IPNet {
	_, subnet, err := net.ParseCIDR(n.config["ipv6.gateway"])
	if err != nil {
		return nil
	}

	return subnet
}
<commit_msg>lxd/network/driver/physical: Change checkParentUse to return a bool if in use<commit_after>package network

import (
	"fmt"
	"net"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/validate"
)

// physical represents a LXD physical network.
type physical struct {
	common
}

// Type returns the network type.
func (n *physical) Type() string {
	return "physical"
}

// DBType returns the network type DB ID.
func (n *physical) DBType() db.NetworkType {
	return db.NetworkTypePhysical
}

// Validate network config.
func (n *physical) Validate(config map[string]string) error {
	rules := map[string]func(value string) error{
		"parent":                      validate.Required(validate.IsNotEmpty, validInterfaceName),
		"mtu":                         validate.Optional(validate.IsNetworkMTU),
		"vlan":                        validate.Optional(validate.IsNetworkVLAN),
		"maas.subnet.ipv4":            validate.IsAny,
		"maas.subnet.ipv6":            validate.IsAny,
		"ipv4.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV4),
		"ipv6.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV6),
		"ipv4.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV4List),
		"ipv6.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV6List),
		"dns.nameservers":             validate.Optional(validate.IsNetworkAddressList),
		"volatile.last_state.created": validate.Optional(validate.IsBool),
	}

	err := n.validate(config, rules)
	if err != nil {
		return err
	}

	return nil
}

// checkParentUse checks if parent is already in use by another network or instance device.
func (n *physical) checkParentUse(ourConfig map[string]string) (bool, error) {
	// Get all managed networks across all projects.
	var err error
	var projectNetworks map[string]map[int64]api.Network

	err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		projectNetworks, err = tx.GetNonPendingNetworks()
		return err
	})
	if err != nil {
		return false, errors.Wrapf(err, "Failed to load all networks")
	}

	for projectName, networks := range projectNetworks {
		if projectName != project.Default {
			continue // Only default project networks can possibly reference a physical interface.
		}

		for _, network := range networks {
			if network.Name == n.name {
				continue // Ignore our own DB record.
			}

			// Check if another network is using our parent.
			if network.Config["parent"] == ourConfig["parent"] {
				// If either network doesn't specify a vlan, or both specify same vlan,
				// then we can't use this parent.
				if (network.Config["vlan"] == "" || ourConfig["vlan"] == "") || network.Config["vlan"] == ourConfig["vlan"] {
					return true, nil
				}
			}
		}
	}

	return false, nil
}

// Create checks whether the referenced parent interface is used by other networks or instance devices, as we
// need to have exclusive access to the interface.
func (n *physical) Create(clientType cluster.ClientType) error {
	n.logger.Debug("Create", log.Ctx{"clientType": clientType, "config": n.config})

	// We only need to check in the database once, not on every clustered node.
	if clientType == cluster.ClientTypeNormal {
		inUse, err := n.checkParentUse(n.config)
		if err != nil {
			return err
		}
		if inUse {
			return fmt.Errorf("Parent interface %q in use by another network", n.config["parent"])
		}
	}

	return nil
}

// Delete deletes a network.
func (n *physical) Delete(clientType cluster.ClientType) error {
	n.logger.Debug("Delete", log.Ctx{"clientType": clientType})

	err := n.Stop()
	if err != nil {
		return err
	}

	return n.common.delete(clientType)
}

// Rename renames a network.
func (n *physical) Rename(newName string) error {
	n.logger.Debug("Rename", log.Ctx{"newName": newName})

	// Rename common steps.
	err := n.common.rename(newName)
	if err != nil {
		return err
	}

	return nil
}

// Start starts is a no-op.
func (n *physical) Start() error {
	n.logger.Debug("Start")

	if n.status == api.NetworkStatusPending {
		return fmt.Errorf("Cannot start pending network")
	}

	revert := revert.New()
	defer revert.Fail()

	hostName := GetHostDevice(n.config["parent"], n.config["vlan"])
	created, err := VLANInterfaceCreate(n.config["parent"], hostName, n.config["vlan"])
	if err != nil {
		return err
	}
	if created {
		revert.Add(func() { InterfaceRemove(hostName) })
	}

	// Set the MTU.
	if n.config["mtu"] != "" {
		err = InterfaceSetMTU(hostName, n.config["mtu"])
		if err != nil {
			return err
		}
	}

	// Record if we created this device or not (if we have not already recorded that we created it previously),
	// so it can be removed on stop. This way we won't overwrite the setting on LXD restart.
	if !shared.IsTrue(n.config["volatile.last_state.created"]) {
		n.config["volatile.last_state.created"] = fmt.Sprintf("%t", created)
		err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
			return tx.UpdateNetwork(n.id, n.description, n.config)
		})
		if err != nil {
			return errors.Wrapf(err, "Failed saving volatile config")
		}
	}

	revert.Success()
	return nil
}

// Stop stops is a no-op.
func (n *physical) Stop() error {
	n.logger.Debug("Stop")

	hostName := GetHostDevice(n.config["parent"], n.config["vlan"])

	// Only try and remove created VLAN interfaces.
	if n.config["vlan"] != "" && shared.IsTrue(n.config["volatile.last_state.created"]) && InterfaceExists(hostName) {
		err := InterfaceRemove(hostName)
		if err != nil {
			return err
		}
	}

	// Reset MTU back to 1500 if overridden in config.
	if n.config["mtu"] != "" && InterfaceExists(hostName) {
		err := InterfaceSetMTU(hostName, "1500")
		if err != nil {
			return err
		}
	}

	// Remove last state config.
	delete(n.config, "volatile.last_state.created")
	err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		return tx.UpdateNetwork(n.id, n.description, n.config)
	})
	if err != nil {
		return errors.Wrapf(err, "Failed removing volatile config")
	}

	return nil
}

// Update updates the network. Accepts notification boolean indicating if this update request is coming from a
// cluster notification, in which case do not update the database, just apply local changes needed.
func (n *physical) Update(newNetwork api.NetworkPut, targetNode string, clientType cluster.ClientType) error {
	n.logger.Debug("Update", log.Ctx{"clientType": clientType, "newNetwork": newNetwork})

	dbUpdateNeeeded, changedKeys, oldNetwork, err := n.common.configChanged(newNetwork)
	if err != nil {
		return err
	}

	if !dbUpdateNeeeded {
		return nil // Nothing changed.
	}

	revert := revert.New()
	defer revert.Fail()

	hostNameChanged := shared.StringInSlice("vlan", changedKeys) || shared.StringInSlice("parent", changedKeys)

	// We only need to check in the database once, not on every clustered node.
	if clientType == cluster.ClientTypeNormal {
		if hostNameChanged {
			isUsed, err := n.IsUsed()
			if isUsed || err != nil {
				return fmt.Errorf("Cannot update network host name when in use")
			}

			inUse, err := n.checkParentUse(newNetwork.Config)
			if err != nil {
				return err
			}
			if inUse {
				return fmt.Errorf("Parent interface %q in use by another network", newNetwork.Config["parent"])
			}
		}
	}

	if hostNameChanged {
		err = n.Stop()
		if err != nil {
			return err
		}

		// Remove the volatile last state from submitted new config if present.
		delete(newNetwork.Config, "volatile.last_state.created")
	}

	// Define a function which reverts everything.
	revert.Add(func() {
		// Reset changes to all nodes and database.
		n.common.update(oldNetwork, targetNode, clientType)
	})

	// Apply changes to all nodes and databse.
	err = n.common.update(newNetwork, targetNode, clientType)
	if err != nil {
		return err
	}

	err = n.Start()
	if err != nil {
		return err
	}

	revert.Success()
	return nil
}

// DHCPv4Subnet returns the DHCPv4 subnet (if DHCP is enabled on network).
func (n *physical) DHCPv4Subnet() *net.IPNet {
	_, subnet, err := net.ParseCIDR(n.config["ipv4.gateway"])
	if err != nil {
		return nil
	}

	return subnet
}

// DHCPv6Subnet returns the DHCPv6 subnet (if DHCP or SLAAC is enabled on network).
func (n *physical) DHCPv6Subnet() *net.IPNet {
	_, subnet, err := net.ParseCIDR(n.config["ipv6.gateway"])
	if err != nil {
		return nil
	}

	return subnet
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
)

// PlackPlugin mackerel plugin for Plack
type PlackPlugin struct {
	URI         string
	Prefix      string
	LabelPrefix string
}

// {
//   "Uptime": "1410520211",
//   "TotalAccesses": "2",
//   "IdleWorkers": "2",
//   "TotalKbytes": "0",
//   "BusyWorkers": "1",
//   "stats": [
//     {
//       "pid": 11062,
//       "method": "GET",
//       "ss": 51,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "status": "_",
//       "uri": "/server-status?json"
//     },
//     {
//       "ss": 41,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "pid": 11063,
//       "method": "GET",
//       "status": "_",
//       "uri": "/server-status?json"
//     },
//     {
//       "ss": 0,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "pid": 11064,
//       "method": "GET",
//       "status": "A",
//       "uri": "/server-status?json"
//     }
//   ]
// }

// field types vary between versions

// PlackRequest request
type PlackRequest struct{}

// PlackServerStatus sturct for server-status's json
type PlackServerStatus struct {
	Uptime        string         `json:"Uptime"`
	TotalAccesses string         `json:"TotalAccesses"`
	TotalKbytes   string         `json:"TotalKbytes"`
	BusyWorkers   string         `json:"BusyWorkers"`
	IdleWorkers   string         `json:"IdleWorkers"`
	Stats         []PlackRequest `json:"stats"`
}

// FetchMetrics interface for mackerelplugin
func (p PlackPlugin) FetchMetrics() (map[string]interface{}, error) {
	resp, err := http.Get(p.URI)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return p.parseStats(resp.Body)
}

func (p PlackPlugin) parseStats(body io.Reader) (map[string]interface{}, error) {
	stat := make(map[string]interface{})
	decoder := json.NewDecoder(body)

	var s PlackServerStatus
	err := decoder.Decode(&s)
	if err != nil {
		return nil, err
	}

	stat["busy_workers"], err = strconv.ParseFloat(s.BusyWorkers, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["idle_workers"], err = strconv.ParseFloat(s.IdleWorkers, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["requests"], err = strconv.ParseUint(s.TotalAccesses, 10, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["bytes_sent"], err = strconv.ParseUint(s.TotalKbytes, 10, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}
	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p PlackPlugin) GraphDefinition() map[string](mp.Graphs) {
	var graphdef = map[string](mp.Graphs){
		(p.Prefix + ".workers"): mp.Graphs{
			Label: p.LabelPrefix + " Workers",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "busy_workers", Label: "Busy Workers", Diff: false, Stacked: true},
				mp.Metrics{Name: "idle_workers", Label: "Idle Workers", Diff: false, Stacked: true},
			},
		},
		(p.Prefix + ".req"): mp.Graphs{
			Label: p.LabelPrefix + " Requests",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "requests", Label: "Requests", Diff: true, Type: "uint64"},
			},
		},
		(p.Prefix + ".bytes"): mp.Graphs{
			Label: p.LabelPrefix + " Bytes",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "bytes_sent", Label: "Bytes Sent", Diff: true, Type: "uint64"},
			},
		},
	}

	return graphdef
}

func main() {
	optURI := flag.String("uri", "", "URI")
	optScheme := flag.String("scheme", "http", "Scheme")
	optHost := flag.String("host", "localhost", "Hostname")
	optPort := flag.String("port", "5000", "Port")
	optPath := flag.String("path", "/server-status?json", "Path")
	optPrefix := flag.String("prefix", "plack", "Prefix")
	optLabelPrefix := flag.String("label-prefix", "", "Label Prefix")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	plack := PlackPlugin{URI: *optURI, Prefix: *optPrefix, LabelPrefix: *optLabelPrefix}
	if plack.URI == "" {
		plack.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath)
	}
	if plack.LabelPrefix == "" {
		plack.LabelPrefix = strings.Title(plack.Prefix)
	}

	helper := mp.NewMackerelPlugin(plack)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-plack")
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<commit_msg>renamed prefix option names to adjust for common option names<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
)

// PlackPlugin mackerel plugin for Plack
type PlackPlugin struct {
	URI         string
	Prefix      string
	LabelPrefix string
}

// {
//   "Uptime": "1410520211",
//   "TotalAccesses": "2",
//   "IdleWorkers": "2",
//   "TotalKbytes": "0",
//   "BusyWorkers": "1",
//   "stats": [
//     {
//       "pid": 11062,
//       "method": "GET",
//       "ss": 51,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "status": "_",
//       "uri": "/server-status?json"
//     },
//     {
//       "ss": 41,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "pid": 11063,
//       "method": "GET",
//       "status": "_",
//       "uri": "/server-status?json"
//     },
//     {
//       "ss": 0,
//       "remote_addr": "127.0.0.1",
//       "host": "localhost:8000",
//       "protocol": "HTTP/1.1",
//       "pid": 11064,
//       "method": "GET",
//       "status": "A",
//       "uri": "/server-status?json"
//     }
//   ]
// }

// field types vary between versions

// PlackRequest request
type PlackRequest struct{}

// PlackServerStatus sturct for server-status's json
type PlackServerStatus struct {
	Uptime        string         `json:"Uptime"`
	TotalAccesses string         `json:"TotalAccesses"`
	TotalKbytes   string         `json:"TotalKbytes"`
	BusyWorkers   string         `json:"BusyWorkers"`
	IdleWorkers   string         `json:"IdleWorkers"`
	Stats         []PlackRequest `json:"stats"`
}

// FetchMetrics interface for mackerelplugin
func (p PlackPlugin) FetchMetrics() (map[string]interface{}, error) {
	resp, err := http.Get(p.URI)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return p.parseStats(resp.Body)
}

func (p PlackPlugin) parseStats(body io.Reader) (map[string]interface{}, error) {
	stat := make(map[string]interface{})
	decoder := json.NewDecoder(body)

	var s PlackServerStatus
	err := decoder.Decode(&s)
	if err != nil {
		return nil, err
	}

	stat["busy_workers"], err = strconv.ParseFloat(s.BusyWorkers, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["idle_workers"], err = strconv.ParseFloat(s.IdleWorkers, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["requests"], err = strconv.ParseUint(s.TotalAccesses, 10, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}

	stat["bytes_sent"], err = strconv.ParseUint(s.TotalKbytes, 10, 64)
	if err != nil {
		return nil, errors.New("cannot get values")
	}
	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p PlackPlugin) GraphDefinition() map[string](mp.Graphs) {
	var graphdef = map[string](mp.Graphs){
		(p.Prefix + ".workers"): mp.Graphs{
			Label: p.LabelPrefix + " Workers",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "busy_workers", Label: "Busy Workers", Diff: false, Stacked: true},
				mp.Metrics{Name: "idle_workers", Label: "Idle Workers", Diff: false, Stacked: true},
			},
		},
		(p.Prefix + ".req"): mp.Graphs{
			Label: p.LabelPrefix + " Requests",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "requests", Label: "Requests", Diff: true, Type: "uint64"},
			},
		},
		(p.Prefix + ".bytes"): mp.Graphs{
			Label: p.LabelPrefix + " Bytes",
			Unit:  "integer",
			Metrics: [](mp.Metrics){
				mp.Metrics{Name: "bytes_sent", Label: "Bytes Sent", Diff: true, Type: "uint64"},
			},
		},
	}

	return graphdef
}

func main() {
	optURI := flag.String("uri", "", "URI")
	optScheme := flag.String("scheme", "http", "Scheme")
	optHost := flag.String("host", "localhost", "Hostname")
	optPort := flag.String("port", "5000", "Port")
	optPath := flag.String("path", "/server-status?json", "Path")
	optPrefix := flag.String("metric-key-prefix", "plack", "Prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "", "Label Prefix")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	plack := PlackPlugin{URI: *optURI, Prefix: *optPrefix, LabelPrefix: *optLabelPrefix}
	if plack.URI == "" {
		plack.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath)
	}
	if plack.LabelPrefix == "" {
		plack.LabelPrefix = strings.Title(plack.Prefix)
	}

	helper := mp.NewMackerelPlugin(plack)
	if *optTempfile != "" {
		helper.Tempfile = *optTempfile
	} else {
		helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-plack")
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		helper.OutputDefinitions()
	} else {
		helper.OutputValues()
	}
}
<|endoftext|>


<commit_before>package httpclient

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gobs/pretty"
	"github.com/gobs/simplejson"

	"github.com/jbenet/go-reuseport"
)

var (
	DefaultClient = &http.Client{} // we use our own default client, so we can change the TLS configuration

	NoRedirect       = errors.New("No redirect")
	TooManyRedirects = errors.New("stopped after 10 redirects")
)

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		DefaultClient.Transport = tr
	} else {
		DefaultClient.Transport = nil
	}
}

//
// Set connection timeout
//
func SetTimeout(t time.Duration) {
	DefaultClient.Timeout = t
}

//
// HTTP error
//
type HttpError struct {
	Code       int
	Message    string
	RetryAfter int
}

func (e HttpError) Error() string {
	return e.Message
}

func (e HttpError) String() string {
	return fmt.Sprintf("ERROR: %v %v", e.Code, e.Message)
}

//
// CloseResponse makes sure we close the response body
//
func CloseResponse(r *http.Response) {
	if r != nil && r.Body != nil {
		io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
	}
}

//
// A wrapper for http.Response
//
type HttpResponse struct {
	http.Response
}

func (r *HttpResponse) ContentType() string {
	content_type := r.Header.Get("Content-Type")
	if len(content_type) == 0 {
		return content_type
	}

	return strings.TrimSpace(strings.Split(content_type, ";")[0])
}

//
// Close makes sure that all data from the body is read
// before closing the reader.
//
// If that is not the desider behaviour, just call HttpResponse.Body.Close()
//
func (r *HttpResponse) Close() {
	if r != nil {
		CloseResponse(&r.Response)
	}
}

//
// ResponseError checks the StatusCode and return an error if needed.
// The error is of type HttpError
//
func (r *HttpResponse) ResponseError() error {
	class := r.StatusCode / 100
	if class != 2 && class != 3 {
		rt := 0

		if h := r.Header.Get("Retry-After"); h != "" {
			rt, _ = strconv.Atoi(h)
		}

		return HttpError{Code: r.StatusCode, Message: "HTTP " + r.Status, RetryAfter: rt}
	}

	return nil
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		CloseResponse(resp)
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		CloseResponse(resp)
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err, " - read ", len(body), " bytes")
	}
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() (json *simplejson.Json) {
	json, _ = simplejson.LoadBytes(resp.Content())
	return
}

////////////////////////////////////////////////////////////////////////

//
// http.Client with some defaults and stuff
//
type HttpClient struct {
	// the http.Client
	client *http.Client

	// the base URL for this client
	BaseURL *url.URL

	// overrides Host header
	Host string

	// the client UserAgent string
	UserAgent string

	// Common headers to be passed on each request
	Headers map[string]string

	// if Verbose, log request and response info
	Verbose bool

	// if Close, all requests will set Connection: close
	// (no keep-alive)
	Close bool
}

//
// Create a new HttpClient
//
func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

//
// Set Transport
//
func (self *HttpClient) SetTransport(tr http.RoundTripper) {
	self.client.Transport = tr
}

//
// Get current Transport
//
func (self *HttpClient) GetTransport() http.RoundTripper {
	return self.client.Transport
}

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func (self *HttpClient) AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		self.client.Transport = tr
	} else {
		self.client.Transport = nil
	}
}

//
// Set connection timeout
//
func (self *HttpClient) SetTimeout(t time.Duration) {
	self.client.Timeout = t
}

//
// Set LocalAddr in Dialer
// (this assumes you also want the SO_REUSEPORT/SO_REUSEADDR stuff)
//
func (self *HttpClient) SetLocalAddr(addr string) {
	transport, ok := self.client.Transport.(*http.Transport)
	if transport == nil {
		if transport, ok = http.DefaultTransport.(*http.Transport); !ok {
			log.Println("SetLocalAddr for http.DefaultTransport != http.Transport")
			return
		}
	} else if !ok {
		log.Println("SetLocalAddr for client.Transport != http.Transport")
		return
	}
	if tcpaddr, err := net.ResolveTCPAddr("tcp", addr); err == nil {
		dialer := &reuseport.Dialer{
			D: net.Dialer{
				Timeout:   30 * time.Second, // defaults from net/http DefaultTransport
				KeepAlive: 30 * time.Second, // defaults from net/http DefaultTransport
				LocalAddr: tcpaddr,
			}}
		transport.Dial = dialer.Dial
	} else {
		log.Println("Failed to resolve", addr, " to a TCP address")
	}
}

//
// add default headers plus extra headers
//
func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for k, v := range headers {
		if strings.ToLower(k) == "content-length" {
			if len, err := strconv.Atoi(v); err == nil && req.ContentLength <= 0 {
				req.ContentLength = int64(len)
			}
		} else {
			req.Header.Set(k, v)
		}
	}

}

//
// the callback for CheckRedirect, used to pass along the headers in case of redirection
//
func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if req.Method == "HEAD" {
		// don't follow redirects on a HEAD request
		return NoRedirect
	}

	DebugLog(self.Verbose).Println("REDIRECT:", len(via), req.URL)

	if len(via) >= 10 {
		return TooManyRedirects
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

//
// Create a request object given the method, path, body and extra headers
//
func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	req.Close = self.Close
	req.Host = self.Host

	self.addHeaders(req, headers)
	return
}

//
// Execute request
//
func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	DebugLog(self.Verbose).Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))

	resp, err := self.client.Do(req)
	if urlerr, ok := err.(*url.Error); ok && urlerr.Err == NoRedirect {
		err = nil // redirect on HEAD is not an error
	}
	if err == nil {
		DebugLog(self.Verbose).Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		return &HttpResponse{*resp}, nil
	} else {
		DebugLog(self.Verbose).Println("ERROR:", err,
			"REQUEST:", req.Method, req.URL,
			pretty.PrettyFormat(req.Header))
		CloseResponse(resp)
		return nil, err
	}
}

//
// Execute a DELETE request
//
func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

//
// Execute a HEAD request
//
func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a GET request
//
func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a POST request
//
func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	return self.Do(req)
}

func (self *HttpClient) PostForm(path string, data url.Values, headers map[string]string) (*HttpResponse, error) {
        if headers == nil {
            headers = map[string]string{}
        }
	headers["Content-Type"] = "application/x-www-form-urlencoded"
	req := self.Request("POST", path, strings.NewReader(data.Encode()), headers)
	return self.Do(req)
}

//
// Execute a PUT request
//
func (self *HttpClient) Put(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("PUT", path, content, headers)
	return self.Do(req)
}
<commit_msg>Added Cookies property to HttpClient to set the list of cookies to send with the request. Also added PostForm to post form data.<commit_after>package httpclient

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gobs/pretty"
	"github.com/gobs/simplejson"

	"github.com/jbenet/go-reuseport"
)

var (
	DefaultClient = &http.Client{} // we use our own default client, so we can change the TLS configuration

	NoRedirect       = errors.New("No redirect")
	TooManyRedirects = errors.New("stopped after 10 redirects")
)

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		DefaultClient.Transport = tr
	} else {
		DefaultClient.Transport = nil
	}
}

//
// Set connection timeout
//
func SetTimeout(t time.Duration) {
	DefaultClient.Timeout = t
}

//
// HTTP error
//
type HttpError struct {
	Code       int
	Message    string
	RetryAfter int
}

func (e HttpError) Error() string {
	return e.Message
}

func (e HttpError) String() string {
	return fmt.Sprintf("ERROR: %v %v", e.Code, e.Message)
}

//
// CloseResponse makes sure we close the response body
//
func CloseResponse(r *http.Response) {
	if r != nil && r.Body != nil {
		io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
	}
}

//
// A wrapper for http.Response
//
type HttpResponse struct {
	http.Response
}

func (r *HttpResponse) ContentType() string {
	content_type := r.Header.Get("Content-Type")
	if len(content_type) == 0 {
		return content_type
	}

	return strings.TrimSpace(strings.Split(content_type, ";")[0])
}

//
// Close makes sure that all data from the body is read
// before closing the reader.
//
// If that is not the desider behaviour, just call HttpResponse.Body.Close()
//
func (r *HttpResponse) Close() {
	if r != nil {
		CloseResponse(&r.Response)
	}
}

//
// ResponseError checks the StatusCode and return an error if needed.
// The error is of type HttpError
//
func (r *HttpResponse) ResponseError() error {
	class := r.StatusCode / 100
	if class != 2 && class != 3 {
		rt := 0

		if h := r.Header.Get("Retry-After"); h != "" {
			rt, _ = strconv.Atoi(h)
		}

		return HttpError{Code: r.StatusCode, Message: "HTTP " + r.Status, RetryAfter: rt}
	}

	return nil
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		CloseResponse(resp)
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := DefaultClient.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		CloseResponse(resp)
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err, " - read ", len(body), " bytes")
	}
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() (json *simplejson.Json) {
	json, _ = simplejson.LoadBytes(resp.Content())
	return
}

////////////////////////////////////////////////////////////////////////

//
// http.Client with some defaults and stuff
//
type HttpClient struct {
	// the http.Client
	client *http.Client

	// the base URL for this client
	BaseURL *url.URL

	// overrides Host header
	Host string

	// the client UserAgent string
	UserAgent string

	// Common headers to be passed on each request
	Headers map[string]string

	// Cookies to be passed on each request
	Cookies []*http.Cookie

	// if Verbose, log request and response info
	Verbose bool

	// if Close, all requests will set Connection: close
	// (no keep-alive)
	Close bool
}

//
// Create a new HttpClient
//
func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

//
// Set Transport
//
func (self *HttpClient) SetTransport(tr http.RoundTripper) {
	self.client.Transport = tr
}

//
// Get current Transport
//
func (self *HttpClient) GetTransport() http.RoundTripper {
	return self.client.Transport
}

//
// Allow connections via HTTPS even if something is wrong with the certificate
// (self-signed or expired)
//
func (self *HttpClient) AllowInsecure(insecure bool) {
	if insecure {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		self.client.Transport = tr
	} else {
		self.client.Transport = nil
	}
}

//
// Set connection timeout
//
func (self *HttpClient) SetTimeout(t time.Duration) {
	self.client.Timeout = t
}

//
// Set LocalAddr in Dialer
// (this assumes you also want the SO_REUSEPORT/SO_REUSEADDR stuff)
//
func (self *HttpClient) SetLocalAddr(addr string) {
	transport, ok := self.client.Transport.(*http.Transport)
	if transport == nil {
		if transport, ok = http.DefaultTransport.(*http.Transport); !ok {
			log.Println("SetLocalAddr for http.DefaultTransport != http.Transport")
			return
		}
	} else if !ok {
		log.Println("SetLocalAddr for client.Transport != http.Transport")
		return
	}
	if tcpaddr, err := net.ResolveTCPAddr("tcp", addr); err == nil {
		dialer := &reuseport.Dialer{
			D: net.Dialer{
				Timeout:   30 * time.Second, // defaults from net/http DefaultTransport
				KeepAlive: 30 * time.Second, // defaults from net/http DefaultTransport
				LocalAddr: tcpaddr,
			}}
		transport.Dial = dialer.Dial
	} else {
		log.Println("Failed to resolve", addr, " to a TCP address")
	}
}

//
// add default headers plus extra headers
//
func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for _, c := range self.Cookies {
		req.AddCookie(c)
	}

	for k, v := range headers {
		if strings.ToLower(k) == "content-length" {
			if len, err := strconv.Atoi(v); err == nil && req.ContentLength <= 0 {
				req.ContentLength = int64(len)
			}
		} else {
			req.Header.Set(k, v)
		}
	}

}

//
// the callback for CheckRedirect, used to pass along the headers in case of redirection
//
func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if req.Method == "HEAD" {
		// don't follow redirects on a HEAD request
		return NoRedirect
	}

	DebugLog(self.Verbose).Println("REDIRECT:", len(via), req.URL)

	if len(via) >= 10 {
		return TooManyRedirects
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

//
// Create a request object given the method, path, body and extra headers
//
func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	req.Close = self.Close
	req.Host = self.Host

	self.addHeaders(req, headers)

	return
}

//
// Execute request
//
func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	DebugLog(self.Verbose).Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))

	resp, err := self.client.Do(req)
	if urlerr, ok := err.(*url.Error); ok && urlerr.Err == NoRedirect {
		err = nil // redirect on HEAD is not an error
	}
	if err == nil {
		DebugLog(self.Verbose).Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		return &HttpResponse{*resp}, nil
	} else {
		DebugLog(self.Verbose).Println("ERROR:", err,
			"REQUEST:", req.Method, req.URL,
			pretty.PrettyFormat(req.Header))
		CloseResponse(resp)
		return nil, err
	}
}

//
// Execute a DELETE request
//
func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

//
// Execute a HEAD request
//
func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a GET request
//
func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a POST request
//
func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	return self.Do(req)
}

func (self *HttpClient) PostForm(path string, data url.Values, headers map[string]string) (*HttpResponse, error) {
	if headers == nil {
		headers = map[string]string{}
	}
	headers["Content-Type"] = "application/x-www-form-urlencoded"
	req := self.Request("POST", path, strings.NewReader(data.Encode()), headers)
	return self.Do(req)
}

//
// Execute a PUT request
//
func (self *HttpClient) Put(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("PUT", path, content, headers)
	return self.Do(req)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

var testDir = "test/"

func TestMain(m *testing.M) {
	m.Run()
}

func TestGlob(t *testing.T) {
	expected := []string{
		testDir + "B",
		testDir + "a",
		testDir + "a - 01.txt",
		testDir + "b - 02.txt",
		testDir + "c - 03.txt",
	}
	files, err := glob(testDir)

	if err != nil {
		t.Fatal(err)
	}

	if reflect.DeepEqual(files, expected) == false {
		t.Fatal("files are different from expected ones.", files, expected)
	}
}

func TestExtractDirname(t *testing.T) {
	var cases = []struct {
		n        string
		expected string
	}{
		{"a", ""},
		{"a.txt", ""},
		{"a - 01.txt", "a"},
	}

	for _, v := range cases {
		if result := extractDirname(v.n, " - "); result != v.expected {
			t.Errorf("expected %+v, but got %+v", v.expected, result)
		}
	}
}

func TestMoveFail(t *testing.T) {
	fname := "./test/a - 01.txt"
	name := extractDirname(fname, " - ")
	name, err := mkDir(name, false)
	err = move(filepath.Join("./test/", name, filepath.Base(fname)), fname)

	if err == nil {
		t.Error("An error should be occured.")
	}
}

func TestGlobDir(t *testing.T) {
	expected := []string{testDir + "B"}
	dirs, err := globDir(testDir)

	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(dirs, expected) {
		t.Fatal("dirs are different from expected ones.", dirs, expected)
	}

}

func TestMkDir(t *testing.T) {
	dirName, err := mkDir(testDir+"B", false)
	if err != nil {
		t.Fatal("err should be nil.", err)
	}
	if dirName != testDir+"B" {
		t.Errorf("testDir should be %s but got %s", testDir+"B", dirName)
	}

	dirName, err = mkDir(testDir+"b", true)
	if err != nil {
		t.Fatal("err should be nil.", err)
	}
	if dirName != testDir+"B" {
		t.Errorf("testDir should be %s but got %s", testDir+"B", dirName)
	}

	dirName, err = mkDir(testDir+"a", false)
	if err != nil {
		t.Fatal(err)
	}
	if dirName != testDir+"a" {
		t.Errorf("testDir should be %s but got %s", testDir+"a", dirName)
	}

}

func TestMove(t *testing.T) {
	originalName := "./test/c - 03.txt"
	fname := filepath.Base(originalName)
	dest := extractDirname(fname, " - ")
	if dest != "c" {
		t.Errorf("dest should be 'c'")
	}
	dest = filepath.Join("./test", dest)
	dest, err := mkDir(dest, false)
	if err != nil {
		t.Error(err)
	}
	err = move(originalName, filepath.Join(dest, fname))
	if err != nil {
		t.Error(err)
	}

	if _, err = os.Stat("./test/c/c - 03.txt"); err != nil {
		t.Errorf("error %v", err)
	}

	move("./test/c/c - 03.txt", "./test/c - 03.txt")
	os.Remove("./test/c")
}

func TestWinCase(t *testing.T) {
	var cases = []struct {
		input    string
		expected string
	}{
		{"<", "＜"},
		{">", "＞"},
		{":", "："},
		{"\"", "”"},
		{"/", "／"},
		{"\\", "＼"},
		{"|", "｜"},
		{"?", "？"},
		{"*", "＊"},
	}

	for _, v := range cases {
		if renamed := winCaseRename(v.input); renamed != v.expected {
			t.Errorf("%s should be %s", renamed, v.expected)
		}
	}
}

func TestGetFilename(t *testing.T) {
	cases := []struct {
		f       string
		wincase bool
		file    string
	}{
		{
			"./test/hoge*.txt",
			true,
			"hoge＊.txt",
		},
	}

	for _, c := range cases {
		f := getFilename(c.f, c.wincase)
		if f != c.file {
			t.Errorf("%s should be equal to %s", f, c.file)
		}
	}
}

func TestGetDestDirName(t *testing.T) {
	cases := []struct {
		filename  string
		delimiter string
		dir       string
		out       string
	}{
		{
			"hoge - fuga.txt",
			" - ",
			"./test",
			"test/hoge",
		},
	}
	for _, c := range cases {
		result := getDestDirName(c.filename, c.delimiter, c.dir)
		if result != c.out {
			t.Errorf("%s should be equal to %s", result, c.out)
		}
	}
}

//TODO truncate オプション but how?
<commit_msg>add a test case for truncate option<commit_after>package main

import (
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

var testDir = "test/"

func TestMain(m *testing.M) {
	m.Run()
}

func TestGlob(t *testing.T) {
	expected := []string{
		testDir + "B",
		testDir + "a",
		testDir + "a - 01.txt",
		testDir + "b - 02.txt",
		testDir + "c - 03.txt",
	}
	files, err := glob(testDir)

	if err != nil {
		t.Fatal(err)
	}

	if reflect.DeepEqual(files, expected) == false {
		t.Fatal("files are different from expected ones.", files, expected)
	}
}

func TestExtractDirname(t *testing.T) {
	var cases = []struct {
		n        string
		expected string
	}{
		{"a", ""},
		{"a.txt", ""},
		{"a - 01.txt", "a"},
	}

	for _, v := range cases {
		if result := extractDirname(v.n, " - "); result != v.expected {
			t.Errorf("expected %+v, but got %+v", v.expected, result)
		}
	}
}

func TestMoveFail(t *testing.T) {
	fname := "./test/a - 01.txt"
	name := extractDirname(fname, " - ")
	name, err := mkDir(name, false)
	err = move(filepath.Join("./test/", name, filepath.Base(fname)), fname)

	if err == nil {
		t.Error("An error should be occured.")
	}
}

func TestGlobDir(t *testing.T) {
	expected := []string{testDir + "B"}
	dirs, err := globDir(testDir)

	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(dirs, expected) {
		t.Fatal("dirs are different from expected ones.", dirs, expected)
	}

}

func TestMkDir(t *testing.T) {
	dirName, err := mkDir(testDir+"B", false)
	if err != nil {
		t.Fatal("err should be nil.", err)
	}
	if dirName != testDir+"B" {
		t.Errorf("testDir should be %s but got %s", testDir+"B", dirName)
	}

	dirName, err = mkDir(testDir+"b", true)
	if err != nil {
		t.Fatal("err should be nil.", err)
	}
	if dirName != testDir+"B" {
		t.Errorf("testDir should be %s but got %s", testDir+"B", dirName)
	}

	dirName, err = mkDir(testDir+"a", false)
	if err != nil {
		t.Fatal(err)
	}
	if dirName != testDir+"a" {
		t.Errorf("testDir should be %s but got %s", testDir+"a", dirName)
	}

}

func TestMove(t *testing.T) {
	originalName := "./test/c - 03.txt"
	fname := filepath.Base(originalName)
	dest := extractDirname(fname, " - ")
	if dest != "c" {
		t.Errorf("dest should be 'c'")
	}
	dest = filepath.Join("./test", dest)
	dest, err := mkDir(dest, false)
	if err != nil {
		t.Error(err)
	}
	err = move(originalName, filepath.Join(dest, fname))
	if err != nil {
		t.Error(err)
	}

	if _, err = os.Stat("./test/c/c - 03.txt"); err != nil {
		t.Errorf("error %v", err)
	}

	move("./test/c/c - 03.txt", "./test/c - 03.txt")
	os.Remove("./test/c")
}

func TestWinCase(t *testing.T) {
	var cases = []struct {
		input    string
		expected string
	}{
		{"<", "＜"},
		{">", "＞"},
		{":", "："},
		{"\"", "”"},
		{"/", "／"},
		{"\\", "＼"},
		{"|", "｜"},
		{"?", "？"},
		{"*", "＊"},
	}

	for _, v := range cases {
		if renamed := winCaseRename(v.input); renamed != v.expected {
			t.Errorf("%s should be %s", renamed, v.expected)
		}
	}
}

func TestGetFilename(t *testing.T) {
	cases := []struct {
		f       string
		wincase bool
		file    string
	}{
		{
			"./test/hoge*.txt",
			true,
			"hoge＊.txt",
		},
	}

	for _, c := range cases {
		f := getFilename(c.f, c.wincase)
		if f != c.file {
			t.Errorf("%s should be equal to %s", f, c.file)
		}
	}
}

func TestGetDestDirName(t *testing.T) {
	cases := []struct {
		filename  string
		delimiter string
		dir       string
		out       string
	}{
		{
			"hoge - fuga.txt",
			" - ",
			"./test",
			"test/hoge",
		},
	}
	for _, c := range cases {
		result := getDestDirName(c.filename, c.delimiter, c.dir)
		if result != c.out {
			t.Errorf("%s should be equal to %s", result, c.out)
		}
	}
}

//TODO truncate オプション but how?
func TestGetTruncatedFilename(t *testing.T) {
	cases := []struct {
		delimiter string
		filename  string
		out       string
	}{
		{
			delimiter: " - ",
			filename:  "hoge - fuga.txt",
			out:       "fuga.txt",
		},
		{
			delimiter: " - ",
			filename:  "hoge_fuga.txt",
			out:       "hoge_fuga.txt",
		},
		{
			delimiter: "_",
			filename:  "hoge_fuga.txt",
			out:       "fuga.txt",
		},
	}

	for _, c := range cases {
		actual := getTruncatedFilename(c.filename, c.delimiter)
		if actual != c.out {
			t.Errorf("%s should be equal %s", actual, c.out)
		}
	}
}
<|endoftext|>


<commit_before>// Package generic contains the generic.Type marker type.
package generic
<commit_msg>doc tweak<commit_after>// Package generic contains the generic marker types.
package generic
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package index

// subColFlds is a Collection wrapper which overrides the Field
// method.
type subColFlds struct {
	Collection
	flds map[string]interface{}
}

func (scf subColFlds) Field(f string) interface{} {
	if x, ok := scf.flds[f]; ok {
		return x
	}
	return scf.Collection.Field(f)
}

// subGrpFlds is a Group wrapper which overrids the Field method
type subGrpFlds struct {
	Group
	tracks []Track
	flds   map[string]interface{}
}

func (sgf subGrpFlds) Tracks() []Track {
	if sgf.tracks != nil {
		return sgf.tracks
	}
	return sgf.Group.Tracks()
}

// Field implements Group.
func (sgf subGrpFlds) Field(field string) interface{} {
	if x, ok := sgf.flds[field]; ok {
		return x
	}
	return sgf.Group.Field(field)
}

// SumGroupIntAttr recurses through the Group and assigns the field with the sum
// of fields from children (Groups or Tracks).
func SumGroupIntAttr(field string, g Group) Group {
	if c, ok := g.(Collection); ok {
		return sumCollectionIntAttr(field, c)
	}
	return sumGroupIntAttr(field, g)
}

func sumCollectionIntAttr(field string, c Collection) Collection {
	nc := subCol{
		Collection: c,
		grps:       make(map[Key]Group, len(c.Keys())),
		flds:       make(map[string]interface{}),
	}
	var total int
	for _, k := range c.Keys() {
		g := c.Get(k)
		g = SumGroupIntAttr(field, g)
		total += g.Field(field).(int)
		nc.grps[k] = g
	}
	nc.flds = map[string]interface{}{
		field: total,
	}
	return nc
}

func sumGroupIntAttr(field string, g Group) Group {
	ng := subGrpFlds{
		Group: g,
		flds:  map[string]interface{}{},
	}
	var total int
	for _, t := range g.Tracks() {
		total += t.GetInt(field)
	}
	ng.flds[field] = total
	return ng
}

// CommonGroupAttr recurses through the Group and assigns fields on all sub groups
// which are common amoungst their children (Groups or Tracks).  If there is no common
// field, then the associated Field value is not set.
func CommonGroupAttr(attrs []Attr, g Group) Group {
	if c, ok := g.(Collection); ok {
		return commonCollectionTrackAttr(attrs, c)
	}
	return commonGroupTrackAttr(attrs, g)
}

func commonCollectionTrackAttr(attrs []Attr, c Collection) Collection {
	grps := make(map[Key]Group, len(c.Keys()))
	flds := make(map[string]interface{}, len(attrs))

	keys := c.Keys()
	if len(keys) > 0 {
		k0 := keys[0]
		g0 := c.Get(k0)
		g0 = CommonGroupAttr(attrs, g0)
		grps[k0] = g0

		for _, a := range attrs {
			flds[a.Field()] = g0.Field(a.Field())
		}

		if len(keys) > 1 {
			for _, k := range keys[1:] {
				g1 := c.Get(k)
				g1 = CommonGroupAttr(attrs, g1)
				grps[k] = g1

				for _, a := range attrs {
					f := a.Field()
					flds[f] = a.Intersect(flds[f], g1.Field(f))
				}
			}
		}
	}

	for _, a := range attrs {
		f := a.Field()
		if v, ok := flds[f]; ok && a.IsEmpty(v) {
			delete(flds, f)
		}
	}

	return subCol{
		Collection: c,
		grps:       grps,
		flds:       flds,
	}
}

func commonGroupTrackAttr(attrs []Attr, g Group) Group {
	flds := make(map[string]interface{}, len(attrs))
	tracks := g.Tracks()

	if len(tracks) > 0 {
		t0 := tracks[0]
		for _, a := range attrs {
			f := a.Field()
			flds[f] = a.Value(t0)
		}

		if len(tracks) > 1 {
			for _, t := range tracks[1:] {
				for _, a := range attrs {
					f := a.Field()
					flds[f] = a.Intersect(flds[f], a.Value(t))
				}
			}
		}
	}

	for _, a := range attrs {
		f := a.Field()
		if v, ok := flds[f]; ok && a.IsEmpty(v) {
			delete(flds, f)
		}
	}

	return subGrpFlds{
		Group: g,
		flds:  flds,
	}
}

// subGrpName is a Group wrapper which overrides Name.
type subGrpName struct {
	Group
	name string
}

// Name implements Group.
func (s subGrpName) Name() string {
	return s.name
}

// RemoveEmptyCollections recursively goes through each sub Collection contained
// in the Group and removes any which don't have any tracks/groups in them.
func RemoveEmptyCollections(g Group) Group {
	gc, ok := g.(Collection)
	if ok {
		keys := gc.Keys()
		if len(keys) == 1 {
			gc0 := gc.Get(keys[0])
			_, col := gc0.(Collection)
			if !col && gc0.Name() == "" {
				return subGrpName{
					name:  gc.Name(),
					Group: gc0,
				}
			}
		}
		ngc := subCol{
			Collection: gc,
			grps:       make(map[Key]Group, len(gc.Keys())),
		}
		for _, k := range keys {
			ngc.grps[k] = RemoveEmptyCollections(gc.Get(k))
		}
		return ngc
	}
	return g
}

func firstTrack(g Group) Track {
	c, ok := g.(Collection)
	if ok {
		keys := c.Keys()
		if len(keys) > 0 {
			return firstTrack(c.Get(keys[0]))
		}
		return nil
	}

	ts := g.Tracks()
	if len(ts) > 0 {
		return ts[0]
	}
	return nil
}

func fieldsGroup(m map[string]interface{}, g Group) Group {
	if c, ok := g.(Collection); ok {
		return subColFlds{
			Collection: c,
			flds:       m,
		}
	}
	return subGrpFlds{
		Group: g,
		flds:  m,
	}
}

// Attr is a type which wraps a closure to get an attribute from an implementation of the
// Attr interface.
type Attr struct {
	field     string
	empty     interface{}
	isEmpty   func(x interface{}) bool
	fn        func(t Track) interface{}
	intersect func(x, y interface{}) interface{}
}

// Field returns the underlying field name.
func (g Attr) Field() string {
	return g.field
}

// Empty returns the empty value of the underlying field (the empty value of the field type).
func (g Attr) Empty() interface{} {
	return g.empty
}

// IsEmpty returns true iff the given value represents the empty value of the underlying attribute
// type.
func (g Attr) IsEmpty(x interface{}) bool {
	return g.isEmpty(x)
}

// Value returns the value of the attribute for the given track.
func (g Attr) Value(t Track) interface{} {
	return g.fn(t)
}

// Intersect returns the intersection of the two attribute values.
func (g Attr) Intersect(x, y interface{}) interface{} {
	return g.intersect(x, y)
}

// StringAttr constructs an Attr which will retrieve the string field from an implementation
// of Track.
func StringAttr(field string) Attr {
	return Attr{
		field: field,
		empty: "",
		isEmpty: func(x interface{}) bool {
			return x == ""
		},
		intersect: func(x, y interface{}) interface{} {
			if x == y {
				return x
			}
			return ""
		},
		fn: func(t Track) interface{} {
			return t.GetString(field)
		},
	}
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

// StringSliceIntersect computes the intersection of two string slices (ignoring ordering).
func StringSliceIntersect(s, t []string) []string {
	var res []string
	m := make(map[string]bool)
	for _, x := range s {
		m[x] = true
	}
	for _, y := range t {
		if m[y] {
			res = append(res, y)
		}
	}
	return res
}

// StringsIntersect computes the intersection of the two interface values assumed to be
// of type []string.
func StringsIntersect(x, y interface{}) interface{} {
	if x == nil || y == nil {
		return nil
	}
	xs := x.([]string)
	ys := y.([]string)
	return StringSliceIntersect(xs, ys)
}

// StringsAttr returns an Attr which will retrieve the strings field from an implementation of Track.
func StringsAttr(field string) Attr {
	return Attr{
		field:     field,
		empty:     nil,
		intersect: StringsIntersect,
		isEmpty: func(x interface{}) bool {
			if x == nil {
				return true
			}
			xs := x.([]string)
			return len(xs) == 0
		},
		fn: func(t Track) interface{} {
			return t.GetStrings(field)
		},
	}
}

// IntAttr constructs an Attr which will retrieve the int field from an implementation of Track.
func IntAttr(field string) Attr {
	return Attr{
		field:   field,
		empty:   0,
		isEmpty: func(x interface{}) bool { return x == 0 },
		intersect: func(x, y interface{}) interface{} {
			if x == y {
				return x
			}
			return 0
		},
		fn: func(t Track) interface{} { return t.GetInt(field) },
	}
}

// FirstTrackAttr wraps the given Group adding a string field `field` with the value taken
// from the first track.
func FirstTrackAttr(attr Attr, g Group) Group {
	t := firstTrack(g)
	if t == nil {
		return g
	}

	v := attr.Value(t)
	if attr.IsEmpty(v) {
		return g
	}
	m := map[string]interface{}{
		attr.field: v,
	}
	return fieldsGroup(m, g)
}
<commit_msg>Remove unused Attr.Empty() method.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package index

// subColFlds is a Collection wrapper which overrides the Field
// method.
type subColFlds struct {
	Collection
	flds map[string]interface{}
}

func (scf subColFlds) Field(f string) interface{} {
	if x, ok := scf.flds[f]; ok {
		return x
	}
	return scf.Collection.Field(f)
}

// subGrpFlds is a Group wrapper which overrids the Field method
type subGrpFlds struct {
	Group
	tracks []Track
	flds   map[string]interface{}
}

func (sgf subGrpFlds) Tracks() []Track {
	if sgf.tracks != nil {
		return sgf.tracks
	}
	return sgf.Group.Tracks()
}

// Field implements Group.
func (sgf subGrpFlds) Field(field string) interface{} {
	if x, ok := sgf.flds[field]; ok {
		return x
	}
	return sgf.Group.Field(field)
}

// SumGroupIntAttr recurses through the Group and assigns the field with the sum
// of fields from children (Groups or Tracks).
func SumGroupIntAttr(field string, g Group) Group {
	if c, ok := g.(Collection); ok {
		return sumCollectionIntAttr(field, c)
	}
	return sumGroupIntAttr(field, g)
}

func sumCollectionIntAttr(field string, c Collection) Collection {
	nc := subCol{
		Collection: c,
		grps:       make(map[Key]Group, len(c.Keys())),
		flds:       make(map[string]interface{}),
	}
	var total int
	for _, k := range c.Keys() {
		g := c.Get(k)
		g = SumGroupIntAttr(field, g)
		total += g.Field(field).(int)
		nc.grps[k] = g
	}
	nc.flds = map[string]interface{}{
		field: total,
	}
	return nc
}

func sumGroupIntAttr(field string, g Group) Group {
	ng := subGrpFlds{
		Group: g,
		flds:  map[string]interface{}{},
	}
	var total int
	for _, t := range g.Tracks() {
		total += t.GetInt(field)
	}
	ng.flds[field] = total
	return ng
}

// CommonGroupAttr recurses through the Group and assigns fields on all sub groups
// which are common amoungst their children (Groups or Tracks).  If there is no common
// field, then the associated Field value is not set.
func CommonGroupAttr(attrs []Attr, g Group) Group {
	if c, ok := g.(Collection); ok {
		return commonCollectionTrackAttr(attrs, c)
	}
	return commonGroupTrackAttr(attrs, g)
}

func commonCollectionTrackAttr(attrs []Attr, c Collection) Collection {
	grps := make(map[Key]Group, len(c.Keys()))
	flds := make(map[string]interface{}, len(attrs))

	keys := c.Keys()
	if len(keys) > 0 {
		k0 := keys[0]
		g0 := c.Get(k0)
		g0 = CommonGroupAttr(attrs, g0)
		grps[k0] = g0

		for _, a := range attrs {
			flds[a.Field()] = g0.Field(a.Field())
		}

		if len(keys) > 1 {
			for _, k := range keys[1:] {
				g1 := c.Get(k)
				g1 = CommonGroupAttr(attrs, g1)
				grps[k] = g1

				for _, a := range attrs {
					f := a.Field()
					flds[f] = a.Intersect(flds[f], g1.Field(f))
				}
			}
		}
	}

	for _, a := range attrs {
		f := a.Field()
		if v, ok := flds[f]; ok && a.IsEmpty(v) {
			delete(flds, f)
		}
	}

	return subCol{
		Collection: c,
		grps:       grps,
		flds:       flds,
	}
}

func commonGroupTrackAttr(attrs []Attr, g Group) Group {
	flds := make(map[string]interface{}, len(attrs))
	tracks := g.Tracks()

	if len(tracks) > 0 {
		t0 := tracks[0]
		for _, a := range attrs {
			f := a.Field()
			flds[f] = a.Value(t0)
		}

		if len(tracks) > 1 {
			for _, t := range tracks[1:] {
				for _, a := range attrs {
					f := a.Field()
					flds[f] = a.Intersect(flds[f], a.Value(t))
				}
			}
		}
	}

	for _, a := range attrs {
		f := a.Field()
		if v, ok := flds[f]; ok && a.IsEmpty(v) {
			delete(flds, f)
		}
	}

	return subGrpFlds{
		Group: g,
		flds:  flds,
	}
}

// subGrpName is a Group wrapper which overrides Name.
type subGrpName struct {
	Group
	name string
}

// Name implements Group.
func (s subGrpName) Name() string {
	return s.name
}

// RemoveEmptyCollections recursively goes through each sub Collection contained
// in the Group and removes any which don't have any tracks/groups in them.
func RemoveEmptyCollections(g Group) Group {
	gc, ok := g.(Collection)
	if ok {
		keys := gc.Keys()
		if len(keys) == 1 {
			gc0 := gc.Get(keys[0])
			_, col := gc0.(Collection)
			if !col && gc0.Name() == "" {
				return subGrpName{
					name:  gc.Name(),
					Group: gc0,
				}
			}
		}
		ngc := subCol{
			Collection: gc,
			grps:       make(map[Key]Group, len(gc.Keys())),
		}
		for _, k := range keys {
			ngc.grps[k] = RemoveEmptyCollections(gc.Get(k))
		}
		return ngc
	}
	return g
}

func firstTrack(g Group) Track {
	c, ok := g.(Collection)
	if ok {
		keys := c.Keys()
		if len(keys) > 0 {
			return firstTrack(c.Get(keys[0]))
		}
		return nil
	}

	ts := g.Tracks()
	if len(ts) > 0 {
		return ts[0]
	}
	return nil
}

func fieldsGroup(m map[string]interface{}, g Group) Group {
	if c, ok := g.(Collection); ok {
		return subColFlds{
			Collection: c,
			flds:       m,
		}
	}
	return subGrpFlds{
		Group: g,
		flds:  m,
	}
}

// Attr is a type which wraps a closure to get an attribute from an implementation of the
// Attr interface.
type Attr struct {
	field     string
	empty     interface{}
	isEmpty   func(x interface{}) bool
	fn        func(t Track) interface{}
	intersect func(x, y interface{}) interface{}
}

// Field returns the underlying field name.
func (g Attr) Field() string {
	return g.field
}

// IsEmpty returns true iff the given value represents the empty value of the underlying attribute
// type.
func (g Attr) IsEmpty(x interface{}) bool {
	return g.isEmpty(x)
}

// Value returns the value of the attribute for the given track.
func (g Attr) Value(t Track) interface{} {
	return g.fn(t)
}

// Intersect returns the intersection of the two attribute values.
func (g Attr) Intersect(x, y interface{}) interface{} {
	return g.intersect(x, y)
}

// StringAttr constructs an Attr which will retrieve the string field from an implementation
// of Track.
func StringAttr(field string) Attr {
	return Attr{
		field: field,
		empty: "",
		isEmpty: func(x interface{}) bool {
			return x == ""
		},
		intersect: func(x, y interface{}) interface{} {
			if x == y {
				return x
			}
			return ""
		},
		fn: func(t Track) interface{} {
			return t.GetString(field)
		},
	}
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

// StringSliceIntersect computes the intersection of two string slices (ignoring ordering).
func StringSliceIntersect(s, t []string) []string {
	var res []string
	m := make(map[string]bool)
	for _, x := range s {
		m[x] = true
	}
	for _, y := range t {
		if m[y] {
			res = append(res, y)
		}
	}
	return res
}

// StringsIntersect computes the intersection of the two interface values assumed to be
// of type []string.
func StringsIntersect(x, y interface{}) interface{} {
	if x == nil || y == nil {
		return nil
	}
	xs := x.([]string)
	ys := y.([]string)
	return StringSliceIntersect(xs, ys)
}

// StringsAttr returns an Attr which will retrieve the strings field from an implementation of Track.
func StringsAttr(field string) Attr {
	return Attr{
		field:     field,
		empty:     nil,
		intersect: StringsIntersect,
		isEmpty: func(x interface{}) bool {
			if x == nil {
				return true
			}
			xs := x.([]string)
			return len(xs) == 0
		},
		fn: func(t Track) interface{} {
			return t.GetStrings(field)
		},
	}
}

// IntAttr constructs an Attr which will retrieve the int field from an implementation of Track.
func IntAttr(field string) Attr {
	return Attr{
		field:   field,
		empty:   0,
		isEmpty: func(x interface{}) bool { return x == 0 },
		intersect: func(x, y interface{}) interface{} {
			if x == y {
				return x
			}
			return 0
		},
		fn: func(t Track) interface{} { return t.GetInt(field) },
	}
}

// FirstTrackAttr wraps the given Group adding a string field `field` with the value taken
// from the first track.
func FirstTrackAttr(attr Attr, g Group) Group {
	t := firstTrack(g)
	if t == nil {
		return g
	}

	v := attr.Value(t)
	if attr.IsEmpty(v) {
		return g
	}
	m := map[string]interface{}{
		attr.field: v,
	}
	return fieldsGroup(m, g)
}
<|endoftext|>