<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package parser

import (
	"flag"
	"strings"
	"testing"

	"github.com/google/mtail/internal/testutil"
	"github.com/google/mtail/internal/vm/ast"
	"github.com/google/mtail/internal/vm/position"
)

var parserTestDebug = flag.Bool("parser_test_debug", false, "Turn on parser debug output if set.")

var parserTests = []struct {
	name    string
	program string
}{
	{"empty",
		""},

	{"newline",
		"\n"},

	{"declare counter",
		"counter lines_total\n"},

	{"declare counter string name",
		"counter lines_total as \"line-count\"\n"},

	{"declare dimensioned counter",
		"counter foo by bar\n"},

	{"declare multi-dimensioned counter",
		"counter foo by bar, baz, quux\n"},

	{"declare hidden counter",
		"hidden counter foo\n"},

	{"declare gauge",
		"gauge foo\n"},

	{"declare timer",
		"timer foo\n"},

	{"declare text",
		"text stringy\n"},

	{"declare histogram",
		"histogram foo buckets 0, 1, 2\n"},
	{"declare histogram float",
		"histogram foo buckets 0, 0.01, 0.1, 1, 10\n"},
	{"declare histogram by ",
		"histogram foo by code buckets 0, 1, 2\n"},
	{"declare histogram reversed syntax ",
		"histogram foo buckets 0, 1, 2 by code\n"},

	{"simple pattern action",
		"/foo/ {}\n"},

	{"increment counter",
		"counter lines_total\n" +
			"/foo/ {\n" +
			"  lines_total++\n" +
			"}\n"},

	{"decrement counter",
		`counter i
/foo/ {
  i--
}
`},

	{"regex match includes escaped slashes",
		"counter foo\n" +
			"/foo\\// { foo++\n}\n"},

	{"numeric capture group reference",
		"/(foo)/ {\n" +
			"  $1++\n" +
			"}\n"},

	{"strptime and capref",
		"/(.*)/ {\n" +
			"strptime($1, \"2006-01-02T15:04:05Z07:00\")\n" +
			" }\n"},

	{"named capture group reference",
		"/(?P<date>[[:digit:]-\\/ ])/ {\n" +
			"  strptime($date, \"%Y/%m/%d %H:%M:%S\")\n" +
			"}\n"},

	{"nested match conditions",
		"counter foo\n" +
			"counter bar\n" +
			"/match(\\d+)/ {\n" +
			"  foo += $1\n" +
			"  /^bleh (\\S+)/ {\n" +
			"    bar++\n" +
			"    $1++\n" +
			"  }\n" +
			"}\n"},

	{"nested scope",
		"counter foo\n" +
			"/fo(o)/ {\n" +
			"  $1++\n" +
			"  /bar(xxx)/ {\n" +
			"    $1 += $1\n" +
			"    foo = $1\n" +
			"  }\n" +
			"}\n"},

	{"comment then code",
		"# %d [%p]\n" +
			"/^(?P<date>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) \\[(?P<pid>\\d+)\\] / {\n" +
			"  strptime($1, \"2006/01/02 15:04:05\")\n" +
			"}\n"},

	{"assignment",
		"counter variable\n" +
			"/(?P<foo>.*)/ {\n" +
			"variable = $foo\n" +
			"}\n"},

	{"increment operator",
		"counter var\n" +
			"/foo/ {\n" +
			"  var++\n" +
			"}\n"},

	{"incby operator",
		"counter var\n" +
			"/foo/ {\n  var += 2\n}\n"},

	{"additive",
		"counter time_total\n" +
			"/(?P<foo>.*)/ {\n" +
			" time_total = timestamp() - time_total\n" +
			"}\n"},

	{"multiplicative",
		"counter a\n" +
			"counter b\n" +
			"   /foo/ {\n   a = a * b\n" +
			"      a = a ** b\n" +
			"}\n"},

	{"additive and mem storage",
		"counter time_total\n" +
			"counter variable by foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  time_total += timestamp() - variable[$foo]\n" +
			"}\n"},

	{"conditional expressions",
		"counter foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  $foo > 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo >= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo < 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo <= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo == 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo != 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"}\n"},

	{"decorator definition and invocation",
		"def foo { next\n }\n" +
			"@foo { }\n",
	},

	{"const regex",
		"const X /foo/\n" +
			"/foo / + X + / bar/ {\n" +
			"}\n",
	},

	{"multiline regex",
		"/foo / +\n" +
			"/barrr/ {\n" +
			"}\n",
	},

	{"len",
		"/(?P<foo>foo)/ {\n" +
			"len($foo) > 0 {\n" +
			"}\n" +
			"}\n",
	},

	{"def and next",
		"def foobar {/(?P<date>.*)/ {" +
			"  next" +
			"}" +
			"}",
	},

	{"const",
		`const IP /\d+(\.\d+){3}/`},

	{"bitwise",
		`gauge a
/foo(\d)/ {
  a = $1 & 7
  a = $1 | 8
  a = $1 << 4
  a = $1 >> 20
  a = $1 ^ 15
  a = ~ 1
}`},

	{"logical",
		`0 || 1 && 0 {
}
`,
	},

	{"floats",
		`gauge foo
/foo/ {
foo = 3.14
}`},

	{"simple otherwise action",
		"otherwise {}\n"},

	{"pattern action then otherwise action",
		`counter lines_total by type
		/foo/ {
			lines_total["foo"]++
		}
		otherwise {
			lines_total["misc"] += 10
		}`},

	{"simple else clause",
		"/foo/ {} else {}"},

	{"nested else clause",
		"/foo/ { / bar/ {}  } else { /quux/ {} else {} }"},

	{"mod operator",
		`gauge a
/foo/ {
  a = 3 % 1
}`},

	{"delete",
		`counter foo by bar
/foo/ {
  del foo[$1]
}`},

	{"delete after",
		`counter foo by bar
/foo/ {
  del foo[$1] after 168h
}`},

	{"getfilename", `
getfilename()
`},

	{"indexed expression arg list", `
counter foo by a,b
/(\d) (\d+)/ {
  foo[$1,$2]++
}`},

	{"paren expr", `
(0) || (1 && 3) {
}`},

	{"regex cond expr", `
/(\d)/ && 1 {
}
`},

	{"concat expr 1", `
const X /foo/
/bar/ + X {
}`},
	{"concat expr 2", `
const X /foo/
X {
}`},

	{"match expression 1", `
$foo =~ /bar/ {
}
$foo !~ /bar/ {
}
`},
	{"match expression 2", `
$foo =~ /bar/ + X {
}`},
	{"match expression 3", `
const X /foo/
$foo =~ X {
}`},

	{"capref used in def", `
/(?P<x>.*)/ && $x > 0 {
}`},

	{"match expr 4", `
/(?P<foo>.{6}) (?P<bar>.*)/ {
  $foo =~ $bar {
  }
}`},

	{"stop", `
// {
  stop
}`},

	{"substitution", `
/(\d,\d)/ {
  subst(",", ",", $1)
}`},
}

func TestParserRoundTrip(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			r := mtailParse(p)

			if r != 0 || p.root == nil || len(p.errors) > 0 {
				t.Error("1st pass parse errors:\n")
				for _, e := range p.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Fatal()
			}

			if *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}

			u := Unparser{}
			output := u.Unparse(p.root)

			p2 := newParser(tc.name+" 2", strings.NewReader(output))
			r = mtailParse(p2)
			if r != 0 || p2.root == nil || len(p2.errors) > 0 {
				t.Errorf("2nd pass parse errors:\n")
				for _, e := range p2.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Logf("2nd pass input was:\n%s", output)
				t.Logf("2nd pass diff:\n%s", testutil.Diff(tc.program, output))
				t.Fatal()
			}

			u = Unparser{}
			output2 := u.Unparse(p2.root)

			testutil.ExpectNoDiff(t, output2, output)
		})
	}
}

type parserInvalidProgram struct {
	name    string
	program string
	errors  []string
}

