<commit_before>package govaluation

import (
	"testing"
)

/*
	Represents the contents of a test of token parsing
*/
type TokenParsingTest struct {

	Name string
	Input string
	Expected []ExpressionToken
}

type EvaluationTest struct {

	Name string
	Input string
	Parameters map[string]interface{}
	Expected interface{}
}

func TestConstantParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Single numeric",
			Input: "1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Single string",
			Input: "'foo'",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: STRING,
						Value: "foo",
					},
			},
		},
		TokenParsingTest {

			Name: "Single boolean",
			Input: "true",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
			},
		},
		TokenParsingTest {

			Name: "Single numeric",
			Input: "1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Single large numeric",
			Input: "1234567890",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1234567890,
					},
			},
		},
		TokenParsingTest {

			Name: "Single floating-point",
			Input: "0.5",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 0.5,
					},
			},
		},
		TokenParsingTest {

			Name: "Single large floating point",
			Input: "3.14567471",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 3.14567471,
					},
			},
		},
		TokenParsingTest {

			Name: "Single false boolean",
			Input: "false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestLogicalOperatorParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Boolean AND",
			Input: "true && false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "&&",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
		TokenParsingTest {

			Name: "Boolean OR",
			Input: "true || false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "||",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
		TokenParsingTest {

			Name: "Multiple logical operators",
			Input: "true || false && true",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "||",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "&&",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestComparatorParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Numeric EQ",
			Input: "1 == 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "==",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric NEQ",
			Input: "1 != 2",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "!=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 2,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric GT",
			Input: "1 > 0",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: ">",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 0,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric LT",
			Input: "1 < 2",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "<",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 2,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric GTE",
			Input: "1 >= 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: ">=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric LTE",
			Input: "1 <= 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "<=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestModifierParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Numeric PLUS",
			Input: "1 + 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "+",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MINUS",
			Input: "1 - 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "-",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MULTIPLY",
			Input: "1 * 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "*",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric DIVIDE",
			Input: "1 / 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "/",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MODULUS",
			Input: "1 % 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "%",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestNoParameterEvaluation(test *testing.T) {

	evaluationTests := []EvaluationTest {

		EvaluationTest {

			Name: "Single PLUS",
			Input: "51 + 49",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Single MINUS",
			Input: "100 - 51",
			Expected: 49,
		},
		EvaluationTest {

			Name: "Single MULTIPLY",
			Input: "5 * 20",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Single DIVIDE",
			Input: "100 / 20",
			Expected: 5,
		},
		EvaluationTest {

			Name: "Single MODULUS",
			Input: "100 % 2",
			Expected: 0,
		},
		EvaluationTest {

			Name: "Compound PLUS",
			Input: "20 + 30 + 50",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Mutiple operators",
			Input: "20 * 5 - 49",
			Expected: 51,
		},
		EvaluationTest {

			Name: "Paren usage",
			Input: "100 - (5 * 10)",
			Expected: 50,
		},
		EvaluationTest {

			Name: "Nested parentheses",
			Input: "50 + (5 * (5 - 3))",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Implicit boolean",
			Input: "2 > 1",
			Expected: true,
		},
		EvaluationTest {

			Name: "Compound boolean",
			Input: "5 < 10 && 1 < 5",
			Expected: true,
		},
		EvaluationTest {

			Name: "Parenthesis boolean",
			Input: "10 < 50 && (1 != 2 && 1 == 1)",
			Expected: true,
		},
		EvaluationTest {

			Name: "Comparison of string constants",
			Input: "'foo' == 'foo'",
			Expected: true,
		},
		EvaluationTest {

			Name: "NEQ comparison of string constants",
			Input: "'foo' != 'bar'",
			Expected: true,
		},
	}

	runEvaluationTests(evaluationTests, test)
}

func TestParameterizedEvaluation(test *testing.T) {
}

func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T) {

	var expression *EvaluableExpression
	var expectedToken ExpressionToken
	var expectedTokenLength, actualTokenLength int

	// Run the test cases.
	for _, parsingTest := range tokenParsingTests {

		expression = NewEvaluableExpression(parsingTest.Input)

		expectedTokenLength = len(parsingTest.Expected)
		actualTokenLength = len(expression.Tokens)

		if(actualTokenLength != expectedTokenLength) {

			test.Log("Test '",parsingTest.Name,"' failed:")
			test.Log("Expected ", expectedTokenLength, " tokens, actually found '", actualTokenLength, "'")
			test.Fail()
		}

		for i, token := range expression.Tokens {

			expectedToken = parsingTest.Expected[i]
			if(token.Kind != expectedToken.Kind) {

				test.Log("Test '", parsingTest.Name, "' failed:")
				test.Log("Expected token kind '", expectedToken.Kind, "' does not match '", token.Kind, "'")
				test.Fail()
			}

			if(token.Value != expectedToken.Value) {

				test.Log("Test '", parsingTest.Name, "' failed:")
				test.Log("Expected token value '", expectedToken.Kind, "' does not match '", token.Kind, "'")
				test.Fail()
			}
		}
	}
}

func runEvaluationTests(evaluationTests []EvaluationTest, test *testing.T) {

	var expression *EvaluableExpression
	var result interface{}

	// Run the test cases.
	for _, evaluationTest := range evaluationTests {

		expression = NewEvaluableExpression(evaluationTest.Input)
		result = expression.Evaluate(evaluationTest.Parameters)

		if(result != evaluationTest.Expected) {

			test.Log("Test '", evaluationTest.Name, "' failed:")
			test.Log("Expected evaluation result '", evaluationTest.Expected, "' does not match '", result, "'")
			test.Fail()
		}
	}
}
<commit_msg>Added some tests for parameterized evaluation<commit_after>package govaluation

import (
	"testing"
)

/*
	Represents the contents of a test of token parsing
*/
type TokenParsingTest struct {

	Name string
	Input string
	Expected []ExpressionToken
}

type EvaluationTest struct {

	Name string
	Input string
	Parameters []EvaluationParameter
	Expected interface{}
}

type EvaluationParameter struct {

	Name string
	Value interface{}
}

func TestConstantParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Single numeric",
			Input: "1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Single string",
			Input: "'foo'",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: STRING,
						Value: "foo",
					},
			},
		},
		TokenParsingTest {

			Name: "Single boolean",
			Input: "true",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
			},
		},
		TokenParsingTest {

			Name: "Single numeric",
			Input: "1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Single large numeric",
			Input: "1234567890",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1234567890,
					},
			},
		},
		TokenParsingTest {

			Name: "Single floating-point",
			Input: "0.5",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 0.5,
					},
			},
		},
		TokenParsingTest {

			Name: "Single large floating point",
			Input: "3.14567471",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 3.14567471,
					},
			},
		},
		TokenParsingTest {

			Name: "Single false boolean",
			Input: "false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestLogicalOperatorParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Boolean AND",
			Input: "true && false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "&&",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
		TokenParsingTest {

			Name: "Boolean OR",
			Input: "true || false",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "||",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
			},
		},
		TokenParsingTest {

			Name: "Multiple logical operators",
			Input: "true || false && true",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "||",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: false,
					},
					ExpressionToken {
						Kind: LOGICALOP,
						Value: "&&",
					},
					ExpressionToken {
						Kind: BOOLEAN,
						Value: true,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestComparatorParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Numeric EQ",
			Input: "1 == 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "==",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric NEQ",
			Input: "1 != 2",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "!=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 2,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric GT",
			Input: "1 > 0",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: ">",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 0,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric LT",
			Input: "1 < 2",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "<",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 2,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric GTE",
			Input: "1 >= 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: ">=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric LTE",
			Input: "1 <= 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: COMPARATOR,
						Value: "<=",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestModifierParsing(test *testing.T) {

	tokenParsingTests := []TokenParsingTest {

		TokenParsingTest {

			Name: "Numeric PLUS",
			Input: "1 + 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "+",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MINUS",
			Input: "1 - 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "-",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MULTIPLY",
			Input: "1 * 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "*",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric DIVIDE",
			Input: "1 / 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "/",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
		TokenParsingTest {

			Name: "Numeric MODULUS",
			Input: "1 % 1",
			Expected: []ExpressionToken {
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
					ExpressionToken {
						Kind: MODIFIER,
						Value: "%",
					},
					ExpressionToken {
						Kind: NUMERIC,
						Value: 1,
					},
			},
		},
	}

	runTokenParsingTest(tokenParsingTests, test)
}

func TestNoParameterEvaluation(test *testing.T) {

	evaluationTests := []EvaluationTest {

		EvaluationTest {

			Name: "Single PLUS",
			Input: "51 + 49",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Single MINUS",
			Input: "100 - 51",
			Expected: 49,
		},
		EvaluationTest {

			Name: "Single MULTIPLY",
			Input: "5 * 20",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Single DIVIDE",
			Input: "100 / 20",
			Expected: 5,
		},
		EvaluationTest {

			Name: "Single MODULUS",
			Input: "100 % 2",
			Expected: 0,
		},
		EvaluationTest {

			Name: "Compound PLUS",
			Input: "20 + 30 + 50",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Mutiple operators",
			Input: "20 * 5 - 49",
			Expected: 51,
		},
		EvaluationTest {

			Name: "Paren usage",
			Input: "100 - (5 * 10)",
			Expected: 50,
		},
		EvaluationTest {

			Name: "Nested parentheses",
			Input: "50 + (5 * (5 - 3))",
			Expected: 100,
		},
		EvaluationTest {

			Name: "Implicit boolean",
			Input: "2 > 1",
			Expected: true,
		},
		EvaluationTest {

			Name: "Compound boolean",
			Input: "5 < 10 && 1 < 5",
			Expected: true,
		},
		EvaluationTest {

			Name: "Parenthesis boolean",
			Input: "10 < 50 && (1 != 2 && 1 == 1)",
			Expected: true,
		},
		EvaluationTest {

			Name: "Comparison of string constants",
			Input: "'foo' == 'foo'",
			Expected: true,
		},
		EvaluationTest {

			Name: "NEQ comparison of string constants",
			Input: "'foo' != 'bar'",
			Expected: true,
		},
	}

	runEvaluationTests(evaluationTests, test)
}

func TestParameterizedEvaluation(test *testing.T) {

	evaluationTests := []EvaluationTest {

		EvaluationTest {

			Name: "Single parameter modified by constant",
			Input: "foo + 2",
			Parameters: []EvaluationParameter {

				EvaluationParameter {
					Name: "foo",
					Value: 2,
				},
			},
			Expected: 4,
		},
		EvaluationTest {

			Name: "Single parameter modified by constant",
			Input: "foo * bar",
			Parameters: []EvaluationParameter {

				EvaluationParameter {
					Name: "foo",
					Value: 5,
				},
				EvaluationParameter {
					Name: "bar",
					Value: 2,
				},
			},
			Expected: 10,
		},
	}

	runEvaluationTests(evaluationTests, test)
}

func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T) {

	var expression *EvaluableExpression
	var expectedToken ExpressionToken
	var expectedTokenLength, actualTokenLength int

	// Run the test cases.
	for _, parsingTest := range tokenParsingTests {

		expression = NewEvaluableExpression(parsingTest.Input)

		expectedTokenLength = len(parsingTest.Expected)
		actualTokenLength = len(expression.Tokens)

		if(actualTokenLength != expectedTokenLength) {

			test.Log("Test '",parsingTest.Name,"' failed:")
			test.Log("Expected ", expectedTokenLength, " tokens, actually found '", actualTokenLength, "'")
			test.Fail()
		}

		for i, token := range expression.Tokens {

			expectedToken = parsingTest.Expected[i]
			if(token.Kind != expectedToken.Kind) {

				test.Log("Test '", parsingTest.Name, "' failed:")
				test.Log("Expected token kind '", expectedToken.Kind, "' does not match '", token.Kind, "'")
				test.Fail()
			}

			if(token.Value != expectedToken.Value) {

				test.Log("Test '", parsingTest.Name, "' failed:")
				test.Log("Expected token value '", expectedToken.Kind, "' does not match '", token.Kind, "'")
				test.Fail()
			}
		}
	}
}

func runEvaluationTests(evaluationTests []EvaluationTest, test *testing.T) {

	var expression *EvaluableExpression
	var result interface{}
	var parameters map[string]interface{}

	// Run the test cases.
	for _, evaluationTest := range evaluationTests {

		expression = NewEvaluableExpression(evaluationTest.Input)
		parameters = make(map[string]interface{}, 8)

		for _, parameter := range evaluationTest.Parameters {
			parameters[parameter.Name] = parameter.Value
		}

		result = expression.Evaluate(parameters)

		if(result != evaluationTest.Expected) {

			test.Log("Test '", evaluationTest.Name, "' failed:")
			test.Log("Expected evaluation result '", evaluationTest.Expected, "' does not match '", result, "'")
			test.Fail()
		}
	}
}
<|endoftext|>


<commit_before>package govsphereutils

import (
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/vim25/mo"
)

type SearchFunction func(govmomi.Reference) bool

func FindVMByName(c *govmomi.Client, name string) govmomi.VirtualMachine {

}

func RecursiveSearch(c *govmomi.Client, sf SearchFunction) (govmomi.Reference, error) {
	// Take root vSphere folder and loop through each datacenter
	children, err := c.RootFolder().Children(c)
	if err != nil {
		return err
	}

	// Loop through all datacenters and recursively search their trees
	for _, v := range children {
		ref, err := recursion(c, v, sf)
		if err != nil {
			return err
		}

		if ref != nil {
			return ref, nil
		}
	}
}

// recursion is meant to take a datacenter and recursively move down the tree
func recursion(c *govmomi.Client, ref govmomi.Reference, sf SearchFunction) (govmomi.Reference, error) {
	// First thing is to check if we have found our reference
	if sf(ref) {
		return ref, nil
	}

	// We have not found our reference so keep moving down the tree
	switch ref.Reference().Type {
	case "Datacenter":
		dc := govmomi.NewDatacenter(ref.Reference())
		dcFolders, err := dc.Folders(c)
		if err != nil {
			return nil, err
		}

		// Walk the VM folder
		found, err = recursion(c, dcFolders.VmFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// Walk the Host folder
		found, err = recursion(c, dcFolders.HostFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// Walk the Datastore Folder
		found, err = recursion(c, dcFolders.DatastoreFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// walk the Network folder
		found, err = recursion(c, dcFolders.NetworkFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

	case "Folder":
		folder := govmomi.NewFolder(ref.Reference())
		children, err := folder.Children(c)
		if err != nil {
			return nil, err
		}

		for _, v := range children {
			found, err := recursion(c, v, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "StoragePod":
		var sp mo.StoragePod
		c.Properties(ref.Reference(), nil, &sp)
		govmomi.StoragePod
		for _, v := range sp.ChildEntity {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "ClusterComputeResource", "ComputeResource":
		var ccr mo.ClusterComputeResource
		c.Properties(ref.Reference(), nil, &ccr)

		for _, v := range ccr.Host {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "VmwareDistributedVirtualSwitch", "DistributedVirtualSwitch":
		var vDVS mo.VmwareDistributedVirtualSwitch
		c.Properties(ref.Reference(), nil, &vDVS)

		for _, v := range vDVS.Portgroup {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	default:
		return nil, nil
	}
}

func newReference(e types.ManagedObjectReference) Reference {
	switch e.Type {
	case "Folder":
		return &Folder{ManagedObjectReference: e}
	case "StoragePod":
		return &StoragePod{
			Folder{ManagedObjectReference: e},
		}
	case "Datacenter":
		return &Datacenter{ManagedObjectReference: e}
	case "VirtualMachine":
		return &VirtualMachine{ManagedObjectReference: e}
	case "VirtualApp":
		return &VirtualApp{
			ResourcePool{ManagedObjectReference: e},
		}
	case "ComputeResource":
		return &ComputeResource{ManagedObjectReference: e}
	case "ClusterComputeResource":
		return &ClusterComputeResource{
			ComputeResource{ManagedObjectReference: e},
		}
	case "HostSystem":
		return &HostSystem{ManagedObjectReference: e}
	case "Network":
		return &Network{ManagedObjectReference: e}
	case "ResourcePool":
		return &ResourcePool{ManagedObjectReference: e}
	case "DistributedVirtualSwitch":
		return &DistributedVirtualSwitch{ManagedObjectReference: e}
	case "VmwareDistributedVirtualSwitch":
		return &VmwareDistributedVirtualSwitch{
			DistributedVirtualSwitch{ManagedObjectReference: e},
		}
	case "DistributedVirtualPortgroup":
		return &DistributedVirtualPortgroup{ManagedObjectReference: e}
	case "Datastore":
		return &Datastore{ManagedObjectReference: e}
	default:
		panic("Unknown managed entity: " + e.Type)
	}
}
<commit_msg>Fixed build errors after initial commit<commit_after>package govsphereutils

import (
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
)

type SearchFunction func(govmomi.Reference) bool

func RecursiveSearch(c *govmomi.Client, sf SearchFunction) (govmomi.Reference, error) {
	// Take root vSphere folder and loop through each datacenter
	children, err := c.RootFolder().Children(c)
	if err != nil {
		return nil, err
	}

	// Loop through all datacenters and recursively search their trees
	for _, v := range children {
		ref, err := recursion(c, v, sf)
		if err != nil {
			return nil, err
		}

		if ref != nil {
			return ref, nil
		}
	}

	return nil, nil
}

// recursion is meant to take a datacenter and recursively move down the tree
func recursion(c *govmomi.Client, ref govmomi.Reference, sf SearchFunction) (govmomi.Reference, error) {
	// First thing is to check if we have found our reference
	if sf(ref) {
		return ref, nil
	}

	// We have not found our reference so keep moving down the tree
	switch ref.Reference().Type {
	case "Datacenter":
		dc := govmomi.NewDatacenter(ref.Reference())
		dcFolders, err := dc.Folders(c)
		if err != nil {
			return nil, err
		}

		// Walk the VM folder
		found, err := recursion(c, dcFolders.VmFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// Walk the Host folder
		found, err = recursion(c, dcFolders.HostFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// Walk the Datastore Folder
		found, err = recursion(c, dcFolders.DatastoreFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

		// walk the Network folder
		found, err = recursion(c, dcFolders.NetworkFolder, sf)
		if err != nil {
			return nil, err
		}
		if found != nil {
			return found, nil
		}

	case "Folder":
		folder := govmomi.NewFolder(ref.Reference())
		children, err := folder.Children(c)
		if err != nil {
			return nil, err
		}

		for _, v := range children {
			found, err := recursion(c, v, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "StoragePod":
		var sp mo.StoragePod
		c.Properties(ref.Reference(), nil, &sp)

		for _, v := range sp.ChildEntity {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "ClusterComputeResource", "ComputeResource":
		var ccr mo.ClusterComputeResource
		c.Properties(ref.Reference(), nil, &ccr)

		for _, v := range ccr.Host {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	case "VmwareDistributedVirtualSwitch", "DistributedVirtualSwitch":
		var vDVS mo.VmwareDistributedVirtualSwitch
		c.Properties(ref.Reference(), nil, &vDVS)

		for _, v := range vDVS.Portgroup {
			newRef := newReference(v)
			found, err := recursion(c, newRef, sf)
			if err != nil {
				return nil, err
			}
			if found != nil {
				return found, nil
			}
		}
	}

	return nil, nil
}

func newReference(e types.ManagedObjectReference) govmomi.Reference {
	switch e.Type {
	case "Folder":
		return &govmomi.Folder{ManagedObjectReference: e}
	case "StoragePod":
		return &govmomi.StoragePod{
			govmomi.Folder{ManagedObjectReference: e},
		}
	case "Datacenter":
		return &govmomi.Datacenter{ManagedObjectReference: e}
	case "VirtualMachine":
		return &govmomi.VirtualMachine{ManagedObjectReference: e}
	case "VirtualApp":
		return &govmomi.VirtualApp{
			govmomi.ResourcePool{ManagedObjectReference: e},
		}
	case "ComputeResource":
		return &govmomi.ComputeResource{ManagedObjectReference: e}
	case "ClusterComputeResource":
		return &govmomi.ClusterComputeResource{
			govmomi.ComputeResource{ManagedObjectReference: e},
		}
	case "HostSystem":
		return &govmomi.HostSystem{ManagedObjectReference: e}
	case "Network":
		return &govmomi.Network{ManagedObjectReference: e}
	case "ResourcePool":
		return &govmomi.ResourcePool{ManagedObjectReference: e}
	case "DistributedVirtualSwitch":
		return &govmomi.DistributedVirtualSwitch{ManagedObjectReference: e}
	case "VmwareDistributedVirtualSwitch":
		return &govmomi.VmwareDistributedVirtualSwitch{
			govmomi.DistributedVirtualSwitch{ManagedObjectReference: e},
		}
	case "DistributedVirtualPortgroup":
		return &govmomi.DistributedVirtualPortgroup{ManagedObjectReference: e}
	case "Datastore":
		return &govmomi.Datastore{ManagedObjectReference: e}
	default:
		panic("Unknown managed entity: " + e.Type)
	}
}
<|endoftext|>


<commit_before>package envflag

import (
	"errors"
	"flag"
	"os"
	"strings"
)

var MalformedValue = errors.New("Malformed environment key/value pair, expected <key>=<value>")

type Value []string

var _ flag.Value = &Value{}

// Set implements the flag.Value interface
func (v *Value) Set(s string) error {
	parts := strings.Split(s, "=")
	if len(parts) != 2 {
		return MalformedValue
	}

	*v = append(*v, s)

	return os.Setenv(parts[0], parts[1])
}

func (v *Value) String() string {
	return strings.Join(*v, ",")
}
<commit_msg>docs + convenient methodss added<commit_after>// Package envflag provides a way to override environment variables with flags
package envflag

import (
	"errors"
	"flag"
	"os"
	"strings"
)

// MalformedValue is returned by Value.Set if the given key/value pair is not
// valid.
var MalformedValue = errors.New("Malformed environment key/value pair, expected <key>=<value>")

// Value implements the flag.Value interface and can be used as a target for
// flag.Var to override environment variables. It doesn’t save any value.
type Value struct{}

var _ flag.Value = &Value{}

// NewValue returns a pointer on a Value
func NewValue() *Value { return &Value{} }

// Set implements the flag.Value interface
func (v *Value) Set(s string) error {
	parts := strings.SplitN(s, "=", 2)
	if len(parts) != 2 {
		return MalformedValue
	}

	return os.Setenv(parts[0], parts[1])
}

func (v *Value) String() string {
	// we want to stay light and thus don't keep track of the flags.
	return "{env}"
}

// Setup creates a flag that will be used to override environment variables. It
// must be called before flag.Parse(). The flag can occur multiple times in the
// command-line.
func Setup(flagName, usage string) {
	flag.Var(&Value{}, flagName, usage)
}

// AutoSetup is a shortcut for Setup("e", "Override environment variables"). It
// must be called before flag.Parse().
func AutoSetup() {
	flag.Var(&Value{}, "e", "Override environment variables")
}
<|endoftext|>


<commit_before>//go:build linux || darwin
// +build linux darwin

package command

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/mount"
	"github.com/chrislusf/seaweedfs/weed/mount/meta_cache"
	"github.com/chrislusf/seaweedfs/weed/mount/unmount"
	"github.com/chrislusf/seaweedfs/weed/pb"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/storage/types"
	"github.com/hanwen/go-fuse/v2/fuse"
	"net/http"
	"os"
	"os/user"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/util/grace"
)

func runMount2(cmd *Command, args []string) bool {

	if *mount2Options.debug {
		go http.ListenAndServe(fmt.Sprintf(":%d", *mount2Options.debugPort), nil)
	}

	grace.SetupProfiling(*mountCpuProfile, *mountMemProfile)
	if *mountReadRetryTime < time.Second {
		*mountReadRetryTime = time.Second
	}
	util.RetryWaitTime = *mountReadRetryTime

	umask, umaskErr := strconv.ParseUint(*mount2Options.umaskString, 8, 64)
	if umaskErr != nil {
		fmt.Printf("can not parse umask %s", *mount2Options.umaskString)
		return false
	}

	if len(args) > 0 {
		return false
	}

	return RunMount2(&mount2Options, os.FileMode(umask))
}

func RunMount2(option *Mount2Options, umask os.FileMode) bool {

	// basic checks
	chunkSizeLimitMB := *mount2Options.chunkSizeLimitMB
	if chunkSizeLimitMB <= 0 {
		fmt.Printf("Please specify a reasonable buffer size.")
		return false
	}

	// try to connect to filer
	filerAddresses := pb.ServerAddresses(*option.filer).ToAddresses()
	util.LoadConfiguration("security", false)
	grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
	var cipher bool
	var err error
	for i := 0; i < 10; i++ {
		err = pb.WithOneOfGrpcFilerClients(false, filerAddresses, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
			resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
			if err != nil {
				return fmt.Errorf("get filer grpc address %v configuration: %v", filerAddresses, err)
			}
			cipher = resp.Cipher
			return nil
		})
		if err != nil {
			glog.V(0).Infof("failed to talk to filer %v: %v", filerAddresses, err)
			glog.V(0).Infof("wait for %d seconds ...", i+1)
			time.Sleep(time.Duration(i+1) * time.Second)
		}
	}
	if err != nil {
		glog.Errorf("failed to talk to filer %v: %v", filerAddresses, err)
		return true
	}

	filerMountRootPath := *option.filerMountRootPath

	// clean up mount point
	dir := util.ResolvePath(*option.dir)
	if dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}

	unmount.Unmount(dir)

	// detect mount folder mode
	if *option.dirAutoCreate {
		os.MkdirAll(dir, os.FileMode(0777)&^umask)
	}
	fileInfo, err := os.Stat(dir)

	// collect uid, gid
	uid, gid := uint32(0), uint32(0)
	mountMode := os.ModeDir | 0777
	if err == nil {
		mountMode = os.ModeDir | os.FileMode(0777)&^umask
		uid, gid = util.GetFileUidGid(fileInfo)
		fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, mountMode)
	} else {
		fmt.Printf("can not stat %s\n", dir)
		return false
	}

	// detect uid, gid
	if uid == 0 {
		if u, err := user.Current(); err == nil {
			if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil {
				uid = uint32(parsedId)
			}
			if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil {
				gid = uint32(parsedId)
			}
			fmt.Printf("current uid=%d gid=%d\n", uid, gid)
		}
	}

	// mapping uid, gid
	uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
	if err != nil {
		fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err)
		return false
	}

	// Ensure target mount point availability
	if isValid := checkMountPointAvailable(dir); !isValid {
		glog.Fatalf("Expected mount to still be active, target mount point: %s, please check!", dir)
		return true
	}

	// mount fuse
	fuseMountOptions := &fuse.MountOptions{
		AllowOther:               *option.allowOthers,
		Options:                  nil,
		MaxBackground:            128,
		MaxWrite:                 1024 * 1024 * 2,
		MaxReadAhead:             1024 * 1024 * 2,
		IgnoreSecurityLabels:     false,
		RememberInodes:           false,
		FsName:                   *option.filer + ":" + filerMountRootPath,
		Name:                     "seaweedfs",
		SingleThreaded:           false,
		DisableXAttrs:            false,
		Debug:                    false, // *option.debug,
		EnableLocks:              false,
		ExplicitDataCacheControl: false,
		// SyncRead:                 false, // set to false to enable the FUSE_CAP_ASYNC_READ capability
		DirectMount:      true,
		DirectMountFlags: 0,
		// EnableAcl:                false,
	}

	// find mount point
	mountRoot := filerMountRootPath
	if mountRoot != "/" && strings.HasSuffix(mountRoot, "/") {
		mountRoot = mountRoot[0 : len(mountRoot)-1]
	}

	seaweedFileSystem := mount.NewSeaweedFileSystem(&mount.Option{
		MountDirectory:     dir,
		FilerAddresses:     filerAddresses,
		GrpcDialOption:     grpcDialOption,
		FilerMountRootPath: mountRoot,
		Collection:         *option.collection,
		Replication:        *option.replication,
		TtlSec:             int32(*option.ttlSec),
		DiskType:           types.ToDiskType(*option.diskType),
		ChunkSizeLimit:     int64(chunkSizeLimitMB) * 1024 * 1024,
		ConcurrentWriters:  *option.concurrentWriters,
		CacheDir:           *option.cacheDir,
		CacheSizeMB:        *option.cacheSizeMB,
		DataCenter:         *option.dataCenter,
		MountUid:           uid,
		MountGid:           gid,
		MountMode:          mountMode,
		MountCtime:         fileInfo.ModTime(),
		MountMtime:         time.Now(),
		Umask:              umask,
		VolumeServerAccess: *mount2Options.volumeServerAccess,
		Cipher:             cipher,
		UidGidMapper:       uidGidMapper,
	})

	server, err := fuse.NewServer(seaweedFileSystem, dir, fuseMountOptions)
	if err != nil {
		glog.Fatalf("Mount fail: %v", err)
	}
	grace.OnInterrupt(func() {
		unmount.Unmount(dir)
	})

	seaweedFileSystem.StartBackgroundTasks()

	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH)

	server.Serve()

	return true
}
<commit_msg>mount2: add back readonly mode<commit_after>//go:build linux || darwin
// +build linux darwin

package command

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/mount"
	"github.com/chrislusf/seaweedfs/weed/mount/meta_cache"
	"github.com/chrislusf/seaweedfs/weed/mount/unmount"
	"github.com/chrislusf/seaweedfs/weed/pb"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/storage/types"
	"github.com/hanwen/go-fuse/v2/fuse"
	"net/http"
	"os"
	"os/user"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/util/grace"
)

func runMount2(cmd *Command, args []string) bool {

	if *mount2Options.debug {
		go http.ListenAndServe(fmt.Sprintf(":%d", *mount2Options.debugPort), nil)
	}

	grace.SetupProfiling(*mountCpuProfile, *mountMemProfile)
	if *mountReadRetryTime < time.Second {
		*mountReadRetryTime = time.Second
	}
	util.RetryWaitTime = *mountReadRetryTime

	umask, umaskErr := strconv.ParseUint(*mount2Options.umaskString, 8, 64)
	if umaskErr != nil {
		fmt.Printf("can not parse umask %s", *mount2Options.umaskString)
		return false
	}

	if len(args) > 0 {
		return false
	}

	return RunMount2(&mount2Options, os.FileMode(umask))
}

func RunMount2(option *Mount2Options, umask os.FileMode) bool {

	// basic checks
	chunkSizeLimitMB := *mount2Options.chunkSizeLimitMB
	if chunkSizeLimitMB <= 0 {
		fmt.Printf("Please specify a reasonable buffer size.")
		return false
	}

	// try to connect to filer
	filerAddresses := pb.ServerAddresses(*option.filer).ToAddresses()
	util.LoadConfiguration("security", false)
	grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
	var cipher bool
	var err error
	for i := 0; i < 10; i++ {
		err = pb.WithOneOfGrpcFilerClients(false, filerAddresses, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
			resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
			if err != nil {
				return fmt.Errorf("get filer grpc address %v configuration: %v", filerAddresses, err)
			}
			cipher = resp.Cipher
			return nil
		})
		if err != nil {
			glog.V(0).Infof("failed to talk to filer %v: %v", filerAddresses, err)
			glog.V(0).Infof("wait for %d seconds ...", i+1)
			time.Sleep(time.Duration(i+1) * time.Second)
		}
	}
	if err != nil {
		glog.Errorf("failed to talk to filer %v: %v", filerAddresses, err)
		return true
	}

	filerMountRootPath := *option.filerMountRootPath

	// clean up mount point
	dir := util.ResolvePath(*option.dir)
	if dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}

	unmount.Unmount(dir)

	// detect mount folder mode
	if *option.dirAutoCreate {
		os.MkdirAll(dir, os.FileMode(0777)&^umask)
	}
	fileInfo, err := os.Stat(dir)

	// collect uid, gid
	uid, gid := uint32(0), uint32(0)
	mountMode := os.ModeDir | 0777
	if err == nil {
		mountMode = os.ModeDir | os.FileMode(0777)&^umask
		uid, gid = util.GetFileUidGid(fileInfo)
		fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, mountMode)
	} else {
		fmt.Printf("can not stat %s\n", dir)
		return false
	}

	// detect uid, gid
	if uid == 0 {
		if u, err := user.Current(); err == nil {
			if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil {
				uid = uint32(parsedId)
			}
			if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil {
				gid = uint32(parsedId)
			}
			fmt.Printf("current uid=%d gid=%d\n", uid, gid)
		}
	}

	// mapping uid, gid
	uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
	if err != nil {
		fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err)
		return false
	}

	// Ensure target mount point availability
	if isValid := checkMountPointAvailable(dir); !isValid {
		glog.Fatalf("Expected mount to still be active, target mount point: %s, please check!", dir)
		return true
	}

	// mount fuse
	fuseMountOptions := &fuse.MountOptions{
		AllowOther:               *option.allowOthers,
		Options:                  nil,
		MaxBackground:            128,
		MaxWrite:                 1024 * 1024 * 2,
		MaxReadAhead:             1024 * 1024 * 2,
		IgnoreSecurityLabels:     false,
		RememberInodes:           false,
		FsName:                   *option.filer + ":" + filerMountRootPath,
		Name:                     "seaweedfs",
		SingleThreaded:           false,
		DisableXAttrs:            false,
		Debug:                    false, // *option.debug,
		EnableLocks:              false,
		ExplicitDataCacheControl: false,
		DirectMount:              true,
		DirectMountFlags:         0,
		//SyncRead:                 false, // set to false to enable the FUSE_CAP_ASYNC_READ capability
		//EnableAcl:                true,
	}
	if *option.nonempty {
		fuseMountOptions.Options = append(fuseMountOptions.Options, "nonempty")
	}
	if *option.readOnly {
		if runtime.GOOS == "darwin" {
			fuseMountOptions.Options = append(fuseMountOptions.Options, "rdonly")
		} else {
			fuseMountOptions.Options = append(fuseMountOptions.Options, "ro")
		}
	}
	if runtime.GOOS == "darwin" {
		// https://github-wiki-see.page/m/macfuse/macfuse/wiki/Mount-Options
		ioSizeMB := 1
		for ioSizeMB*2 <= *option.chunkSizeLimitMB && ioSizeMB*2 <= 32 {
			ioSizeMB *= 2
		}
		fuseMountOptions.Options = append(fuseMountOptions.Options, "daemon_timeout=600")
		fuseMountOptions.Options = append(fuseMountOptions.Options, "noapplexattr")
		fuseMountOptions.Options = append(fuseMountOptions.Options, "novncache")
		fuseMountOptions.Options = append(fuseMountOptions.Options, "slow_statfs")
		fuseMountOptions.Options = append(fuseMountOptions.Options, "volname="+*option.filer)
		fuseMountOptions.Options = append(fuseMountOptions.Options, fmt.Sprintf("iosize=%d", ioSizeMB*1024*1024))
	}

	// find mount point
	mountRoot := filerMountRootPath
	if mountRoot != "/" && strings.HasSuffix(mountRoot, "/") {
		mountRoot = mountRoot[0 : len(mountRoot)-1]
	}

	seaweedFileSystem := mount.NewSeaweedFileSystem(&mount.Option{
		MountDirectory:     dir,
		FilerAddresses:     filerAddresses,
		GrpcDialOption:     grpcDialOption,
		FilerMountRootPath: mountRoot,
		Collection:         *option.collection,
		Replication:        *option.replication,
		TtlSec:             int32(*option.ttlSec),
		DiskType:           types.ToDiskType(*option.diskType),
		ChunkSizeLimit:     int64(chunkSizeLimitMB) * 1024 * 1024,
		ConcurrentWriters:  *option.concurrentWriters,
		CacheDir:           *option.cacheDir,
		CacheSizeMB:        *option.cacheSizeMB,
		DataCenter:         *option.dataCenter,
		MountUid:           uid,
		MountGid:           gid,
		MountMode:          mountMode,
		MountCtime:         fileInfo.ModTime(),
		MountMtime:         time.Now(),
		Umask:              umask,
		VolumeServerAccess: *mount2Options.volumeServerAccess,
		Cipher:             cipher,
		UidGidMapper:       uidGidMapper,
	})

	server, err := fuse.NewServer(seaweedFileSystem, dir, fuseMountOptions)
	if err != nil {
		glog.Fatalf("Mount fail: %v", err)
	}
	grace.OnInterrupt(func() {
		unmount.Unmount(dir)
	})

	seaweedFileSystem.StartBackgroundTasks()

	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH)

	server.Serve()

	return true
}
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

// Package ethtool  aims to provide a library giving a simple access to the
// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations
// from a network device like statistics, driver related informations or
// even the peer of a VETH interface.
package ethtool

import (
	"bytes"
	"fmt"
	"syscall"
	"unsafe"
)

// Maximum size of an interface name
const (
	IFNAMSIZ = 16
)

// ioctl ethtool request
const (
	SIOCETHTOOL = 0x8946
)

// ethtool stats related constants.
const (
	ETH_GSTRING_LEN  = 32
	ETH_SS_STATS     = 1
	ETHTOOL_GDRVINFO = 0x00000003
	ETHTOOL_GSTRINGS = 0x0000001b
	ETHTOOL_GSTATS   = 0x0000001d
	// other CMDs from ethtool-copy.h of ethtool-3.5 package
	ETHTOOL_GSET    = 0x00000001 /* Get settings. */
	ETHTOOL_SSET    = 0x00000002 /* Set settings. */
	ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
	ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
)

// MAX_GSTRINGS maximum number of stats entries that ethtool can
// retrieve currently.
const (
	MAX_GSTRINGS = 1000
)

type ifreq struct {
	ifr_name [IFNAMSIZ]byte
	ifr_data uintptr
}

type ethtoolDrvInfo struct {
	cmd          uint32
	driver       [32]byte
	version      [32]byte
	fw_version   [32]byte
	bus_info     [32]byte
	erom_version [32]byte
	reserved2    [12]byte
	n_priv_flags uint32
	n_stats      uint32
	testinfo_len uint32
	eedump_len   uint32
	regdump_len  uint32
}

type ethtoolGStrings struct {
	cmd        uint32
	string_set uint32
	len        uint32
	data       [MAX_GSTRINGS * ETH_GSTRING_LEN]byte
}

type ethtoolStats struct {
	cmd     uint32
	n_stats uint32
	data    [MAX_GSTRINGS]uint64
}

type Ethtool struct {
	fd int
}

// DriverName returns the driver name of the given interface.
func (e *Ethtool) DriverName(intf string) (string, error) {
	info, err := e.getDriverInfo(intf)
	if err != nil {
		return "", err
	}
	return string(bytes.Trim(info.driver[:], "\x00")), nil
}

// BusInfo returns the bus info of the given interface.
func (e *Ethtool) BusInfo(intf string) (string, error) {
	info, err := e.getDriverInfo(intf)
	if err != nil {
		return "", err
	}
	return string(bytes.Trim(info.bus_info[:], "\x00")), nil
}

func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
	drvinfo := ethtoolDrvInfo{
		cmd: ETHTOOL_GDRVINFO,
	}

	var name [IFNAMSIZ]byte
	copy(name[:], []byte(intf))

	ifr := ifreq{
		ifr_name: name,
		ifr_data: uintptr(unsafe.Pointer(&drvinfo)),
	}

	_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return ethtoolDrvInfo{}, syscall.Errno(ep)
	}

	return drvinfo, nil
}

// Stats retrieves stats of the given interface name.
func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
	drvinfo := ethtoolDrvInfo{
		cmd: ETHTOOL_GDRVINFO,
	}

	var name [IFNAMSIZ]byte
	copy(name[:], []byte(intf))

	ifr := ifreq{
		ifr_name: name,
		ifr_data: uintptr(unsafe.Pointer(&drvinfo)),
	}

	_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN {
		return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, drvinfo.n_stats)
	}

	gstrings := ethtoolGStrings{
		cmd:        ETHTOOL_GSTRINGS,
		string_set: ETH_SS_STATS,
		len:        drvinfo.n_stats,
		data:       [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
	}
	ifr.ifr_data = uintptr(unsafe.Pointer(&gstrings))

	_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	stats := ethtoolStats{
		cmd:     ETHTOOL_GSTATS,
		n_stats: drvinfo.n_stats,
		data:    [MAX_GSTRINGS]uint64{},
	}

	ifr.ifr_data = uintptr(unsafe.Pointer(&stats))

	_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	var result = make(map[string]uint64)
	for i := 0; i != int(drvinfo.n_stats); i++ {
		b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
		key := string(bytes.Trim(b, "\x00"))
		result[key] = stats.data[i]
	}

	return result, nil
}

func (e *Ethtool) Close() {
	syscall.Close(e.fd)
}

func NewEthtool() (*Ethtool, error) {
	fd, _, err := syscall.RawSyscall(syscall.SYS_SOCKET, syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
	if err != 0 {
		return nil, syscall.Errno(err)
	}

	return &Ethtool{
		fd: int(fd),
	}, nil
}

func BusInfo(intf string) (string, error) {
	e, err := NewEthtool()
	if err != nil {
		return "", err
	}
	defer e.Close()
	return e.BusInfo(intf)
}

func DriverName(intf string) (string, error) {
	e, err := NewEthtool()
	if err != nil {
		return "", err
	}
	defer e.Close()
	return e.DriverName(intf)
}

func Stats(intf string) (map[string]uint64, error) {
	e, err := NewEthtool()
	if err != nil {
		return nil, err
	}
	defer e.Close()
	return e.Stats(intf)
}
<commit_msg>use Go facilities to get a socket<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

// Package ethtool  aims to provide a library giving a simple access to the
// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations
// from a network device like statistics, driver related informations or
// even the peer of a VETH interface.
package ethtool

import (
	"bytes"
	"fmt"
	"syscall"
	"unsafe"
)

// Maximum size of an interface name
const (
	IFNAMSIZ = 16
)

// ioctl ethtool request
const (
	SIOCETHTOOL = 0x8946
)

// ethtool stats related constants.
const (
	ETH_GSTRING_LEN  = 32
	ETH_SS_STATS     = 1
	ETHTOOL_GDRVINFO = 0x00000003
	ETHTOOL_GSTRINGS = 0x0000001b
	ETHTOOL_GSTATS   = 0x0000001d
	// other CMDs from ethtool-copy.h of ethtool-3.5 package
	ETHTOOL_GSET    = 0x00000001 /* Get settings. */
	ETHTOOL_SSET    = 0x00000002 /* Set settings. */
	ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
	ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
)

// MAX_GSTRINGS maximum number of stats entries that ethtool can
// retrieve currently.
const (
	MAX_GSTRINGS = 1000
)

type ifreq struct {
	ifr_name [IFNAMSIZ]byte
	ifr_data uintptr
}

type ethtoolDrvInfo struct {
	cmd          uint32
	driver       [32]byte
	version      [32]byte
	fw_version   [32]byte
	bus_info     [32]byte
	erom_version [32]byte
	reserved2    [12]byte
	n_priv_flags uint32
	n_stats      uint32
	testinfo_len uint32
	eedump_len   uint32
	regdump_len  uint32
}

type ethtoolGStrings struct {
	cmd        uint32
	string_set uint32
	len        uint32
	data       [MAX_GSTRINGS * ETH_GSTRING_LEN]byte
}

type ethtoolStats struct {
	cmd     uint32
	n_stats uint32
	data    [MAX_GSTRINGS]uint64
}

type Ethtool struct {
	fd int
}

// DriverName returns the driver name of the given interface.
func (e *Ethtool) DriverName(intf string) (string, error) {
	info, err := e.getDriverInfo(intf)
	if err != nil {
		return "", err
	}
	return string(bytes.Trim(info.driver[:], "\x00")), nil
}

// BusInfo returns the bus info of the given interface.
func (e *Ethtool) BusInfo(intf string) (string, error) {
	info, err := e.getDriverInfo(intf)
	if err != nil {
		return "", err
	}
	return string(bytes.Trim(info.bus_info[:], "\x00")), nil
}

func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
	drvinfo := ethtoolDrvInfo{
		cmd: ETHTOOL_GDRVINFO,
	}

	var name [IFNAMSIZ]byte
	copy(name[:], []byte(intf))

	ifr := ifreq{
		ifr_name: name,
		ifr_data: uintptr(unsafe.Pointer(&drvinfo)),
	}

	_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return ethtoolDrvInfo{}, syscall.Errno(ep)
	}

	return drvinfo, nil
}

// Stats retrieves stats of the given interface name.
func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
	drvinfo := ethtoolDrvInfo{
		cmd: ETHTOOL_GDRVINFO,
	}

	var name [IFNAMSIZ]byte
	copy(name[:], []byte(intf))

	ifr := ifreq{
		ifr_name: name,
		ifr_data: uintptr(unsafe.Pointer(&drvinfo)),
	}

	_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN {
		return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, drvinfo.n_stats)
	}

	gstrings := ethtoolGStrings{
		cmd:        ETHTOOL_GSTRINGS,
		string_set: ETH_SS_STATS,
		len:        drvinfo.n_stats,
		data:       [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
	}
	ifr.ifr_data = uintptr(unsafe.Pointer(&gstrings))

	_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	stats := ethtoolStats{
		cmd:     ETHTOOL_GSTATS,
		n_stats: drvinfo.n_stats,
		data:    [MAX_GSTRINGS]uint64{},
	}

	ifr.ifr_data = uintptr(unsafe.Pointer(&stats))

	_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
	if ep != 0 {
		return nil, syscall.Errno(ep)
	}

	var result = make(map[string]uint64)
	for i := 0; i != int(drvinfo.n_stats); i++ {
		b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
		key := string(bytes.Trim(b, "\x00"))
		result[key] = stats.data[i]
	}

	return result, nil
}

