<commit_before>package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

type Record struct {
	ID             int       `json:"-",db:"id"`
	LogName        string    `json:"LogName",db:"log_name"`
	Category       string    `json:"Category",db:"category"`
	CategoryNumber int       `json:"CategoryNumber",db:"category_number"`
	Data           []byte    `json:"Data,omitempty",db:"data"`
	EntryType      string    `json:"EntryType",db:"entry_type"`
	EventID        int       `json:"EventID",db:"event_id"`
	Index          int       `json:"Index",db:"event_index"`
	InstanceId     int       `json:"InstanceId",db:"instance_id"`
	MachineName    string    `json:"MachineName",db:"machine_name"`
	Message        string    `json:"Message",db:"message"`
	Source         string    `json:"Source",db:"source"`
	TimeGenerated  time.Time `json:"TimeGenerated",db:"time_generated"`
	TimeWritten    time.Time `json:"TimeWritten",db:"time_written"`
	UserName       *string   `json:"UserName,omitempty",db:"user_name"`
}

const CreateTableStmt = `
CREATE TABLE IF NOT EXISTS event_records (
	id              INTEGER PRIMARY KEY,
	log_name        TEXT NOT NULL,
	category        TEXT NOT NULL,
	category_number INTEGER NOT NULL,
	data            BLOB,
	entry_type      TEXT NOT NULL,
	event_id        INTEGER NOT NULL,
	event_index     INTEGER NOT NULL,
	instance_id     INTEGER NOT NULL,
	machine_name    TEXT NOT NULL,
	message         TEXT NOT NULL,
	source          TEXT NOT NULL,
	time_generated  TIMESTAMP NOT NULL,
	time_written    TIMESTAMP NOT NULL,
	user_name       TEXT
);`

const InsertStmt = `
INSERT INTO event_records (
	log_name,
	category,
	category_number,
	data,
	entry_type,
	event_id,
	event_index,
	instance_id,
	machine_name,
	message,
	source,
	time_generated,
	time_written,
	user_name
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
`

func InsertRecord(stmt *sql.Stmt, rec *Record) error {
	_, err := stmt.Exec(rec.LogName,
		rec.Category,
		rec.CategoryNumber,
		rec.Data,
		rec.EntryType,
		rec.EventID,
		rec.Index,
		rec.InstanceId,
		rec.MachineName,
		rec.Message,
		rec.Source,
		rec.TimeGenerated,
		rec.TimeWritten,
		rec.UserName,
	)
	return err
}

func Usage() {
	const format = `
Usage: %s EVENT_LOG DATABASE_NAME

  Imports JSON EventLog records EVENT_LOG into Sqlite3 DATABASE_NAME.

`
	fmt.Fprintf(os.Stderr, format, filepath.Base(os.Args[0]))
	os.Exit(2)
}

// Skips the UTF-8 BOM if present - because Windows
func SkipBOM(f *os.File) error {
	bom := []byte{0xEF, 0xBB, 0xBF}

	b := make([]byte, 3)
	if _, err := f.Read(b); err != nil {
		return err
	}
	// If we found the BOM theres nothing to do as we've seeked
	// past it, if we didn't find reset the read offset to 0.
	if !bytes.Equal(b, bom) {
		_, err := f.Seek(0, 0)
		return err
	}
	return nil
}

func main() {
	if len(os.Args) != 3 {
		Usage()
	}
	recfile := os.Args[1]
	dbname := os.Args[2]

	f, err := os.Open(recfile)
	if err != nil {
		Fatal(err, 1)
	}
	defer f.Close()

	if err := SkipBOM(f); err != nil {
		Fatal(fmt.Sprintf("reading event log file (%s): %s", recfile, err), 1)
	}

	if fi, err := os.Stat(dbname); err == nil {
		if fi.IsDir() {
			Fatal(fmt.Sprintf("cannot use directory (%s) as a sqlite database", dbname), 1)
		}
		Fatal(fmt.Sprintf("refusing to overwrite database file: %s", dbname), 1)
	}
	db, err := sql.Open("sqlite3", dbname)
	if err != nil {
		Fatal(err, 1)
	}
	defer db.Close()

	if _, err := db.Exec(CreateTableStmt); err != nil {
		Fatal(err, 1)
	}

	// insert records in a transaction
	tx, err := db.Begin()
	if err != nil {
		Fatal(err, 1)
	}
	// abort will rollback the transaction and delete the db file.
	abort := func(err interface{}) {
		os.Remove(dbname)
		tx.Rollback()
		Fatal(err, 2) // skip 2 stack frames
	}

	// use a prepared statement to speed insertion
	stmt, err := tx.Prepare(InsertStmt)
	if err != nil {
		abort(err)
	}

	dec := json.NewDecoder(f)
	for n := 0; err == nil; n++ {
		var rec Record
		if err = dec.Decode(&rec); err != nil && err != io.EOF {
			abort(fmt.Sprintf("decoding record (%d): %s", n, err))
		}
		if err := InsertRecord(stmt, &rec); err != nil {
			abort(fmt.Sprintf("inserting record (%d): %s", n, err))
		}
	}
	if err := stmt.Close(); err != nil {
		abort(err)
	}
	if err := tx.Commit(); err != nil {
		abort(err)
	}
}

func Fatal(err interface{}, skip int) {
	if err != nil {
		if skip < 1 {
			skip = 1
		}
		var format string
		if _, file, line, ok := runtime.Caller(skip); ok && file != "" {
			format = fmt.Sprintf("Error (%s:%d)", filepath.Base(file), line)
		} else {
			format = "Error"
		}
		switch err.(type) {
		case error, string, fmt.Stringer:
			fmt.Fprintf(os.Stderr, "%s: %s\n", format, err)
		default:
			fmt.Fprintf(os.Stderr, "%s: %#v\n", format, err)
		}
		os.Exit(1)
	}
}
<commit_msg>Util: rollback transaction before deleting database file<commit_after>package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

type Record struct {
	ID             int       `json:"-",db:"id"`
	LogName        string    `json:"LogName",db:"log_name"`
	Category       string    `json:"Category",db:"category"`
	CategoryNumber int       `json:"CategoryNumber",db:"category_number"`
	Data           []byte    `json:"Data,omitempty",db:"data"`
	EntryType      string    `json:"EntryType",db:"entry_type"`
	EventID        int       `json:"EventID",db:"event_id"`
	Index          int       `json:"Index",db:"event_index"`
	InstanceId     int       `json:"InstanceId",db:"instance_id"`
	MachineName    string    `json:"MachineName",db:"machine_name"`
	Message        string    `json:"Message",db:"message"`
	Source         string    `json:"Source",db:"source"`
	TimeGenerated  time.Time `json:"TimeGenerated",db:"time_generated"`
	TimeWritten    time.Time `json:"TimeWritten",db:"time_written"`
	UserName       *string   `json:"UserName,omitempty",db:"user_name"`
}

const CreateTableStmt = `
CREATE TABLE IF NOT EXISTS event_records (
	id              INTEGER PRIMARY KEY,
	log_name        TEXT NOT NULL,
	category        TEXT NOT NULL,
	category_number INTEGER NOT NULL,
	data            BLOB,
	entry_type      TEXT NOT NULL,
	event_id        INTEGER NOT NULL,
	event_index     INTEGER NOT NULL,
	instance_id     INTEGER NOT NULL,
	machine_name    TEXT NOT NULL,
	message         TEXT NOT NULL,
	source          TEXT NOT NULL,
	time_generated  TIMESTAMP NOT NULL,
	time_written    TIMESTAMP NOT NULL,
	user_name       TEXT
);`

const InsertStmt = `
INSERT INTO event_records (
	log_name,
	category,
	category_number,
	data,
	entry_type,
	event_id,
	event_index,
	instance_id,
	machine_name,
	message,
	source,
	time_generated,
	time_written,
	user_name
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
`

func InsertRecord(stmt *sql.Stmt, rec *Record) error {
	_, err := stmt.Exec(rec.LogName,
		rec.Category,
		rec.CategoryNumber,
		rec.Data,
		rec.EntryType,
		rec.EventID,
		rec.Index,
		rec.InstanceId,
		rec.MachineName,
		rec.Message,
		rec.Source,
		rec.TimeGenerated,
		rec.TimeWritten,
		rec.UserName,
	)
	return err
}

func Usage() {
	const format = `
Usage: %s EVENT_LOG DATABASE_NAME

  Imports JSON EventLog records EVENT_LOG into Sqlite3 DATABASE_NAME.

`
	fmt.Fprintf(os.Stderr, format, filepath.Base(os.Args[0]))
	os.Exit(2)
}

// Skips the UTF-8 BOM if present - because Windows
func SkipBOM(f *os.File) error {
	bom := []byte{0xEF, 0xBB, 0xBF}

	b := make([]byte, 3)
	if _, err := f.Read(b); err != nil {
		return err
	}
	// If we found the BOM theres nothing to do as we've seeked
	// past it, if we didn't find reset the read offset to 0.
	if !bytes.Equal(b, bom) {
		_, err := f.Seek(0, 0)
		return err
	}
	return nil
}

func main() {
	if len(os.Args) != 3 {
		Usage()
	}
	recfile := os.Args[1]
	dbname := os.Args[2]

	f, err := os.Open(recfile)
	if err != nil {
		Fatal(err, 1)
	}
	defer f.Close()

	if err := SkipBOM(f); err != nil {
		Fatal(fmt.Sprintf("reading event log file (%s): %s", recfile, err), 1)
	}

	if fi, err := os.Stat(dbname); err == nil {
		if fi.IsDir() {
			Fatal(fmt.Sprintf("cannot use directory (%s) as a sqlite database", dbname), 1)
		}
		Fatal(fmt.Sprintf("refusing to overwrite database file: %s", dbname), 1)
	}
	db, err := sql.Open("sqlite3", dbname)
	if err != nil {
		Fatal(err, 1)
	}
	defer db.Close()

	if _, err := db.Exec(CreateTableStmt); err != nil {
		Fatal(err, 1)
	}

	// insert records in a transaction
	tx, err := db.Begin()
	if err != nil {
		Fatal(err, 1)
	}
	// abort will rollback the transaction and delete the db file.
	abort := func(err interface{}) {
		tx.Rollback()
		db.Close()
		os.Remove(dbname)
		Fatal(err, 2) // skip 2 stack frames
	}

	// use a prepared statement to speed insertion
	stmt, err := tx.Prepare(InsertStmt)
	if err != nil {
		abort(err)
	}

	dec := json.NewDecoder(f)
	for n := 0; err == nil; n++ {
		var rec Record
		if err = dec.Decode(&rec); err != nil && err != io.EOF {
			abort(fmt.Sprintf("decoding record (%d): %s", n, err))
		}
		if err := InsertRecord(stmt, &rec); err != nil {
			abort(fmt.Sprintf("inserting record (%d): %s", n, err))
		}
	}
	if err := stmt.Close(); err != nil {
		abort(err)
	}
	if err := tx.Commit(); err != nil {
		abort(err)
	}
}