var parserInvalidPrograms = []parserInvalidProgram{
	{"unknown character",
		"?\n",
		[]string{"unknown character:1:1: Unexpected input: '?'"}},

	{"unterminated regex",
		"/foo\n",
		[]string{"unterminated regex:1:2-4: Unterminated regular expression: \"/foo\"",
			"unterminated regex:1:2-4: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unterminated string",
		" \"foo }\n",
		[]string{"unterminated string:1:2-7: Unterminated quoted string: \"\\\"foo }\""}},

	{"unterminated const regex",
		"const X /(?P<foo>",
		[]string{"unterminated const regex:1:10-17: Unterminated regular expression: \"/(?P<foo>\"",
			"unterminated const regex:1:10-17: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unbalanced {",
		"/foo/ {\n",
		[]string{"unbalanced {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced else {",
		"/foo/ { } else {\n",
		[]string{"unbalanced else {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced otherwise {",
		"otherwise {\n",
		[]string{"unbalanced otherwise {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},

	{"index of non-terminal 1",
		`// {
	foo++[$1]++
	}`,
		[]string{"index of non-terminal 1:2:7: syntax error: unexpected indexing of an expression"}},
	{"index of non-terminal 2",
		`// {
	0[$1]++
	}`,
		[]string{"index of non-terminal 2:2:3: syntax error: unexpected indexing of an expression"}},

	{"statement with no effect",
		`/(\d)foo/ {
 timestamp() - $1
}`, []string{"statement with no effect:3:18: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},

	{"pattern without block",
		`/(?P<a>.)/
	/(?P<b>.)/ {}
	`,
		[]string{"pattern without block:2:11: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},
}

func TestParseInvalidPrograms(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserInvalidPrograms {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			mtailParse(p)

			testutil.ExpectNoDiff(t,
				strings.Join(tc.errors, "\n"),             // want
				strings.TrimRight(p.errors.Error(), "\n")) // got
			if p.errors.Error() == "no errors" && *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}
		})
	}
}

var parsePositionTests = []struct {
	name      string
	program   string
	positions []*position.Position
}{
	{
		"empty",
		"",
		nil,
	},
	{
		"variable",
		`counter foo`,
		[]*position.Position{{"variable", 0, 8, 10}},
	},
	{
		"pattern",
		`const ID /foo/`,
		[]*position.Position{{"pattern", 0, 6, 13}},
	},
}

func TestParsePositionTests(t *testing.T) {
	for _, tc := range parsePositionTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			// Not t.Parallel() because the parser is not reentrant, and mtailDebug is a global.
			root, err := Parse(tc.name, strings.NewReader(tc.program))
			testutil.FatalIfErr(t, err)
			p := &positionCollector{}
			ast.Walk(p, root)
			testutil.ExpectNoDiff(t, tc.positions, p.positions, testutil.AllowUnexported(position.Position{}))
		})
	}
}

type positionCollector struct {
	positions []*position.Position
}

func (p *positionCollector) VisitBefore(node ast.Node) (ast.Visitor, ast.Node) {
	switch n := node.(type) {
	case *ast.VarDecl, *ast.PatternLit:
		p.positions = append(p.positions, n.Pos())
	}
	return p, node
}

func (p *positionCollector) VisitAfter(node ast.Node) ast.Node {
	return node
}
<commit_msg>Add a more granular test for pattern expression with no following block.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package parser

import (
	"flag"
	"strings"
	"testing"

	"github.com/google/mtail/internal/testutil"
	"github.com/google/mtail/internal/vm/ast"
	"github.com/google/mtail/internal/vm/position"
)

var parserTestDebug = flag.Bool("parser_test_debug", false, "Turn on parser debug output if set.")

var parserTests = []struct {
	name    string
	program string
}{
	{"empty",
		""},

	{"newline",
		"\n"},

	{"declare counter",
		"counter lines_total\n"},

	{"declare counter string name",
		"counter lines_total as \"line-count\"\n"},

	{"declare dimensioned counter",
		"counter foo by bar\n"},

	{"declare multi-dimensioned counter",
		"counter foo by bar, baz, quux\n"},

	{"declare hidden counter",
		"hidden counter foo\n"},

	{"declare gauge",
		"gauge foo\n"},

	{"declare timer",
		"timer foo\n"},

	{"declare text",
		"text stringy\n"},

	{"declare histogram",
		"histogram foo buckets 0, 1, 2\n"},
	{"declare histogram float",
		"histogram foo buckets 0, 0.01, 0.1, 1, 10\n"},
	{"declare histogram by ",
		"histogram foo by code buckets 0, 1, 2\n"},
	{"declare histogram reversed syntax ",
		"histogram foo buckets 0, 1, 2 by code\n"},

	{"simple pattern action",
		"/foo/ {}\n"},

	{"increment counter",
		"counter lines_total\n" +
			"/foo/ {\n" +
			"  lines_total++\n" +
			"}\n"},

	{"decrement counter",
		`counter i
/foo/ {
  i--
}
`},

	{"regex match includes escaped slashes",
		"counter foo\n" +
			"/foo\\// { foo++\n}\n"},

	{"numeric capture group reference",
		"/(foo)/ {\n" +
			"  $1++\n" +
			"}\n"},

	{"strptime and capref",
		"/(.*)/ {\n" +
			"strptime($1, \"2006-01-02T15:04:05Z07:00\")\n" +
			" }\n"},

	{"named capture group reference",
		"/(?P<date>[[:digit:]-\\/ ])/ {\n" +
			"  strptime($date, \"%Y/%m/%d %H:%M:%S\")\n" +
			"}\n"},

	{"nested match conditions",
		"counter foo\n" +
			"counter bar\n" +
			"/match(\\d+)/ {\n" +
			"  foo += $1\n" +
			"  /^bleh (\\S+)/ {\n" +
			"    bar++\n" +
			"    $1++\n" +
			"  }\n" +
			"}\n"},

	{"nested scope",
		"counter foo\n" +
			"/fo(o)/ {\n" +
			"  $1++\n" +
			"  /bar(xxx)/ {\n" +
			"    $1 += $1\n" +
			"    foo = $1\n" +
			"  }\n" +
			"}\n"},

	{"comment then code",
		"# %d [%p]\n" +
			"/^(?P<date>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) \\[(?P<pid>\\d+)\\] / {\n" +
			"  strptime($1, \"2006/01/02 15:04:05\")\n" +
			"}\n"},

	{"assignment",
		"counter variable\n" +
			"/(?P<foo>.*)/ {\n" +
			"variable = $foo\n" +
			"}\n"},

	{"increment operator",
		"counter var\n" +
			"/foo/ {\n" +
			"  var++\n" +
			"}\n"},

	{"incby operator",
		"counter var\n" +
			"/foo/ {\n  var += 2\n}\n"},

	{"additive",
		"counter time_total\n" +
			"/(?P<foo>.*)/ {\n" +
			" time_total = timestamp() - time_total\n" +
			"}\n"},

	{"multiplicative",
		"counter a\n" +
			"counter b\n" +
			"   /foo/ {\n   a = a * b\n" +
			"      a = a ** b\n" +
			"}\n"},

	{"additive and mem storage",
		"counter time_total\n" +
			"counter variable by foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  time_total += timestamp() - variable[$foo]\n" +
			"}\n"},

	{"conditional expressions",
		"counter foo\n" +
			"/(?P<foo>.*)/ {\n" +
			"  $foo > 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo >= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo < 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo <= 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo == 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"  $foo != 0 {\n" +
			"    foo += $foo\n" +
			"  }\n" +
			"}\n"},

	{"decorator definition and invocation",
		"def foo { next\n }\n" +
			"@foo { }\n",
	},

	{"const regex",
		"const X /foo/\n" +
			"/foo / + X + / bar/ {\n" +
			"}\n",
	},

	{"multiline regex",
		"/foo / +\n" +
			"/barrr/ {\n" +
			"}\n",
	},

	{"len",
		"/(?P<foo>foo)/ {\n" +
			"len($foo) > 0 {\n" +
			"}\n" +
			"}\n",
	},

	{"def and next",
		"def foobar {/(?P<date>.*)/ {" +
			"  next" +
			"}" +
			"}",
	},

	{"const",
		`const IP /\d+(\.\d+){3}/`},

	{"bitwise",
		`gauge a
/foo(\d)/ {
  a = $1 & 7
  a = $1 | 8
  a = $1 << 4
  a = $1 >> 20
  a = $1 ^ 15
  a = ~ 1
}`},

	{"logical",
		`0 || 1 && 0 {
}
`,
	},

	{"floats",
		`gauge foo
/foo/ {
foo = 3.14
}`},

	{"simple otherwise action",
		"otherwise {}\n"},

	{"pattern action then otherwise action",
		`counter lines_total by type
		/foo/ {
			lines_total["foo"]++
		}
		otherwise {
			lines_total["misc"] += 10
		}`},

	{"simple else clause",
		"/foo/ {} else {}"},

	{"nested else clause",
		"/foo/ { / bar/ {}  } else { /quux/ {} else {} }"},

	{"mod operator",
		`gauge a
/foo/ {
  a = 3 % 1
}`},

	{"delete",
		`counter foo by bar
/foo/ {
  del foo[$1]
}`},

	{"delete after",
		`counter foo by bar
/foo/ {
  del foo[$1] after 168h
}`},

	{"getfilename", `
getfilename()
`},

	{"indexed expression arg list", `
counter foo by a,b
/(\d) (\d+)/ {
  foo[$1,$2]++
}`},

	{"paren expr", `
(0) || (1 && 3) {
}`},

	{"regex cond expr", `
/(\d)/ && 1 {
}
`},

	{"concat expr 1", `
const X /foo/
/bar/ + X {
}`},
	{"concat expr 2", `
const X /foo/
X {
}`},

	{"match expression 1", `
$foo =~ /bar/ {
}
$foo !~ /bar/ {
}
`},
	{"match expression 2", `
$foo =~ /bar/ + X {
}`},
	{"match expression 3", `
const X /foo/
$foo =~ X {
}`},

	{"capref used in def", `
/(?P<x>.*)/ && $x > 0 {
}`},

	{"match expr 4", `
/(?P<foo>.{6}) (?P<bar>.*)/ {
  $foo =~ $bar {
  }
}`},

	{"stop", `
// {
  stop
}`},

	{"substitution", `
/(\d,\d)/ {
  subst(",", ",", $1)
}`},
}

func TestParserRoundTrip(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			r := mtailParse(p)

			if r != 0 || p.root == nil || len(p.errors) > 0 {
				t.Error("1st pass parse errors:\n")
				for _, e := range p.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Fatal()
			}

			if *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}

			u := Unparser{}
			output := u.Unparse(p.root)

			p2 := newParser(tc.name+" 2", strings.NewReader(output))
			r = mtailParse(p2)
			if r != 0 || p2.root == nil || len(p2.errors) > 0 {
				t.Errorf("2nd pass parse errors:\n")
				for _, e := range p2.errors {
					t.Errorf("\t%s\n", e)
				}
				t.Logf("2nd pass input was:\n%s", output)
				t.Logf("2nd pass diff:\n%s", testutil.Diff(tc.program, output))
				t.Fatal()
			}

			u = Unparser{}
			output2 := u.Unparse(p2.root)

			testutil.ExpectNoDiff(t, output2, output)
		})
	}
}

type parserInvalidProgram struct {
	name    string
	program string
	errors  []string
}

var parserInvalidPrograms = []parserInvalidProgram{
	{"unknown character",
		"?\n",
		[]string{"unknown character:1:1: Unexpected input: '?'"}},

	{"unterminated regex",
		"/foo\n",
		[]string{"unterminated regex:1:2-4: Unterminated regular expression: \"/foo\"",
			"unterminated regex:1:2-4: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unterminated string",
		" \"foo }\n",
		[]string{"unterminated string:1:2-7: Unterminated quoted string: \"\\\"foo }\""}},

	{"unterminated const regex",
		"const X /(?P<foo>",
		[]string{"unterminated const regex:1:10-17: Unterminated regular expression: \"/(?P<foo>\"",
			"unterminated const regex:1:10-17: syntax error: unexpected end of file, expecting '/' to end regex"}},

	{"unbalanced {",
		"/foo/ {\n",
		[]string{"unbalanced {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced else {",
		"/foo/ { } else {\n",
		[]string{"unbalanced else {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},
	{"unbalanced otherwise {",
		"otherwise {\n",
		[]string{"unbalanced otherwise {:2:1: syntax error: unexpected end of file, expecting '}' to end block"}},

	{"index of non-terminal 1",
		`// {
	foo++[$1]++
	}`,
		[]string{"index of non-terminal 1:2:7: syntax error: unexpected indexing of an expression"}},
	{"index of non-terminal 2",
		`// {
	0[$1]++
	}`,
		[]string{"index of non-terminal 2:2:3: syntax error: unexpected indexing of an expression"}},

	{"statement with no effect",
		`/(\d)foo/ {
 timestamp() - $1
}`, []string{"statement with no effect:3:18: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},

	{"pattern without block",
		`/(?P<a>.)/
`, []string{"pattern without block:2:11: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},

	{"paired pattern without block",
		`/(?P<a>.)/
	/(?P<b>.)/ {}
	`,
		[]string{"paired pattern without block:2:11: syntax error: statement with no effect, missing an assignment, `+' concatenation, or `{}' block?"}},
}

func TestParseInvalidPrograms(t *testing.T) {
	if *parserTestDebug {
		mtailDebug = 3
	}
	for _, tc := range parserInvalidPrograms {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			p := newParser(tc.name, strings.NewReader(tc.program))
			mtailParse(p)

			testutil.ExpectNoDiff(t,
				strings.Join(tc.errors, "\n"),             // want
				strings.TrimRight(p.errors.Error(), "\n")) // got
			if p.errors.Error() == "no errors" && *parserTestDebug {
				s := Sexp{}
				t.Log("AST:\n" + s.Dump(p.root))
			}
		})
	}
}

var parsePositionTests = []struct {
	name      string
	program   string
	positions []*position.Position
}{
	{
		"empty",
		"",
		nil,
	},
	{
		"variable",
		`counter foo`,
		[]*position.Position{{"variable", 0, 8, 10}},
	},
	{
		"pattern",
		`const ID /foo/`,
		[]*position.Position{{"pattern", 0, 6, 13}},
	},
}

func TestParsePositionTests(t *testing.T) {
	for _, tc := range parsePositionTests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			// Not t.Parallel() because the parser is not reentrant, and mtailDebug is a global.
			root, err := Parse(tc.name, strings.NewReader(tc.program))
			testutil.FatalIfErr(t, err)
			p := &positionCollector{}
			ast.Walk(p, root)
			testutil.ExpectNoDiff(t, tc.positions, p.positions, testutil.AllowUnexported(position.Position{}))
		})
	}
}

type positionCollector struct {
	positions []*position.Position
}

func (p *positionCollector) VisitBefore(node ast.Node) (ast.Visitor, ast.Node) {
	switch n := node.(type) {
	case *ast.VarDecl, *ast.PatternLit:
		p.positions = append(p.positions, n.Pos())
	}
	return p, node
}

func (p *positionCollector) VisitAfter(node ast.Node) ast.Node {
	return node
}
<|endoftext|>


<commit_before>package search_client

import (
	"testing"

	"github.com/algolia/algoliasearch-client-go/algoliasearch"

	"github.com/stretchr/testify/require"

	"github.com/algolia/algoliasearch-client-go/it"
)

func TestAPIKeys(t *testing.T) {
	t.Parallel()
	client := it.InitSearchClient1(t)

	key := algoliasearch.Key{
		ACL:                    []string{"search"},
		CreatedAt:              0,
		Description:            "A description",
		Indexes:                []string{"index"},
		MaxHitsPerQuery:        1000,
		MaxQueriesPerIPPerHour: 1000,
		QueryParameters:        "typoTolerance=strict",
		Referers:               []string{"referer"},
		Validity:               600,
		Value:                  "",
	}
	acl := key.ACL
	params := algoliasearch.Map{
		"description":            key.Description,
		"indexes":                key.Indexes,
		"maxHitsPerQuery":        key.MaxHitsPerQuery,
		"maxQueriesPerIPPerHour": key.MaxQueriesPerIPPerHour,
		"queryParameters":        key.QueryParameters,
		"referers":               key.Referers,
		"validity":               key.Validity,
	}

	{
		res, err := client.AddAPIKey(acl, params)
		require.NoError(t, err)
		key.Value = res.Key
	}

	defer client.DeleteAPIKey(key.Value)

	{
		it.Retry(func() bool {
			_, err := client.GetAPIKey(key.Value)
			return err == nil
		})

		found, err := client.GetAPIKey(key.Value)
		require.NoError(t, err)

		key.CreatedAt = found.CreatedAt
		key.Validity = found.Validity
		require.Equal(t, key, found)
	}

	{
		keys, err := client.ListAPIKeys()
		require.NoError(t, err)

		found := false
		for _, k := range keys {
			found = k.Value == key.Value
			if found {
				break
			}
		}
		require.True(t, found)
	}

	{
		key.MaxHitsPerQuery = 42
		params = algoliasearch.Map{"maxHitsPerQuery": key.MaxHitsPerQuery}
		_, err := client.UpdateAPIKey(key.Value, params)
		require.NoError(t, err)

		it.Retry(func() bool {
			found, err := client.GetAPIKey(key.Value)
			require.NoError(t, err)
			return found.MaxHitsPerQuery == key.MaxHitsPerQuery
		})
	}

	{
		_, err := client.DeleteAPIKey(key.Value)
		require.NoError(t, err)

		it.Retry(func() bool {
			_, err := client.GetAPIKey(key.Value)
			return err != nil
		})
	}
}
<commit_msg>test: add RestoreAPIKey test<commit_after>package search_client

import (
	"testing"

	"github.com/algolia/algoliasearch-client-go/algoliasearch"

	"github.com/stretchr/testify/require"

	"github.com/algolia/algoliasearch-client-go/it"
)

func TestAPIKeys(t *testing.T) {
	t.Parallel()
	client := it.InitSearchClient1(t)

	key := algoliasearch.Key{
		ACL:                    []string{"search"},
		CreatedAt:              0,
		Description:            "A description",
		Indexes:                []string{"index"},
		MaxHitsPerQuery:        1000,
		MaxQueriesPerIPPerHour: 1000,
		QueryParameters:        "typoTolerance=strict",
		Referers:               []string{"referer"},
		Validity:               600,
		Value:                  "",
	}
	acl := key.ACL
	params := algoliasearch.Map{
		"description":            key.Description,
		"indexes":                key.Indexes,
		"maxHitsPerQuery":        key.MaxHitsPerQuery,
		"maxQueriesPerIPPerHour": key.MaxQueriesPerIPPerHour,
		"queryParameters":        key.QueryParameters,
		"referers":               key.Referers,
		"validity":               key.Validity,
	}

	{
		res, err := client.AddAPIKey(acl, params)
		require.NoError(t, err)
		key.Value = res.Key
	}

	defer client.DeleteAPIKey(key.Value)

	{
		it.Retry(func() bool {
			_, err := client.GetAPIKey(key.Value)
			return err == nil
		})

		found, err := client.GetAPIKey(key.Value)
		require.NoError(t, err)

		key.CreatedAt = found.CreatedAt
		key.Validity = found.Validity
		require.Equal(t, key, found)
	}

	{
		keys, err := client.ListAPIKeys()
		require.NoError(t, err)

		found := false
		for _, k := range keys {
			found = k.Value == key.Value
			if found {
				break
			}
		}
		require.True(t, found)
	}

	{
		key.MaxHitsPerQuery = 42
		params = algoliasearch.Map{"maxHitsPerQuery": key.MaxHitsPerQuery}
		_, err := client.UpdateAPIKey(key.Value, params)
		require.NoError(t, err)

		it.Retry(func() bool {
			found, err := client.GetAPIKey(key.Value)
			require.NoError(t, err)
			return found.MaxHitsPerQuery == key.MaxHitsPerQuery
		})
	}

	{
		_, err := client.DeleteAPIKey(key.Value)
		require.NoError(t, err)

		it.Retry(func() bool {
			_, err := client.GetAPIKey(key.Value)
			return err != nil
		})
	}

	{
		_, err := client.RestoreAPIKey(key.Value)
		require.NoError(t, err)

		it.Retry(func() bool {
			_, err := client.GetAPIKey(key.Value)
			return err == nil
		})
	}
}
<|endoftext|>


<commit_before>// Package sentences provides a scanner for Unicode text segmentation sentence boundaries: https://unicode.org/reports/tr29/#Sentence_Boundaries
package sentences

import (
	"bufio"
	"bytes"
	"io"
	"unicode"
)

// NewScanner tokenizes a reader into a stream of sentence tokens according to Unicode Text Segmentation sentence boundaries https://unicode.org/reports/tr29/#Sentence_Boundaries
// Iterate through the stream by calling Scan() until false.
//	text := "This is an example. And another!"
//	reader := strings.NewReader(text)
//
//	scanner := sentences.NewScanner(reader)
//	for scanner.Scan() {
//		fmt.Printf("%s\n", scanner.Text())
//	}
//	if err := scanner.Err(); err != nil {
//		log.Fatal(err)
//	}
func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		incoming: bufio.NewReaderSize(r, 64*1024),
	}
}

// Scanner is the structure for scanning an input Reader. Use NewScanner to instantiate.
type Scanner struct {
	incoming *bufio.Reader

	// a buffer of runes to evaluate
	buffer []rune
	// a cursor for runes in the buffer
	pos int

	bb bytes.Buffer

	// outputs
	bytes []byte
	err   error
}

// reset creates a new bytes.Buffer on the Scanner, and clears previous values
func (sc *Scanner) reset() {
	// Drop the emitted runes (optimization to avoid growing array)
	copy(sc.buffer, sc.buffer[sc.pos:])
	sc.buffer = sc.buffer[:len(sc.buffer)-sc.pos]

	sc.pos = 0

	var bb bytes.Buffer
	sc.bb = bb

	sc.bytes = nil
	sc.err = nil
}

// Scan advances to the next token, returning true if successful. Returns false on error or EOF.
func (sc *Scanner) Scan() bool {
	sc.reset()

	for {
		// Fill the buffer with enough runes for lookahead
		for len(sc.buffer) < sc.pos+8 {
			r, eof, err := sc.readRune()
			if err != nil {
				sc.err = err
				return false
			}
			if eof {
				break
			}
			sc.buffer = append(sc.buffer, r)
		}

		switch {
		case sc.sb1():
			// true indicates continue
			sc.accept()
			continue
		case sc.sb2():
			// true indicates break
			break
		case
			sc.sb3():
			// true indicates continue
			sc.accept()
			continue
		case
			sc.sb4():
			// true indicates break
			break
		case
			sc.sb5(),
			sc.sb6(),
			sc.sb7(),
			sc.sb8(),
			sc.sb8a(),
			sc.sb9(),
			sc.sb10():
			// true indicates continue
			sc.accept()
			continue
		case
			sc.sb11():
			// true indicates break
			break
		case
			sc.sb998():
			// true indicates continue
			sc.accept()
			continue
		}

		// If we fall through all the above rules, it's a sentence break
		break
	}

	return sc.emit()
}

// Bytes returns the current token as a byte slice, after a successful call to Scan
func (sc *Scanner) Bytes() []byte {
	return sc.bytes
}

// Text returns the current token, after a successful call to Scan
func (sc *Scanner) Text() string {
	return string(sc.bytes)
}

// Err returns the current error, after an unsuccessful call to Scan
func (sc *Scanner) Err() error {
	return sc.err
}

// Sentence boundary rules: https://unicode.org/reports/tr29/#Sentence_Boundaries
// In most cases, returning true means 'keep going'; check the name of the return var for clarity

var is = unicode.Is

// sb1 implements https://unicode.org/reports/tr29/#SB1
func (sc *Scanner) sb1() (accept bool) {
	sot := sc.pos == 0 // "start of text"
	eof := len(sc.buffer) == sc.pos
	return sot && !eof
}

// sb2 implements https://unicode.org/reports/tr29/#SB2
func (sc *Scanner) sb2() (breaking bool) {
	// eof
	return len(sc.buffer) == sc.pos
}

// sb3 implements https://unicode.org/reports/tr29/#SB3
func (sc *Scanner) sb3() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(LF, current) {
		return false
	}

	previous := sc.buffer[sc.pos-1]
	return is(CR, previous)
}

// sb4 implements https://unicode.org/reports/tr29/#SB4
func (sc *Scanner) sb4() (breaking bool) {
	previous := sc.buffer[sc.pos-1]
	return is(_mergedParaSep, previous)
}

// sb5 implements https://unicode.org/reports/tr29/#SB5
func (sc *Scanner) sb5() (accept bool) {
	current := sc.buffer[sc.pos]
	return is(_mergedExtendFormat, current)
}

// seekForward looks ahead until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format
// See: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekForward(pos int, rts ...*unicode.RangeTable) bool {
	for i := pos + 1; i < len(sc.buffer); i++ {
		r := sc.buffer[i]

		// Ignore Extend|Format
		if is(_mergedExtendFormat, r) {
			continue
		}

		// See if any of the range tables apply
		for _, rt := range rts {
			if is(rt, r) {
				return true
			}
		}

		// If we get this far, it's not there
		break
	}

	return false
}