func (e *Ethtool) Close() {
	syscall.Close(e.fd)
}

func NewEthtool() (*Ethtool, error) {
	fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
	if err != nil {
		return nil, err
	}

	return &Ethtool{
		fd: int(fd),
	}, nil
}

func BusInfo(intf string) (string, error) {
	e, err := NewEthtool()
	if err != nil {
		return "", err
	}
	defer e.Close()
	return e.BusInfo(intf)
}

func DriverName(intf string) (string, error) {
	e, err := NewEthtool()
	if err != nil {
		return "", err
	}
	defer e.Close()
	return e.DriverName(intf)
}

func Stats(intf string) (map[string]uint64, error) {
	e, err := NewEthtool()
	if err != nil {
		return nil, err
	}
	defer e.Close()
	return e.Stats(intf)
}
<|endoftext|>


<commit_before>package eval

import (
	"fmt"
	"reflect"

	"github.com/elves/elvish/eval/vartypes"
)

// Ns is a map from names to variables.
type Ns map[string]vartypes.Variable

var _ interface{} = Ns(nil)

func (Ns) Kind() string {
	return "ns"
}

func (ns Ns) Hash() uint32 {
	return uint32(addrOf(ns))
}

func (ns Ns) Equal(rhs interface{}) bool {
	if ns2, ok := rhs.(Ns); ok {
		return addrOf(ns) == addrOf(ns2)
	}
	return false
}