func Fatal(err interface{}, skip int) {
	if err != nil {
		if skip < 1 {
			skip = 1
		}
		var format string
		if _, file, line, ok := runtime.Caller(skip); ok && file != "" {
			format = fmt.Sprintf("Error (%s:%d)", filepath.Base(file), line)
		} else {
			format = "Error"
		}
		switch err.(type) {
		case error, string, fmt.Stringer:
			fmt.Fprintf(os.Stderr, "%s: %s\n", format, err)
		default:
			fmt.Fprintf(os.Stderr, "%s: %#v\n", format, err)
		}
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package stdlib

import (
	"testing"

	"github.com/PuerkitoBio/agora/runtime"
)

func TestStringsMatches(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  [][]string
	}{
		0: {
			args: []runtime.Val{
				runtime.String("this is a string"),
				runtime.String(`^.+$`),
			},
			exp: [][]string{
				0: []string{
					0: "this is a string",
				},
			},
		},
		1: {
			args: []runtime.Val{
				runtime.String("this is a string"),
				runtime.String(".*?(is)"),
			},
			exp: [][]string{
				0: []string{
					0: "this",
					1: "is",
				},
				1: []string{
					0: " is",
					1: "is",
				},
			},
		},
		2: {
			args: []runtime.Val{
				runtime.String("what whatever who where"),
				runtime.String(`(w.)\w+`),
				runtime.Number(2),
			},
			exp: [][]string{
				0: []string{
					0: "what",
					1: "wh",
				},
				1: []string{
					0: "whatever",
					1: "wh",
				},
			},
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Matches(c.args...)
		ob := ret.(runtime.Object)
		if int64(len(c.exp)) != ob.Len().Int() {
			t.Errorf("[%d] - expected %d matches, got %d", i, len(c.exp), ob.Len().Int())
		} else {
			for j := int64(0); j < ob.Len().Int(); j++ {
				// For each match, there's 0..n number of matches (0 is the full match)
				mtch := ob.Get(runtime.Number(j))
				mo := mtch.(runtime.Object)
				if int64(len(c.exp[j])) != mo.Len().Int() {
					t.Errorf("[%d] - expected %d groups in match %d, got %d", i, len(c.exp[j]), j, mo.Len().Int())
				} else {
					for k := int64(0); k < mo.Len().Int(); k++ {
						grp := mo.Get(runtime.Number(k))
						gro := grp.(runtime.Object)
						st := gro.Get(runtime.String("Start"))
						e := gro.Get(runtime.String("End"))
						if e.Int() != st.Int()+int64(len(c.exp[j][k])) {
							t.Errorf("[%d] - expected end %d for group %d of match %d, got %d", i, st.Int()+int64(len(c.exp[j][k])), k, j, e.Int())
						}
						s := gro.Get(runtime.String("Text"))
						if s.String() != c.exp[j][k] {
							t.Errorf("[%d] - expected text '%s' for group %d of match %d, got '%s'", i, c.exp[j][k], k, j, s)
						}
					}
				}
			}
		}
	}
}

func TestStringsToUpper(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_ToUpper(runtime.String("this"), runtime.String("Is"), runtime.String("A"), runtime.String("... strInG"))
	exp := "THISISA... STRING"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsToLower(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_ToLower(runtime.String("this"), runtime.String("Is"), runtime.String("A"), runtime.String("... strInG"))
	exp := "thisisa... string"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsHasPrefix(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_HasPrefix(runtime.String("what prefix?"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("wh"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_HasPrefix(runtime.String("what prefix?"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsHasSuffix(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_HasSuffix(runtime.String("suffix, you say"), runtime.String("ay"), runtime.Nil, runtime.Number(3), runtime.String("wh"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_HasSuffix(runtime.String("suffix, you say"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsByteAt(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	src := "some string"
	ix := 0
	ret := sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != string(src[ix]) {
		t.Errorf("expected byte %s at index %d, got %s", string(src[ix]), ix, ret)
	}
	ix = 3
	ret = sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != string(src[ix]) {
		t.Errorf("expected byte %s at index %d, got %s", string(src[ix]), ix, ret)
	}
	ix = 22
	ret = sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != "" {
		t.Errorf("expected byte %s at index %d, got %s", "", ix, ret)
	}
}

func TestStringsConcat(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Concat(runtime.String("hello"), runtime.Number(12), runtime.Bool(true), runtime.String("end"))
	exp := "hello12trueend"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsContains(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Contains(runtime.String("contains something"), runtime.String("what"), runtime.Nil, runtime.Number(3), runtime.String("some"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_Contains(runtime.String("contains something"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsIndex(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Index(runtime.String("agora"), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp := 1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
	ret = sm.strings_Index(runtime.String("agora"), runtime.Number(2), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp = -1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
}

func TestStringsLastIndex(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_LastIndex(runtime.String("agoragore"), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp := 5
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
	ret = sm.strings_Index(runtime.String("agoragore"), runtime.Number(6), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp = -1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
}

func TestStringsSlice(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Slice(runtime.String("agora"), runtime.Number(2))
	exp := "ora"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
	ret = sm.strings_Slice(runtime.String("agora"), runtime.Number(2), runtime.Number(4))
	exp = "or"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsSplit(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Split(runtime.String("aa:bb::dd"), runtime.String(":"))
	ob := ret.(runtime.Object)
	exp := []string{"aa", "bb", "", "dd"}
	if l := ob.Len().Int(); l != int64(len(exp)) {
		t.Errorf("expected split length of %d, got %d", len(exp), l)
	}
	for i, v := range exp {
		got := ob.Get(runtime.Number(i))
		if got.String() != v {
			t.Errorf("expected split index %d to be %s, got %s", i, v, got)
		}
	}
	ret = sm.strings_Split(runtime.String("aa:bb::dd:ee:"), runtime.String(":"), runtime.Number(2))
	ob = ret.(runtime.Object)
	exp = []string{"aa", "bb::dd:ee:"}
	if l := ob.Len().Int(); l != int64(len(exp)) {
		t.Errorf("expected split length of %d, got %d", len(exp), l)
	}
	for i, v := range exp {
		got := ob.Get(runtime.Number(i))
		if got.String() != v {
			t.Errorf("expected split index %d to be %s, got %s", i, v, got)
		}
	}
}

func TestStringsJoin(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	parts := []string{"this", "is", "", "it!"}
	ob := runtime.NewObject()
	for i, v := range parts {
		ob.Set(runtime.Number(i), runtime.String(v))
	}
	ret := sm.strings_Join(ob)
	exp := "thisisit!"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
	ret = sm.strings_Join(ob, runtime.String("--"))
	exp = "this--is----it!"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsReplace(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  string
	}{
		0: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("th"),
			},
			exp: "is is e source",
		},
		1: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("th"),
				runtime.Number(1),
			},
			exp: "is is the source",
		},
		2: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("t"),
				runtime.String("T"),
			},
			exp: "This is The source",
		},
		3: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("t"),
				runtime.String("T"),
				runtime.Number(1),
			},
			exp: "This is the source",
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Replace(c.args...)
		if ret.String() != c.exp {
			t.Errorf("[%d] - expected %s, got %s", i, c.exp, ret)
		}
	}
}

func TestStringsTrim(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  string
	}{
		0: {
			args: []runtime.Val{
				runtime.String(" "),
			},
			exp: "",
		},
		1: {
			args: []runtime.Val{
				runtime.String("\n  \t   hi \r"),
			},
			exp: "hi",
		},
		2: {
			args: []runtime.Val{
				runtime.String("xoxolovexox"),
				runtime.String("xo"),
			},
			exp: "love",
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Trim(c.args...)
		if ret.String() != c.exp {
			t.Errorf("[%d] - expected %s, got %s", i, c.exp, ret)
		}
	}
}
<commit_msg>try to fix git hook<commit_after>package stdlib

import (
	"testing"

	"github.com/PuerkitoBio/agora/runtime"
)

func TestStringsMatches(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  [][]string
	}{
		0: {
			args: []runtime.Val{
				runtime.String("this is a string"),
				runtime.String(`^.+$`),
			},
			exp: [][]string{
				0: []string{
					0: "this is a string",
				},
			},
		},
		1: {
			args: []runtime.Val{
				runtime.String("this is a string"),
				runtime.String(".*?(is)"),
			},
			exp: [][]string{
				0: []string{
					0: "this",
					1: "is",
				},
				1: []string{
					0: " is",
					1: "is",
				},
			},
		},
		2: {
			args: []runtime.Val{
				runtime.String("what whatever who where"),
				runtime.String(`(w.)\w+`),
				runtime.Number(2),
			},
			exp: [][]string{
				0: []string{
					0: "what",
					1: "wh",
				},
				1: []string{
					0: "whatever",
					1: "wh",
				},
			},
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Matches(c.args...)
		ob := ret.(runtime.Object)
		if int64(len(c.exp)) != ob.Len().Int() {
			t.Errorf("[%f] - expected %d matches, got %d", i, len(c.exp), ob.Len().Int())
		} else {
			for j := int64(0); j < ob.Len().Int(); j++ {
				// For each match, there's 0..n number of matches (0 is the full match)
				mtch := ob.Get(runtime.Number(j))
				mo := mtch.(runtime.Object)
				if int64(len(c.exp[j])) != mo.Len().Int() {
					t.Errorf("[%d] - expected %d groups in match %d, got %d", i, len(c.exp[j]), j, mo.Len().Int())
				} else {
					for k := int64(0); k < mo.Len().Int(); k++ {
						grp := mo.Get(runtime.Number(k))
						gro := grp.(runtime.Object)
						st := gro.Get(runtime.String("Start"))
						e := gro.Get(runtime.String("End"))
						if e.Int() != st.Int()+int64(len(c.exp[j][k])) {
							t.Errorf("[%d] - expected end %d for group %d of match %d, got %d", i, st.Int()+int64(len(c.exp[j][k])), k, j, e.Int())
						}
						s := gro.Get(runtime.String("Text"))
						if s.String() != c.exp[j][k] {
							t.Errorf("[%d] - expected text '%s' for group %d of match %d, got '%s'", i, c.exp[j][k], k, j, s)
						}
					}
				}
			}
		}
	}
}

func TestStringsToUpper(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_ToUpper(runtime.String("this"), runtime.String("Is"), runtime.String("A"), runtime.String("... strInG"))
	exp := "THISISA... STRING"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsToLower(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_ToLower(runtime.String("this"), runtime.String("Is"), runtime.String("A"), runtime.String("... strInG"))
	exp := "thisisa... string"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsHasPrefix(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_HasPrefix(runtime.String("what prefix?"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("wh"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_HasPrefix(runtime.String("what prefix?"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsHasSuffix(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_HasSuffix(runtime.String("suffix, you say"), runtime.String("ay"), runtime.Nil, runtime.Number(3), runtime.String("wh"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_HasSuffix(runtime.String("suffix, you say"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsByteAt(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	src := "some string"
	ix := 0
	ret := sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != string(src[ix]) {
		t.Errorf("expected byte %s at index %d, got %s", string(src[ix]), ix, ret)
	}
	ix = 3
	ret = sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != string(src[ix]) {
		t.Errorf("expected byte %s at index %d, got %s", string(src[ix]), ix, ret)
	}
	ix = 22
	ret = sm.strings_ByteAt(runtime.String(src), runtime.Number(ix))
	if ret.String() != "" {
		t.Errorf("expected byte %s at index %d, got %s", "", ix, ret)
	}
}

func TestStringsConcat(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Concat(runtime.String("hello"), runtime.Number(12), runtime.Bool(true), runtime.String("end"))
	exp := "hello12trueend"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsContains(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Contains(runtime.String("contains something"), runtime.String("what"), runtime.Nil, runtime.Number(3), runtime.String("some"))
	if !ret.Bool() {
		t.Errorf("expected true, got false")
	}
	ret = sm.strings_Contains(runtime.String("contains something"), runtime.String("no"), runtime.Nil, runtime.Number(3), runtime.String("hw"))
	if ret.Bool() {
		t.Errorf("expected false, got true")
	}
}

func TestStringsIndex(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Index(runtime.String("agora"), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp := 1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
	ret = sm.strings_Index(runtime.String("agora"), runtime.Number(2), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp = -1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
}

func TestStringsLastIndex(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_LastIndex(runtime.String("agoragore"), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp := 5
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
	ret = sm.strings_Index(runtime.String("agoragore"), runtime.Number(6), runtime.String("arg"), runtime.Nil, runtime.Number(3), runtime.String("go"))
	exp = -1
	if ret.Int() != int64(exp) {
		t.Errorf("expected %d, got %d", exp, ret.Int())
	}
}

func TestStringsSlice(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Slice(runtime.String("agora"), runtime.Number(2))
	exp := "ora"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
	ret = sm.strings_Slice(runtime.String("agora"), runtime.Number(2), runtime.Number(4))
	exp = "or"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsSplit(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	ret := sm.strings_Split(runtime.String("aa:bb::dd"), runtime.String(":"))
	ob := ret.(runtime.Object)
	exp := []string{"aa", "bb", "", "dd"}
	if l := ob.Len().Int(); l != int64(len(exp)) {
		t.Errorf("expected split length of %d, got %d", len(exp), l)
	}
	for i, v := range exp {
		got := ob.Get(runtime.Number(i))
		if got.String() != v {
			t.Errorf("expected split index %d to be %s, got %s", i, v, got)
		}
	}
	ret = sm.strings_Split(runtime.String("aa:bb::dd:ee:"), runtime.String(":"), runtime.Number(2))
	ob = ret.(runtime.Object)
	exp = []string{"aa", "bb::dd:ee:"}
	if l := ob.Len().Int(); l != int64(len(exp)) {
		t.Errorf("expected split length of %d, got %d", len(exp), l)
	}
	for i, v := range exp {
		got := ob.Get(runtime.Number(i))
		if got.String() != v {
			t.Errorf("expected split index %d to be %s, got %s", i, v, got)
		}
	}
}

func TestStringsJoin(t *testing.T) {
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	parts := []string{"this", "is", "", "it!"}
	ob := runtime.NewObject()
	for i, v := range parts {
		ob.Set(runtime.Number(i), runtime.String(v))
	}
	ret := sm.strings_Join(ob)
	exp := "thisisit!"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
	ret = sm.strings_Join(ob, runtime.String("--"))
	exp = "this--is----it!"
	if ret.String() != exp {
		t.Errorf("expected %s, got %s", exp, ret)
	}
}

func TestStringsReplace(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  string
	}{
		0: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("th"),
			},
			exp: "is is e source",
		},
		1: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("th"),
				runtime.Number(1),
			},
			exp: "is is the source",
		},
		2: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("t"),
				runtime.String("T"),
			},
			exp: "This is The source",
		},
		3: {
			args: []runtime.Val{
				runtime.String("this is the source"),
				runtime.String("t"),
				runtime.String("T"),
				runtime.Number(1),
			},
			exp: "This is the source",
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Replace(c.args...)
		if ret.String() != c.exp {
			t.Errorf("[%d] - expected %s, got %s", i, c.exp, ret)
		}
	}
}

func TestStringsTrim(t *testing.T) {
	cases := []struct {
		args []runtime.Val
		exp  string
	}{
		0: {
			args: []runtime.Val{
				runtime.String(" "),
			},
			exp: "",
		},
		1: {
			args: []runtime.Val{
				runtime.String("\n  \t   hi \r"),
			},
			exp: "hi",
		},
		2: {
			args: []runtime.Val{
				runtime.String("xoxolovexox"),
				runtime.String("xo"),
			},
			exp: "love",
		},
	}
	ctx := runtime.NewCtx(nil, nil)
	sm := new(StringsMod)
	sm.SetCtx(ctx)
	for i, c := range cases {
		ret := sm.strings_Trim(c.args...)
		if ret.String() != c.exp {
			t.Errorf("[%d] - expected %s, got %s", i, c.exp, ret)
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>add better short desc for serve cmd<commit_after><|endoftext|>


<commit_before>package test

import (
	"fmt"
	"testing"
)

func linuxSuite(t *testing.T, img *dockerfile, total, after string) {
	if debug {
		fmt.Println("Linux start")
	}

	// List
	img.CertManage("list", "-count", "|", "grep", total)
	// Backup
	img.CertManage("backup")
	img.RunSplit(fmt.Sprintf("ls -1 /usr/share/ca-certificates/* | wc -l | grep %s", total))
	img.RunSplit(fmt.Sprintf("ls -1 /usr/share/ca-certificates.backup/* | wc -l | grep %s", total))
	// Whitelist
	img.CertManage("whitelist", "-file", "/whitelist.json")
	img.CertManage("list", "-count", "|", "grep", after)
	// Restore
	img.CertManage("restore")
	img.CertManage("list", "-count", "|", "grep", total)
	img.SuccessT(t)

	if debug {
		fmt.Println("Linux end")
	}
}

func javaSuite(t *testing.T, img *dockerfile, total, after string) {
	if debug {
		fmt.Println("Java start")
	}

	// List
	img.CertManage("list", "-count", "-app", "java", "|", "grep", total)
	// Backup
	img.CertManage("backup", "-app", "java")
	img.RunSplit("ls -1 ~/.cert-manage/java | wc -l | grep 1")
	// Check java
	img.RunSplit("cd / && java Download")
	// Whitelist java
	img.CertManage("whitelist", "-file", "/whitelist.json", "-app", "java")
	img.CertManage("list", "-app", "java", "-count", "|", "grep", after)
	// Verify google.com fails to load
	img.Run("cd", "/")
	img.ShouldFail("java", "Download", "2>&1", "|", "grep", `'PKIX path building failed'`)
	// Restore
	img.CertManage("restore", "-app", "java")
	img.CertManage("list", "-app", "java", "-count", "|", "grep", total)
	// Verify Restore
	img.RunSplit("cd / && java Download")
	img.SuccessT(t)

	if debug {
		fmt.Println("Java end")
	}
}
<commit_msg>test: err on empty total or after checks<commit_after>package test

import (
	"fmt"
	"testing"
)

func linuxSuite(t *testing.T, img *dockerfile, total, after string) {
	if total == "" || after == "" {
		t.Fatalf("total=%q or after=%q is blank", total, after)
	}
	if debug {
		fmt.Println("Linux start")
	}

	// List
	img.CertManage("list", "-count", "|", "grep", total)
	// Backup
	img.CertManage("backup")
	img.RunSplit(fmt.Sprintf("ls -1 /usr/share/ca-certificates/* | wc -l | grep %s", total))
	img.RunSplit(fmt.Sprintf("ls -1 /usr/share/ca-certificates.backup/* | wc -l | grep %s", total))
	// Whitelist
	img.CertManage("whitelist", "-file", "/whitelist.json")
	img.CertManage("list", "-count", "|", "grep", after)
	// Restore
	img.CertManage("restore")
	img.CertManage("list", "-count", "|", "grep", total)
	img.SuccessT(t)

	if debug {
		fmt.Println("Linux end")
	}
}

func javaSuite(t *testing.T, img *dockerfile, total, after string) {
	if total == "" || after == "" {
		t.Fatalf("total=%q or after=%q is blank", total, after)
	}
	if debug {
		fmt.Println("Java start")
	}

	// List
	img.CertManage("list", "-count", "-app", "java", "|", "grep", total)
	// Backup
	img.CertManage("backup", "-app", "java")
	img.RunSplit("ls -1 ~/.cert-manage/java | wc -l | grep 1")
	// Check java
	img.RunSplit("cd / && java Download")
	// Whitelist java
	img.CertManage("whitelist", "-file", "/whitelist.json", "-app", "java")
	img.CertManage("list", "-app", "java", "-count", "|", "grep", after)
	// Verify google.com fails to load
	img.Run("cd", "/")
	img.ShouldFail("java", "Download", "2>&1", "|", "grep", `'PKIX path building failed'`)
	// Restore
	img.CertManage("restore", "-app", "java")
	img.CertManage("list", "-app", "java", "-count", "|", "grep", total)
	// Verify Restore
	img.RunSplit("cd / && java Download")
	img.SuccessT(t)

	if debug {
		fmt.Println("Java end")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/boltdb/bolt"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

const testingDatabaseName = "test.db" // very original

// Client structure to be injected into functions to perform HTTP calls
type Client struct {
	HTTPClient *http.Client
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

// TestDB - holds connection to database during tests
var TestDB *bolt.DB

func testTools(code int, body string) (*httptest.Server, *DBClient) {

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, body)
	}))

	tr := &http.Transport{
		Proxy: func(req *http.Request) (*url.URL, error) {
			return url.Parse(server.URL)
		},
	}
	// creating random buckets for everyone!
	bucket := GetRandomName(10)
	cache := Cache{db: TestDB, requestsBucket: bucket}

	cfg := InitSettings()
	// preparing client
	dbClient := &DBClient{
		http:  &http.Client{Transport: tr},
		cache: cache,
		cfg:   cfg,
	}
	return server, dbClient
}

var src = rand.NewSource(time.Now().UnixNano())

// GetRandomName - provides random name for buckets. Each test case gets it's own bucket
func GetRandomName(n int) []byte {
	b := make([]byte, n)
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIdxBits
		remain--
	}

	return b
}

func setup() {
	db := getDB(testingDatabaseName)
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	TestDB.Close()
	os.Remove(testingDatabaseName)
}
<commit_msg>increasing log level to not display errors since we have tests that try to create errors..<commit_after>package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"reflect"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

const testingDatabaseName = "test.db" // very original

// Client structure to be injected into functions to perform HTTP calls
type Client struct {
	HTTPClient *http.Client
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

// TestDB - holds connection to database during tests
var TestDB *bolt.DB

func testTools(code int, body string) (*httptest.Server, *DBClient) {

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, body)
	}))

	tr := &http.Transport{
		Proxy: func(req *http.Request) (*url.URL, error) {
			return url.Parse(server.URL)
		},
	}
	// creating random buckets for everyone!
	bucket := GetRandomName(10)
	cache := Cache{db: TestDB, requestsBucket: bucket}

	cfg := InitSettings()
	// preparing client
	dbClient := &DBClient{
		http:  &http.Client{Transport: tr},
		cache: cache,
		cfg:   cfg,
	}
	return server, dbClient
}

var src = rand.NewSource(time.Now().UnixNano())

// GetRandomName - provides random name for buckets. Each test case gets it's own bucket
func GetRandomName(n int) []byte {
	b := make([]byte, n)
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIdxBits
		remain--
	}

	return b
}

func setup() {
	// we don't really want to see what's happening
	log.SetLevel(log.FatalLevel)
	db := getDB(testingDatabaseName)
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	TestDB.Close()
	os.Remove(testingDatabaseName)
}
<|endoftext|>


<commit_before>package gson

import "sync"

// MaxStringLen maximum length of string value insdie json document.
const MaxStringLen = 1024 * 1024

// MaxCollateLen maximum length of collated value.
const MaxCollateLen = 1024 * 1024

type memConfig struct {
	strlen  int // maximum length of string value inside JSON document
	numkeys int // maximum number of keys that a property object can have
	itemlen int // maximum length of collated value.
	ptrlen  int // maximum length of json-pointer can take
}

type mempools struct {
	prefixPool *sync.Pool // maximum length of json pointer
	stringPool *sync.Pool // scratch pad for string objects
	keysPool   *sync.Pool // property keys
	keypool    *sync.Pool
	codepool   *sync.Pool
}

func newMempool(strlen, numkeys, itemlen, jptrlen int) mempools {
	m := mempools{}
	m.prefixPool = &sync.Pool{
		New: func() interface{} { return make([]byte, 0, jptrlen) },
	}
	m.stringPool = &sync.Pool{
		New: func() interface{} { return make([]byte, strlen) },
	}
	m.keysPool = &sync.Pool{
		New: func() interface{} { return make([]string, 0, numkeys) },
	}
	m.keypool = &sync.Pool{
		New: func() interface{} { return make(kvrefs, numkeys) },
	}
	m.codepool = &sync.Pool{
		New: func() interface{} { return make([]byte, itemlen) },
	}
	return m
}
<commit_msg>cosmetics.<commit_after>package gson

import "sync"

// MaxStringLen maximum length of string value insdie json document.
const MaxStringLen = 1024 * 1024

// MaxCollateLen maximum length of collated value.
const MaxCollateLen = 1024 * 1024

type memConfig struct {
	strlen  int // maximum length of string value inside JSON document
	numkeys int // maximum number of keys that a property object can have
	itemlen int // maximum length of collated value.
	ptrlen  int // maximum length of json-pointer can take
}

type mempools struct {
	prefixPool *sync.Pool // for computing list of possible pointers
	stringPool *sync.Pool
	keysPool   *sync.Pool
	keypool    *sync.Pool
	codepool   *sync.Pool
}

func newMempool(strlen, numkeys, itemlen, jptrlen int) mempools {
	m := mempools{}
	m.prefixPool = &sync.Pool{
		New: func() interface{} { return make([]byte, 0, jptrlen) },
	}
	m.stringPool = &sync.Pool{
		New: func() interface{} { return make([]byte, strlen) },
	}
	m.keysPool = &sync.Pool{
		New: func() interface{} { return make([]string, 0, numkeys) },
	}
	m.keypool = &sync.Pool{
		New: func() interface{} { return make(kvrefs, numkeys) },
	}
	m.codepool = &sync.Pool{
		New: func() interface{} { return make([]byte, itemlen) },
	}
	return m
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/go-yaml/yaml"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
)

// PostsFolder is the default path for posts.
const PostsFolder = "posts"

// FileHeader is a structure to store post file header data.
type FileHeader struct {
	Title       string `yaml:"title"`
	Subtitle    string `yaml:"subtitle"`
	PublishDate string `yaml:"date"`
	Slug        string `yaml:"slug"`
}

// Post is a structure to store a posts title, subtitle, publishing date and content.
type Post struct {
	Title       string
	Subtitle    string
	PublishDate time.Time
	MDContent   string
	HTMLContent string
	Slug        string
}

// ByAge implements a interface to sort a slice of posts by publishing date.
type ByAge []Post

func (b ByAge) Len() int {
	return len(b)
}
func (b ByAge) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByAge) Less(i, j int) bool {
	return b[i].PublishDate.Unix() > b[j].PublishDate.Unix()
}

// BlogPosts is the slice of published posts.
var BlogPosts []Post

// Render generates HTML from a posts markdown content.
func (p *Post) Render() {
	output := blackfriday.MarkdownCommon([]byte(p.MDContent))
	p.HTMLContent = string(bluemonday.UGCPolicy().SanitizeBytes([]byte(output)))
}

// GetURL generates the absolute URL from /.
func (p Post) GetURL() string {
	rgx, _ := regexp.Compile("[^A-Za-z\\-]")
	slugged := rgx.ReplaceAllString(p.Slug, "")
	return PostBaseURL + "/" + strings.ToLower(slugged)
}

// NewPost creates a new post with a specified title, subtitle, publishing date and content.
func NewPost(title, subtitle string, date time.Time, content, slug string) Post {
	p := Post{title, subtitle, date, content, "", slug}
	p.Render()
	return p
}

func parsePostFile(file string) (Post, error) {
	// Open the post file
	input, openError := os.Open(path.Join(BlogFolder, PostsFolder, file))
	if openError != nil {
		return Post{}, openError
	}
	defer input.Close()

	// Scan all input lines
	inputScanner := bufio.NewScanner(input)
	inputScanner.Split(bufio.ScanLines)

	header, body, headerIndex := "", "", 0
	for inputScanner.Scan() {
		line := inputScanner.Text()
		switch headerIndex {
		case 0:
			if line == "---" {
				headerIndex = 1
			}
		case 1:
			if line == "---" {
				headerIndex = 2
			} else {
				header += line + "\n"
			}
		case 2:
			body += line + "\n"
		}
	}

	headerData := FileHeader{}

	// Decode JSON header
	yamlError := yaml.Unmarshal([]byte(header), &headerData)
	if yamlError != nil {
		return Post{}, yamlError
	}

	// parse Date from header data
	date, parseError := time.Parse("2006-Jan-02", headerData.PublishDate)
	if parseError != nil {
		return Post{}, parseError
	}

	if len(headerData.Slug) == 0 {
		headerData.Slug = strings.TrimSuffix(file, filepath.Ext(file))
	}

	return NewPost(headerData.Title, headerData.Subtitle, date, body, headerData.Slug), nil
}

// LoadPosts loads all published posts from the blog folder.
func LoadPosts() {
	BlogPosts = make([]Post, 0)
	dirEntries, readDirError := ioutil.ReadDir(path.Join(BlogFolder, PostsFolder))
	if readDirError != nil {
		Warning.Println("Failed to open post folder:", readDirError)
		return
	}
	for _, entry := range dirEntries {
		// Ignore directories
		if entry.IsDir() {
			continue
		}

		post, parseError := parsePostFile(entry.Name())
		if parseError != nil {
			Warning.Println("Failed to parse file:", parseError)
			continue
		}
		BlogPosts = append(BlogPosts, post)
		Trace.Println("Read post file", entry.Name())
	}

	// Sort all posts by age
	sort.Sort(ByAge(BlogPosts))
	Trace.Println("Serving", len(BlogPosts), "blog posts")
}

// GetLatestsPosts returns a slice of the latest posts.
func GetLatestsPosts(count int) []Post {
	size := len(BlogPosts)
	if size < count {
		return BlogPosts
	}
	return BlogPosts[:count]
}

// FindPost returns a post with the specified slug.
func FindPost(slug string) (*Post, error) {
	for _, p := range BlogPosts {
		if p.Slug == slug {
			return &p, nil
		}
	}
	return nil, errors.New("Post not found.")
}
<commit_msg>Add method for loading pages<commit_after>package main

import (
	"bufio"
	"errors"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/go-yaml/yaml"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
)

// PostsFolder is the default path for posts.
const (
	PostsFolder = "posts"
	PagesFolder = "pages"
)

// FileHeader is a structure to store post file header data.
type FileHeader struct {
	Title       string `yaml:"title"`
	Subtitle    string `yaml:"subtitle"`
	PublishDate string `yaml:"date"`
	Slug        string `yaml:"slug"`
}

// Post is a structure to store a posts title, subtitle, publishing date and content.
type Post struct {
	Title       string
	Subtitle    string
	PublishDate time.Time
	MDContent   string
	HTMLContent string
	Slug        string
}

// ByAge implements a interface to sort a slice of posts by publishing date.
type ByAge []Post

func (b ByAge) Len() int {
	return len(b)
}
func (b ByAge) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByAge) Less(i, j int) bool {
	return b[i].PublishDate.Unix() > b[j].PublishDate.Unix()
}

// BlogPosts is the slice of published posts.
var BlogPosts, BlogPages []Post

// Render generates HTML from a posts markdown content.
func (p *Post) Render() {
	output := blackfriday.MarkdownCommon([]byte(p.MDContent))
	p.HTMLContent = string(bluemonday.UGCPolicy().SanitizeBytes([]byte(output)))
}

// GetURL generates the absolute URL from /.
func (p Post) GetURL() string {
	rgx, _ := regexp.Compile("[^A-Za-z\\-]")
	slugged := rgx.ReplaceAllString(p.Slug, "")
	return PostBaseURL + "/" + strings.ToLower(slugged)
}

// NewPost creates a new post with a specified title, subtitle, publishing date and content.
func NewPost(title, subtitle string, date time.Time, content, slug string) Post {
	p := Post{title, subtitle, date, content, "", slug}
	p.Render()
	return p
}

func parsePostFile(folder, file string) (Post, error) {
	// Open the post file
	input, openError := os.Open(path.Join(BlogFolder, folder, file))
	if openError != nil {
		return Post{}, openError
	}
	defer input.Close()

	// Scan all input lines
	inputScanner := bufio.NewScanner(input)
	inputScanner.Split(bufio.ScanLines)

	header, body, headerIndex := "", "", 0
	for inputScanner.Scan() {
		line := inputScanner.Text()
		switch headerIndex {
		case 0:
			if line == "---" {
				headerIndex = 1
			}
		case 1:
			if line == "---" {
				headerIndex = 2
			} else {
				header += line + "\n"
			}
		case 2:
			body += line + "\n"
		}
	}

	headerData := FileHeader{}

	// Decode JSON header
	yamlError := yaml.Unmarshal([]byte(header), &headerData)
	if yamlError != nil {
		return Post{}, yamlError
	}

	// parse Date from header data
	date, parseError := time.Parse("2006-Jan-02", headerData.PublishDate)
	if parseError != nil {
		return Post{}, parseError
	}

	if len(headerData.Slug) == 0 {
		headerData.Slug = strings.TrimSuffix(file, filepath.Ext(file))
	}

	return NewPost(headerData.Title, headerData.Subtitle, date, body, headerData.Slug), nil
}

// LoadPosts loads all published posts from the blog folder.
func LoadPosts() {
	BlogPosts = make([]Post, 0)
	dirEntries, readDirError := ioutil.ReadDir(path.Join(BlogFolder, PostsFolder))
	if readDirError != nil {
		Warning.Println("Failed to open post folder:", readDirError)
		return
	}
	for _, entry := range dirEntries {
		// Ignore directories
		if entry.IsDir() {
			continue
		}

		post, parseError := parsePostFile(PostsFolder, entry.Name())
		if parseError != nil {
			Warning.Println("Failed to parse file:", parseError)
			continue
		}
		BlogPosts = append(BlogPosts, post)
		Trace.Println("Read post file", entry.Name())
	}

	// Sort all posts by age
	sort.Sort(ByAge(BlogPosts))
	Trace.Println("Serving", len(BlogPosts), "blog posts")
}

// LoadPages loads all pages from the pages/ folder.
func LoadPages() {
	BlogPages = make([]Post, 0)
	dirEntries, readDirError := ioutil.ReadDir(path.Join(BlogFolder, PagesFolder))
	if readDirError != nil {
		Warning.Println("Failed to open page folder:", readDirError)
		return
	}
	for _, entry := range dirEntries {
		// Ignore directories
		if entry.IsDir() {
			continue
		}

		page, parseError := parsePostFile(PagesFolder, entry.Name())
		if parseError != nil {
			Warning.Println("Failed to parse file:", parseError)
			continue
		}
		BlogPages = append(BlogPages, page)
		Trace.Println("Read page file", entry.Name())
	}

	// Sort all posts by age
	Trace.Println("Serving", len(BlogPages), "blog pages")
}

// GetLatestsPosts returns a slice of the latest posts.
func GetLatestsPosts(count int) []Post {
	size := len(BlogPosts)
	if size < count {
		return BlogPosts
	}
	return BlogPosts[:count]
}

// FindPost returns a post with the specified slug.
func FindPost(slug string) (*Post, error) {
	for _, p := range BlogPosts {
		if p.Slug == slug {
			return &p, nil
		}
	}
	return nil, errors.New("Post not found.")
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

import "testing"

func TestLiteralID(t *testing.T) {
	testcases := []struct {
		in  string
		id  int
		out string
	}{{
		in:  "`aa`",
		id:  ID,
		out: "aa",
	}, {
		in:  "```a```",
		id:  ID,
		out: "`a`",
	}, {
		in:  "`a``b`",
		id:  ID,
		out: "a`b",
	}, {
		in:  "`a``b`c",
		id:  ID,
		out: "a`b",
	}, {
		in:  "`a``b",
		id:  LEX_ERROR,
		out: "a`b",
	}, {
		in:  "`a``b``",
		id:  LEX_ERROR,
		out: "a`b`",
	}, {
		in:  "``",
		id:  LEX_ERROR,
		out: "",
	}}

	for _, tcase := range testcases {
		tkn := NewStringTokenizer(tcase.in)
		id, out := tkn.Scan()
		if tcase.id != id || string(out) != tcase.out {
			t.Errorf("Scan(%s): %d, %s, want %d, %s", tcase.in, id, out, tcase.id, tcase.out)
		}
	}
}
<commit_msg>Added multiple tests for scanString().<commit_after>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

import (
	"fmt"
	"testing"
)

func TestLiteralID(t *testing.T) {
	testcases := []struct {
		in  string
		id  int
		out string
	}{{
		in:  "`aa`",
		id:  ID,
		out: "aa",
	}, {
		in:  "```a```",
		id:  ID,
		out: "`a`",
	}, {
		in:  "`a``b`",
		id:  ID,
		out: "a`b",
	}, {
		in:  "`a``b`c",
		id:  ID,
		out: "a`b",
	}, {
		in:  "`a``b",
		id:  LEX_ERROR,
		out: "a`b",
	}, {
		in:  "`a``b``",
		id:  LEX_ERROR,
		out: "a`b`",
	}, {
		in:  "``",
		id:  LEX_ERROR,
		out: "",
	}}

	for _, tcase := range testcases {
		tkn := NewStringTokenizer(tcase.in)
		id, out := tkn.Scan()
		if tcase.id != id || string(out) != tcase.out {
			t.Errorf("Scan(%s): %d, %s, want %d, %s", tcase.in, id, out, tcase.id, tcase.out)
		}
	}
}

func tokenName(id int) string {
	if id == STRING {
		return "STRING"
	} else if id == LEX_ERROR {
		return "LEX_ERROR"
	}
	return fmt.Sprintf("%d", id)
}

func TestString(t *testing.T) {
	testcases := []struct {
		in   string
		id   int
		want string
	}{{
		in:   "''",
		id:   STRING,
		want: "",
	}, {
		in:   "''''",
		id:   STRING,
		want: "'",
	}, {
		in:   "'hello'",
		id:   STRING,
		want: "hello",
	}, {
		in:   "'\\n'",
		id:   STRING,
		want: "\n",
	}, {
		in:   "'\\nhello\\n'",
		id:   STRING,
		want: "\nhello\n",
	}, {
		in:   "'a''b'",
		id:   STRING,
		want: "a'b",
	}, {
		in:   "'a\\'b'",
		id:   STRING,
		want: "a'b",
	}, {
		in:   "'\\'",
		id:   LEX_ERROR,
		want: "'",
	}, {
		in:   "'",
		id:   LEX_ERROR,
		want: "",
	}, {
		in:   "'hello\\'",
		id:   LEX_ERROR,
		want: "hello'",
	}, {
		in:   "'hello",
		id:   LEX_ERROR,
		want: "hello",
	}, {
		in:   "'hello\\",
		id:   LEX_ERROR,
		want: "hello",
	}}

	for _, tcase := range testcases {
		id, got := NewStringTokenizer(tcase.in).Scan()
		if tcase.id != id || string(got) != tcase.want {
			t.Errorf("Scan(%q) = (%s, %q), want (%s, %q)", tcase.in, tokenName(id), got, tokenName(tcase.id), tcase.want)
		}
	}
}
<|endoftext|>


<commit_before>package psn

import (
	"bufio"
	"bytes"
	"os"
	"os/exec"
	"strings"
	"sync"

	"github.com/kr/pty"
)

// TopStream provides top command output stream.
type TopStream struct {
	cmd *exec.Cmd

	pmu sync.Mutex
	pt  *os.File

	// broadcast updates whenver available available
	wg                sync.WaitGroup
	rcond             *sync.Cond
	rmu               sync.RWMutex // protect results
	queue             []TopCommandRow
	pid2TopCommandRow map[int64]TopCommandRow
	err               error
	errc              chan error
}

// StartStream starts 'top' command stream.
func (cfg *TopConfig) StartStream() (*TopStream, error) {
	if err := cfg.createCmd(); err != nil {
		return nil, err
	}
	pt, err := pty.Start(cfg.cmd)
	if err != nil {
		return nil, err
	}

	str := &TopStream{
		cmd: cfg.cmd,

		pmu: sync.Mutex{},
		pt:  pt,

		wg:  sync.WaitGroup{},
		rmu: sync.RWMutex{},

		// pre-allocate
		queue:             make([]TopCommandRow, 0, 100),
		pid2TopCommandRow: make(map[int64]TopCommandRow, 500),
		err:               nil,
		errc:              make(chan error, 1),
	}
	str.rcond = sync.NewCond(&str.rmu)

	str.wg.Add(1)
	go str.enqueue()
	go str.dequeue()

	return str, nil
}

// ErrChan returns the error from stream.
func (str *TopStream) ErrChan() <-chan error {
	return str.errc
}

// feed new top results into the queue
func (str *TopStream) enqueue() {
	defer str.wg.Done()
	reader := bufio.NewReader(str.pt)
	for str.err == nil {
		// lock for pty
		str.pmu.Lock()
		data, _, lerr := reader.ReadLine()
		str.pmu.Unlock()

		data = bytes.TrimSpace(data)
		if topRowToSkip(data) {
			continue
		}
		line := string(data)

		// lock for results
		str.rmu.Lock()

		str.err = lerr
		if line == "" {
			str.rmu.Unlock()
			continue
		}

		row := strings.Fields(line)
		if len(row) != len(TopRowHeaders) {
			str.rmu.Unlock()
			continue
		}
		r, rerr := parseTopRow(row)
		if rerr != nil {
			str.err = rerr
		}

		str.queue = append(str.queue, r)
		if len(str.queue) == 1 {
			// we have a new output; signal!
			str.rcond.Signal()
		}
		str.rmu.Unlock()
	}

	// we got error; signal!
	str.rcond.Signal()
}

// dequeue polls from 'top' process.
// And signals error channel if any.
func (str *TopStream) dequeue() {
	str.rmu.Lock()
	for {
		// wait until there's output
		for len(str.queue) == 0 && str.err == nil {
			str.rcond.Wait()
		}

		// no output; should be error
		if len(str.queue) == 0 {
			break
		}

		row := str.queue[0]
		str.queue = str.queue[1:]

		str.pid2TopCommandRow[row.PID] = row
	}
	if expectedErr(str.err) {
		str.err = nil
	}
	str.rmu.Unlock()

	if str.err != nil {
		str.errc <- str.err
	}
}

func (str *TopStream) close(kill bool) (err error) {
	if str.cmd == nil {
		return str.err
	}
	if kill {
		str.cmd.Process.Kill()
	}

	err = str.cmd.Wait()

	str.pmu.Lock()
	str.pt.Close() // close file
	str.pmu.Unlock()

	str.wg.Wait()

	if err != nil {
		str.err = err
		if !kill && strings.Contains(err.Error(), "exit status") {
			str.err = nil // non-zero exit code
		} else if kill && expectedErr(err) {
			str.err = nil
		}
	}
	str.cmd = nil
	return str.err
}

func expectedErr(err error) bool {
	if err == nil {
		return true
	}
	es := err.Error()
	return strings.Contains(es, "signal:") || strings.Contains(es, "/dev/ptmx: input/output error")
}

// Stop kills the 'top' process and waits for it to exit.
func (str *TopStream) Stop() error {
	return str.close(true)
}

// Wait just waits for the 'top' process to exit.
func (str *TopStream) Wait() error {
	return str.close(false)
}

// Latest returns the latest top command outputs.
func (str *TopStream) Latest() map[int64]TopCommandRow {
	str.rmu.RLock()
	cm := make(map[int64]TopCommandRow, len(str.pid2TopCommandRow))
	for k, v := range str.pid2TopCommandRow {
		cm[k] = v
	}
	str.rmu.RUnlock()
	return cm
}
<commit_msg>top: fix races<commit_after>package psn

import (
	"bufio"
	"bytes"
	"os"
	"os/exec"
	"strings"
	"sync"

	"github.com/kr/pty"
)

// TopStream provides top command output stream.
type TopStream struct {
	cmd *exec.Cmd

	pmu sync.Mutex
	pt  *os.File

	// broadcast updates whenver available available
	wg                sync.WaitGroup
	rcond             *sync.Cond
	rmu               sync.RWMutex // protect results
	queue             []TopCommandRow
	pid2TopCommandRow map[int64]TopCommandRow
	err               error
	errc              chan error
}

// StartStream starts 'top' command stream.
func (cfg *TopConfig) StartStream() (*TopStream, error) {
	if err := cfg.createCmd(); err != nil {
		return nil, err
	}
	pt, err := pty.Start(cfg.cmd)
	if err != nil {
		return nil, err
	}

	str := &TopStream{
		cmd: cfg.cmd,

		pmu: sync.Mutex{},
		pt:  pt,

		wg:  sync.WaitGroup{},
		rmu: sync.RWMutex{},

		// pre-allocate
		queue:             make([]TopCommandRow, 0, 100),
		pid2TopCommandRow: make(map[int64]TopCommandRow, 500),
		err:               nil,
		errc:              make(chan error, 1),
	}
	str.rcond = sync.NewCond(&str.rmu)

	str.wg.Add(1)
	go str.enqueue()
	go str.dequeue()

	return str, nil
}

// ErrChan returns the error from stream.
func (str *TopStream) ErrChan() <-chan error {
	return str.errc
}

// feed new top results into the queue
func (str *TopStream) enqueue() {
	defer str.wg.Done()
	reader := bufio.NewReader(str.pt)
	for str.err == nil {
		// lock for pty
		str.pmu.Lock()
		data, _, lerr := reader.ReadLine()
		str.pmu.Unlock()

		data = bytes.TrimSpace(data)
		if topRowToSkip(data) {
			continue
		}
		line := string(data)

		// lock for results
		str.rmu.Lock()

		str.err = lerr
		if line == "" {
			str.rmu.Unlock()
			continue
		}

		row := strings.Fields(line)
		if len(row) != len(TopRowHeaders) {
			str.rmu.Unlock()
			continue
		}
		r, rerr := parseTopRow(row)
		if rerr != nil {
			str.err = rerr
		}

		str.queue = append(str.queue, r)
		if len(str.queue) == 1 {
			// we have a new output; signal!
			str.rcond.Signal()
		}
		str.rmu.Unlock()
	}

	// we got error; signal!
	str.rcond.Signal()
}

// dequeue polls from 'top' process.
// And signals error channel if any.
func (str *TopStream) dequeue() {
	str.rmu.Lock()
	for {
		// wait until there's output
		for len(str.queue) == 0 && str.err == nil {
			str.rcond.Wait()
		}

		// no output; should be error
		if len(str.queue) == 0 {
			break
		}

		row := str.queue[0]
		str.queue = str.queue[1:]

		str.pid2TopCommandRow[row.PID] = row
	}
	if expectedErr(str.err) {
		str.err = nil
	}
	if str.err != nil {
		str.errc <- str.err
	}
	str.rmu.Unlock()
}

func (str *TopStream) close(kill bool) (err error) {
	if str.cmd == nil {
		return str.err
	}
	if kill {
		str.cmd.Process.Kill()
	}

	err = str.cmd.Wait()

	str.pmu.Lock()
	str.pt.Close() // close file
	str.pmu.Unlock()

	str.wg.Wait()

	if err != nil {
		if !kill && strings.Contains(err.Error(), "exit status") {
			err = nil // non-zero exit code
		} else if kill && expectedErr(err) {
			err = nil
		}
	}
	str.cmd = nil
	return err
}

func expectedErr(err error) bool {
	if err == nil {
		return true
	}
	es := err.Error()
	return strings.Contains(es, "signal:") || strings.Contains(es, "/dev/ptmx: input/output error")
}

// Stop kills the 'top' process and waits for it to exit.
func (str *TopStream) Stop() error {
	return str.close(true)
}

// Wait just waits for the 'top' process to exit.
func (str *TopStream) Wait() error {
	return str.close(false)
}

// Latest returns the latest top command outputs.
func (str *TopStream) Latest() map[int64]TopCommandRow {
	str.rmu.RLock()
	cm := make(map[int64]TopCommandRow, len(str.pid2TopCommandRow))
	for k, v := range str.pid2TopCommandRow {
		cm[k] = v
	}
	str.rmu.RUnlock()
	return cm
}
<|endoftext|>


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

// Parasitic Traceroute API -  Forward/Reverse TCP traceroute API which uses Linux Netfilter Queue
package trace

import (
	"code.google.com/p/gopacket"
	"code.google.com/p/gopacket/layers"
	"encoding/binary"
	"sync"
)

type TcpIpFlow struct {
	ipFlow  gopacket.Flow
	tcpFlow gopacket.Flow
}

func NewTcpIpFlowFromLayers(ipLayer layers.IPv4, tcpLayer layers.TCP) TcpIpFlow {
	return TcpIpFlow{
		ipFlow:  ipLayer.NetworkFlow(),
		tcpFlow: tcpLayer.TransportFlow(),
	}
}

func NewTcpIpFlowFromFlows(ipFlow gopacket.Flow, tcpFlow gopacket.Flow) TcpIpFlow {
	// XXX todo: check that the flow types are correct
	return TcpIpFlow{
		ipFlow:  ipFlow,
		tcpFlow: tcpFlow,
	}
}

// getPacketFlow returns a tcp/ip flowKey
// given a byte array packet
func NewTcpIpFlowFromPacket(packet []byte) TcpIpFlow {
	var ip layers.IPv4
	var tcp layers.TCP
	decoded := []gopacket.LayerType{}
	parser := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv4, &ip, &tcp)
	err := parser.DecodeLayers(packet, &decoded)
	if err != nil {
		// XXX last 64 bits... we only use the last 32 bits
		tcpHead := packet[len(packet)-8 : len(packet)]
		tcpFlow := GetTCPFlowFromTCPHead(tcpHead)
		return TcpIpFlow{
			ipFlow:  ip.NetworkFlow(),
			tcpFlow: tcpFlow,
		}
	}
	return TcpIpFlow{
		ipFlow:  ip.NetworkFlow(),
		tcpFlow: tcp.TransportFlow(),
	}
}

func (t *TcpIpFlow) Layers() (gopacket.Flow, gopacket.Flow) {
	return t.ipFlow, t.tcpFlow
}

// TcpBidirectionalFlowKey struct can be used as a hashmap key.
// Bidirectional in this case means that each of these keys
// for each TCP connection can be represented by two TcpFlowKey`s
type TcpBidirectionalFlowKey struct {
	flow TcpIpFlow
}

// NewTcpBidirectionalFlowKeyFromTcpIpFlow takes a TcpIpFlow argument
// and returns a TcpBidirectionalFlowKey
// XXX can we please have short names for things? What should we rename it to?
func NewTcpBidirectionalFlowKeyFromTcpIpFlow(tcpipFlow TcpIpFlow) TcpBidirectionalFlowKey {
	var tcpSrc, tcpDst, ipSrcEnd, ipDstEnd gopacket.Endpoint

	ipflow, tcpflow := tcpipFlow.Layers()
	srcIP, dstIP := ipflow.Endpoints()
	if srcIP.LessThan(dstIP) {
		ipSrcEnd = srcIP
		ipDstEnd = dstIP
	} else {
		ipSrcEnd = dstIP
		ipDstEnd = srcIP
	}
	ipFlow, _ := gopacket.FlowFromEndpoints(ipSrcEnd, ipDstEnd)

	srcPortEnd, dstPortEnd := tcpflow.Endpoints()
	if srcPortEnd.LessThan(dstPortEnd) {
		tcpSrc = srcPortEnd
		tcpDst = dstPortEnd
	} else {
		tcpSrc = dstPortEnd
		tcpDst = srcPortEnd
	}
	tcpFlow, _ := gopacket.FlowFromEndpoints(tcpSrc, tcpDst)

	return TcpBidirectionalFlowKey{
		flow: TcpIpFlow{
			ipFlow:  ipFlow,
			tcpFlow: tcpFlow,
		},
	}
}

// Get method is probably not useful. XXX
func (f *TcpBidirectionalFlowKey) Get() TcpIpFlow {
	return f.flow
}

// FlowTracker struct is a concurrent-safe hashmap of tcp/ip-flowKeys to NFQueueTraceroute`s
type FlowTracker struct {
	lock          *sync.RWMutex
	flowMap       map[TcpIpFlow]*NFQueueTraceroute
	connectionMap map[TcpBidirectionalFlowKey]*NFQueueTraceroute
}

// NewFlowTracker returns a new FlowTracker struct
func NewFlowTracker() *FlowTracker {
	return &FlowTracker{
		lock:          new(sync.RWMutex),
		flowMap:       make(map[TcpIpFlow]*NFQueueTraceroute),
		connectionMap: make(map[TcpBidirectionalFlowKey]*NFQueueTraceroute),
	}
}

// HasFlow returns true if the specified flowKey is
// a key in our flowMap hashmap.
func (f *FlowTracker) HasFlow(flow TcpIpFlow) bool {
	f.lock.RLock()
	defer f.lock.RUnlock()
	_, ok := f.flowMap[flow]
	return ok
}

// HasConnection returns true if the specified TcpBidirectionalFlowKey
// is a key in our connectionMap hashmap.
func (f *FlowTracker) HasConnection(biflow TcpBidirectionalFlowKey) bool {
	f.lock.RLock()
	defer f.lock.RUnlock()
	_, ok := f.connectionMap[biflow]
	return ok
}

// GetConnectionTrace returns the NFQueueTraceroute struct pointer associated with
// a specified TcpBidirectionalFlowKey
func (f *FlowTracker) GetConnectionTrace(flow TcpBidirectionalFlowKey) *NFQueueTraceroute {
	return f.connectionMap[flow]
}

// AddFlow adds a NFQueueTraceroute struct pointer to our bookeeping hashmaps
//XXX needs some cleanup?
func (f *FlowTracker) AddFlow(flow TcpIpFlow, nfqTrace *NFQueueTraceroute) {
	f.lock.Lock()
	defer f.lock.Unlock()
	f.flowMap[flow] = nfqTrace
	f.connectionMap[NewTcpBidirectionalFlowKeyFromTcpIpFlow(flow)] = nfqTrace
}

// Delete removes the hashmap keys of the item.
// We have two hashmaps; one for flows and one
// for connections (bidirectional flows)...
func (f *FlowTracker) Delete(flow TcpIpFlow) {
	f.lock.Lock()
	defer f.lock.Unlock()
	delete(f.flowMap, flow)
	connFlow := NewTcpBidirectionalFlowKeyFromTcpIpFlow(flow)
	delete(f.connectionMap, connFlow)
}

// GetFlowTrace returns a NFQueueTraceroute struct pointer
// given a flowKey
//func (f *FlowTracker) GetFlowTrace(flow TcpIpFlow) *NFQueueTraceroute {
func (f *FlowTracker) GetFlow(flow TcpIpFlow) *NFQueueTraceroute {
	f.lock.RLock()
	defer f.lock.RUnlock()
	ret := f.flowMap[flow]
	return ret
}

// GetTCPFlowFromTCPHead is used to deal with rfc792 implementations where
// the original outbound packet is NOT sent back via ICMP payload but
// instead 64 bits of the original packet are sent.
// https://tools.ietf.org/html/rfc792
// Returns assumes TCP and returns a gopacket.Flow.
// XXX obviously the 64 bits could be from a UDP packet or something else
// however this is *good-enough* for NFQueue TCP traceroute!
// XXX should perhaps look at the protocol number specified in the IP header
// and set it's type here? I don't have a use-case for that right now.
func GetTCPFlowFromTCPHead(data []byte) gopacket.Flow {
	var srcPort, dstPort layers.TCPPort
	srcPort = layers.TCPPort(binary.BigEndian.Uint16(data[0:2]))
	dstPort = layers.TCPPort(binary.BigEndian.Uint16(data[2:4]))
	// XXX convert to tcp/ip flow
	tcpSrc := layers.NewTCPPortEndpoint(srcPort)
	tcpDst := layers.NewTCPPortEndpoint(dstPort)
	tcpFlow, _ := gopacket.FlowFromEndpoints(tcpSrc, tcpDst)
	// error (^ _) is only non-nil if the two endpoint types don't match
	return tcpFlow
}

// SerializeWithTTL takes a gopacket.Packet (containing a TCP/IP layers) and a TTL
// and returns a byte array of the serialized packet with the specified TTL
func SerializeWithTTL(p gopacket.Packet, ttl uint8) []byte {
	ipLayer := p.Layer(layers.LayerTypeIPv4)
	if ipLayer == nil {
		return nil
	}
	tcpLayer := p.Layer(layers.LayerTypeTCP)
	if tcpLayer == nil {
		return nil
	}
	ip, _ := ipLayer.(*layers.IPv4)
	ip.TTL = ttl
	tcp, _ := tcpLayer.(*layers.TCP)
	opts := gopacket.SerializeOptions{
		FixLengths:       true,
		ComputeChecksums: true,
	}
	tcp.SetNetworkLayerForChecksum(ip)
	rawPacketBuf := gopacket.NewSerializeBuffer()
	if err := gopacket.SerializeLayers(rawPacketBuf, opts, ip, tcp); err != nil {
		return nil
	}
	return rawPacketBuf.Bytes()
}
<commit_msg>Add docstring comments to TcpIpFlow methods<commit_after>/*
 * flow.go - Parasitic Traceroute flow tracking api
 * Copyright (c) 2014 David Anthony Stainton
 *
 * The MIT License (MIT)
 * Copyright (c) 2014 David Anthony Stainton
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

// Parasitic Traceroute API -  Forward/Reverse TCP traceroute API which uses Linux Netfilter Queue
package trace

import (
	"code.google.com/p/gopacket"
	"code.google.com/p/gopacket/layers"
	"encoding/binary"
	"sync"
)

// TcpIpFlow is used for tracking unidirectional TCP flows
type TcpIpFlow struct {
	ipFlow  gopacket.Flow
	tcpFlow gopacket.Flow
}

// NewTcpIpFlowFromLayers given IPv4 and TCP layers it returns a TcpIpFlow
func NewTcpIpFlowFromLayers(ipLayer layers.IPv4, tcpLayer layers.TCP) TcpIpFlow {
	return TcpIpFlow{
		ipFlow:  ipLayer.NetworkFlow(),
		tcpFlow: tcpLayer.TransportFlow(),
	}
}

// NewTcpIpFlowFromFlows given an IP flow and TCP flow returns a TcpIpFlow
func NewTcpIpFlowFromFlows(ipFlow gopacket.Flow, tcpFlow gopacket.Flow) TcpIpFlow {
	// XXX todo: check that the flow types are correct
	return TcpIpFlow{
		ipFlow:  ipFlow,
		tcpFlow: tcpFlow,
	}
}

// getPacketFlow returns a tcp/ip flowKey
// given a byte array packet
func NewTcpIpFlowFromPacket(packet []byte) TcpIpFlow {
	var ip layers.IPv4
	var tcp layers.TCP
	decoded := []gopacket.LayerType{}
	parser := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv4, &ip, &tcp)
	err := parser.DecodeLayers(packet, &decoded)
	if err != nil {
		// XXX last 64 bits... we only use the last 32 bits
		tcpHead := packet[len(packet)-8 : len(packet)]
		tcpFlow := GetTCPFlowFromTCPHead(tcpHead)
		return TcpIpFlow{
			ipFlow:  ip.NetworkFlow(),
			tcpFlow: tcpFlow,
		}
	}
	return TcpIpFlow{
		ipFlow:  ip.NetworkFlow(),
		tcpFlow: tcp.TransportFlow(),
	}
}

// Layers returns the component flow structs IPv4, TCP
func (t *TcpIpFlow) Layers() (gopacket.Flow, gopacket.Flow) {
	return t.ipFlow, t.tcpFlow
}

// TcpBidirectionalFlowKey struct can be used as a hashmap key.
// Bidirectional in this case means that each of these keys
// for each TCP connection can be represented by two TcpFlowKey`s
type TcpBidirectionalFlowKey struct {
	flow TcpIpFlow
}

// NewTcpBidirectionalFlowKeyFromTcpIpFlow takes a TcpIpFlow argument
// and returns a TcpBidirectionalFlowKey
// XXX can we please have short names for things? What should we rename it to?
func NewTcpBidirectionalFlowKeyFromTcpIpFlow(tcpipFlow TcpIpFlow) TcpBidirectionalFlowKey {
	var tcpSrc, tcpDst, ipSrcEnd, ipDstEnd gopacket.Endpoint

	ipflow, tcpflow := tcpipFlow.Layers()
	srcIP, dstIP := ipflow.Endpoints()
	if srcIP.LessThan(dstIP) {
		ipSrcEnd = srcIP
		ipDstEnd = dstIP
	} else {
		ipSrcEnd = dstIP
		ipDstEnd = srcIP
	}
	ipFlow, _ := gopacket.FlowFromEndpoints(ipSrcEnd, ipDstEnd)

	srcPortEnd, dstPortEnd := tcpflow.Endpoints()
	if srcPortEnd.LessThan(dstPortEnd) {
		tcpSrc = srcPortEnd
		tcpDst = dstPortEnd
	} else {
		tcpSrc = dstPortEnd
		tcpDst = srcPortEnd
	}
	tcpFlow, _ := gopacket.FlowFromEndpoints(tcpSrc, tcpDst)

	return TcpBidirectionalFlowKey{
		flow: TcpIpFlow{
			ipFlow:  ipFlow,
			tcpFlow: tcpFlow,
		},
	}
}

// Get method is probably not useful. XXX
func (f *TcpBidirectionalFlowKey) Get() TcpIpFlow {
	return f.flow
}

// FlowTracker struct is a concurrent-safe hashmap of tcp/ip-flowKeys to NFQueueTraceroute`s
type FlowTracker struct {
	lock          *sync.RWMutex
	flowMap       map[TcpIpFlow]*NFQueueTraceroute
	connectionMap map[TcpBidirectionalFlowKey]*NFQueueTraceroute
}

// NewFlowTracker returns a new FlowTracker struct
func NewFlowTracker() *FlowTracker {
	return &FlowTracker{
		lock:          new(sync.RWMutex),
		flowMap:       make(map[TcpIpFlow]*NFQueueTraceroute),
		connectionMap: make(map[TcpBidirectionalFlowKey]*NFQueueTraceroute),
	}
}

// HasFlow returns true if the specified flowKey is
// a key in our flowMap hashmap.
func (f *FlowTracker) HasFlow(flow TcpIpFlow) bool {
	f.lock.RLock()
	defer f.lock.RUnlock()
	_, ok := f.flowMap[flow]
	return ok
}

// HasConnection returns true if the specified TcpBidirectionalFlowKey
// is a key in our connectionMap hashmap.
func (f *FlowTracker) HasConnection(biflow TcpBidirectionalFlowKey) bool {
	f.lock.RLock()
	defer f.lock.RUnlock()
	_, ok := f.connectionMap[biflow]
	return ok
}

// GetConnectionTrace returns the NFQueueTraceroute struct pointer associated with
// a specified TcpBidirectionalFlowKey
func (f *FlowTracker) GetConnectionTrace(flow TcpBidirectionalFlowKey) *NFQueueTraceroute {
	return f.connectionMap[flow]
}

// AddFlow adds a NFQueueTraceroute struct pointer to our bookeeping hashmaps
//XXX needs some cleanup?
func (f *FlowTracker) AddFlow(flow TcpIpFlow, nfqTrace *NFQueueTraceroute) {
	f.lock.Lock()
	defer f.lock.Unlock()
	f.flowMap[flow] = nfqTrace
	f.connectionMap[NewTcpBidirectionalFlowKeyFromTcpIpFlow(flow)] = nfqTrace
}

// Delete removes the hashmap keys of the item.
// We have two hashmaps; one for flows and one
// for connections (bidirectional flows)...
func (f *FlowTracker) Delete(flow TcpIpFlow) {
	f.lock.Lock()
	defer f.lock.Unlock()
	delete(f.flowMap, flow)
	connFlow := NewTcpBidirectionalFlowKeyFromTcpIpFlow(flow)
	delete(f.connectionMap, connFlow)
}

// GetFlowTrace returns a NFQueueTraceroute struct pointer
// given a flowKey
//func (f *FlowTracker) GetFlowTrace(flow TcpIpFlow) *NFQueueTraceroute {
func (f *FlowTracker) GetFlow(flow TcpIpFlow) *NFQueueTraceroute {
	f.lock.RLock()
	defer f.lock.RUnlock()
	ret := f.flowMap[flow]
	return ret
}

// GetTCPFlowFromTCPHead is used to deal with rfc792 implementations where
// the original outbound packet is NOT sent back via ICMP payload but
// instead 64 bits of the original packet are sent.
// https://tools.ietf.org/html/rfc792
// Returns assumes TCP and returns a gopacket.Flow.
// XXX obviously the 64 bits could be from a UDP packet or something else
// however this is *good-enough* for NFQueue TCP traceroute!
// XXX should perhaps look at the protocol number specified in the IP header
// and set it's type here? I don't have a use-case for that right now.
func GetTCPFlowFromTCPHead(data []byte) gopacket.Flow {
	var srcPort, dstPort layers.TCPPort
	srcPort = layers.TCPPort(binary.BigEndian.Uint16(data[0:2]))
	dstPort = layers.TCPPort(binary.BigEndian.Uint16(data[2:4]))
	// XXX convert to tcp/ip flow
	tcpSrc := layers.NewTCPPortEndpoint(srcPort)
	tcpDst := layers.NewTCPPortEndpoint(dstPort)
	tcpFlow, _ := gopacket.FlowFromEndpoints(tcpSrc, tcpDst)
	// error (^ _) is only non-nil if the two endpoint types don't match
	return tcpFlow
}

// SerializeWithTTL takes a gopacket.Packet (containing a TCP/IP layers) and a TTL
// and returns a byte array of the serialized packet with the specified TTL
func SerializeWithTTL(p gopacket.Packet, ttl uint8) []byte {
	ipLayer := p.Layer(layers.LayerTypeIPv4)
	if ipLayer == nil {
		return nil
	}
	tcpLayer := p.Layer(layers.LayerTypeTCP)
	if tcpLayer == nil {
		return nil
	}
	ip, _ := ipLayer.(*layers.IPv4)
	ip.TTL = ttl
	tcp, _ := tcpLayer.(*layers.TCP)
	opts := gopacket.SerializeOptions{
		FixLengths:       true,
		ComputeChecksums: true,
	}
	tcp.SetNetworkLayerForChecksum(ip)
	rawPacketBuf := gopacket.NewSerializeBuffer()
	if err := gopacket.SerializeLayers(rawPacketBuf, opts, ip, tcp); err != nil {
		return nil
	}
	return rawPacketBuf.Bytes()
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

package echo

import (
	"errors"
	"fmt"
	"strings"
)

// T 标记为多语言文本
func T(format string, args ...interface{}) string {
	if len(args) > 0 {
		return fmt.Sprintf(format, args...)
	}
	return format
}

func E(format string, args ...interface{}) error {
	if len(args) > 0 {
		return fmt.Errorf(format, args...)
	}
	return errors.New(format)
}

type Translator interface {
	T(format string, args ...interface{}) string
	E(format string, args ...interface{}) error
	Lang() LangCode
}

type LangCode interface {
	String() string    // all uppercase characters
	Normalize() string // lowercase(language)-uppercase(region)
	Format(regionUppercase bool, separator ...string) string
	Language() string
	Region(regionUppercase bool) string
}

func NewLangCode(language string, separator ...string) LangCode {
	l := langCode{}
	sep := `-`
	if len(separator) > 0 && len(separator[0]) > 0 {
		sep = separator[0]
	}
	lg := strings.SplitN(language, sep, 2)
	switch len(lg) {
	case 2:
		l.regionLowercase = strings.ToLower(lg[1])
		l.region = strings.ToUpper(lg[1])
		fallthrough
	case 1:
		l.language = strings.ToLower(lg[0])
	}
	return l
}

type langCode struct {
	language        string
	region          string
	regionLowercase string
}

func (l langCode) String() string {
	if len(l.regionLowercase) > 0 {
		return l.language + `-` + l.regionLowercase
	}
	return l.language
}

func (l langCode) Normalize() string {
	if len(l.region) > 0 {
		return l.language + `-` + l.region
	}
	return l.language
}

func (l langCode) Language() string {
	return l.language
}

func (l langCode) Region(regionUppercase bool) string {
	if regionUppercase {
		return l.region
	}
	return l.regionLowercase
}

func (l langCode) Format(regionUppercase bool, separator ...string) string {
	var region string
	if regionUppercase {
		region = l.region
	} else {
		region = l.regionLowercase
	}
	if len(region) > 0 {
		if len(separator) > 0 {
			return l.language + separator[0] + region
		}
		return l.language + `-` + region
	}
	return l.language
}

var DefaultNopTranslate Translator = &NopTranslate{
	code: langCode{
		language: `en`,
	},
}

type NopTranslate struct {
	code LangCode
}

func (n *NopTranslate) T(format string, args ...interface{}) string {
	return T(format, args...)
}

func (n *NopTranslate) E(format string, args ...interface{}) error {
	return E(format, args...)
}

func (n *NopTranslate) Lang() LangCode {
	return n.code
}
<commit_msg>fix:comment<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

package echo

import (
	"errors"
	"fmt"
	"strings"
)

// T 标记为多语言文本
func T(format string, args ...interface{}) string {
	if len(args) > 0 {
		return fmt.Sprintf(format, args...)
	}
	return format
}

func E(format string, args ...interface{}) error {
	if len(args) > 0 {
		return fmt.Errorf(format, args...)
	}
	return errors.New(format)
}

type Translator interface {
	T(format string, args ...interface{}) string
	E(format string, args ...interface{}) error
	Lang() LangCode
}

type LangCode interface {
	String() string    // all lowercase characters
	Normalize() string // lowercase(language)-uppercase(region)
	Format(regionUppercase bool, separator ...string) string
	Language() string
	Region(regionUppercase bool) string
}

func NewLangCode(language string, separator ...string) LangCode {
	l := langCode{}
	sep := `-`
	if len(separator) > 0 && len(separator[0]) > 0 {
		sep = separator[0]
	}
	lg := strings.SplitN(language, sep, 2)
	switch len(lg) {
	case 2:
		l.regionLowercase = strings.ToLower(lg[1])
		l.region = strings.ToUpper(lg[1])
		fallthrough
	case 1:
		l.language = strings.ToLower(lg[0])
	}
	return l
}

type langCode struct {
	language        string
	region          string
	regionLowercase string
}

func (l langCode) String() string {
	if len(l.regionLowercase) > 0 {
		return l.language + `-` + l.regionLowercase
	}
	return l.language
}

func (l langCode) Normalize() string {
	if len(l.region) > 0 {
		return l.language + `-` + l.region
	}
	return l.language
}

func (l langCode) Language() string {
	return l.language
}

func (l langCode) Region(regionUppercase bool) string {
	if regionUppercase {
		return l.region
	}
	return l.regionLowercase
}

func (l langCode) Format(regionUppercase bool, separator ...string) string {
	var region string
	if regionUppercase {
		region = l.region
	} else {
		region = l.regionLowercase
	}
	if len(region) > 0 {
		if len(separator) > 0 {
			return l.language + separator[0] + region
		}
		return l.language + `-` + region
	}
	return l.language
}

var DefaultNopTranslate Translator = &NopTranslate{
	code: langCode{
		language: `en`,
	},
}

type NopTranslate struct {
	code LangCode
}

func (n *NopTranslate) T(format string, args ...interface{}) string {
	return T(format, args...)
}

func (n *NopTranslate) E(format string, args ...interface{}) error {
	return E(format, args...)
}

func (n *NopTranslate) Lang() LangCode {
	return n.code
}
<|endoftext|>