// seekPreviousIndex works backward until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format, and returns the index of the rune in the buffer
// See: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekPreviousIndex(pos int, rts ...*unicode.RangeTable) int {
	// Start at the end of the buffer and move backwards
	for i := pos - 1; i >= 0; i-- {
		r := sc.buffer[i]

		// Ignore Extend|Format
		if is(_mergedExtendFormat, r) {
			continue
		}

		// See if any of the range tables apply
		for _, rt := range rts {
			if is(rt, r) {
				return i
			}
		}

		// If we get this far, it's not there
		break
	}

	return -1
}

// seekPreviousIndex works backward ahead until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format, reporting success
// Logic is here: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekPrevious(pos int, rts ...*unicode.RangeTable) bool {
	return sc.seekPreviousIndex(pos, rts...) >= 0
}

// sb6 implements https://unicode.org/reports/tr29/#SB6
func (sc *Scanner) sb6() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(Numeric, current) {
		return false
	}

	return sc.seekPrevious(sc.pos, ATerm)
}

// sb7 implements https://unicode.org/reports/tr29/#SB7
func (sc *Scanner) sb7() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(Upper, current) {
		return false
	}

	previous := sc.seekPreviousIndex(sc.pos, ATerm)
	if previous < 0 {
		return false
	}

	return sc.seekPrevious(previous, _mergedUpperLower)
}

// sb8 implements https://unicode.org/reports/tr29/#SB8
func (sc *Scanner) sb8() (accept bool) {
	// This loop is the 'regex':
	// ( ¬(OLetter | Upper | Lower | ParaSep | SATerm) )*
	pos := sc.pos
	for pos < len(sc.buffer) {
		current := sc.buffer[pos]
		if is(_mergedOLetterUpperLowerParaSepSATerm, current) {
			break
		}
		pos++
	}

	if !sc.seekForward(pos-1, Lower) {
		return false
	}

	pos = sc.pos

	sp := pos
	for {
		sp = sc.seekPreviousIndex(sp, Sp)
		if sp < 0 {
			break
		}
		pos = sp
	}

	close := pos
	for {
		close = sc.seekPreviousIndex(close, Close)
		if close < 0 {
			break
		}
		pos = close
	}

	return sc.seekPrevious(pos, ATerm)
}

// sb8a implements https://unicode.org/reports/tr29/#SB8a
func (sc *Scanner) sb8a() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(_mergedSContinueSATerm, current) {
		return false
	}

	pos := sc.pos

	sp := pos
	for {
		sp = sc.seekPreviousIndex(sp, Sp)
		if sp < 0 {
			break
		}
		pos = sp
	}

	close := pos
	for {
		close = sc.seekPreviousIndex(close, Close)
		if close < 0 {
			break
		}
		pos = close
	}

	return sc.seekPrevious(pos, _mergedSATerm)
}

// sb9 implements https://unicode.org/reports/tr29/#SB9
func (sc *Scanner) sb9() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(_mergedCloseSpParaSep, current) {
		return false
	}

	pos := sc.pos

	close := pos
	for {
		close = sc.seekPreviousIndex(close, Close)
		if close < 0 {
			break
		}
		pos = close
	}

	return sc.seekPrevious(pos, _mergedSATerm)
}

// sb10 implements https://unicode.org/reports/tr29/#SB10
func (sc *Scanner) sb10() (accept bool) {
	current := sc.buffer[sc.pos]
	if !is(_mergedSpParaSep, current) {
		return false
	}

	pos := sc.pos

	sp := pos
	for {
		sp = sc.seekPreviousIndex(sp, Sp)
		if sp < 0 {
			break
		}
		pos = sp
	}

	close := pos
	for {
		close = sc.seekPreviousIndex(close, Close)
		if close < 0 {
			break
		}
		pos = close
	}

	return sc.seekPrevious(pos, _mergedSATerm)
}

// sb11 implements https://unicode.org/reports/tr29/#SB11
func (sc *Scanner) sb11() (breaking bool) {
	pos := sc.pos

	ps := sc.seekPreviousIndex(pos, _mergedSpParaSep)
	if ps >= 0 {
		pos = ps
	}

	sp := pos
	for {
		sp = sc.seekPreviousIndex(sp, Sp)
		if sp < 0 {
			break
		}
		pos = sp
	}

	close := pos
	for {
		close = sc.seekPreviousIndex(close, Close)
		if close < 0 {
			break
		}
		pos = close
	}

	return sc.seekPrevious(pos, _mergedSATerm)
}

// sb998 implements https://unicode.org/reports/tr29/#SB998
func (sc *Scanner) sb998() bool {
	return sc.pos > 0
}