func (ns Ns) Repr(int) string {
	return fmt.Sprintf("<ns 0x%x>", addrOf(ns))
}

func addrOf(a interface{}) uintptr {
	return reflect.ValueOf(a).Pointer()
}

func (ns Ns) static() staticNs {
	static := make(staticNs)
	for name := range ns {
		static.set(name)
	}
	return static
}

// staticNs represents static information of an Ns.
type staticNs map[string]struct{}

func (ns staticNs) set(name string) {
	ns[name] = struct{}{}
}

func (ns staticNs) del(name string) {
	delete(ns, name)
}

func (ns staticNs) has(name string) bool {
	_, ok := ns[name]
	return ok
}
<commit_msg>Support indexing a namespace value.<commit_after>package eval

import (
	"fmt"
	"reflect"

	"github.com/elves/elvish/eval/vartypes"
)

// Ns is a map from names to variables.
type Ns map[string]vartypes.Variable

var _ interface{} = Ns(nil)

func (Ns) Kind() string {
	return "ns"
}

func (ns Ns) Hash() uint32 {
	return uint32(addrOf(ns))
}

func (ns Ns) Equal(rhs interface{}) bool {
	if ns2, ok := rhs.(Ns); ok {
		return addrOf(ns) == addrOf(ns2)
	}
	return false
}