func (sc *Scanner) emit() bool {
	sc.bytes = sc.bb.Bytes()
	return len(sc.bytes) > 0
}

// accept forwards the buffer cursor (pos) by 1
func (sc *Scanner) accept() {
	sc.bb.WriteRune(sc.buffer[sc.pos])
	sc.pos++
}

// readRune gets the next rune, advancing the reader
func (sc *Scanner) readRune() (r rune, eof bool, err error) {
	r, _, err = sc.incoming.ReadRune()

	if err != nil {
		if err == io.EOF {
			return r, true, nil
		}
		return r, false, err
	}

	return r, false, nil
}
<commit_msg>Inline sentences rules<commit_after>// Package sentences provides a scanner for Unicode text segmentation sentence boundaries: https://unicode.org/reports/tr29/#Sentence_Boundaries
package sentences

import (
	"bufio"
	"bytes"
	"io"
	"unicode"
)

// NewScanner tokenizes a reader into a stream of sentence tokens according to Unicode Text Segmentation sentence boundaries https://unicode.org/reports/tr29/#Sentence_Boundaries
// Iterate through the stream by calling Scan() until false.
//	text := "This is an example. And another!"
//	reader := strings.NewReader(text)
//
//	scanner := sentences.NewScanner(reader)
//	for scanner.Scan() {
//		fmt.Printf("%s\n", scanner.Text())
//	}
//	if err := scanner.Err(); err != nil {
//		log.Fatal(err)
//	}
func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		incoming: bufio.NewReaderSize(r, 64*1024),
	}
}

// Scanner is the structure for scanning an input Reader. Use NewScanner to instantiate.
type Scanner struct {
	incoming *bufio.Reader

	// a buffer of runes to evaluate
	buffer []rune
	// a cursor for runes in the buffer
	pos int

	bb bytes.Buffer

	// outputs
	bytes []byte
	err   error
}

// reset creates a new bytes.Buffer on the Scanner, and clears previous values
func (sc *Scanner) reset() {
	// Drop the emitted runes (optimization to avoid growing array)
	copy(sc.buffer, sc.buffer[sc.pos:])
	sc.buffer = sc.buffer[:len(sc.buffer)-sc.pos]

	sc.pos = 0

	var bb bytes.Buffer
	sc.bb = bb

	sc.bytes = nil
	sc.err = nil
}

// Scan advances to the next token, returning true if successful. Returns false on error or EOF.
func (sc *Scanner) Scan() bool {
	sc.reset()

	for {
		// Fill the buffer with enough runes for lookahead
		for len(sc.buffer) < sc.pos+8 {
			r, eof, err := sc.readRune()
			if err != nil {
				sc.err = err
				return false
			}
			if eof {
				break
			}
			sc.buffer = append(sc.buffer, r)
		}

		// SB1
		sot := sc.pos == 0 // "start of text"
		eof := len(sc.buffer) == sc.pos
		if sot && !eof {
			sc.accept()
			continue
		}

		// SB2
		if eof {
			break
		}

		current := sc.buffer[sc.pos]
		previous := sc.buffer[sc.pos-1]

		// SB3
		if is(LF, current) && is(CR, previous) {
			sc.accept()
			continue
		}

		// SB4
		if is(_mergedParaSep, previous) {
			break
		}

		// SB5
		if is(_mergedExtendFormat, current) {
			sc.accept()
			continue
		}

		// SB6
		if is(Numeric, current) && sc.seekPrevious(sc.pos, ATerm) {
			sc.accept()
			continue
		}

		// SB7
		if is(Upper, current) {
			previousIndex := sc.seekPreviousIndex(sc.pos, ATerm)
			if previousIndex >= 0 && sc.seekPrevious(previousIndex, _mergedUpperLower) {
				sc.accept()
				continue
			}
		}

		// SB8
		{
			// This loop is the 'regex':
			// ( ¬(OLetter | Upper | Lower | ParaSep | SATerm) )*
			pos := sc.pos
			for pos < len(sc.buffer) {
				current := sc.buffer[pos]
				if is(_mergedOLetterUpperLowerParaSepSATerm, current) {
					break
				}
				pos++
			}

			if sc.seekForward(pos-1, Lower) {
				pos := sc.pos

				sp := pos
				for {
					sp = sc.seekPreviousIndex(sp, Sp)
					if sp < 0 {
						break
					}
					pos = sp
				}

				close := pos
				for {
					close = sc.seekPreviousIndex(close, Close)
					if close < 0 {
						break
					}
					pos = close
				}

				if sc.seekPrevious(pos, ATerm) {
					sc.accept()
					continue
				}
			}
		}

		// SB8a
		if is(_mergedSContinueSATerm, current) {
			pos := sc.pos

			sp := pos
			for {
				sp = sc.seekPreviousIndex(sp, Sp)
				if sp < 0 {
					break
				}
				pos = sp
			}

			close := pos
			for {
				close = sc.seekPreviousIndex(close, Close)
				if close < 0 {
					break
				}
				pos = close
			}

			if sc.seekPrevious(pos, _mergedSATerm) {
				sc.accept()
				continue
			}
		}

		// SB9
		if is(_mergedCloseSpParaSep, current) {
			pos := sc.pos

			close := pos
			for {
				close = sc.seekPreviousIndex(close, Close)
				if close < 0 {
					break
				}
				pos = close
			}

			if sc.seekPrevious(pos, _mergedSATerm) {
				sc.accept()
				continue
			}
		}

		// SB10
		if is(_mergedSpParaSep, current) {
			pos := sc.pos

			sp := pos
			for {
				sp = sc.seekPreviousIndex(sp, Sp)
				if sp < 0 {
					break
				}
				pos = sp
			}

			close := pos
			for {
				close = sc.seekPreviousIndex(close, Close)
				if close < 0 {
					break
				}
				pos = close
			}

			if sc.seekPrevious(pos, _mergedSATerm) {
				sc.accept()
				continue
			}
		}

		// SB11
		{
			pos := sc.pos

			ps := sc.seekPreviousIndex(pos, _mergedSpParaSep)
			if ps >= 0 {
				pos = ps
			}

			sp := pos
			for {
				sp = sc.seekPreviousIndex(sp, Sp)
				if sp < 0 {
					break
				}
				pos = sp
			}

			close := pos
			for {
				close = sc.seekPreviousIndex(close, Close)
				if close < 0 {
					break
				}
				pos = close
			}

			if sc.seekPrevious(pos, _mergedSATerm) {
				break
			}
		}

		// SB998
		if sc.pos > 0 {
			sc.accept()
			continue
		}

		// If we fall through all the above rules, it's a sentence break
		break
	}

	return sc.token()
}

// Bytes returns the current token as a byte slice, after a successful call to Scan
func (sc *Scanner) Bytes() []byte {
	return sc.bytes
}

// Text returns the current token, after a successful call to Scan
func (sc *Scanner) Text() string {
	return string(sc.bytes)
}

// Err returns the current error, after an unsuccessful call to Scan
func (sc *Scanner) Err() error {
	return sc.err
}

// Sentence boundary rules: https://unicode.org/reports/tr29/#Sentence_Boundaries
// In most cases, returning true means 'keep going'; check the name of the return var for clarity

var is = unicode.Is

// seekForward looks ahead until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format
// See: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekForward(pos int, rts ...*unicode.RangeTable) bool {
	for i := pos + 1; i < len(sc.buffer); i++ {
		r := sc.buffer[i]

		// Ignore Extend|Format
		if is(_mergedExtendFormat, r) {
			continue
		}

		// See if any of the range tables apply
		for _, rt := range rts {
			if is(rt, r) {
				return true
			}
		}

		// If we get this far, it's not there
		break
	}

	return false
}

// seekPreviousIndex works backward until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format, and returns the index of the rune in the buffer
// See: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekPreviousIndex(pos int, rts ...*unicode.RangeTable) int {
	// Start at the end of the buffer and move backwards
	for i := pos - 1; i >= 0; i-- {
		r := sc.buffer[i]

		// Ignore Extend|Format
		if is(_mergedExtendFormat, r) {
			continue
		}

		// See if any of the range tables apply
		for _, rt := range rts {
			if is(rt, r) {
				return i
			}
		}

		// If we get this far, it's not there
		break
	}

	return -1
}

// seekPreviousIndex works backward ahead until it hits a rune satisfying one of the range tables,
// ignoring Extend|Format, reporting success
// Logic is here: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules (driven by SB5)
func (sc *Scanner) seekPrevious(pos int, rts ...*unicode.RangeTable) bool {
	return sc.seekPreviousIndex(pos, rts...) >= 0
}

func (sc *Scanner) token() bool {
	sc.bytes = sc.bb.Bytes()
	return len(sc.bytes) > 0
}

// accept forwards the buffer cursor (pos) by 1
func (sc *Scanner) accept() {
	sc.bb.WriteRune(sc.buffer[sc.pos])
	sc.pos++
}

// readRune gets the next rune, advancing the reader
func (sc *Scanner) readRune() (r rune, eof bool, err error) {
	r, _, err = sc.incoming.ReadRune()

	if err != nil {
		if err == io.EOF {
			return r, true, nil
		}
		return r, false, err
	}

	return r, false, nil
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"
	"os"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/oskanberg/retrospectiv.es/server/models"
)

// Route represents an API route
type Route struct {
	Name        string
	Method      string
	Pattern     string
	HandlerFunc http.HandlerFunc
}

// Routes respresents multiple routes
type Routes []Route

// BoardAPI in an interface for the BoardAPI commands
type BoardAPI interface {
	Start(string)
}

type api struct {
	boards models.ItemBoards
}

func (a *api) Start(port string) {
	var routes = Routes{
		Route{
			Name:        "Boards",
			Method:      "GET",
			Pattern:     "/api/boards",
			HandlerFunc: a.GetAllBoardsHandler,
		},
		Route{
			Name:        "SpecificBoard",
			Method:      "GET",
			Pattern:     "/api/boards/{board_id}",
			HandlerFunc: a.GetSpecificBoardHandler,
		},
		Route{
			Name:        "SpecificBoardItems",
			Method:      "GET",
			Pattern:     "/api/boards/{board_id}/items",
			HandlerFunc: a.GetSpecificBoardItemsHandler,
		},
		Route{
			Name:        "AddItemToSpecificBoard",
			Method:      "POST",
			Pattern:     "/api/boards/{board_id}/items",
			HandlerFunc: a.AddItemToSpecificBoard,
		},
		Route{
			Name:        "AddNewBoard",
			Method:      "POST",
			Pattern:     "/api/boards",
			HandlerFunc: a.AddNewBoard,
		},
	}

	r := mux.NewRouter().StrictSlash(true)
	for _, route := range routes {
		r.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(route.HandlerFunc)
	}

	log := handlers.LoggingHandler(os.Stdout, r)
	cors := handlers.CORS()(log)
	http.ListenAndServe(port, cors)
}

// NewBoardAPI returns a pointer to an implementation of BoardAPI
func NewBoardAPI() BoardAPI {
	return &api{
		boards: models.NewItemBoards(),
	}
}
<commit_msg>Print error returned from listenAndServe<commit_after>package api

import (
	"fmt"
	"net/http"
	"os"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/oskanberg/retrospectiv.es/server/models"
)

// Route represents an API route
type Route struct {
	Name        string
	Method      string
	Pattern     string
	HandlerFunc http.HandlerFunc
}

// Routes respresents multiple routes
type Routes []Route

// BoardAPI in an interface for the BoardAPI commands
type BoardAPI interface {
	Start(string)
}

type api struct {
	boards models.ItemBoards
}

func (a *api) Start(port string) {
	var routes = Routes{
		Route{
			Name:        "Boards",
			Method:      "GET",
			Pattern:     "/api/boards",
			HandlerFunc: a.GetAllBoardsHandler,
		},
		Route{
			Name:        "SpecificBoard",
			Method:      "GET",
			Pattern:     "/api/boards/{board_id}",
			HandlerFunc: a.GetSpecificBoardHandler,
		},
		Route{
			Name:        "SpecificBoardItems",
			Method:      "GET",
			Pattern:     "/api/boards/{board_id}/items",
			HandlerFunc: a.GetSpecificBoardItemsHandler,
		},
		Route{
			Name:        "AddItemToSpecificBoard",
			Method:      "POST",
			Pattern:     "/api/boards/{board_id}/items",
			HandlerFunc: a.AddItemToSpecificBoard,
		},
		Route{
			Name:        "AddNewBoard",
			Method:      "POST",
			Pattern:     "/api/boards",
			HandlerFunc: a.AddNewBoard,
		},
	}

	r := mux.NewRouter().StrictSlash(true)
	for _, route := range routes {
		r.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(route.HandlerFunc)
	}

	log := handlers.LoggingHandler(os.Stdout, r)
	cors := handlers.CORS()(log)
	fmt.Println(http.ListenAndServe(port, cors))
}