func (ns Ns) Repr(int) string {
	return fmt.Sprintf("<ns 0x%x>", addrOf(ns))
}

func (ns Ns) Get(k interface{}) (interface{}, bool) {
	if kstring, ok := k.(string); ok {
		if v, ok := ns[kstring]; ok {
			return v.Get(), true
		}
	}
	return nil, false
}

func addrOf(a interface{}) uintptr {
	return reflect.ValueOf(a).Pointer()
}

func (ns Ns) static() staticNs {
	static := make(staticNs)
	for name := range ns {
		static.set(name)
	}
	return static
}

// staticNs represents static information of an Ns.
type staticNs map[string]struct{}

func (ns staticNs) set(name string) {
	ns[name] = struct{}{}
}

func (ns staticNs) del(name string) {
	delete(ns, name)
}

func (ns staticNs) has(name string) bool {
	_, ok := ns[name]
	return ok
}
<|endoftext|>


<commit_before>package gospel

import (
	"fmt"
	"io/ioutil"
	"runtime"
	"strings"
)

// it(message, evaluator) will create this object.
type Example struct {
	*Describing
	Message string
	Evaluator func(Expect)
	HasFailure bool
}

// Run() is called as soon as it(message, evaluator) is called.
func (example *Example) Run() {
	example.Started()
	example.Evaluate()
	example.Done()
}

// Evaluate() invokes its Evaluator function.
func (example *Example) Evaluate() {
	example.Evaluator(func(value interface{}) *Expectation {
		return &Expectation{example, value}
	})
}

// Started() is called at start of this example, printing doc-style example message.
func (example *Example) Started() {
	fullMessage := ""
	if example.DoneExamplesCount == 0 {
		fullMessage += example.Description + "\n"
	}
	if len(example.SubDescriptions) > 0 {
		differenceIsFound := false
		for i, subscription := range example.SubDescriptions {
			if !differenceIsFound && i <= len(example.PreviousSubDescriptions) - 1 {
				if subscription == example.PreviousSubDescriptions[i] {
					continue
				}
			}
			fullMessage += strings.Repeat("\t", i + 1) + subscription + "\n"
			differenceIsFound = true
		}
	}
	fmt.Print(fullMessage)
	example.PreviousSubDescriptions = append(make([]string, 0), example.SubDescriptions...)
}

// Done() is called at end of this example.
func (example *Example) Done() {
	example.DoneExamplesCount++
	if !example.HasFailure {
		example.Succeeded()
	}
}

// Succeeded() is called when all of its expectations are passed.
func (example *Example) Succeeded() {
	fmt.Println(example.LeftMargin() + green(example.Message))
}

// Failed() is called when any of its expectation failed.
func (example *Example) Failed(message string, actual, expected interface{}) {
	_, filename, line, _ := runtime.Caller(3)
	buffer, _ := ioutil.ReadFile(filename)
	lines := strings.Split(string(buffer), "\n")[line-2:line+2]
	fmt.Printf(
		red("%s%s\n") +
		grey("%sExpected `%v` to %s `%v`\n") +
		grey("%s%s:%d\n") +
		grey("%s%4d.%s\n") +
		grey("%s%4d.%s\n") +
		grey("%s%4d.%s\n"),
		example.LeftMargin(), example.Message,
		example.LeftMargin(), actual, message, expected,
		example.LeftMargin(), filename, line,
		example.LeftMargin(), line - 1, lines[0],
		example.LeftMargin(), line + 0, lines[1],
		example.LeftMargin(), line + 1, lines[2],
	)
	example.Describing.T.Fail()
	example.HasFailure = true
}

// Utility method to put margin.
func (example *Example) LeftMargin() string {
	return strings.Repeat("\t", len(example.SubDescriptions) + 1)
}

// Add red terminal ANSI color
func red(str string) string {
	return "\033[31m\033[1m" + str + "\033[0m"
}

// Add green terminal ANSI color
func green(str string) string {
	return "\033[32m\033[1m" + str + "\033[0m"
}

// Add grey terminal ANSI color
func grey(str string) string {
	return "\x1B[90m" + str + "\033[0m"
}
<commit_msg>Use 2-spaces instead of a hardtab for test output<commit_after>package gospel

import (
	"fmt"
	"io/ioutil"
	"runtime"
	"strings"
)

// it(message, evaluator) will create this object.
type Example struct {
	*Describing
	Message string
	Evaluator func(Expect)
	HasFailure bool
}

// Run() is called as soon as it(message, evaluator) is called.
func (example *Example) Run() {
	example.Started()
	example.Evaluate()
	example.Done()
}

// Evaluate() invokes its Evaluator function.
func (example *Example) Evaluate() {
	example.Evaluator(func(value interface{}) *Expectation {
		return &Expectation{example, value}
	})
}

// Started() is called at start of this example, printing doc-style example message.
func (example *Example) Started() {
	fullMessage := ""
	if example.DoneExamplesCount == 0 {
		fullMessage += example.Description + "\n"
	}
	if len(example.SubDescriptions) > 0 {
		differenceIsFound := false
		for i, subscription := range example.SubDescriptions {
			if !differenceIsFound && i <= len(example.PreviousSubDescriptions) - 1 {
				if subscription == example.PreviousSubDescriptions[i] {
					continue
				}
			}
			fullMessage += strings.Repeat("  ", i + 1) + subscription + "\n"
			differenceIsFound = true
		}
	}
	fmt.Print(fullMessage)
	example.PreviousSubDescriptions = append(make([]string, 0), example.SubDescriptions...)
}

// Done() is called at end of this example.
func (example *Example) Done() {
	example.DoneExamplesCount++
	if !example.HasFailure {
		example.Succeeded()
	}
}

// Succeeded() is called when all of its expectations are passed.
func (example *Example) Succeeded() {
	fmt.Println(example.LeftMargin() + green(example.Message))
}

// Failed() is called when any of its expectation failed.
func (example *Example) Failed(message string, actual, expected interface{}) {
	_, filename, line, _ := runtime.Caller(3)
	buffer, _ := ioutil.ReadFile(filename)
	lines := strings.Split(string(buffer), "\n")[line-2:line+2]
	fmt.Printf(
		red("%s%s\n") +
		grey("%sExpected `%v` to %s `%v`\n") +
		grey("%s%s:%d\n") +
		grey("%s%4d.%s\n") +
		grey("%s%4d.%s\n") +
		grey("%s%4d.%s\n"),
		example.LeftMargin(), example.Message,
		example.LeftMargin(), actual, message, expected,
		example.LeftMargin(), filename, line,
		example.LeftMargin(), line - 1, lines[0],
		example.LeftMargin(), line + 0, lines[1],
		example.LeftMargin(), line + 1, lines[2],
	)
	example.Describing.T.Fail()
	example.HasFailure = true
}

// Utility method to put margin.
func (example *Example) LeftMargin() string {
	return strings.Repeat("  ", len(example.SubDescriptions) + 1)
}

// Add red terminal ANSI color
func red(str string) string {
	return "\033[31m\033[1m" + str + "\033[0m"
}