// NewBoardAPI returns a pointer to an implementation of BoardAPI
func NewBoardAPI() BoardAPI {
	return &api{
		boards: models.NewItemBoards(),
	}
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/gilcrest/env/datastore"
	"github.com/gilcrest/errors"
	"github.com/gilcrest/movie"
)

// handlePost handles POST requests for the /movie endpoint
// and creates a movie in the database
func (s *Server) handlePost() http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {

		// request is the expected service request fields
		type request struct {
			Title    string `json:"Title"`
			Year     int    `json:"Year"`
			Rated    string `json:"Rated"`
			Released string `json:"ReleaseDate"`
			RunTime  int    `json:"RunTime"`
			Director string `json:"Director"`
			Writer   string `json:"Writer"`
		}

		// response is the expected service response fields
		type response struct {
			request
			CreateTimestamp string `json:"CreateTimestamp"`
		}

		// dateFormat is the expected date format for any date fields
		const dateFormat string = "Jan 02 2006"

		// retrieve the context from the http.Request
		ctx := req.Context()

		// Declare rqst as an instance of request
		// Decode JSON HTTP request body into a Decoder type
		// and unmarshal that into rqst
		rqst := new(request)
		err := json.NewDecoder(req.Body).Decode(&rqst)
		defer req.Body.Close()
		if err != nil {
			err = errors.RE(http.StatusBadRequest, errors.InvalidRequest, err)
			errors.HTTPError(w, err)
			return
		}

		// declare a new instance of movie.Movie
		movie := new(movie.Movie)
		movie.Title = rqst.Title
		movie.Year = rqst.Year
		movie.Rated = rqst.Rated
		t, err := time.Parse(dateFormat, rqst.Released)
		if err != nil {
			err = errors.RE(http.StatusBadRequest,
				errors.Validation,
				errors.Code("invalid_date_format"),
				errors.Parameter("ReleaseDate"),
				err)
			errors.HTTPError(w, err)
			return
		}
		movie.Released = t
		movie.RunTime = rqst.RunTime
		movie.Director = rqst.Director
		movie.Writer = rqst.Writer

		// get a new DB Tx from the PostgreSQL datastore within the server struct
		tx, err := s.DS.BeginTx(ctx, nil, datastore.AppDB)
		if err != nil {
			err = errors.RE(http.StatusInternalServerError, errors.Database)
			errors.HTTPError(w, err)
			return
		}

		// Call the create method of the Movie object to validate and insert the data
		err = movie.Create(ctx, s.Logger, tx)
		if err != nil {
			// log error
			s.Logger.Error().Err(err).Msg("")
			// All errors should be an errors.Error type
			// Use Kind, Code and Error from lower level errors to populate
			// RE (Response Error)
			if e, ok := err.(*errors.Error); ok {
				err := errors.RE(http.StatusBadRequest, e.Kind, e.Param, e.Code, err)
				errors.HTTPError(w, err)
				return
			}

			// if falls through type assertion, then serve an unanticipated error
			err := errors.RE(http.StatusInternalServerError, errors.Unanticipated)
			errors.HTTPError(w, err)
			return
		}

		// If we got this far, we created/committed the db transaction and can consider
		// this transaction successful and return a response

		// create a new response struct and set Audit and other
		// relevant elements
		resp := new(response)
		resp.Title = movie.Title
		resp.Year = movie.Year
		resp.Rated = movie.Rated
		resp.Released = movie.Released.Format(dateFormat)
		resp.RunTime = movie.RunTime
		resp.Director = movie.Director
		resp.Writer = movie.Writer
		resp.CreateTimestamp = movie.CreateTimestamp.Format(time.RFC3339)

		// Encode response struct to JSON for the response body
		json.NewEncoder(w).Encode(*resp)
		if err != nil {
			err = errors.RE(http.StatusInternalServerError, errors.Internal)
			errors.HTTPError(w, err)
			return
		}
	}
}
<commit_msg>comment update<commit_after>package server

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/gilcrest/env/datastore"
	"github.com/gilcrest/errors"
	"github.com/gilcrest/movie"
)