// Add green terminal ANSI color
func green(str string) string {
	return "\033[32m\033[1m" + str + "\033[0m"
}

// Add grey terminal ANSI color
func grey(str string) string {
	return "\x1B[90m" + str + "\033[0m"
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"go/build"
	"log"
	"os"
	"path/filepath"
	"strings"

	gps "github.com/sdboyer/vsolver"
)

func main() {
	// Operate on the current directory
	root, _ := os.Getwd()
	// Assume the current directory is correctly placed on a GOPATH, and derive
	// the ProjectRoot from it
	importroot := strings.TrimPrefix(filepath.Join(build.Default.GOPATH, "src"), root)

	params := gps.SolveParameters{
		RootDir:     root,
		ImportRoot:  gps.ProjectRoot(importroot),
		Trace:       true,
		TraceLogger: log.New(os.Stdout, "", 0),
	}

	sourcemgr, _ := gps.NewSourceManager(MyAnalyzer{}, "path/to/repocache", false)
	defer sourcemgr.Release()

	solver, _ := gps.Prepare(params, sourcemgr)
	solution, err := solver.Solve()
	if err == nil {
		os.RemoveAll(filepath.Join(root, "vendor"))
		gps.CreateVendorTree(filepath.Join(root, "vendor"), solution, sourcemgr, true)
	}
}

type MyAnalyzer struct{}

func (a MyAnalyzer) GetInfo(path string, n gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
	return nil, nil, nil
}
<commit_msg>Clean up the example so it works<commit_after>// +build ignore

package main

import (
	"go/build"
	"log"
	"os"
	"path/filepath"
	"strings"

	gps "github.com/sdboyer/vsolver"
)

func main() {
	// Operate on the current directory
	root, _ := os.Getwd()
	// Assume the current directory is correctly placed on a GOPATH, and derive
	// the ProjectRoot from it
	importroot := strings.TrimPrefix(root, filepath.Join(build.Default.GOPATH, "src")+string(filepath.Separator))

	params := gps.SolveParameters{
		RootDir:     root,
		ImportRoot:  gps.ProjectRoot(importroot),
		Trace:       true,
		TraceLogger: log.New(os.Stdout, "", 0),
	}

	sourcemgr, _ := gps.NewSourceManager(MyAnalyzer{}, ".repocache", false)
	defer sourcemgr.Release()

	solver, _ := gps.Prepare(params, sourcemgr)
	solution, err := solver.Solve()
	if err == nil {
		os.RemoveAll(filepath.Join(root, "vendor"))
		gps.CreateVendorTree(filepath.Join(root, "vendor"), solution, sourcemgr, true)
	}
}

type MyAnalyzer struct{}

func (a MyAnalyzer) GetInfo(path string, n gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
	return nil, nil, nil
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"go/build"
	"log"
	"os"
	"path/filepath"
	"strings"

	gps "github.com/sdboyer/gps"
)

// This is probably the simplest possible implementation of gps. It does the
// substantive work that `go get` does, except:
//  1. It drops the resulting tree into vendor instead of GOPATH
//  2. It prefers semver tags (if available) over branches
//  3. It removes any vendor directories nested within dependencies
//
//  This will compile and work...and then blow away the vendor directory present
//  in the cwd, if any. Be careful!
func main() {
	// Operate on the current directory
	root, _ := os.Getwd()
	// Assume the current directory is correctly placed on a GOPATH, and derive
	// the ProjectRoot from it
	srcprefix := filepath.Join(build.Default.GOPATH, "src") + string(filepath.Separator)
	importroot := filepath.ToSlash(strings.TrimPrefix(root, srcprefix))

	// Set up params, including tracing
	params := gps.SolveParameters{
		RootDir:     root,
		ImportRoot:  gps.ProjectRoot(importroot),
		Trace:       true,
		TraceLogger: log.New(os.Stdout, "", 0),
	}

	// Set up a SourceManager with the NaiveAnalyzer
	sourcemgr, _ := gps.NewSourceManager(NaiveAnalyzer{}, ".repocache", false)
	defer sourcemgr.Release()

	// Prep and run the solver
	solver, _ := gps.Prepare(params, sourcemgr)
	solution, err := solver.Solve()
	if err == nil {
		// If no failure, blow away the vendor dir and write a new one out,
		// stripping nested vendor directories as we go.
		os.RemoveAll(filepath.Join(root, "vendor"))
		gps.CreateVendorTree(filepath.Join(root, "vendor"), solution, sourcemgr, true)
	}
}

type NaiveAnalyzer struct{}

func (a NaiveAnalyzer) GetInfo(path string, n gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
	return nil, nil, nil
}
<commit_msg>Don't need to rename the import anymore<commit_after>// +build ignore

package main

import (
	"go/build"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/sdboyer/gps"
)

// This is probably the simplest possible implementation of gps. It does the
// substantive work that `go get` does, except:
//  1. It drops the resulting tree into vendor instead of GOPATH
//  2. It prefers semver tags (if available) over branches
//  3. It removes any vendor directories nested within dependencies
//
//  This will compile and work...and then blow away the vendor directory present
//  in the cwd, if any. Be careful!
func main() {
	// Operate on the current directory
	root, _ := os.Getwd()
	// Assume the current directory is correctly placed on a GOPATH, and derive
	// the ProjectRoot from it
	srcprefix := filepath.Join(build.Default.GOPATH, "src") + string(filepath.Separator)
	importroot := filepath.ToSlash(strings.TrimPrefix(root, srcprefix))

	// Set up params, including tracing
	params := gps.SolveParameters{
		RootDir:     root,
		ImportRoot:  gps.ProjectRoot(importroot),
		Trace:       true,
		TraceLogger: log.New(os.Stdout, "", 0),
	}

	// Set up a SourceManager with the NaiveAnalyzer
	sourcemgr, _ := gps.NewSourceManager(NaiveAnalyzer{}, ".repocache", false)
	defer sourcemgr.Release()

	// Prep and run the solver
	solver, _ := gps.Prepare(params, sourcemgr)
	solution, err := solver.Solve()
	if err == nil {
		// If no failure, blow away the vendor dir and write a new one out,
		// stripping nested vendor directories as we go.
		os.RemoveAll(filepath.Join(root, "vendor"))
		gps.CreateVendorTree(filepath.Join(root, "vendor"), solution, sourcemgr, true)
	}
}

type NaiveAnalyzer struct{}

func (a NaiveAnalyzer) GetInfo(path string, n gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
	return nil, nil, nil
}
<|endoftext|>


<commit_before>package syncmsg

// BaseMessage 接收消息
type BaseMessage struct {
	MsgID              string `json:"msgid"`           // 消息ID
	OpenKFID           string `json:"open_kfid"`       // 客服帐号ID（msgtype为event，该字段不返回）
	ExternalUserID     string `json:"external_userid"` // 客户UserID（msgtype为event，该字段不返回）
	ReceptionistUserID string `json:"servicer_userid"` // 接待客服userID
	SendTime           uint64 `json:"send_time"`       // 消息发送时间
	Origin             uint32 `json:"origin"`          // 消息来源。3-微信客户发送的消息 4-系统推送的事件消息 5-接待人员在企业微信客户端发送的消息
}

// Text 文本消息
type Text struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：text
	Text    struct {
		Content string `json:"content"` // 文本内容
		MenuID  string `json:"menu_id"` // 客户点击菜单消息，触发的回复消息中附带的菜单ID
	} `json:"text"` // 文本消息
}

// Image 图片消息
type Image struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：image
	Image   struct {
		MediaID string `json:"media_id"` // 图片文件ID
	} `json:"image"` // 图片消息
}

// Voice 语音消息
type Voice struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：voice
	Voice   struct {
		MediaID string `json:"media_id"` // 语音文件ID
	} `json:"voice"` // 语音消息
}

// Video 视频消息
type Video struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：video
	Video   struct {
		MediaID string `json:"media_id"` // 文件ID
	} `json:"video"` // 视频消息
}

// File 文件消息
type File struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：file
	File    struct {
		MediaID string `json:"media_id"` // 文件ID
	} `json:"file"` // 文件消息
}

// Location 地理位置消息
type Location struct {
	BaseMessage
	MsgType  string `json:"msgtype"` // 消息类型，此时固定为：location
	Location struct {
		Latitude  float32 `json:"latitude"`  // 纬度
		Longitude float32 `json:"longitude"` // 经度
		Name      string  `json:"name"`      // 位置名
		Address   string  `json:"address"`   // 地址详情说明
	} `json:"location"` // 地理位置消息
}

// Link 链接消息
type Link struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：link
	Link    struct {
		Title  string `json:"title"`   // 标题
		Desc   string `json:"desc"`    // 描述
		URL    string `json:"url"`     // 点击后跳转的链接
		PicURL string `json:"pic_url"` // 缩略图链接
	} `json:"link"` // 链接消息
}

// BusinessCard 名片消息
type BusinessCard struct {
	BaseMessage
	MsgType      string `json:"msgtype"` // 消息类型，此时固定为：business_card
	BusinessCard struct {
		UserID string `json:"userid"` // 名片 userid
	} `json:"business_card"` // 名片消息
}

// MiniProgram 小程序消息
type MiniProgram struct {
	BaseMessage
	MsgType     string `json:"msgtype"` // 消息类型，此时固定为：miniprogram
	MiniProgram struct {
		AppID        string `json:"appid"`          // 小程序appid，必须是关联到企业的小程序应用
		Title        string `json:"title"`          // 小程序消息标题，最多64个字节，超过会自动截断
		ThumbMediaID string `json:"thumb_media_id"` // 小程序消息封面的mediaid，封面图建议尺寸为520*416
		PagePath     string `json:"pagepath"`       // 点击消息卡片后进入的小程序页面路径
	} `json:"miniprogram"` // 小程序消息
}

// EventMessage 事件消息
type EventMessage struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType string `json:"event_type"` // 事件类型
	} `json:"event"` // 事件消息
}

// EnterSessionEvent 用户进入会话事件
type EnterSessionEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType      string `json:"event_type"`      // 事件类型。此处固定为：enter_session
		OpenKFID       string `json:"open_kfid"`       // 客服账号ID
		ExternalUserID string `json:"external_userid"` // 客户UserID
		Scene          string `json:"scene"`           // 进入会话的场景值，获取客服帐号链接开发者自定义的场景值
		SceneParam     string `json:"scene_param"`     // 进入会话的自定义参数，获取客服帐号链接返回的url，开发者按规范拼接的scene_param参数
		WelcomeCode    string `json:"welcome_code"`    // 如果满足发送欢迎语条件（条件为：1. 企业没有在管理端配置了原生欢迎语；2. 用户在过去48小时里未收过欢迎语，且未向该用户发过消息），会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。
	} `json:"event"` // 事件消息
}

// MsgSendFailEvent 消息发送失败事件
type MsgSendFailEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType      string `json:"event_type"`      // 事件类型。此处固定为：msg_send_fail
		OpenKFID       string `json:"open_kfid"`       // 客服账号ID
		ExternalUserID string `json:"external_userid"` // 客户UserID
		FailMsgID      string `json:"fail_msgid"`      // 发送失败的消息msgid
		FailType       uint32 `json:"fail_type"`       // 失败类型。0-未知原因 1-客服账号已删除 2-应用已关闭 4-会话已过期，超过48小时 5-会话已关闭 6-超过5条限制 7-未绑定视频号 8-主体未验证 9-未绑定视频号且主体未验证 10-用户拒收
	} `json:"event"` // 事件消息
}

// ReceptionistStatusChangeEvent 客服人员接待状态变更事件
type ReceptionistStatusChangeEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType          string `json:"event_type"`      // 事件类型。此处固定为：servicer_status_change
		ReceptionistUserID string `json:"servicer_userid"` // 客服人员userid
		Status             uint32 `json:"status"`          // 状态类型。1-接待中 2-停止接待
	} `json:"event"`
}

// SessionStatusChangeEvent 会话状态变更事件
type SessionStatusChangeEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType             string `json:"event_type"`          // 事件类型。此处固定为：session_status_change
		OpenKFID              string `json:"open_kfid"`           // 客服账号ID
		ExternalUserID        string `json:"external_userid"`     // 客户UserID
		ChangeType            uint32 `json:"change_type"`         // 变更类型。1-从接待池接入会话 2-转接会话 3-结束会话
		OldReceptionistUserID string `json:"old_servicer_userid"` // 老的客服人员userid。仅change_type为2和3有值
		NewReceptionistUserID string `json:"new_servicer_userid"` // 新的客服人员userid。仅change_type为1和2有值
		MsgCode               string `json:"msg_code"`            // 用于发送事件响应消息的code，仅change_type为1和3时，会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。
	} `json:"event"` // 事件消息
}
<commit_msg>feat: 微信客服会话变更事件支持查看客服账号ID (#510)<commit_after>package syncmsg

// BaseMessage 接收消息
type BaseMessage struct {
	MsgID              string `json:"msgid"`           // 消息ID
	OpenKFID           string `json:"open_kfid"`       // 客服帐号ID（msgtype为event，该字段不返回）
	ExternalUserID     string `json:"external_userid"` // 客户UserID（msgtype为event，该字段不返回）
	ReceptionistUserID string `json:"servicer_userid"` // 接待客服userID
	SendTime           uint64 `json:"send_time"`       // 消息发送时间
	Origin             uint32 `json:"origin"`          // 消息来源。3-微信客户发送的消息 4-系统推送的事件消息 5-接待人员在企业微信客户端发送的消息
}

// Text 文本消息
type Text struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：text
	Text    struct {
		Content string `json:"content"` // 文本内容
		MenuID  string `json:"menu_id"` // 客户点击菜单消息，触发的回复消息中附带的菜单ID
	} `json:"text"` // 文本消息
}

// Image 图片消息
type Image struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：image
	Image   struct {
		MediaID string `json:"media_id"` // 图片文件ID
	} `json:"image"` // 图片消息
}

// Voice 语音消息
type Voice struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：voice
	Voice   struct {
		MediaID string `json:"media_id"` // 语音文件ID
	} `json:"voice"` // 语音消息
}

// Video 视频消息
type Video struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：video
	Video   struct {
		MediaID string `json:"media_id"` // 文件ID
	} `json:"video"` // 视频消息
}

// File 文件消息
type File struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：file
	File    struct {
		MediaID string `json:"media_id"` // 文件ID
	} `json:"file"` // 文件消息
}

// Location 地理位置消息
type Location struct {
	BaseMessage
	MsgType  string `json:"msgtype"` // 消息类型，此时固定为：location
	Location struct {
		Latitude  float32 `json:"latitude"`  // 纬度
		Longitude float32 `json:"longitude"` // 经度
		Name      string  `json:"name"`      // 位置名
		Address   string  `json:"address"`   // 地址详情说明
	} `json:"location"` // 地理位置消息
}

// Link 链接消息
type Link struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：link
	Link    struct {
		Title  string `json:"title"`   // 标题
		Desc   string `json:"desc"`    // 描述
		URL    string `json:"url"`     // 点击后跳转的链接
		PicURL string `json:"pic_url"` // 缩略图链接
	} `json:"link"` // 链接消息
}

// BusinessCard 名片消息
type BusinessCard struct {
	BaseMessage
	MsgType      string `json:"msgtype"` // 消息类型，此时固定为：business_card
	BusinessCard struct {
		UserID string `json:"userid"` // 名片 userid
	} `json:"business_card"` // 名片消息
}

// MiniProgram 小程序消息
type MiniProgram struct {
	BaseMessage
	MsgType     string `json:"msgtype"` // 消息类型，此时固定为：miniprogram
	MiniProgram struct {
		AppID        string `json:"appid"`          // 小程序appid，必须是关联到企业的小程序应用
		Title        string `json:"title"`          // 小程序消息标题，最多64个字节，超过会自动截断
		ThumbMediaID string `json:"thumb_media_id"` // 小程序消息封面的mediaid，封面图建议尺寸为520*416
		PagePath     string `json:"pagepath"`       // 点击消息卡片后进入的小程序页面路径
	} `json:"miniprogram"` // 小程序消息
}

// EventMessage 事件消息
type EventMessage struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType string `json:"event_type"` // 事件类型
	} `json:"event"` // 事件消息
}

// EnterSessionEvent 用户进入会话事件
type EnterSessionEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType      string `json:"event_type"`      // 事件类型。此处固定为：enter_session
		OpenKFID       string `json:"open_kfid"`       // 客服账号ID
		ExternalUserID string `json:"external_userid"` // 客户UserID
		Scene          string `json:"scene"`           // 进入会话的场景值，获取客服帐号链接开发者自定义的场景值
		SceneParam     string `json:"scene_param"`     // 进入会话的自定义参数，获取客服帐号链接返回的url，开发者按规范拼接的scene_param参数
		WelcomeCode    string `json:"welcome_code"`    // 如果满足发送欢迎语条件（条件为：1. 企业没有在管理端配置了原生欢迎语；2. 用户在过去48小时里未收过欢迎语，且未向该用户发过消息），会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。
	} `json:"event"` // 事件消息
}

// MsgSendFailEvent 消息发送失败事件
type MsgSendFailEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType      string `json:"event_type"`      // 事件类型。此处固定为：msg_send_fail
		OpenKFID       string `json:"open_kfid"`       // 客服账号ID
		ExternalUserID string `json:"external_userid"` // 客户UserID
		FailMsgID      string `json:"fail_msgid"`      // 发送失败的消息msgid
		FailType       uint32 `json:"fail_type"`       // 失败类型。0-未知原因 1-客服账号已删除 2-应用已关闭 4-会话已过期，超过48小时 5-会话已关闭 6-超过5条限制 7-未绑定视频号 8-主体未验证 9-未绑定视频号且主体未验证 10-用户拒收
	} `json:"event"` // 事件消息
}

// ReceptionistStatusChangeEvent 客服人员接待状态变更事件
type ReceptionistStatusChangeEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType          string `json:"event_type"`      // 事件类型。此处固定为：servicer_status_change
		ReceptionistUserID string `json:"servicer_userid"` // 客服人员userid
		OpenKFID           string `json:"open_kfid"`       // 客服帐号ID
		Status             uint32 `json:"status"`          // 状态类型。1-接待中 2-停止接待
	} `json:"event"`
}

// SessionStatusChangeEvent 会话状态变更事件
type SessionStatusChangeEvent struct {
	BaseMessage
	MsgType string `json:"msgtype"` // 消息类型，此时固定为：event
	Event   struct {
		EventType             string `json:"event_type"`          // 事件类型。此处固定为：session_status_change
		OpenKFID              string `json:"open_kfid"`           // 客服账号ID
		ExternalUserID        string `json:"external_userid"`     // 客户UserID
		ChangeType            uint32 `json:"change_type"`         // 变更类型。1-从接待池接入会话 2-转接会话 3-结束会话
		OldReceptionistUserID string `json:"old_servicer_userid"` // 老的客服人员userid。仅change_type为2和3有值
		NewReceptionistUserID string `json:"new_servicer_userid"` // 新的客服人员userid。仅change_type为1和2有值
		MsgCode               string `json:"msg_code"`            // 用于发送事件响应消息的code，仅change_type为1和3时，会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。
	} `json:"event"` // 事件消息
}
<|endoftext|>


<commit_before>package worker

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/manveru/gostalk/gostalkc"
)

type BeanstalkdQueue struct {
	name  string
	addr  string
	Debug io.Writer
}

func NewBeanstalkdQueue(name, addr string) (bq *BeanstalkdQueue) {
	bq = new(BeanstalkdQueue)
	bq.name = name
	bq.addr = addr

	return
}

func (bq *BeanstalkdQueue) newClient() (*gostalkc.Client, error) {
	return gostalkc.DialTimeout(bq.addr, time.Minute)
}

// for test
var parseInterval = func(interval uint64) string {
	return strconv.FormatUint(interval*60, 10)
}

func (bq *BeanstalkdQueue) putJob(job *QorJob) (err error) {
	client, err := bq.newClient()
	if err != nil {
		return
	}

	delay := uint64(job.StartAt.Sub(time.Now()).Seconds())
	if delay < 0 {
		delay = 0
	}
	jobId := fmt.Sprintf("%d", job.ID)
	interval := parseInterval(job.Interval)
	body := strings.Join([]string{jobId, interval}, ",")
	queueJobId, _, err := client.Put(0, delay, 60, []byte(body))
	if err != nil {
		return
	}

	job.QueueJobId = fmt.Sprint(queueJobId)

	return
}

func (bq *BeanstalkdQueue) Name() string {
	return bq.name
}

func (bq *BeanstalkdQueue) Enqueue(job *QorJob) (err error) {
	return bq.putJob(job)
}

func (bq *BeanstalkdQueue) Dequeue() (jobId uint64, err error) {
	for {
		var client *gostalkc.Client
		client, err = bq.newClient()
		if err != nil {
			return
		}
		if err = client.Conn.SetDeadline(time.Now().Add(time.Minute * 5)); err != nil {
			client.Quit()

			if bq.Debug != nil {
				bq.Debug.Write([]byte("beanstalkd: failed to set deadline"))
			}

			continue
		}

		var body []byte
		var queueJobId uint64
		queueJobId, body, err = client.ReserveWithTimeout(300)
		if err != nil {
			if err.Error() == gostalkc.TIMED_OUT {
				if bq.Debug != nil {
					bq.Debug.Write([]byte("beanstalkd: reserve timeout"))
				}
			} else if _, ok := err.(*net.OpError); ok {
				if bq.Debug != nil {
					bq.Debug.Write([]byte("beanstalkd: conn deadline"))
				}
			} else {
				return
			}

			continue
		}

		parts := bytes.Split(body, []byte(","))
		jobId, err = strconv.ParseUint(string(parts[0]), 10, 0)
		if err != nil {
			return
		}

		if bq.Debug != nil {
			fmt.Fprintln(bq.Debug, "beanstalkd: receive job ", jobId)
		}

		if interval := string(parts[1]); interval == "0" {
			if err = client.Delete(queueJobId); err != nil {
				if bq.Debug != nil {
					fmt.Fprintln(bq.Debug, "beanstalkd: delete error", err)
				}
				return
			}
		} else {
			var i uint64
			i, err = strconv.ParseUint(interval, 10, 0)
			if err != nil {
				return
			}
			_, err = client.Release(queueJobId, 0, i)
			if err != nil {
				if bq.Debug != nil {
					fmt.Fprintln(bq.Debug, "beanstalkd: release error", err)
				}
				return
			}
		}

		client.Quit()

		return
	}

	return
}