// handlePost handles POST requests for the /movie endpoint
// and creates a movie in the database
func (s *Server) handlePost() http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {

		// request is the expected service request fields
		type request struct {
			Title    string `json:"Title"`
			Year     int    `json:"Year"`
			Rated    string `json:"Rated"`
			Released string `json:"ReleaseDate"`
			RunTime  int    `json:"RunTime"`
			Director string `json:"Director"`
			Writer   string `json:"Writer"`
		}

		// response is the expected service response fields
		type response struct {
			request
			CreateTimestamp string `json:"CreateTimestamp"`
		}

		// dateFormat is the expected date format for any date fields
		// in the request
		const dateFormat string = "Jan 02 2006"

		// retrieve the context from the http.Request
		ctx := req.Context()

		// Declare rqst as an instance of request
		// Decode JSON HTTP request body into a Decoder type
		// and unmarshal that into rqst
		rqst := new(request)
		err := json.NewDecoder(req.Body).Decode(&rqst)
		defer req.Body.Close()
		if err != nil {
			err = errors.RE(http.StatusBadRequest, errors.InvalidRequest, err)
			errors.HTTPError(w, err)
			return
		}

		// declare a new instance of movie.Movie
		movie := new(movie.Movie)
		movie.Title = rqst.Title
		movie.Year = rqst.Year
		movie.Rated = rqst.Rated
		t, err := time.Parse(dateFormat, rqst.Released)
		if err != nil {
			err = errors.RE(http.StatusBadRequest,
				errors.Validation,
				errors.Code("invalid_date_format"),
				errors.Parameter("ReleaseDate"),
				err)
			errors.HTTPError(w, err)
			return
		}
		movie.Released = t
		movie.RunTime = rqst.RunTime
		movie.Director = rqst.Director
		movie.Writer = rqst.Writer

		// get a new DB Tx from the PostgreSQL datastore within the server struct
		tx, err := s.DS.BeginTx(ctx, nil, datastore.AppDB)
		if err != nil {
			err = errors.RE(http.StatusInternalServerError, errors.Database)
			errors.HTTPError(w, err)
			return
		}

		// Call the create method of the Movie object to validate and insert the data
		err = movie.Create(ctx, s.Logger, tx)
		if err != nil {
			// log error
			s.Logger.Error().Err(err).Msg("")
			// All errors should be an errors.Error type
			// Use Kind, Code and Error from lower level errors to populate
			// RE (Response Error)
			if e, ok := err.(*errors.Error); ok {
				err := errors.RE(http.StatusBadRequest, e.Kind, e.Param, e.Code, err)
				errors.HTTPError(w, err)
				return
			}

			// if falls through type assertion, then serve an unanticipated error
			err := errors.RE(http.StatusInternalServerError, errors.Unanticipated)
			errors.HTTPError(w, err)
			return
		}

		// If we got this far, we created/committed the db transaction and can consider
		// this transaction successful and return a response

		// create a new response struct and set Audit and other
		// relevant elements
		resp := new(response)
		resp.Title = movie.Title
		resp.Year = movie.Year
		resp.Rated = movie.Rated
		resp.Released = movie.Released.Format(dateFormat)
		resp.RunTime = movie.RunTime
		resp.Director = movie.Director
		resp.Writer = movie.Writer
		resp.CreateTimestamp = movie.CreateTimestamp.Format(time.RFC3339)

		// Encode response struct to JSON for the response body
		json.NewEncoder(w).Encode(*resp)
		if err != nil {
			err = errors.RE(http.StatusInternalServerError, errors.Internal)
			errors.HTTPError(w, err)
			return
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"log"
	"time"
	"warcluster/db_manager"
	"warcluster/entities"
)

// StartMissionary is used when a call to initiate a new mission is rescived.
// 1. When the delay ends the thread ends the mission calling EndMission
// 2. The end of the mission is bradcasted to all clients and the mission entry is erased from the DB.
func StartMissionary(mission *entities.Mission) {
	target_key := fmt.Sprintf("planet.%d_%d", mission.Target[0], mission.Target[1])
	time.Sleep(time.Duration(mission.TravelTime * 1e6))

	target_entity, err := db_manager.GetEntity(target_key)
	if err != nil {
		log.Print("Error in target planet fetch: ", err.Error())
	}
	target := target_entity.(*entities.Planet)

	var target_owner *entities.Player

	if target.Owner != "" {
		owner_id = fmt.Sprintf("player.%d", target.Owner)
		target_owner_entity, err := db_manager.GetEntity(owner_id)
		if err != nil {
			log.Print("Error in target planet owner fetch: ", err.Error(), " Searched for: ", owner_id)
		}
		if target_owner_entity != nil {
			target_owner = target_owner_entity.(*entities.Player)
		} else {
			log.Print("Error in target planet owner cast. Owner is nil!")
		}
	} else {
		target_owner = nil
	}

	target.UpdateShipCount()

	result := entities.EndMission(target, target_owner, mission)
	key, serialized_planet, err := result.Serialize()
	if err == nil {
		db_manager.SetEntity(result)
		sessions.Broadcast([]byte(fmt.Sprintf("{\"Command\": \"state_change\", \"Planets\": {\"%s\": %s}}", key, serialized_planet)))
	}
	db_manager.DeleteEntity(mission.GetKey())
}
<commit_msg>Oops<commit_after>package server

import (
	"fmt"
	"log"
	"time"
	"warcluster/db_manager"
	"warcluster/entities"
)

// StartMissionary is used when a call to initiate a new mission is rescived.
// 1. When the delay ends the thread ends the mission calling EndMission
// 2. The end of the mission is bradcasted to all clients and the mission entry is erased from the DB.
func StartMissionary(mission *entities.Mission) {
	target_key := fmt.Sprintf("planet.%d_%d", mission.Target[0], mission.Target[1])
	time.Sleep(time.Duration(mission.TravelTime * 1e6))

	target_entity, err := db_manager.GetEntity(target_key)
	if err != nil {
		log.Print("Error in target planet fetch: ", err.Error())
	}
	target := target_entity.(*entities.Planet)

	var target_owner *entities.Player

	if target.Owner != "" {
		owner_id := fmt.Sprintf("player.%d", target.Owner)
		target_owner_entity, err := db_manager.GetEntity(owner_id)
		if err != nil {
			log.Print("Error in target planet owner fetch: ", err.Error(), " Searched for: ", owner_id)
		}
		if target_owner_entity != nil {
			target_owner = target_owner_entity.(*entities.Player)
		} else {
			log.Print("Error in target planet owner cast. Owner is nil!")
		}
	} else {
		target_owner = nil
	}

	target.UpdateShipCount()

	result := entities.EndMission(target, target_owner, mission)
	key, serialized_planet, err := result.Serialize()
	if err == nil {
		db_manager.SetEntity(result)
		sessions.Broadcast([]byte(fmt.Sprintf("{\"Command\": \"state_change\", \"Planets\": {\"%s\": %s}}", key, serialized_planet)))
	}
	db_manager.DeleteEntity(mission.GetKey())
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	c "github.com/micro/go-micro/context"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	log "github.com/golang/glog"

	"golang.org/x/net/context"
)

type rpcServer struct {
	rpc  *server
	exit chan chan error

	sync.RWMutex
	opts        Options
	handlers    map[string]Handler
	subscribers map[*subscriber][]broker.Subscriber
}

func newRpcServer(opts ...Option) Server {
	options := newOptions(opts...)
	return &rpcServer{
		opts: options,
		rpc: &server{
			name:         options.Name,
			serviceMap:   make(map[string]*service),
			hdlrWrappers: options.HdlrWrappers,
		},
		handlers:    make(map[string]Handler),
		subscribers: make(map[*subscriber][]broker.Subscriber),
		exit:        make(chan chan error),
	}
}

func (s *rpcServer) accept(sock transport.Socket) {
	defer func() {
		if r := recover(); r != nil {
			log.Error(r, string(debug.Stack()))
			sock.Close()
		}
	}()

	var msg transport.Message
	if err := sock.Recv(&msg); err != nil {
		return
	}

	ct := msg.Header["Content-Type"]
	cf, err := s.newCodec(ct)
	// TODO: needs better error handling
	if err != nil {
		sock.Send(&transport.Message{
			Header: map[string]string{
				"Content-Type": "text/plain",
			},
			Body: []byte(err.Error()),
		})
		sock.Close()
		return
	}

	codec := newRpcPlusCodec(&msg, sock, cf)

	// strip our headers
	hdr := make(map[string]string)
	for k, v := range msg.Header {
		hdr[k] = v
	}
	delete(hdr, "Content-Type")

	ctx := c.WithMetadata(context.Background(), hdr)

	// TODO: needs better error handling
	if err := s.rpc.serveRequest(ctx, codec, ct); err != nil {
		log.Errorf("Unexpected error serving request, closing socket: %v", err)
		sock.Close()
	}
}

func (s *rpcServer) newCodec(contentType string) (codec.NewCodec, error) {
	if cf, ok := s.opts.Codecs[contentType]; ok {
		return cf, nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (s *rpcServer) Options() Options {
	s.RLock()
	opts := s.opts
	s.RUnlock()
	return opts
}

func (s *rpcServer) Init(opts ...Option) error {
	s.Lock()
	for _, opt := range opts {
		opt(&s.opts)
	}
	s.Unlock()
	return nil
}

func (s *rpcServer) NewHandler(h interface{}) Handler {
	return newRpcHandler(h)
}

func (s *rpcServer) Handle(h Handler) error {
	if err := s.rpc.register(h.Handler()); err != nil {
		return err
	}
	s.Lock()
	s.handlers[h.Name()] = h
	s.Unlock()
	return nil
}

func (s *rpcServer) NewSubscriber(topic string, sb interface{}) Subscriber {
	return newSubscriber(topic, sb)
}

func (s *rpcServer) Subscribe(sb Subscriber) error {
	sub, ok := sb.(*subscriber)
	if !ok {
		return fmt.Errorf("invalid subscriber: expected *subscriber")
	}
	if len(sub.handlers) == 0 {
		return fmt.Errorf("invalid subscriber: no handler functions")
	}

	if err := validateSubscriber(sb); err != nil {
		return err
	}

	s.Lock()
	_, ok = s.subscribers[sub]
	if ok {
		return fmt.Errorf("subscriber %v already exists", s)
	}
	s.subscribers[sub] = nil
	s.Unlock()
	return nil
}

func (s *rpcServer) Register() error {
	// parse address for host, port
	config := s.Options()
	var advt, host string
	var port int

	// check the advertise address first
	// if it exists then use it, otherwise
	// use the address
	if len(config.Advertise) > 0 {
		advt = config.Advertise
	} else {
		advt = config.Address
	}

	parts := strings.Split(advt, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port, _ = strconv.Atoi(parts[len(parts)-1])
	} else {
		host = parts[0]
	}

	addr, err := extractAddress(host)
	if err != nil {
		return err
	}

	// register service
	node := &registry.Node{
		Id:       config.Name + "-" + config.Id,
		Address:  addr,
		Port:     port,
		Metadata: config.Metadata,
	}

	node.Metadata["transport"] = config.Transport.String()
	node.Metadata["broker"] = config.Broker.String()
	node.Metadata["server"] = s.String()

	s.RLock()
	var endpoints []*registry.Endpoint
	for _, e := range s.handlers {
		endpoints = append(endpoints, e.Endpoints()...)
	}
	for e, _ := range s.subscribers {
		endpoints = append(endpoints, e.Endpoints()...)
	}
	s.RUnlock()

	service := &registry.Service{
		Name:      config.Name,
		Version:   config.Version,
		Nodes:     []*registry.Node{node},
		Endpoints: endpoints,
	}

	log.Infof("Registering node: %s", node.Id)
	if err := config.Registry.Register(service); err != nil {
		return err
	}

	s.Lock()
	defer s.Unlock()

	for sb, _ := range s.subscribers {
		handler := s.createSubHandler(sb, s.opts)
		sub, err := config.Broker.Subscribe(sb.Topic(), handler)
		if err != nil {
			return err
		}
		s.subscribers[sb] = []broker.Subscriber{sub}
	}

	return nil
}

func (s *rpcServer) Deregister() error {
	config := s.Options()
	var advt, host string
	var port int

	// check the advertise address first
	// if it exists then use it, otherwise
	// use the address
	if len(config.Advertise) > 0 {
		advt = config.Advertise
	} else {
		advt = config.Address
	}

	parts := strings.Split(advt, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port, _ = strconv.Atoi(parts[len(parts)-1])
	} else {
		host = parts[0]
	}

	addr, err := extractAddress(host)
	if err != nil {
		return err
	}

	node := &registry.Node{
		Id:      config.Name + "-" + config.Id,
		Address: addr,
		Port:    port,
	}

	service := &registry.Service{
		Name:    config.Name,
		Version: config.Version,
		Nodes:   []*registry.Node{node},
	}

	log.Infof("Deregistering node: %s", node.Id)
	if err := config.Registry.Deregister(service); err != nil {
		return err
	}

	s.Lock()
	for sb, subs := range s.subscribers {
		for _, sub := range subs {
			log.Infof("Unsubscribing from topic: %s", sub.Topic())
			sub.Unsubscribe()
		}
		s.subscribers[sb] = nil
	}
	s.Unlock()
	return nil
}

func (s *rpcServer) Start() error {
	registerHealthChecker(s)
	config := s.Options()

	ts, err := config.Transport.Listen(config.Address)
	if err != nil {
		return err
	}

	log.Infof("Listening on %s", ts.Addr())
	s.Lock()
	s.opts.Address = ts.Addr()
	s.Unlock()

	go ts.Accept(s.accept)

	go func() {
		ch := <-s.exit
		ch <- ts.Close()
		config.Broker.Disconnect()
	}()

	// TODO: subscribe to cruft
	return config.Broker.Connect()
}

func (s *rpcServer) Stop() error {
	ch := make(chan error)
	s.exit <- ch
	return <-ch
}

func (s *rpcServer) String() string {
	return "rpc"
}
<commit_msg>Tell me what the registry is too<commit_after>package server

import (
	"fmt"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	c "github.com/micro/go-micro/context"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	log "github.com/golang/glog"

	"golang.org/x/net/context"
)

type rpcServer struct {
	rpc  *server
	exit chan chan error

	sync.RWMutex
	opts        Options
	handlers    map[string]Handler
	subscribers map[*subscriber][]broker.Subscriber
}

func newRpcServer(opts ...Option) Server {
	options := newOptions(opts...)
	return &rpcServer{
		opts: options,
		rpc: &server{
			name:         options.Name,
			serviceMap:   make(map[string]*service),
			hdlrWrappers: options.HdlrWrappers,
		},
		handlers:    make(map[string]Handler),
		subscribers: make(map[*subscriber][]broker.Subscriber),
		exit:        make(chan chan error),
	}
}

func (s *rpcServer) accept(sock transport.Socket) {
	defer func() {
		if r := recover(); r != nil {
			log.Error(r, string(debug.Stack()))
			sock.Close()
		}
	}()

	var msg transport.Message
	if err := sock.Recv(&msg); err != nil {
		return
	}

	ct := msg.Header["Content-Type"]
	cf, err := s.newCodec(ct)
	// TODO: needs better error handling
	if err != nil {
		sock.Send(&transport.Message{
			Header: map[string]string{
				"Content-Type": "text/plain",
			},
			Body: []byte(err.Error()),
		})
		sock.Close()
		return
	}

	codec := newRpcPlusCodec(&msg, sock, cf)

	// strip our headers
	hdr := make(map[string]string)
	for k, v := range msg.Header {
		hdr[k] = v
	}
	delete(hdr, "Content-Type")

	ctx := c.WithMetadata(context.Background(), hdr)

	// TODO: needs better error handling
	if err := s.rpc.serveRequest(ctx, codec, ct); err != nil {
		log.Errorf("Unexpected error serving request, closing socket: %v", err)
		sock.Close()
	}
}

func (s *rpcServer) newCodec(contentType string) (codec.NewCodec, error) {
	if cf, ok := s.opts.Codecs[contentType]; ok {
		return cf, nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (s *rpcServer) Options() Options {
	s.RLock()
	opts := s.opts
	s.RUnlock()
	return opts
}

func (s *rpcServer) Init(opts ...Option) error {
	s.Lock()
	for _, opt := range opts {
		opt(&s.opts)
	}
	s.Unlock()
	return nil
}

func (s *rpcServer) NewHandler(h interface{}) Handler {
	return newRpcHandler(h)
}

func (s *rpcServer) Handle(h Handler) error {
	if err := s.rpc.register(h.Handler()); err != nil {
		return err
	}
	s.Lock()
	s.handlers[h.Name()] = h
	s.Unlock()
	return nil
}

func (s *rpcServer) NewSubscriber(topic string, sb interface{}) Subscriber {
	return newSubscriber(topic, sb)
}

func (s *rpcServer) Subscribe(sb Subscriber) error {
	sub, ok := sb.(*subscriber)
	if !ok {
		return fmt.Errorf("invalid subscriber: expected *subscriber")
	}
	if len(sub.handlers) == 0 {
		return fmt.Errorf("invalid subscriber: no handler functions")
	}

	if err := validateSubscriber(sb); err != nil {
		return err
	}

	s.Lock()
	_, ok = s.subscribers[sub]
	if ok {
		return fmt.Errorf("subscriber %v already exists", s)
	}
	s.subscribers[sub] = nil
	s.Unlock()
	return nil
}

func (s *rpcServer) Register() error {
	// parse address for host, port
	config := s.Options()
	var advt, host string
	var port int

	// check the advertise address first
	// if it exists then use it, otherwise
	// use the address
	if len(config.Advertise) > 0 {
		advt = config.Advertise
	} else {
		advt = config.Address
	}

	parts := strings.Split(advt, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port, _ = strconv.Atoi(parts[len(parts)-1])
	} else {
		host = parts[0]
	}

	addr, err := extractAddress(host)
	if err != nil {
		return err
	}

	// register service
	node := &registry.Node{
		Id:       config.Name + "-" + config.Id,
		Address:  addr,
		Port:     port,
		Metadata: config.Metadata,
	}

	node.Metadata["transport"] = config.Transport.String()
	node.Metadata["broker"] = config.Broker.String()
	node.Metadata["server"] = s.String()
	node.Metadata["registry"] = config.Registry.String()

	s.RLock()
	var endpoints []*registry.Endpoint
	for _, e := range s.handlers {
		endpoints = append(endpoints, e.Endpoints()...)
	}
	for e, _ := range s.subscribers {
		endpoints = append(endpoints, e.Endpoints()...)
	}
	s.RUnlock()

	service := &registry.Service{
		Name:      config.Name,
		Version:   config.Version,
		Nodes:     []*registry.Node{node},
		Endpoints: endpoints,
	}

	log.Infof("Registering node: %s", node.Id)
	if err := config.Registry.Register(service); err != nil {
		return err
	}

	s.Lock()
	defer s.Unlock()

	for sb, _ := range s.subscribers {
		handler := s.createSubHandler(sb, s.opts)
		sub, err := config.Broker.Subscribe(sb.Topic(), handler)
		if err != nil {
			return err
		}
		s.subscribers[sb] = []broker.Subscriber{sub}
	}

	return nil
}

func (s *rpcServer) Deregister() error {
	config := s.Options()
	var advt, host string
	var port int

	// check the advertise address first
	// if it exists then use it, otherwise
	// use the address
	if len(config.Advertise) > 0 {
		advt = config.Advertise
	} else {
		advt = config.Address
	}

	parts := strings.Split(advt, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port, _ = strconv.Atoi(parts[len(parts)-1])
	} else {
		host = parts[0]
	}

	addr, err := extractAddress(host)
	if err != nil {
		return err
	}

	node := &registry.Node{
		Id:      config.Name + "-" + config.Id,
		Address: addr,
		Port:    port,
	}

	service := &registry.Service{
		Name:    config.Name,
		Version: config.Version,
		Nodes:   []*registry.Node{node},
	}

	log.Infof("Deregistering node: %s", node.Id)
	if err := config.Registry.Deregister(service); err != nil {
		return err
	}

	s.Lock()
	for sb, subs := range s.subscribers {
		for _, sub := range subs {
			log.Infof("Unsubscribing from topic: %s", sub.Topic())
			sub.Unsubscribe()
		}
		s.subscribers[sb] = nil
	}
	s.Unlock()
	return nil
}

func (s *rpcServer) Start() error {
	registerHealthChecker(s)
	config := s.Options()

	ts, err := config.Transport.Listen(config.Address)
	if err != nil {
		return err
	}

	log.Infof("Listening on %s", ts.Addr())
	s.Lock()
	s.opts.Address = ts.Addr()
	s.Unlock()

	go ts.Accept(s.accept)

	go func() {
		ch := <-s.exit
		ch <- ts.Close()
		config.Broker.Disconnect()
	}()

	// TODO: subscribe to cruft
	return config.Broker.Connect()
}

func (s *rpcServer) Stop() error {
	ch := make(chan error)
	s.exit <- ch
	return <-ch
}

func (s *rpcServer) String() string {
	return "rpc"
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"text/template"
	"time"

	"github.com/robfig/cron"
	"github.com/samuel/go-zookeeper/zk"
)

//go:generate go run ./scripts/package-templates.go

type defaultLogger struct{}

func (defaultLogger) Printf(format string, a ...interface{}) {
	log.Printf(format, a...)
}

var (
	templateFile     string
	nginxRoot        string
	zookeeperNodes   string
	serviceRoot      string
	t                *template.Template
	renderedTemplate bytes.Buffer
	sitesAvailable   string
	sitesEnabled     string
	hashes           = make(map[string]string)
	logger           defaultLogger
)

// check is a simple wrapper to avoid the verbosity of
// `if err != nil` checks.
func check(err error) {
	if err != nil {
		panic(err)
	}
}

// Config is a simple mapping of service name and upstream endpoints
// so that we can generate nginx service configuration
type Config struct {
	Service           string
	UpstreamEndpoints []string
}

// updateService will iterate through the services available in zookeeper
// and generate a new template for them.
func updateService(zookeeper *zk.Conn, serviceRoot string) {
	children, _, _, err := zookeeper.ChildrenW(serviceRoot)
	check(err)
	reload := false

	for _, child := range children {
		serviceInstance := strings.Join([]string{serviceRoot, child, "instances"}, "/")
		info, _, _, err := zookeeper.ChildrenW(serviceInstance)
		if err != nil {
			panic(err)
		}

		var upstreamEndpoints []string

		for _, instanceInfo := range info {
			i := strings.Join(strings.Split(instanceInfo, "_"), ":")
			upstreamEndpoints = append(upstreamEndpoints, i)
		}

		data := Config{
			Service:           child,
			UpstreamEndpoints: upstreamEndpoints,
		}

		t.Execute(&renderedTemplate, data)
		r := rewriteConfig(child)
		if r == true {
			writeOutput(child)
			symlink(child)
			reload = true
		}

		renderedTemplate.Reset()
	}
	if reload == true {
		reloadNginx()
	}
}

// writeOutput will check if the service exists, remove and recreate it
func writeOutput(service string) {
	fname := fmt.Sprintf("%s/%s.service", sitesAvailable, service)
	err := os.Remove(fname)
	f, err := os.OpenFile(fname, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0444)
	check(err)
	_, err = f.WriteString(renderedTemplate.String())
	check(err)
	f.Close()
}

// rewriteConfig will check if the configuration file needs to be overwritten
// and if it's overwritten it needs to signal that nginx must be reloaded
func rewriteConfig(service string) bool {
	hasher := md5.New()
	hasher.Write([]byte(renderedTemplate.String()))
	renderedHash := hex.EncodeToString(hasher.Sum(nil))
	if val, ok := hashes[service]; ok {
		if val != renderedHash {
			hashes[service] = renderedHash
		} else {
			logger.Printf("%+v :: %+v unchanged. Not updating.", service, renderedHash)
			return false
		}
	} else {
		hashes[service] = renderedHash
	}
	logger.Printf("%+v :: %+v changed. Updating.", service, renderedHash)
	return true
}

// symlink is a wrapper on os.Symlink
func symlink(service string) {
	err := os.Symlink(fmt.Sprintf("%s/%s.service", sitesAvailable, service),
		fmt.Sprintf("%s/%s.service", sitesEnabled, service))
	check(err)
}

// reloadNginx is a wrapper to shell out to reload the configuration
func reloadNginx() {
	reloadCommand := exec.Command("sv", "reload", "nginx")
	err := reloadCommand.Run()
	check(err)
}

func main() {
	flag.StringVar(&templateFile, "template", "", "nginx template to use")
	flag.StringVar(&nginxRoot, "nginx-root", "/etc/nginx/", "The root of the nginx installation")
	flag.StringVar(&zookeeperNodes, "zookeeper-nodes", "127.0.0.1:2181", "The zookeeper instance to connect to")
	flag.StringVar(&serviceRoot, "service-root", "/", "The root path with your service metadata")
	flag.Parse()

	sitesAvailable = fmt.Sprintf("%s/sites-available/", nginxRoot)
	sitesEnabled = fmt.Sprintf("%s/sites-enabled/", nginxRoot)

	var err error
	if len(templateFile) == 0 {
		t, err = template.New("service-template").Parse(defaultService)
		check(err)
	} else {
		t, err = template.New("service-template").ParseFiles(templateFile)
		check(err)
	}

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Interrupt, os.Kill)

	zookeeper, _, err := zk.Connect([]string{zookeeperNodes}, time.Second)
	check(err)

	c := cron.New()
	c.AddFunc("*/10 * * * *", func() {
		updateService(zookeeper, serviceRoot)
	})
	c.Start()

	defer c.Stop()
	<-sigc
}
<commit_msg>Added version information.<commit_after>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"text/template"
	"time"

	"github.com/robfig/cron"
	"github.com/samuel/go-zookeeper/zk"
)

//go:generate go run ./scripts/package-templates.go

type defaultLogger struct{}

func (defaultLogger) Printf(format string, a ...interface{}) {
	log.Printf(format, a...)
}

// Version information
var (
	version    = "0.1.0"
	buildstamp string
	githash    string
)

var (
	templateFile     string
	nginxRoot        string
	zookeeperNodes   string
	serviceRoot      string
	t                *template.Template
	renderedTemplate bytes.Buffer
	sitesAvailable   string
	sitesEnabled     string
	hashes           = make(map[string]string)
	logger           defaultLogger
)

// check is a simple wrapper to avoid the verbosity of
// `if err != nil` checks.
func check(err error) {
	if err != nil {
		panic(err)
	}
}

// Config is a simple mapping of service name and upstream endpoints
// so that we can generate nginx service configuration
type Config struct {
	Service           string
	UpstreamEndpoints []string
}

// updateService will iterate through the services available in zookeeper
// and generate a new template for them.
func updateService(zookeeper *zk.Conn, serviceRoot string) {
	children, _, _, err := zookeeper.ChildrenW(serviceRoot)
	check(err)
	reload := false

	for _, child := range children {
		serviceInstance := strings.Join([]string{serviceRoot, child, "instances"}, "/")
		info, _, _, err := zookeeper.ChildrenW(serviceInstance)
		if err != nil {
			panic(err)
		}

		var upstreamEndpoints []string

		for _, instanceInfo := range info {
			i := strings.Join(strings.Split(instanceInfo, "_"), ":")
			upstreamEndpoints = append(upstreamEndpoints, i)
		}

		data := Config{
			Service:           child,
			UpstreamEndpoints: upstreamEndpoints,
		}

		t.Execute(&renderedTemplate, data)
		r := rewriteConfig(child)
		if r == true {
			writeOutput(child)
			symlink(child)
			reload = true
		}

		renderedTemplate.Reset()
	}
	if reload == true {
		reloadNginx()
	}
}

// writeOutput will check if the service exists, remove and recreate it
func writeOutput(service string) {
	fname := fmt.Sprintf("%s/%s.service", sitesAvailable, service)
	err := os.Remove(fname)
	f, err := os.OpenFile(fname, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0444)
	check(err)
	_, err = f.WriteString(renderedTemplate.String())
	check(err)
	f.Close()
}

// rewriteConfig will check if the configuration file needs to be overwritten
// and if it's overwritten it needs to signal that nginx must be reloaded
func rewriteConfig(service string) bool {
	hasher := md5.New()
	hasher.Write([]byte(renderedTemplate.String()))
	renderedHash := hex.EncodeToString(hasher.Sum(nil))
	if val, ok := hashes[service]; ok {
		if val != renderedHash {
			hashes[service] = renderedHash
		} else {
			logger.Printf("%+v :: %+v unchanged. Not updating.", service, renderedHash)
			return false
		}
	} else {
		hashes[service] = renderedHash
	}
	logger.Printf("%+v :: %+v changed. Updating.", service, renderedHash)
	return true
}

// symlink is a wrapper on os.Symlink
func symlink(service string) {
	err := os.Symlink(fmt.Sprintf("%s/%s.service", sitesAvailable, service),
		fmt.Sprintf("%s/%s.service", sitesEnabled, service))
	check(err)
}

// reloadNginx is a wrapper to shell out to reload the configuration
func reloadNginx() {
	reloadCommand := exec.Command("sv", "reload", "nginx")
	err := reloadCommand.Run()
	check(err)
}

func main() {
	flag.StringVar(&templateFile, "template", "", "nginx template to use")
	flag.StringVar(&nginxRoot, "nginx-root", "/etc/nginx/", "The root of the nginx installation")
	flag.StringVar(&zookeeperNodes, "zookeeper-nodes", "127.0.0.1:2181", "The zookeeper instance to connect to")
	flag.StringVar(&serviceRoot, "service-root", "/", "The root path with your service metadata")
	flag.Parse()

	sitesAvailable = fmt.Sprintf("%s/sites-available/", nginxRoot)
	sitesEnabled = fmt.Sprintf("%s/sites-enabled/", nginxRoot)

	var err error
	if len(templateFile) == 0 {
		t, err = template.New("service-template").Parse(defaultService)
		check(err)
	} else {
		t, err = template.New("service-template").ParseFiles(templateFile)
		check(err)
	}

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Interrupt, os.Kill)

	zookeeper, _, err := zk.Connect([]string{zookeeperNodes}, time.Second)
	check(err)

	c := cron.New()
	c.AddFunc("*/10 * * * *", func() {
		updateService(zookeeper, serviceRoot)
	})
	c.Start()

	defer c.Stop()
	<-sigc
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
)

// usage:
// go run sns_publish_to_topic.go
func main() {
	// Initialize a session in us-west-2 that the SDK will use to load
	// credentials from the shared credentials file ~/.aws/credentials.
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("us-west-2"),
	})

	if err != nil {
		fmt.Println("NewSession error:", err)
		return
	}

	client := sns.New(sess)
	input := &sns.PublishInput{
		Message:  aws.String("Hello world!"),
		TopicArn: aws.String("arn:aws:sns:us-west-2:123456789012:YourTopic"),
	}

	result, err := client.Publish(input)
	if err != nil {
		fmt.Println("Publish error:", err)
		return
	}

	fmt.Println(result)
}
<commit_msg>Update sns_publish_to_topic.go<commit_after>// snippet-comment:[These are tags for the AWS doc team's sample catalog. Do not remove.]
// snippet-sourcedescription:[sns_publish_to_topic.go demonstrates how to list, create, and delete a bucket in Amazon S3.]
// snippet-service:[sns]
// snippet-keyword:[Go]
// snippet-keyword:[Amazon SNS]
// snippet-keyword:[Code Sample]
// snippet-keyword:[Publish]
// snippet-sourcetype:[full-example]
// snippet-sourcedate:[2019-01-30]
// snippet-sourceauthor:[AWS]
/**
 * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * This file is licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 * http://aws.amazon.com/apache2.0/
 *
 * This file 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.
 */
// snippet-start:[sns.go.publish]
package main

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
)

// usage:
// go run sns_publish_to_topic.go
func main() {
	// Initialize a session in us-west-2 that the SDK will use to load
	// credentials from the shared credentials file ~/.aws/credentials.
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("us-west-2"),
	})

	if err != nil {
		fmt.Println("NewSession error:", err)
		return
	}

	client := sns.New(sess)
	input := &sns.PublishInput{
		Message:  aws.String("Hello world!"),
		TopicArn: aws.String("arn:aws:sns:us-west-2:123456789012:YourTopic"),
	}

	result, err := client.Publish(input)
	if err != nil {
		fmt.Println("Publish error:", err)
		return
	}

	fmt.Println(result)
}

// snippet-end:[sns.go.publish]
<|endoftext|>


<commit_before>package index

import (
	"os"
	"sync/atomic"
	"time"

	"github.com/djherbis/times"
)

// EntryPromise describes the promised state of index entry.
type EntryPromise uint32

const (
	EntryPromiseSync   EntryPromise = 1 << iota // S: sync promise, doesn't exist locally.
	EntryPromiseAdd                             // A: sync promise after adding, exists locally.
	EntryPromiseUpdate                          // U: sync promise after updating, exists locally.
	EntryPromiseDel                             // D: sync promise after deleting, doesn't exist locally.
	EntryPromiseUnlink                          // N: sync promise after hard delete, doesn't exist locally.
)

var epMapping = map[byte]EntryPromise{
	'S': EntryPromiseSync,
	'A': EntryPromiseAdd,
	'U': EntryPromiseUpdate,
	'D': EntryPromiseDel,
	'N': EntryPromiseUnlink,
}