func (bq *BeanstalkdQueue) Purge(job *QorJob) (err error) {
	client, err := bq.newClient()
	if err != nil {
		return
	}
	jobId, err := strconv.ParseUint(job.QueueJobId, 10, 0)
	if err != nil {
		return
	}

	err = client.Delete(jobId)

	return
}
<commit_msg>worker: beanstalk-queue: handle zero StartAT<commit_after>package worker

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/manveru/gostalk/gostalkc"
)

type BeanstalkdQueue struct {
	name  string
	addr  string
	Debug io.Writer
}

func NewBeanstalkdQueue(name, addr string) (bq *BeanstalkdQueue) {
	bq = new(BeanstalkdQueue)
	bq.name = name
	bq.addr = addr

	return
}

func (bq *BeanstalkdQueue) newClient() (*gostalkc.Client, error) {
	return gostalkc.DialTimeout(bq.addr, time.Minute)
}

// for test
var parseInterval = func(interval uint64) string {
	return strconv.FormatUint(interval*60, 10)
}

func (bq *BeanstalkdQueue) putJob(job *QorJob) (err error) {
	client, err := bq.newClient()
	if err != nil {
		return
	}

	var delay uint64
	if !job.StartAt.IsZero() {
		delay = uint64(job.StartAt.Sub(time.Now()).Seconds())
		if delay < 0 {
			delay = 0
		}
	}
	jobId := fmt.Sprintf("%d", job.ID)
	interval := parseInterval(job.Interval)
	body := strings.Join([]string{jobId, interval}, ",")
	queueJobId, _, err := client.Put(0, delay, 60, []byte(body))
	if err != nil {
		return
	}

	job.QueueJobId = fmt.Sprint(queueJobId)

	return
}

func (bq *BeanstalkdQueue) Name() string {
	return bq.name
}

func (bq *BeanstalkdQueue) Enqueue(job *QorJob) (err error) {
	return bq.putJob(job)
}

func (bq *BeanstalkdQueue) Dequeue() (jobId uint64, err error) {
	for {
		var client *gostalkc.Client
		client, err = bq.newClient()
		if err != nil {
			return
		}
		if err = client.Conn.SetDeadline(time.Now().Add(time.Minute * 5)); err != nil {
			client.Quit()

			if bq.Debug != nil {
				bq.Debug.Write([]byte("beanstalkd: failed to set deadline"))
			}

			continue
		}

		var body []byte
		var queueJobId uint64
		queueJobId, body, err = client.ReserveWithTimeout(300)
		if err != nil {
			if err.Error() == gostalkc.TIMED_OUT {
				if bq.Debug != nil {
					bq.Debug.Write([]byte("beanstalkd: reserve timeout"))
				}
			} else if _, ok := err.(*net.OpError); ok {
				if bq.Debug != nil {
					bq.Debug.Write([]byte("beanstalkd: conn deadline"))
				}
			} else {
				return
			}

			continue
		}

		parts := bytes.Split(body, []byte(","))
		jobId, err = strconv.ParseUint(string(parts[0]), 10, 0)
		if err != nil {
			return
		}

		if bq.Debug != nil {
			fmt.Fprintln(bq.Debug, "beanstalkd: receive job ", jobId)
		}

		if interval := string(parts[1]); interval == "0" {
			if err = client.Delete(queueJobId); err != nil {
				if bq.Debug != nil {
					fmt.Fprintln(bq.Debug, "beanstalkd: delete error", err)
				}
				return
			}
		} else {
			var i uint64
			i, err = strconv.ParseUint(interval, 10, 0)
			if err != nil {
				return
			}
			_, err = client.Release(queueJobId, 0, i)
			if err != nil {
				if bq.Debug != nil {
					fmt.Fprintln(bq.Debug, "beanstalkd: release error", err)
				}
				return
			}
		}

		client.Quit()

		return
	}

	return
}

func (bq *BeanstalkdQueue) Purge(job *QorJob) (err error) {
	client, err := bq.newClient()
	if err != nil {
		return
	}
	jobId, err := strconv.ParseUint(job.QueueJobId, 10, 0)
	if err != nil {
		return
	}

	err = client.Delete(jobId)

	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"crypto/rand"
	"fmt"
	"github.com/gorilla/mux"
	"golang.org/x/crypto/sha3"
	"html/template"
	"io"
	//"encoding/base64"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

/*
//=====================================
// general strategy:
// we take in a file, the filename is a hashed random string.
// the file is stored with its filename as the hased string.
// the random string (token) is returned back to the user.
//
// now when the user wants to retrive the file, he puts in the
// token (random string from earlier). his request is hashed and
// the stored has is returned. ez
//=====================================
*/

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", landingpage).Methods("GET")
	router.HandleFunc("/css/style.css", css).Methods("GET")
	router.HandleFunc("/js/index.js", js).Methods("GET")
	router.HandleFunc("/bitnuke.png", img).Methods("GET")
	router.HandleFunc("/{fdata}", handlerdynamic).Methods("GET")
	router.HandleFunc("/upload", upload)
	log.Fatal(http.ListenAndServe(":8802", router))
}

func landingpage(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/index.html")
}

func css(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/css/style.css")
}

func js(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/js/index.js")
}

func img(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/bitnuke.png")
}

func handlerdynamic(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	fdata := vars["fdata"]

	// hash the token that is passed
	hash := sha3.Sum512([]byte(fdata))
	hashstr := fmt.Sprintf("%x", hash)

	//token := randStr(8)
	//log.Printf(token)

	// serve up the hashed token if it exists
	path := fmt.Sprintf("./tmpnuke/%s", hashstr)
	// if data exists
	if _, err := os.Stat(path); err == nil {
		log.Printf("data exists")
		tokenserv(w, r, hashstr)
	}
	// if data does not exist
	if _, err := os.Stat(path); err != nil {
		fmt.Fprintf(w, "token not found")
	}
}

func upload(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method)
	if r.Method == "GET" {
		crutime := time.Now().Unix()
		h := md5.New()
		io.WriteString(h, strconv.FormatInt(crutime, 10))
		token := fmt.Sprintf("%x", h.Sum(nil))

		t, _ := template.ParseFiles("upload.gtpl")
		t.Execute(w, token)
	} else {
		r.ParseMultipartForm(32 << 20)
		file, _, err := r.FormFile("uploadfile")
		if err != nil {
			fmt.Println(err)
			return
		}
		defer file.Close()
		//fmt.Fprintf(w, "%v", handler.Header)

		// generate token and hash to save
		token := randStr(8)
		fmt.Fprintf(w, ""+
			""+
			"
bitnuke.io/%v
"+
			"", token, token)
		hash := sha3.Sum512([]byte(token))
		hashstr := fmt.Sprintf("%x", hash)
		fmt.Println(token)

		f, err := os.OpenFile("./tmpnuke/"+hashstr, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer f.Close()
		io.Copy(f, file)
	}
}

func tokenserv(w http.ResponseWriter, r *http.Request, data string) {
	log.Printf("Responsing to", data)
	item := fmt.Sprintf("./tmpnuke/%s", data)
	http.ServeFile(w, r, item)
}

func randStr(strSize int) string {
	dictionary := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

	var bytes = make([]byte, strSize)
	rand.Read(bytes)
	for k, v := range bytes {
		bytes[k] = dictionary[v%byte(len(dictionary))]
	}
	return string(bytes)
}
<commit_msg>new token page<commit_after>package main

import (
	"crypto/md5"
	"crypto/rand"
	"fmt"
	"github.com/gorilla/mux"
	"golang.org/x/crypto/sha3"
	"html/template"
	"io"
	//"encoding/base64"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

/*
//=====================================
// general strategy:
// we take in a file, the filename is a hashed random string.
// the file is stored with its filename as the hased string.
// the random string (token) is returned back to the user.
//
// now when the user wants to retrive the file, he puts in the
// token (random string from earlier). his request is hashed and
// the stored has is returned. ez
//=====================================
*/

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", landingpage).Methods("GET")
	router.HandleFunc("/css/style.css", css).Methods("GET")
	router.HandleFunc("/js/index.js", js).Methods("GET")
	router.HandleFunc("/bitnuke.png", img).Methods("GET")
	router.HandleFunc("/{fdata}", handlerdynamic).Methods("GET")
	router.HandleFunc("/upload", upload)
	log.Fatal(http.ListenAndServe(":8802", router))
}

func landingpage(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/index.html")
}

func css(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/css/style.css")
}

func js(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/js/index.js")
}

func img(w http.ResponseWriter, r *http.Request) {
	//w.Header().Set("Content-Type", "text/html")
	http.ServeFile(w, r, "./upload/bitnuke.png")
}

func handlerdynamic(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	fdata := vars["fdata"]

	// hash the token that is passed
	hash := sha3.Sum512([]byte(fdata))
	hashstr := fmt.Sprintf("%x", hash)

	//token := randStr(8)
	//log.Printf(token)

	// serve up the hashed token if it exists
	path := fmt.Sprintf("./tmpnuke/%s", hashstr)
	// if data exists
	if _, err := os.Stat(path); err == nil {
		log.Printf("data exists")
		tokenserv(w, r, hashstr)
	}
	// if data does not exist
	if _, err := os.Stat(path); err != nil {
		fmt.Fprintf(w, "token not found")
	}
}

func upload(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method)
	if r.Method == "GET" {
		crutime := time.Now().Unix()
		h := md5.New()
		io.WriteString(h, strconv.FormatInt(crutime, 10))
		token := fmt.Sprintf("%x", h.Sum(nil))

		t, _ := template.ParseFiles("upload.gtpl")
		t.Execute(w, token)
	} else {
		r.ParseMultipartForm(32 << 20)
		file, _, err := r.FormFile("uploadfile")
		if err != nil {
			fmt.Println(err)
			return
		}
		defer file.Close()
		//fmt.Fprintf(w, "%v", handler.Header)

		// generate token and hash to save
		token := randStr(8)
		fmt.Fprintf(w, ""+
			""+
			"
bitnuke.io/%v
"+
			"", token, token)
		hash := sha3.Sum512([]byte(token))
		hashstr := fmt.Sprintf("%x", hash)
		fmt.Println(token)

		f, err := os.OpenFile("./tmpnuke/"+hashstr, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer f.Close()
		io.Copy(f, file)
	}
}

func tokenserv(w http.ResponseWriter, r *http.Request, data string) {
	log.Printf("Responsing to", data)
	item := fmt.Sprintf("./tmpnuke/%s", data)
	http.ServeFile(w, r, item)
}

func randStr(strSize int) string {
	dictionary := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

	var bytes = make([]byte, strSize)
	rand.Read(bytes)
	for k, v := range bytes {
		bytes[k] = dictionary[v%byte(len(dictionary))]
	}
	return string(bytes)
}
<|endoftext|>