// String implements fmt.Stringer interface and pretty prints stored promise.
func (ep EntryPromise) String() string {
	var buf [8]byte // Promise is uint32 but we don't need more than 8.
	for c, i := range epMapping {
		w := getPowerOf2(uint64(i))
		if ep&i != 0 {
			buf[w] = c
		} else {
			buf[w] = '-'
		}
	}

	return string(buf[:len(epMapping)])
}

// realEntry describes the part of entry which is independent of underlying file
// system. The fields of this type can be stored on disk and transferred over
// network.
type realEntry struct {
	CTime int64       `json:"c"` // Metadata change time since EPOCH.
	MTime int64       `json:"m"` // File data change time since EPOCH.
	Size  int64       `json:"s"` // Size of the file.
	Mode  os.FileMode `json:"o"` // File mode and permission bits.
}

// virtualEntry stores virtual file system dependent data that is lost during
// serialization and should be recreated by VFS which manages the entries.
type virtualEntry struct {
	inode    uint64       // Inode ID of a mounted file.
	refCount int32        // Reference count of file handlers.
	promise  EntryPromise // Metadata of files's memory state.
}

// Entry represents a single file registered to index.
type Entry struct {
	real    realEntry
	virtual virtualEntry
}

// NewEntry creates a new entry that describes the wile with specified size and
// mode. VFS are zero values and must be set manually.
func NewEntry(size int64, mode os.FileMode) *Entry {
	t := time.Now().UTC().UnixNano()
	return NewEntryTime(t, t, size, mode)
}

// NewEntryFileInfo creates a new entry from a given file info.
func NewEntryFileInfo(info os.FileInfo) *Entry {
	return NewEntryTime(
		ctime(info),
		info.ModTime().UTC().UnixNano(),
		info.Size(),
		info.Mode(),
	)
}

// NewEntryTime creates a new entry with custom file change and modify times.
func NewEntryTime(ctime, mtime, size int64, mode os.FileMode) *Entry {
	return &Entry{
		real: realEntry{
			CTime: ctime,
			MTime: mtime,
			Size:  size,
			Mode:  mode,
		},
	}
}

// NewEntryFile creates a new entry which describes the given file.
func NewEntryFile(path string) (*Entry, error) {
	info, err := os.Lstat(path)
	if err != nil {
		return nil, err
	}

	return NewEntryFileInfo(info), nil
}

// CTime atomically gets entry change time in UNIX nano format.
func (e *Entry) CTime() int64 {
	return atomic.LoadInt64(&e.real.CTime)
}

// SetCTime atomically sets entry's change time. Time must be in UNIX nano format.
func (e *Entry) SetCTime(nano int64) {
	atomic.StoreInt64(&e.real.CTime, nano)
}

// MTime atomically gets entry modification time in UNIX nano format.
func (e *Entry) MTime() int64 {
	return atomic.LoadInt64(&e.real.MTime)
}

// SetMTime atomically sets entry's modification time. Time must be in UNIX nano
// format.
func (e *Entry) SetMTime(nano int64) {
	atomic.StoreInt64(&e.real.MTime, nano)
}

// Size atomically gets entry size in bytes.
func (e *Entry) Size() int64 {
	return atomic.LoadInt64(&e.real.Size)
}

// SetSize atomically sets entry's size. The size is provided in bytes.
func (e *Entry) SetSize(size int64) {
	atomic.StoreInt64(&e.real.Size, size)
}

// Mode atomically gets entry file mode.
func (e *Entry) Mode() os.FileMode {
	return os.FileMode(atomic.LoadUint32((*uint32)(&e.real.Mode)))
}

// SetMode atomically sets entry file mode.
func (e *Entry) SetMode(mode os.FileMode) {
	atomic.StoreUint32((*uint32)(&e.real.Mode), (uint32)(mode))
}

// Inode gets virtual file system inode.
func (e *Entry) Inode() uint64 {
	return atomic.LoadUint64(&e.virtual.inode)
}

// SetInode sets virtual file system inode.
func (e *Entry) SetInode(inode uint64) {
	atomic.StoreUint64(&e.virtual.inode, inode)
}

// IncRefCounter increments entry reference counter.
func (e *Entry) IncRefCounter() int32 {
	return atomic.AddInt32(&e.virtual.refCount, 1)
}

// DecRefCounter decrements entry reference counter.
func (e *Entry) DecRefCounter() int32 {
	return atomic.AddInt32(&e.virtual.refCount, -1)
}

// HasPromise checks if provider promise is set in given entry.
func (e *Entry) HasPromise(promise EntryPromise) bool {
	return EntryPromise(atomic.LoadUint32((*uint32)(&e.virtual.promise)))&promise == promise
}

// Deleted checks if the entry is promised to be deleted.
func (e *Entry) Deleted() bool {
	return e.HasPromise(EntryPromiseDel) || e.HasPromise(EntryPromiseUnlink)
}

// SwapPromise flips the value of a promise field, setting the set bits and
// unsetting the unset ones. This function is thread safe.
func (e *Entry) SwapPromise(set, unset EntryPromise) EntryPromise {
	for {
		older := atomic.LoadUint32((*uint32)(&e.virtual.promise))
		updated := (older | uint32(set)) &^ uint32(unset)

		if atomic.CompareAndSwapUint32((*uint32)(&e.virtual.promise), older, updated) {
			return EntryPromise(updated)
		}
	}
}

// ctime gets file's change time in UNIX Nano format.
func ctime(fi os.FileInfo) int64 {
	if tspec := times.Get(fi); tspec.HasChangeTime() {
		return tspec.ChangeTime().UnixNano()
	}

	return 0
}
<commit_msg>klient/machine: implement JSON interfaces in index.Entry<commit_after>package index

import (
	"encoding/json"
	"os"
	"sync/atomic"
	"time"

	"github.com/djherbis/times"
)

// EntryPromise describes the promised state of index entry.
type EntryPromise uint32

const (
	EntryPromiseSync   EntryPromise = 1 << iota // S: sync promise, doesn't exist locally.
	EntryPromiseAdd                             // A: sync promise after adding, exists locally.
	EntryPromiseUpdate                          // U: sync promise after updating, exists locally.
	EntryPromiseDel                             // D: sync promise after deleting, doesn't exist locally.
	EntryPromiseUnlink                          // N: sync promise after hard delete, doesn't exist locally.
)

var epMapping = map[byte]EntryPromise{
	'S': EntryPromiseSync,
	'A': EntryPromiseAdd,
	'U': EntryPromiseUpdate,
	'D': EntryPromiseDel,
	'N': EntryPromiseUnlink,
}

// String implements fmt.Stringer interface and pretty prints stored promise.
func (ep EntryPromise) String() string {
	var buf [8]byte // Promise is uint32 but we don't need more than 8.
	for c, i := range epMapping {
		w := getPowerOf2(uint64(i))
		if ep&i != 0 {
			buf[w] = c
		} else {
			buf[w] = '-'
		}
	}

	return string(buf[:len(epMapping)])
}

// realEntry describes the part of entry which is independent of underlying file
// system. The fields of this type can be stored on disk and transferred over
// network.
type realEntry struct {
	CTime int64       `json:"c"` // Metadata change time since EPOCH.
	MTime int64       `json:"m"` // File data change time since EPOCH.
	Size  int64       `json:"s"` // Size of the file.
	Mode  os.FileMode `json:"o"` // File mode and permission bits.
}

// virtualEntry stores virtual file system dependent data that is lost during
// serialization and should be recreated by VFS which manages the entries.
type virtualEntry struct {
	inode    uint64       // Inode ID of a mounted file.
	refCount int32        // Reference count of file handlers.
	promise  EntryPromise // Metadata of files's memory state.
}

var (
	_ json.Marshaler   = (*Index)(nil)
	_ json.Unmarshaler = (*Index)(nil)
)

// Entry represents a single file registered to index.
type Entry struct {
	real    realEntry
	virtual virtualEntry
}

// NewEntry creates a new entry that describes the wile with specified size and
// mode. VFS are zero values and must be set manually.
func NewEntry(size int64, mode os.FileMode) *Entry {
	t := time.Now().UTC().UnixNano()
	return NewEntryTime(t, t, size, mode)
}

// NewEntryFileInfo creates a new entry from a given file info.
func NewEntryFileInfo(info os.FileInfo) *Entry {
	return NewEntryTime(
		ctime(info),
		info.ModTime().UTC().UnixNano(),
		info.Size(),
		info.Mode(),
	)
}

// NewEntryTime creates a new entry with custom file change and modify times.
func NewEntryTime(ctime, mtime, size int64, mode os.FileMode) *Entry {
	return &Entry{
		real: realEntry{
			CTime: ctime,
			MTime: mtime,
			Size:  size,
			Mode:  mode,
		},
	}
}

// NewEntryFile creates a new entry which describes the given file.
func NewEntryFile(path string) (*Entry, error) {
	info, err := os.Lstat(path)
	if err != nil {
		return nil, err
	}

	return NewEntryFileInfo(info), nil
}

// CTime atomically gets entry change time in UNIX nano format.
func (e *Entry) CTime() int64 {
	return atomic.LoadInt64(&e.real.CTime)
}

// SetCTime atomically sets entry's change time. Time must be in UNIX nano format.
func (e *Entry) SetCTime(nano int64) {
	atomic.StoreInt64(&e.real.CTime, nano)
}

// MTime atomically gets entry modification time in UNIX nano format.
func (e *Entry) MTime() int64 {
	return atomic.LoadInt64(&e.real.MTime)
}

// SetMTime atomically sets entry's modification time. Time must be in UNIX nano
// format.
func (e *Entry) SetMTime(nano int64) {
	atomic.StoreInt64(&e.real.MTime, nano)
}

// Size atomically gets entry size in bytes.
func (e *Entry) Size() int64 {
	return atomic.LoadInt64(&e.real.Size)
}

// SetSize atomically sets entry's size. The size is provided in bytes.
func (e *Entry) SetSize(size int64) {
	atomic.StoreInt64(&e.real.Size, size)
}

// Mode atomically gets entry file mode.
func (e *Entry) Mode() os.FileMode {
	return os.FileMode(atomic.LoadUint32((*uint32)(&e.real.Mode)))
}

// SetMode atomically sets entry file mode.
func (e *Entry) SetMode(mode os.FileMode) {
	atomic.StoreUint32((*uint32)(&e.real.Mode), (uint32)(mode))
}

// Inode gets virtual file system inode.
func (e *Entry) Inode() uint64 {
	return atomic.LoadUint64(&e.virtual.inode)
}

// SetInode sets virtual file system inode.
func (e *Entry) SetInode(inode uint64) {
	atomic.StoreUint64(&e.virtual.inode, inode)
}

// IncRefCounter increments entry reference counter.
func (e *Entry) IncRefCounter() int32 {
	return atomic.AddInt32(&e.virtual.refCount, 1)
}

// DecRefCounter decrements entry reference counter.
func (e *Entry) DecRefCounter() int32 {
	return atomic.AddInt32(&e.virtual.refCount, -1)
}

// HasPromise checks if provider promise is set in given entry.
func (e *Entry) HasPromise(promise EntryPromise) bool {
	return EntryPromise(atomic.LoadUint32((*uint32)(&e.virtual.promise)))&promise == promise
}

// Deleted checks if the entry is promised to be deleted.
func (e *Entry) Deleted() bool {
	return e.HasPromise(EntryPromiseDel) || e.HasPromise(EntryPromiseUnlink)
}

// SwapPromise flips the value of a promise field, setting the set bits and
// unsetting the unset ones. This function is thread safe.
func (e *Entry) SwapPromise(set, unset EntryPromise) EntryPromise {
	for {
		older := atomic.LoadUint32((*uint32)(&e.virtual.promise))
		updated := (older | uint32(set)) &^ uint32(unset)

		if atomic.CompareAndSwapUint32((*uint32)(&e.virtual.promise), older, updated) {
			return EntryPromise(updated)
		}
	}
}

// MarshalJSON satisfies json.Marshaler interface. It safely marshals entry
// real data to JSON format.
func (e *Entry) MarshalJSON() ([]byte, error) {
	real := realEntry{
		CTime: e.CTime(),
		MTime: e.MTime(),
		Size:  e.Size(),
		Mode:  e.Mode(),
	}

	return json.Marshal(real)
}

// UnmarshalJSON satisfies json.Unmarshaler interface. It is used to unmarshal
// data into private entry fields.
func (e *Entry) UnmarshalJSON(data []byte) error {
	return json.Unmarshal(data, &e.real)
}

// ctime gets file's change time in UNIX Nano format.
func ctime(fi os.FileInfo) int64 {
	if tspec := times.Get(fi); tspec.HasChangeTime() {
		return tspec.ChangeTime().UnixNano()
	}

	return 0
}
<|endoftext|>