<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/jzipfler/htw-ava/filehandler"
	"github.com/jzipfler/htw-ava/server"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"text/tabwriter"
)

const (
	ERROR_HEADER            = "------------------ERROR------------------"
	ERROR_FOOTER            = "^^^^^^^^^^^^^^^^^^ERROR^^^^^^^^^^^^^^^^^^"
	MENU_SEPERATOR          = "-----------------------------------------"
	CONTROL_TYPE_INIT       = 1
	CONTROL_TYPE_EXIT       = 2
	CONTROLLER_MENU_NOTHING = 0
)

var (
	loggingBuffer bytes.Buffer
	logger        *log.Logger
	loggingPrefix string
	logFile       string
	id            int
	ipAddress     string
	port          int
	nodeListFile  string
	isController  bool
	allNodes      map[int]server.NetworkServer
	neighbors     map[int]server.NetworkServer
	thisNode      server.NetworkServer
)

// The ini function is called before the main function is started.
// It is used to do some stuff that should be done before the rest
// of the application starts and on what they may depend on.
func init() {
	flag.StringVar(&nodeListFile, "nodeList", "path/to/nodeList.txt", "A file where nodes are defined as follows: \"ID IP_ADDR:PORT\"")
	flag.IntVar(&id, "id", 1, "The if of the actual starting node.")
	flag.StringVar(&ipAddress, "ipAddress", "127.0.0.1", "The ip address of the actual starting node.")
	flag.IntVar(&port, "port", 15108, "The port of the actual starting node.")
	flag.StringVar(&loggingPrefix, "loggingPrefix", "LOGGING --> ", "This can be used to define which prefix the logger should use to print his messages.")
	flag.StringVar(&logFile, "logFile", "path/to/logfile.txt", "This parameter can be used to print the logging output to the given file.")
	flag.BoolVar(&isController, "isController", false, "Tell the node if he should act as controller or as independent node.")
}

// The main function is used when the programm is called / executed.
func main() {
	if len(os.Args) == 1 {
		flag.Usage()
		os.Exit(0)
	}
	flag.Parse()

	if nodeListFile == "path/to/nodeList.txt" {
		log.Fatalf("The nodeListFile is required.\n%s\n", ERROR_FOOTER)
	}
	var readFromNodeListError error
	allNodes, readFromNodeListError = filehandler.CollectAllFromNodeListFile(nodeListFile)
	if readFromNodeListError != nil {
		log.Fatalf("%s\n%s\n", readFromNodeListError.Error(), ERROR_FOOTER)
	} else {
		if len(allNodes) == 1 {
			log.Fatalf("There is only one node in the nodeList. Ther must be at least 2.\n%s\n", ERROR_FOOTER)
		}
	}

	go signalHandler() // Handle CTRL-C signals
	initializeLogger(loggingPrefix)

	if isController {
		startController()
	} else {
		startIndependentNode()
	}
}

func startIndependentNode() {
	quit := false
	printMessage("Start current instance as independent node.")
	thisNode = allNodes[id]
	printMessage("This node has the folowing settings: ")
	printMessage(thisNode)

	for !quit {
		quit = shouldRestartProgram()
	}
}

// The controller is used to control the independent nodes.
// He can initialize or shutdown the nodes.
func startController() {
	quit := false
	printMessage("Start current instance as controller.")

	for !quit {
		var input string
		printMessage("Printing main menu.")
		printMainMenu()
		fmt.Print("\nEnter ID of the node you would like to send a message.\nInput: ")
		_, err := fmt.Scanln(&input)
		if err != nil {
			printMessage(fmt.Sprintf("Error while reading the input. Quit program...\n%s\n", ERROR_FOOTER))
			os.Exit(1)
		}
		targetId, err := strconv.Atoi(input)
		if err != nil {
			printMessage(fmt.Sprintf("No number read. You have to enter the id of the node where you want to sent a message.\n%s\n", ERROR_FOOTER))
			continue
		}
		if targetId == CONTROLLER_MENU_NOTHING {
			quit = askForProgramRestart()
			continue
		}
		printControlMessageActionMenu()
		fmt.Print("\nEnter value of the control action you would like to sent.\nInput: ")
		_, err = fmt.Scanln(&input)
		if err != nil {
			printMessage(fmt.Sprintf("Error while reading the input. Quit program...\n%s\n", ERROR_FOOTER))
			os.Exit(1)
		}
		controlAction, err := strconv.Atoi(input)
		if controlAction == CONTROLLER_MENU_NOTHING {
			quit = askForProgramRestart()
			continue
		}
		quit = askForProgramRestart()
	}
}

// The chooseThreeNeighbors function uses the allNodes map to return
// another map that contains 3 nodes at the most.
// It calls os.Exit(1) if only one node is available in the allNodes map.
func chooseThreeNeighbors() (neighbors map[int]server.NetworkServer) {
	neighbors = make(map[int]server.NetworkServer, 3)
	// If there are only 1, 2 or 3 possible neighbors...take them.
	switch len(allNodes) {
	case 1:
		printMessage(fmt.Sprintf("There is only one node in the nodeList. Ther must be at least 2.\n%s\n", ERROR_FOOTER))
		os.Exit(1)
	case 2, 3, 4:
		for key, value := range allNodes {
			if key != id {
				neighbors[key] = value
			}
		}
		return
	}
	randomObject := rand.New(rand.NewSource(time.Now().UnixNano()))
	for len(neighbors) != 3 {
		var randomNumber int
		randomNumber = randomObject.Intn(len(allNodes))
		if randomNumber == id {
			continue
		}
		if value, ok := allNodes[randomNumber]; ok {
			neighbors[randomNumber] = value
		}
	}
	return
}
// Asks the user if he want to exit the program.
// Returns true if and only if the user types y or j. False otherwise.
func askForProgramRestart() bool {
	var input string
	printMessage("Would you like to exit the program? (y/j/n)")
	fmt.Print("\nInput: ")
	if _, err := fmt.Scanln(&input); err == nil {
		switch input {
		case "y", "j":
			printMessage("Program exists.")
			return true
		case "n":
			printMessage(input)
			return false
		default:
			printMessage("Please only insert y/j for \"YES\" or n for \"NO\".\n" + ERROR_FOOTER)
			printMessage("Assume a \"n\" as input.")
			return false
		}
	} else {
		printMessage("Please only insert y/j for \"YES\" or n for \"NO\".\n" + ERROR_HEADER)
	}
	return false
}

func printMainMenu() {
	fmt.Println("")
	tabwriterObject := new(tabwriter.Writer)
	defer tabwriterObject.Flush()
	// Format in tab-separated columns with a tab stop of 4.
	tabwriterObject.Init(os.Stdout, 0, 4, 0, '\t', 0)
	fmt.Fprintln(tabwriterObject, "ID\tIP Address\tPort\tProtocol")
	fmt.Fprintln(tabwriterObject, MENU_SEPERATOR)
	for key, value := range allNodes {
		fmt.Fprintf(tabwriterObject, "%d\t%s\t%d\t%s\n", key, value.IpAddressAsString(), value.Port(), value.UsedProtocol())
	}
	fmt.Fprintf(tabwriterObject, "\n%d\tAbort\n", CONTROLLER_MENU_NOTHING)
}

func printControlMessageActionMenu() {
	fmt.Println("")
	tabwriterObject := new(tabwriter.Writer)
	defer tabwriterObject.Flush()
	// Format in tab-separated columns with a tab stop of 4.
	tabwriterObject.Init(os.Stdout, 0, 4, 0, '\t', 0)
	fmt.Fprintln(tabwriterObject, "Value\tControl message action")
	fmt.Fprintln(tabwriterObject, MENU_SEPERATOR)
	fmt.Fprintf(tabwriterObject, "%d\tInitialize\n", CONTROL_TYPE_INIT)
	fmt.Fprintf(tabwriterObject, "%d\tShutdown\n", CONTROL_TYPE_EXIT)
	fmt.Fprintf(tabwriterObject, "\n%d\tAbort\n", CONTROLLER_MENU_NOTHING)
}

// Creates a new logger which uses a buffer where he collects the messages.
func initializeLogger(preface string) {
	if logFile == "path/to/logfile.txt" {
		logger = log.New(os.Stdout, preface, log.Ldate|log.Ltime|log.Lshortfile)
	} else {
		logger = log.New(&loggingBuffer, preface, log.Ldate|log.Ltime|log.Lshortfile)
	}
}

// This function is used as a wrapper for the logging functinality.
// If the logger is not initialized, it calls the log methods from the
// log package.
func printMessage(message interface{}) {
	if logger == nil {
		switch typeValue := message.(type) {
		case fmt.Stringer:
			log.Println(typeValue.String())
		default:
			log.Println(typeValue)
		}
	} else {
		switch typeValue := message.(type) {
		case fmt.Stringer:
			logger.Println(typeValue.String())
		default:
			logger.Println(typeValue)
		}
	}
}

// This function prints the content of the logging buffer to the STDOUT.
// TODO: Decide if it should be printed to console or wrote to a file or simmilar.
func printAndClearLoggerContent() {
	if loggingBuffer.Len() != 0 {
		fmt.Println(&loggingBuffer)
		loggingBuffer.Reset()
	}
}

// The sinalHanlder function waits for a signal and catches it.
// If it is a SIGINT, the program exists with return code 0.
func signalHandler() {
	c := make(chan os.Signal, 1)
	// Signal für CTRL-C abfangen...
	signal.Notify(c, syscall.SIGINT)

	// foreach signal received
	for signal := range c {
		fmt.Println("\nSignal empfangen...")
		fmt.Println(signal.String())
		switch signal {
		case syscall.SIGINT:
			os.Exit(0)
		}
	}
}
<commit_msg>Forgot to add the time import for the chooseThreeNeighbors function. Is needed for the seed.<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/jzipfler/htw-ava/filehandler"
	"github.com/jzipfler/htw-ava/server"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"text/tabwriter"
	"time"
)

const (
	ERROR_HEADER            = "------------------ERROR------------------"
	ERROR_FOOTER            = "^^^^^^^^^^^^^^^^^^ERROR^^^^^^^^^^^^^^^^^^"
	MENU_SEPERATOR          = "-----------------------------------------"
	CONTROL_TYPE_INIT       = 1
	CONTROL_TYPE_EXIT       = 2
	CONTROLLER_MENU_NOTHING = 0
)

var (
	loggingBuffer bytes.Buffer
	logger        *log.Logger
	loggingPrefix string
	logFile       string
	id            int
	ipAddress     string
	port          int
	nodeListFile  string
	isController  bool
	allNodes      map[int]server.NetworkServer
	neighbors     map[int]server.NetworkServer
	thisNode      server.NetworkServer
)

// The ini function is called before the main function is started.
// It is used to do some stuff that should be done before the rest
// of the application starts and on what they may depend on.
func init() {
	flag.StringVar(&nodeListFile, "nodeList", "path/to/nodeList.txt", "A file where nodes are defined as follows: \"ID IP_ADDR:PORT\"")
	flag.IntVar(&id, "id", 1, "The if of the actual starting node.")
	flag.StringVar(&ipAddress, "ipAddress", "127.0.0.1", "The ip address of the actual starting node.")
	flag.IntVar(&port, "port", 15108, "The port of the actual starting node.")
	flag.StringVar(&loggingPrefix, "loggingPrefix", "LOGGING --> ", "This can be used to define which prefix the logger should use to print his messages.")
	flag.StringVar(&logFile, "logFile", "path/to/logfile.txt", "This parameter can be used to print the logging output to the given file.")
	flag.BoolVar(&isController, "isController", false, "Tell the node if he should act as controller or as independent node.")
}

// The main function is used when the programm is called / executed.
func main() {
	if len(os.Args) == 1 {
		flag.Usage()
		os.Exit(0)
	}
	flag.Parse()

	if nodeListFile == "path/to/nodeList.txt" {
		log.Fatalf("The nodeListFile is required.\n%s\n", ERROR_FOOTER)
	}
	var readFromNodeListError error
	allNodes, readFromNodeListError = filehandler.CollectAllFromNodeListFile(nodeListFile)
	if readFromNodeListError != nil {
		log.Fatalf("%s\n%s\n", readFromNodeListError.Error(), ERROR_FOOTER)
	} else {
		if len(allNodes) == 1 {
			log.Fatalf("There is only one node in the nodeList. Ther must be at least 2.\n%s\n", ERROR_FOOTER)
		}
	}

	go signalHandler() // Handle CTRL-C signals
	initializeLogger(loggingPrefix)

	if isController {
		startController()
	} else {
		startIndependentNode()
	}
}

func startIndependentNode() {
	quit := false
	printMessage("Start current instance as independent node.")
	thisNode = allNodes[id]
	printMessage("This node has the folowing settings: ")
	printMessage(thisNode)

	for !quit {
		quit = shouldRestartProgram()
	}
}

// The controller is used to control the independent nodes.
// He can initialize or shutdown the nodes.
func startController() {
	quit := false
	printMessage("Start current instance as controller.")

	for !quit {
		var input string
		printMessage("Printing main menu.")
		printMainMenu()
		fmt.Print("\nEnter ID of the node you would like to send a message.\nInput: ")
		_, err := fmt.Scanln(&input)
		if err != nil {
			printMessage(fmt.Sprintf("Error while reading the input. Quit program...\n%s\n", ERROR_FOOTER))
			os.Exit(1)
		}
		targetId, err := strconv.Atoi(input)
		if err != nil {
			printMessage(fmt.Sprintf("No number read. You have to enter the id of the node where you want to sent a message.\n%s\n", ERROR_FOOTER))
			continue
		}
		if targetId == CONTROLLER_MENU_NOTHING {
			quit = askForProgramRestart()
			continue
		}
		printControlMessageActionMenu()
		fmt.Print("\nEnter value of the control action you would like to sent.\nInput: ")
		_, err = fmt.Scanln(&input)
		if err != nil {
			printMessage(fmt.Sprintf("Error while reading the input. Quit program...\n%s\n", ERROR_FOOTER))
			os.Exit(1)
		}
		controlAction, err := strconv.Atoi(input)
		if controlAction == CONTROLLER_MENU_NOTHING {
			quit = askForProgramRestart()
			continue
		}
		quit = askForProgramRestart()
	}
}

// The chooseThreeNeighbors function uses the allNodes map to return
// another map that contains 3 nodes at the most.
// It calls os.Exit(1) if only one node is available in the allNodes map.
func chooseThreeNeighbors() (neighbors map[int]server.NetworkServer) {
	neighbors = make(map[int]server.NetworkServer, 3)
	// If there are only 1, 2 or 3 possible neighbors...take them.
	switch len(allNodes) {
	case 1:
		printMessage(fmt.Sprintf("There is only one node in the nodeList. Ther must be at least 2.\n%s\n", ERROR_FOOTER))
		os.Exit(1)
	case 2, 3, 4:
		for key, value := range allNodes {
			if key != id {
				neighbors[key] = value
			}
		}
		return
	}
	randomObject := rand.New(rand.NewSource(time.Now().UnixNano()))
	for len(neighbors) != 3 {
		var randomNumber int
		randomNumber = randomObject.Intn(len(allNodes))
		if randomNumber == id {
			continue
		}
		if value, ok := allNodes[randomNumber]; ok {
			neighbors[randomNumber] = value
		}
	}
	return
}
// Asks the user if he want to exit the program.
// Returns true if and only if the user types y or j. False otherwise.
func askForProgramRestart() bool {
	var input string
	printMessage("Would you like to exit the program? (y/j/n)")
	fmt.Print("\nInput: ")
	if _, err := fmt.Scanln(&input); err == nil {
		switch input {
		case "y", "j":
			printMessage("Program exists.")
			return true
		case "n":
			printMessage(input)
			return false
		default:
			printMessage("Please only insert y/j for \"YES\" or n for \"NO\".\n" + ERROR_FOOTER)
			printMessage("Assume a \"n\" as input.")
			return false
		}
	} else {
		printMessage("Please only insert y/j for \"YES\" or n for \"NO\".\n" + ERROR_HEADER)
	}
	return false
}

func printMainMenu() {
	fmt.Println("")
	tabwriterObject := new(tabwriter.Writer)
	defer tabwriterObject.Flush()
	// Format in tab-separated columns with a tab stop of 4.
	tabwriterObject.Init(os.Stdout, 0, 4, 0, '\t', 0)
	fmt.Fprintln(tabwriterObject, "ID\tIP Address\tPort\tProtocol")
	fmt.Fprintln(tabwriterObject, MENU_SEPERATOR)
	for key, value := range allNodes {
		fmt.Fprintf(tabwriterObject, "%d\t%s\t%d\t%s\n", key, value.IpAddressAsString(), value.Port(), value.UsedProtocol())
	}
	fmt.Fprintf(tabwriterObject, "\n%d\tAbort\n", CONTROLLER_MENU_NOTHING)
}

func printControlMessageActionMenu() {
	fmt.Println("")
	tabwriterObject := new(tabwriter.Writer)
	defer tabwriterObject.Flush()
	// Format in tab-separated columns with a tab stop of 4.
	tabwriterObject.Init(os.Stdout, 0, 4, 0, '\t', 0)
	fmt.Fprintln(tabwriterObject, "Value\tControl message action")
	fmt.Fprintln(tabwriterObject, MENU_SEPERATOR)
	fmt.Fprintf(tabwriterObject, "%d\tInitialize\n", CONTROL_TYPE_INIT)
	fmt.Fprintf(tabwriterObject, "%d\tShutdown\n", CONTROL_TYPE_EXIT)
	fmt.Fprintf(tabwriterObject, "\n%d\tAbort\n", CONTROLLER_MENU_NOTHING)
}

// Creates a new logger which uses a buffer where he collects the messages.
func initializeLogger(preface string) {
	if logFile == "path/to/logfile.txt" {
		logger = log.New(os.Stdout, preface, log.Ldate|log.Ltime|log.Lshortfile)
	} else {
		logger = log.New(&loggingBuffer, preface, log.Ldate|log.Ltime|log.Lshortfile)
	}
}

// This function is used as a wrapper for the logging functinality.
// If the logger is not initialized, it calls the log methods from the
// log package.
func printMessage(message interface{}) {
	if logger == nil {
		switch typeValue := message.(type) {
		case fmt.Stringer:
			log.Println(typeValue.String())
		default:
			log.Println(typeValue)
		}
	} else {
		switch typeValue := message.(type) {
		case fmt.Stringer:
			logger.Println(typeValue.String())
		default:
			logger.Println(typeValue)
		}
	}
}

// This function prints the content of the logging buffer to the STDOUT.
// TODO: Decide if it should be printed to console or wrote to a file or simmilar.
func printAndClearLoggerContent() {
	if loggingBuffer.Len() != 0 {
		fmt.Println(&loggingBuffer)
		loggingBuffer.Reset()
	}
}

// The sinalHanlder function waits for a signal and catches it.
// If it is a SIGINT, the program exists with return code 0.
func signalHandler() {
	c := make(chan os.Signal, 1)
	// Signal für CTRL-C abfangen...
	signal.Notify(c, syscall.SIGINT)

	// foreach signal received
	for signal := range c {
		fmt.Println("\nSignal empfangen...")
		fmt.Println(signal.String())
		switch signal {
		case syscall.SIGINT:
			os.Exit(0)
		}
	}
}
<|endoftext|>


<commit_before>package cachet

import (
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestMetricsService_GetAll(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"meta":{"pagination":{"total":4,"count":4,"per_page":20,"current_page":1,"total_pages":1,"links":{"next_page":null,"previous_page":null}}},"data":[{"id":1,"name":"Cups of coffee","suffix":"Cups","description":"How many cups of coffee we've drank.","default_value":0,"calc_type":1,"display_chart":true,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02","places":2,"points":[{"id":1,"metric_id":1,"value":7,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02"}]}]}`)
	})

	got, _, err := testClient.Metrics.GetAll()
	if err != nil {
		t.Errorf("Metrics.GetAll returned error: %v", err)
	}

	expected := &MetricResponse{
		Meta: Meta{
			Pagination: Pagination{
				Total:       4,
				Count:       4,
				PerPage:     20,
				CurrentPage: 1,
				TotalPages:  1,
				Links: Links{
					NextPage:     "",
					PreviousPage: "",
				},
			},
		},
		Metrics: []Metric{
			{
				ID:           1,
				Name:         "Cups of coffee",
				Suffix:       "Cups",
				Description:  "How many cups of coffee we've drank.",
				DefaultValue: 0,
				CalcType:     1,
				DisplayChart: true,
				CreatedAt:    "2015-10-31 14:30:02",
				UpdatedAt:    "2015-10-31 14:30:02",
				Places:       2,
				Points: []Point{
					{
						ID:        1,
						MetricID:  1,
						Value:     7,
						CreatedAt: "2015-10-31 14:30:02",
						UpdatedAt: "2015-10-31 14:30:02",
					},
				},
			},
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.GetAll returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Get(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"data":{"id":1,"name":"Cups of coffee","suffix":"Cups","description":"How many cups of coffee we've drank.","default_value":0,"calc_type":1,"display_chart":true,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02","places":2,"points":[{"id":1,"metric_id":1,"value":7,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02"}]}}`)
	})

	got, _, err := testClient.Metrics.Get(1)
	if err != nil {
		t.Errorf("Metrics.Get returned error: %v", err)
	}

	expected := &Metric{
		ID:           1,
		Name:         "Cups of coffee",
		Suffix:       "Cups",
		Description:  "How many cups of coffee we've drank.",
		DefaultValue: 0,
		CalcType:     1,
		DisplayChart: true,
		CreatedAt:    "2015-10-31 14:30:02",
		UpdatedAt:    "2015-10-31 14:30:02",
		Places:       2,
		Points: []Point{
			{
				ID:        1,
				MetricID:  1,
				Value:     7,
				CreatedAt: "2015-10-31 14:30:02",
				UpdatedAt: "2015-10-31 14:30:02",
			},
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.Get returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Create(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"data":{"name":"Beer2","display_chart":true,"default_value":0,"calc_type":0,"places":1,"suffix":"Bottles","description":"How many beers we drank.","updated_at":"2015-10-31 16:56:18","created_at":"2015-10-31 16:56:18","id":5}}`)
	})

	m := &Metric{
		Name:         "Beer2",
		Suffix:       "Bottles",
		Description:  "How many beers we drank.",
		DefaultValue: 0,
		DisplayChart: true,
		Places:       1,
	}
	got, _, err := testClient.Metrics.Create(m)
	if err != nil {
		t.Errorf("Metrics.Create returned error: %v", err)
	}

	expected := &Metric{
		ID:           5,
		Name:         "Beer2",
		Suffix:       "Bottles",
		Description:  "How many beers we drank.",
		DisplayChart: true,
		DefaultValue: 0,
		CalcType:     0,
		Places:       1,
		CreatedAt:    "2015-10-31 16:56:18",
		UpdatedAt:    "2015-10-31 16:56:18",
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.Create returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Delete(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	resp, err := testClient.Metrics.Delete(1)
	if err != nil {
		t.Errorf("Metrics.Delete returned error: %v", err)
	}

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("Metrics.Delete returned status %+v, want %+v", resp.StatusCode, http.StatusNoContent)
	}
}

func TestMetricsService_GetPoints(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"data":[{"id":1,"metric_id":1,"value":4,"created_at":"2015-10-31 16:30:02","updated_at":"2015-10-31 16:30:02"},{"id":2,"metric_id":1,"value":6,"created_at":"2015-10-31 15:30:02","updated_at":"2015-10-31 16:30:02"}]}`)
	})

	got, _, err := testClient.Metrics.GetPoints(1)
	if err != nil {
		t.Errorf("Metrics.GetPoints returned error: %v", err)
	}

	expected := &[]Point{
		{
			ID:        1,
			MetricID:  1,
			Value:     4,
			CreatedAt: "2015-10-31 16:30:02",
			UpdatedAt: "2015-10-31 16:30:02",
		},
		{
			ID:        2,
			MetricID:  1,
			Value:     6,
			CreatedAt: "2015-10-31 15:30:02",
			UpdatedAt: "2015-10-31 16:30:02",
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.GetPoints returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_AddPoint(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"data":{"metric_id":1,"value":20,"updated_at":"2015-10-31 16:46:04","created_at":"2015-10-31 16:46:04","id":14}}`)
	})

	got, _, err := testClient.Metrics.AddPoint(1, 20, "")
	if err != nil {
		t.Errorf("Metrics.AddPoint returned error: %v", err)
	}

	expected := &Point{

		ID:        14,
		MetricID:  1,
		Value:     20,
		CreatedAt: "2015-10-31 16:46:04",
		UpdatedAt: "2015-10-31 16:46:04",
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.AddPoint returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_DeletePoint(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	resp, err := testClient.Metrics.DeletePoint(1, 1)
	if err != nil {
		t.Errorf("Metrics.DeletePoint returned error: %v", err)
	}

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("Metrics.DeletePoint returned status %+v, want %+v", resp.StatusCode, http.StatusNoContent)
	}
}
<commit_msg>Fixed metrics test<commit_after>package cachet

import (
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestMetricsService_GetAll(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"meta":{"pagination":{"total":4,"count":4,"per_page":20,"current_page":1,"total_pages":1,"links":{"next_page":null,"previous_page":null}}},"data":[{"id":1,"name":"Cups of coffee","suffix":"Cups","description":"How many cups of coffee we've drank.","default_value":0,"calc_type":1,"display_chart":true,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02","places":2,"points":[{"id":1,"metric_id":1,"value":7,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02"}]}]}`)
	})

	got, _, err := testClient.Metrics.GetAll()
	if err != nil {
		t.Errorf("Metrics.GetAll returned error: %v", err)
	}

	expected := &MetricResponse{
		Meta: Meta{
			Pagination: Pagination{
				Total:       4,
				Count:       4,
				PerPage:     20,
				CurrentPage: 1,
				TotalPages:  1,
				Links: Links{
					NextPage:     "",
					PreviousPage: "",
				},
			},
		},
		Metrics: []Metric{
			{
				ID:           1,
				Name:         "Cups of coffee",
				Suffix:       "Cups",
				Description:  "How many cups of coffee we've drank.",
				DefaultValue: 0,
				CalcType:     1,
				DisplayChart: true,
				CreatedAt:    "2015-10-31 14:30:02",
				UpdatedAt:    "2015-10-31 14:30:02",
				Places:       2,
			},
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.GetAll returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Get(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"data":{"id":1,"name":"Cups of coffee","suffix":"Cups","description":"How many cups of coffee we've drank.","default_value":0,"calc_type":1,"display_chart":true,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02","places":2,"points":[{"id":1,"metric_id":1,"value":7,"created_at":"2015-10-31 14:30:02","updated_at":"2015-10-31 14:30:02"}]}}`)
	})

	got, _, err := testClient.Metrics.Get(1)
	if err != nil {
		t.Errorf("Metrics.Get returned error: %v", err)
	}

	expected := &Metric{
		ID:           1,
		Name:         "Cups of coffee",
		Suffix:       "Cups",
		Description:  "How many cups of coffee we've drank.",
		DefaultValue: 0,
		CalcType:     1,
		DisplayChart: true,
		CreatedAt:    "2015-10-31 14:30:02",
		UpdatedAt:    "2015-10-31 14:30:02",
		Places:       2,
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.Get returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Create(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"data":{"name":"Beer2","display_chart":true,"default_value":0,"calc_type":0,"places":1,"suffix":"Bottles","description":"How many beers we drank.","updated_at":"2015-10-31 16:56:18","created_at":"2015-10-31 16:56:18","id":5}}`)
	})

	m := &Metric{
		Name:         "Beer2",
		Suffix:       "Bottles",
		Description:  "How many beers we drank.",
		DefaultValue: 0,
		DisplayChart: true,
		Places:       1,
	}
	got, _, err := testClient.Metrics.Create(m)
	if err != nil {
		t.Errorf("Metrics.Create returned error: %v", err)
	}

	expected := &Metric{
		ID:           5,
		Name:         "Beer2",
		Suffix:       "Bottles",
		Description:  "How many beers we drank.",
		DisplayChart: true,
		DefaultValue: 0,
		CalcType:     0,
		Places:       1,
		CreatedAt:    "2015-10-31 16:56:18",
		UpdatedAt:    "2015-10-31 16:56:18",
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.Create returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_Delete(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	resp, err := testClient.Metrics.Delete(1)
	if err != nil {
		t.Errorf("Metrics.Delete returned error: %v", err)
	}

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("Metrics.Delete returned status %+v, want %+v", resp.StatusCode, http.StatusNoContent)
	}
}

func TestMetricsService_GetPoints(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"data":[{"id":1,"metric_id":1,"value":4,"created_at":"2015-10-31 16:30:02","updated_at":"2015-10-31 16:30:02"},{"id":2,"metric_id":1,"value":6,"created_at":"2015-10-31 15:30:02","updated_at":"2015-10-31 16:30:02"}]}`)
	})

	got, _, err := testClient.Metrics.GetPoints(1)
	if err != nil {
		t.Errorf("Metrics.GetPoints returned error: %v", err)
	}

	expected := &[]Point{
		{
			ID:        1,
			MetricID:  1,
			Value:     4,
			CreatedAt: "2015-10-31 16:30:02",
			UpdatedAt: "2015-10-31 16:30:02",
		},
		{
			ID:        2,
			MetricID:  1,
			Value:     6,
			CreatedAt: "2015-10-31 15:30:02",
			UpdatedAt: "2015-10-31 16:30:02",
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.GetPoints returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_AddPoint(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"data":{"metric_id":1,"value":20,"updated_at":"2015-10-31 16:46:04","created_at":"2015-10-31 16:46:04","id":14}}`)
	})

	got, _, err := testClient.Metrics.AddPoint(1, 20, "")
	if err != nil {
		t.Errorf("Metrics.AddPoint returned error: %v", err)
	}

	expected := &Point{

		ID:        14,
		MetricID:  1,
		Value:     20,
		CreatedAt: "2015-10-31 16:46:04",
		UpdatedAt: "2015-10-31 16:46:04",
	}

	if !reflect.DeepEqual(got, expected) {
		t.Errorf("Metrics.AddPoint returned %+v, want %+v", got, expected)
	}
}

func TestMetricsService_DeletePoint(t *testing.T) {
	setup()
	defer teardown()

	testMux.HandleFunc("/api/v1/metrics/1/points/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	resp, err := testClient.Metrics.DeletePoint(1, 1)
	if err != nil {
		t.Errorf("Metrics.DeletePoint returned error: %v", err)
	}

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("Metrics.DeletePoint returned status %+v, want %+v", resp.StatusCode, http.StatusNoContent)
	}
}
<|endoftext|>


<commit_before>package mgr

import (
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/json-iterator/go"
	"github.com/qiniu/pandora-go-sdk/base/reqerr"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/parser"
	"github.com/qiniu/logkit/parser/grok"
	"github.com/qiniu/logkit/reader"
	"github.com/qiniu/logkit/router"
	"github.com/qiniu/logkit/sender"
	"github.com/qiniu/logkit/transforms"
	. "github.com/qiniu/logkit/utils/models"
)

const DefaultTryTimes = 3

//reader模块中各种type的日志都能获取raw_data
func RawData(readerConfig conf.MapConf) (rawData string, err error) {
	if readerConfig == nil {
		err = fmt.Errorf("reader config cannot be empty")
		return
	}

	var rd reader.Reader
	rd, err = reader.NewReader(readerConfig, true)
	if err != nil {
		return
	}
	defer rd.Close()

	tryCount := DefaultTryTimes
	for {
		if tryCount <= 0 {
			err = fmt.Errorf("get raw data from %s time out, can't get any data", rd.Name())
			return
		}
		tryCount--
		rawData, err = rd.ReadLine()
		if err != nil && err != io.EOF {
			return "", fmt.Errorf("reader %s - error: %v", rd.Name(), err)
		}
		if err == io.EOF {
			return rawData, nil
		}
		if rawData == "" {
			continue
		}
		if len(rawData) >= defaultMaxBatchSize {
			err = errors.New("data size large than 2M and will be discard")
			return "", fmt.Errorf("reader %s - error: %v", rd.Name(), err)
		}
		return
	}
}

//parse模块中各种type的日志都能获取解析后的数据
func ParseData(parserConfig conf.MapConf) (parsedData []Data, err error) {
	parserConfig = parser.ConvertWebParserConfig(parserConfig)
	if parserConfig == nil {
		err = fmt.Errorf("parser config was empty after web config convet")
		return
	}

	sampleData, err := getSampleData(parserConfig)
	if err != nil {
		return nil, err
	}
	parserRegistry := parser.NewRegistry()
	logParser, err := parserRegistry.NewLogParser(parserConfig)
	if err != nil {
		return nil, err
	}
	sampleData, err = checkSampleData(sampleData, logParser)
	if err != nil {
		return nil, err
	}

	parsedData, err = logParser.Parse(sampleData)
	err = checkErr(err, logParser.Name())
	if err != nil {
		return nil, err
	}

	return
}

func TransformData(transformerConfig map[string]interface{}) ([]Data, error) {
	if transformerConfig == nil {
		err := fmt.Errorf("transformer config cannot be empty")
		return nil, err
	}

	create, err := getTransformerCreator(transformerConfig)
	if err != nil {
		return nil, err
	}

	data, err := getDataFromTransformConfig(transformerConfig)
	if err != nil {
		return nil, err
	}

	transformer, err := getTransformer(transformerConfig, create)
	if err != nil {
		return nil, err
	}

	// Transform data
	transformedData, transErr := transformer.Transform(data)
	se, ok := transErr.(*StatsError)
	if ok {
		transErr = se.ErrorDetail
	}
	if transErr != nil {
		se, ok := transErr.(*StatsError)
		if ok {
			transErr = se.ErrorDetail
		}
		err := fmt.Errorf("transform processing error %v", transErr)
		return nil, err
	}
	return transformedData, nil
}

func SendData(senderConfig map[string]interface{}) error {
	if senderConfig == nil {
		return fmt.Errorf("sender config cannot be empty")
	}

	sendersConf, err := getSendersConfig(senderConfig)
	if err != nil {
		return err
	}

	senders, err := getSenders(sendersConf)
	if err != nil {
		return err
	}

	senderCnt := len(senders)
	router, err := getRouter(senderConfig, senderCnt)
	if err != nil {
		return err
	}

	datas, err := getDataFromSenderConfig(senderConfig)
	if err != nil {
		return err
	}

	senderDataList := classifySenderData(datas, router, len(senders))
	for index, s := range senders {
		if err := trySend(s, senderDataList[index], DefaultTryTimes); err != nil {
			return err
		}
	}

	return nil
}

func getSendersConfig(senderConfig map[string]interface{}) ([]conf.MapConf, error) {
	config := senderConfig[KeySendConfig]
	byteSendersConfig, err := jsoniter.Marshal(config)
	if err != nil {
		return nil, err
	}
	var sendersConf []conf.MapConf
	if jsonErr := jsoniter.Unmarshal(byteSendersConfig, &sendersConf); jsonErr != nil {
		return nil, jsonErr
	}
	if sendersConf == nil {
		return nil, fmt.Errorf("sender config cannot be empty")
	}
	return sendersConf, nil
}

func getDataFromSenderConfig(senderConfig map[string]interface{}) ([]Data, error) {
	var datas = []Data{}
	rawData := senderConfig[KeySampleLog]
	rawDataStr, ok := rawData.(string)
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	if rawDataStr == "" {
		err := fmt.Errorf("sender fetched empty sample log")
		return nil, err
	}

	if jsonErr := jsoniter.Unmarshal([]byte(rawDataStr), &datas); jsonErr != nil {
		return nil, jsonErr
	}
	return datas, nil
}

func getSenders(sendersConf []conf.MapConf) ([]sender.Sender, error) {
	senders := make([]sender.Sender, 0)
	sr := sender.NewRegistry()
	for i, senderConfig := range sendersConf {
		senderConfig[sender.KeyFaultTolerant] = "false"
		s, err := sr.NewSender(senderConfig, "")
		if err != nil {
			return nil, err
		}
		senders = append(senders, s)
		delete(sendersConf[i], sender.InnerUserAgent)
	}
	return senders, nil
}

func getRouter(senderConfig map[string]interface{}, senderCnt int) (*router.Router, error) {
	routerConf, err := getRouterConfig(senderConfig)
	if err != nil {
		return nil, err
	}

	router, err := router.NewSenderRouter(routerConf, senderCnt)
	if err != nil {
		return nil, fmt.Errorf("sender router error, %v", err)
	}
	return router, nil
}

func getRouterConfig(senderConfig map[string]interface{}) (router.RouterConfig, error) {
	config := senderConfig[KeyRouterConfig]
	byteRouterConfig, err := jsoniter.Marshal(config)
	if err != nil {
		return router.RouterConfig{}, err
	}

	var routerConfig router.RouterConfig
	if jsonErr := jsoniter.Unmarshal(byteRouterConfig, &routerConfig); jsonErr != nil {
		return router.RouterConfig{}, jsonErr
	}
	return routerConfig, nil
}

// trySend 尝试发送数据，如果此时runner退出返回false，其他情况无论是达到最大重试次数还是发送成功，都返回true
func trySend(s sender.Sender, datas []Data, times int) (err error) {
	if times <= 0 {
		times = DefaultTryTimes
	}
	if len(datas) <= 0 {
		return nil
	}
	cnt := 0
	for {
		if cnt >= times {
			break
		}
		err = s.Send(datas)
		if se, ok := err.(*StatsError); ok {
			err = se.ErrorDetail
		}

		if err != nil {
			cnt++
			se, ok := err.(*reqerr.SendError)
			if ok {
				datas = sender.ConvertDatas(se.GetFailDatas())
				continue
			}
			if cnt < times {
				continue
			}
			err = fmt.Errorf("retry send %v times, but still error %v, discard datas %v ... total %v lines", cnt, err, datas, len(datas))
			return err
		}
		break
	}
	return
}

func getSampleData(parserConfig conf.MapConf) ([]string, error) {
	parserType, _ := parserConfig.GetString(parser.KeyParserType)
	rawData, _ := parserConfig.GetStringOr(KeySampleLog, "")
	var sampleData []string

	switch parserType {
	case parser.TypeCSV, parser.TypeJSON, parser.TypeRaw, parser.TypeNginx, parser.TypeEmpty, parser.TypeKafkaRest, parser.TypeLogv1:
		sampleData = append(sampleData, rawData)
	case parser.TypeSyslog:
		sampleData = strings.Split(rawData, "\n")
		sampleData = append(sampleData, parser.PandoraParseFlushSignal)
	case parser.TypeMySQL:
		sampleData = strings.Split(rawData, "\n")
		sampleData = append(sampleData, parser.PandoraParseFlushSignal)
	case parser.TypeGrok:
		grokMode, _ := parserConfig.GetString(parser.KeyGrokMode)
		if grokMode != grok.ModeMulti {
			sampleData = append(sampleData, rawData)
		} else {
			sampleData = []string{rawData}
		}
	default:
		errMsg := fmt.Sprintf("parser type <%v> is not supported yet", parserType)
		return nil, errors.New(errMsg)
	}

	return sampleData, nil
}

func checkSampleData(sampleData []string, logParser parser.Parser) ([]string, error) {
	if len(sampleData) <= 0 {
		_, ok := logParser.(parser.Flushable)
		if ok {
			sampleData = []string{parser.PandoraParseFlushSignal}
		} else {
			err := fmt.Errorf("parser [%v] fetched 0 lines", logParser.Name())
			return nil, err
		}
	}
	return sampleData, nil
}

func checkErr(err error, parserName string) error {
	se, ok := err.(*StatsError)
	var errorCnt int64
	if ok {
		errorCnt = se.Errors
		err = se.ErrorDetail
	} else if err != nil {
		errorCnt = 1
	}
	if err != nil {
		errMsg := fmt.Sprintf("parser %s, error %v ", parserName, err.Error())
		err = fmt.Errorf("%v parse line errors occured, same as %v", errorCnt, errors.New(errMsg))
	}

	return err
}

func getTransformerCreator(transformerConfig map[string]interface{}) (transforms.Creator, error) {
	transformKeyType, ok := transformerConfig[transforms.KeyType]
	if !ok {
		err := fmt.Errorf("missing param %s", transforms.KeyType)
		return nil, err
	}
	transformKeyTypeStr, ok := transformKeyType.(string)
	if !ok {
		err := fmt.Errorf("param %s must be of type string", transforms.KeyType)
		return nil, err
	}

	create, ok := transforms.Transformers[transformKeyTypeStr]
	if !ok {
		err := fmt.Errorf("transformer of type %v not exist", transformKeyTypeStr)
		return nil, err
	}
	return create, nil
}

func getDataFromTransformConfig(transformerConfig map[string]interface{}) ([]Data, error) {
	rawData, ok := transformerConfig[KeySampleLog]
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	rawDataStr, ok := rawData.(string)
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	if rawDataStr == "" {
		err := fmt.Errorf("transformer fetched empty sample log")
		return nil, err
	}

	var data = []Data{}
	var singleData Data
	if jsonErr := jsoniter.Unmarshal([]byte(rawDataStr), &singleData); jsonErr != nil {
		if jsonErr = jsoniter.Unmarshal([]byte(rawDataStr), &data); jsonErr != nil {
			return nil, jsonErr
		}
	} else {
		// is single log, and method transformer.transform(data []sender.Data) accept a param of slice type
		data = append(data, singleData)
	}
	return data, nil
}

func getTransformer(transConfig map[string]interface{}, create transforms.Creator) (transforms.Transformer, error) {
	trans := create()
	transformerConfig := convertWebTransformerConfig(transConfig)
	delete(transformerConfig, KeySampleLog)
	bts, jsonErr := jsoniter.Marshal(transformerConfig)
	if jsonErr != nil {
		return nil, jsonErr
	}
	if jsonErr = jsoniter.Unmarshal(bts, trans); jsonErr != nil {
		return nil, jsonErr
	}

	if trans, ok := trans.(transforms.Initialize); ok {
		if err := trans.Init(); err != nil {
			return nil, err
		}
	}
	return trans, nil
}
<commit_msg>mgr/dataflow: support ReadData when get raw data<commit_after>package mgr

import (
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/json-iterator/go"
	"github.com/qiniu/pandora-go-sdk/base/reqerr"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/parser"
	"github.com/qiniu/logkit/parser/grok"
	"github.com/qiniu/logkit/reader"
	"github.com/qiniu/logkit/router"
	"github.com/qiniu/logkit/sender"
	"github.com/qiniu/logkit/transforms"
	. "github.com/qiniu/logkit/utils/models"
)

const DefaultTryTimes = 3

// RawData 从 reader 模块中根据 type 获取字符串形式的样例日志
func RawData(readerConfig conf.MapConf) (string, error) {
	if readerConfig == nil {
		return "", fmt.Errorf("reader config cannot be empty")
	}

	rd, err := reader.NewReader(readerConfig, true)
	if err != nil {
		return "", err
	}
	defer rd.Close()

	var rawData string

	// DataReader 设定超时，普通 Reader 设定重试
	if dr, ok := rd.(reader.DataReader); ok {
		type dataErr struct {
			data string
			err  error
		}
		// Note: 添加一位缓冲保证 goroutine 在 runner 已经超时的情况下能够正常退出，避免资源泄露
		readChan := make(chan dataErr, 1)
		go func() {
			data, _, err := dr.ReadData()
			if err != nil && err != io.EOF {
				readChan <- dataErr{"", err}
				return
			}

			p, err := jsoniter.MarshalIndent(data, "", "  ")
			readChan <- dataErr{string(p), err}
		}()

		timeout := time.NewTimer(time.Minute)
		select {
		case de := <-readChan:
			rawData, err = de.data, de.err
			if err != nil {
				return "", fmt.Errorf("reader %q - error: %v", rd.Name(), err)
			}

		case <-timeout.C:
			return "", fmt.Errorf("reader %q read data timeout, no data received", rd.Name())
		}

	} else {
		tryCount := DefaultTryTimes
		for {
			if tryCount <= 0 {
				return "", fmt.Errorf("reader %q read line timeout, no data received", rd.Name())
			}
			tryCount--

			rawData, err = rd.ReadLine()
			if err != nil && err != io.EOF {
				return "", fmt.Errorf("reader %q - error: %v", rd.Name(), err)
			}
			if err == io.EOF {
				return rawData, nil
			}

			if len(rawData) > 0 {
				break
			}
		}
	}

	if len(rawData) >= defaultMaxBatchSize {
		err = errors.New("data size large than 2M and will be discard")
		return "", fmt.Errorf("reader %q - error: %v", rd.Name(), err)
	}
	return rawData, nil
}

//parse模块中各种type的日志都能获取解析后的数据
func ParseData(parserConfig conf.MapConf) (parsedData []Data, err error) {
	parserConfig = parser.ConvertWebParserConfig(parserConfig)
	if parserConfig == nil {
		err = fmt.Errorf("parser config was empty after web config convet")
		return
	}

	sampleData, err := getSampleData(parserConfig)
	if err != nil {
		return nil, err
	}
	parserRegistry := parser.NewRegistry()
	logParser, err := parserRegistry.NewLogParser(parserConfig)
	if err != nil {
		return nil, err
	}
	sampleData, err = checkSampleData(sampleData, logParser)
	if err != nil {
		return nil, err
	}

	parsedData, err = logParser.Parse(sampleData)
	err = checkErr(err, logParser.Name())
	if err != nil {
		return nil, err
	}

	return
}

func TransformData(transformerConfig map[string]interface{}) ([]Data, error) {
	if transformerConfig == nil {
		err := fmt.Errorf("transformer config cannot be empty")
		return nil, err
	}

	create, err := getTransformerCreator(transformerConfig)
	if err != nil {
		return nil, err
	}

	data, err := getDataFromTransformConfig(transformerConfig)
	if err != nil {
		return nil, err
	}

	transformer, err := getTransformer(transformerConfig, create)
	if err != nil {
		return nil, err
	}

	// Transform data
	transformedData, transErr := transformer.Transform(data)
	se, ok := transErr.(*StatsError)
	if ok {
		transErr = se.ErrorDetail
	}
	if transErr != nil {
		se, ok := transErr.(*StatsError)
		if ok {
			transErr = se.ErrorDetail
		}
		err := fmt.Errorf("transform processing error %v", transErr)
		return nil, err
	}
	return transformedData, nil
}

func SendData(senderConfig map[string]interface{}) error {
	if senderConfig == nil {
		return fmt.Errorf("sender config cannot be empty")
	}

	sendersConf, err := getSendersConfig(senderConfig)
	if err != nil {
		return err
	}

	senders, err := getSenders(sendersConf)
	if err != nil {
		return err
	}

	senderCnt := len(senders)
	router, err := getRouter(senderConfig, senderCnt)
	if err != nil {
		return err
	}

	datas, err := getDataFromSenderConfig(senderConfig)
	if err != nil {
		return err
	}

	senderDataList := classifySenderData(datas, router, len(senders))
	for index, s := range senders {
		if err := trySend(s, senderDataList[index], DefaultTryTimes); err != nil {
			return err
		}
	}

	return nil
}

func getSendersConfig(senderConfig map[string]interface{}) ([]conf.MapConf, error) {
	config := senderConfig[KeySendConfig]
	byteSendersConfig, err := jsoniter.Marshal(config)
	if err != nil {
		return nil, err
	}
	var sendersConf []conf.MapConf
	if jsonErr := jsoniter.Unmarshal(byteSendersConfig, &sendersConf); jsonErr != nil {
		return nil, jsonErr
	}
	if sendersConf == nil {
		return nil, fmt.Errorf("sender config cannot be empty")
	}
	return sendersConf, nil
}

func getDataFromSenderConfig(senderConfig map[string]interface{}) ([]Data, error) {
	var datas = []Data{}
	rawData := senderConfig[KeySampleLog]
	rawDataStr, ok := rawData.(string)
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	if rawDataStr == "" {
		err := fmt.Errorf("sender fetched empty sample log")
		return nil, err
	}

	if jsonErr := jsoniter.Unmarshal([]byte(rawDataStr), &datas); jsonErr != nil {
		return nil, jsonErr
	}
	return datas, nil
}

func getSenders(sendersConf []conf.MapConf) ([]sender.Sender, error) {
	senders := make([]sender.Sender, 0)
	sr := sender.NewRegistry()
	for i, senderConfig := range sendersConf {
		senderConfig[sender.KeyFaultTolerant] = "false"
		s, err := sr.NewSender(senderConfig, "")
		if err != nil {
			return nil, err
		}
		senders = append(senders, s)
		delete(sendersConf[i], sender.InnerUserAgent)
	}
	return senders, nil
}

func getRouter(senderConfig map[string]interface{}, senderCnt int) (*router.Router, error) {
	routerConf, err := getRouterConfig(senderConfig)
	if err != nil {
		return nil, err
	}

	router, err := router.NewSenderRouter(routerConf, senderCnt)
	if err != nil {
		return nil, fmt.Errorf("sender router error, %v", err)
	}
	return router, nil
}

func getRouterConfig(senderConfig map[string]interface{}) (router.RouterConfig, error) {
	config := senderConfig[KeyRouterConfig]
	byteRouterConfig, err := jsoniter.Marshal(config)
	if err != nil {
		return router.RouterConfig{}, err
	}

	var routerConfig router.RouterConfig
	if jsonErr := jsoniter.Unmarshal(byteRouterConfig, &routerConfig); jsonErr != nil {
		return router.RouterConfig{}, jsonErr
	}
	return routerConfig, nil
}

// trySend 尝试发送数据，如果此时runner退出返回false，其他情况无论是达到最大重试次数还是发送成功，都返回true
func trySend(s sender.Sender, datas []Data, times int) (err error) {
	if times <= 0 {
		times = DefaultTryTimes
	}
	if len(datas) <= 0 {
		return nil
	}
	cnt := 0
	for {
		if cnt >= times {
			break
		}
		err = s.Send(datas)
		if se, ok := err.(*StatsError); ok {
			err = se.ErrorDetail
		}

		if err != nil {
			cnt++
			se, ok := err.(*reqerr.SendError)
			if ok {
				datas = sender.ConvertDatas(se.GetFailDatas())
				continue
			}
			if cnt < times {
				continue
			}
			err = fmt.Errorf("retry send %v times, but still error %v, discard datas %v ... total %v lines", cnt, err, datas, len(datas))
			return err
		}
		break
	}
	return
}

func getSampleData(parserConfig conf.MapConf) ([]string, error) {
	parserType, _ := parserConfig.GetString(parser.KeyParserType)
	rawData, _ := parserConfig.GetStringOr(KeySampleLog, "")
	var sampleData []string

	switch parserType {
	case parser.TypeCSV, parser.TypeJSON, parser.TypeRaw, parser.TypeNginx, parser.TypeEmpty, parser.TypeKafkaRest, parser.TypeLogv1:
		sampleData = append(sampleData, rawData)
	case parser.TypeSyslog:
		sampleData = strings.Split(rawData, "\n")
		sampleData = append(sampleData, parser.PandoraParseFlushSignal)
	case parser.TypeMySQL:
		sampleData = strings.Split(rawData, "\n")
		sampleData = append(sampleData, parser.PandoraParseFlushSignal)
	case parser.TypeGrok:
		grokMode, _ := parserConfig.GetString(parser.KeyGrokMode)
		if grokMode != grok.ModeMulti {
			sampleData = append(sampleData, rawData)
		} else {
			sampleData = []string{rawData}
		}
	default:
		errMsg := fmt.Sprintf("parser type <%v> is not supported yet", parserType)
		return nil, errors.New(errMsg)
	}

	return sampleData, nil
}

func checkSampleData(sampleData []string, logParser parser.Parser) ([]string, error) {
	if len(sampleData) <= 0 {
		_, ok := logParser.(parser.Flushable)
		if ok {
			sampleData = []string{parser.PandoraParseFlushSignal}
		} else {
			err := fmt.Errorf("parser [%v] fetched 0 lines", logParser.Name())
			return nil, err
		}
	}
	return sampleData, nil
}

func checkErr(err error, parserName string) error {
	se, ok := err.(*StatsError)
	var errorCnt int64
	if ok {
		errorCnt = se.Errors
		err = se.ErrorDetail
	} else if err != nil {
		errorCnt = 1
	}
	if err != nil {
		errMsg := fmt.Sprintf("parser %s, error %v ", parserName, err.Error())
		err = fmt.Errorf("%v parse line errors occured, same as %v", errorCnt, errors.New(errMsg))
	}

	return err
}

func getTransformerCreator(transformerConfig map[string]interface{}) (transforms.Creator, error) {
	transformKeyType, ok := transformerConfig[transforms.KeyType]
	if !ok {
		err := fmt.Errorf("missing param %s", transforms.KeyType)
		return nil, err
	}
	transformKeyTypeStr, ok := transformKeyType.(string)
	if !ok {
		err := fmt.Errorf("param %s must be of type string", transforms.KeyType)
		return nil, err
	}

	create, ok := transforms.Transformers[transformKeyTypeStr]
	if !ok {
		err := fmt.Errorf("transformer of type %v not exist", transformKeyTypeStr)
		return nil, err
	}
	return create, nil
}

func getDataFromTransformConfig(transformerConfig map[string]interface{}) ([]Data, error) {
	rawData, ok := transformerConfig[KeySampleLog]
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	rawDataStr, ok := rawData.(string)
	if !ok {
		err := fmt.Errorf("missing param %s", KeySampleLog)
		return nil, err
	}
	if rawDataStr == "" {
		err := fmt.Errorf("transformer fetched empty sample log")
		return nil, err
	}

	var data = []Data{}
	var singleData Data
	if jsonErr := jsoniter.Unmarshal([]byte(rawDataStr), &singleData); jsonErr != nil {
		if jsonErr = jsoniter.Unmarshal([]byte(rawDataStr), &data); jsonErr != nil {
			return nil, jsonErr
		}
	} else {
		// is single log, and method transformer.transform(data []sender.Data) accept a param of slice type
		data = append(data, singleData)
	}
	return data, nil
}

func getTransformer(transConfig map[string]interface{}, create transforms.Creator) (transforms.Transformer, error) {
	trans := create()
	transformerConfig := convertWebTransformerConfig(transConfig)
	delete(transformerConfig, KeySampleLog)
	bts, jsonErr := jsoniter.Marshal(transformerConfig)
	if jsonErr != nil {
		return nil, jsonErr
	}
	if jsonErr = jsoniter.Unmarshal(bts, trans); jsonErr != nil {
		return nil, jsonErr
	}

	if trans, ok := trans.(transforms.Initialize); ok {
		if err := trans.Init(); err != nil {
			return nil, err
		}
	}
	return trans, nil
}
<|endoftext|>


<commit_before>package levenshtein

import (
	"fmt"
	"sort"
	"testing"
)

func TestMinTreeFuzzySearch(t *testing.T) {
	var err error

	sort.Strings(testwords)
	mt, err = NewMinTree(testwords)
	if err != nil {
		t.Fatalf("Could not create MinTree: %q. Exiting.", err)
	}

	matches := mt.FuzzyMatches("danger", 2)
	fmt.Printf("Fuzzymatch count: %d.\n", len(matches))
	for _, match := range matches {
		fmt.Println(match)
	}
}

var mt *MinTree

func BenchmarkMinTree(b *testing.B) {
	if mt == nil {
		return
	}
	for i := 0; i < b.N; i++ {
		mt.FuzzyMatches("holocaust", 2)
	}
}
<commit_msg>Add only the unique words to the MinTree in the test<commit_after>package levenshtein

import (
	"fmt"
	"sort"
	"testing"
)

func TestMinTreeFuzzySearch(t *testing.T) {
	var err error
	uniq := make(map[string]struct{}, len(testwords)/2)

	for _, w := range testwords {
		uniq[w] = struct{}{}
	}
	uniqwords := make([]string, 0, len(uniq))
	for k, _ := range uniq {
		uniqwords = append(uniqwords, k)
	}
	fmt.Printf("Number of unique words in the MinTree: %d\n", len(uniqwords))

	sort.Strings(uniqwords)
	mt, err = NewMinTree(uniqwords)
	if err != nil {
		t.Fatalf("Could not create MinTree: %q. Exiting.", err)
	}

	matches := mt.FuzzyMatches("danger", 2)
	fmt.Printf("Fuzzymatch count for \"danger\" with distance two: %d\n", len(matches))
	for _, match := range matches {
		fmt.Println(match)
	}
}

var mt *MinTree

func BenchmarkMinTree(b *testing.B) {
	if mt == nil {
		return
	}
	for i := 0; i < b.N; i++ {
		mt.FuzzyMatches("holocaust", 2)
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor:
// - Aaron Meihm ameihm@mozilla.com

package scribe

import (
	"encoding/json"
	"fmt"
	"strings"
)

// Describes the results of a test. The type can be marshaled into a JSON
// string as required.
type TestResult struct {
	TestID      string    `json:"testid" yaml:"testid"`                 // The identifier for the test.
	TestName    string    `json:"name" yaml:"name"`                     // Optional test name for display
	Description string    `json:"description" yaml:"description"`       // Test description
	Tags        []TestTag `json:"tags,omitempty" yaml:"tags,omitempty"` // Tags for the test.

	IsError bool   `json:"iserror" yaml:"iserror"` // True of error is encountered during evaluation.
	Error   string `json:"error" yaml:"error"`     // Error associated with test.

	MasterResult   bool `json:"masterresult" yaml:"masterresult"`     // Master result of test.
	HasTrueResults bool `json:"hastrueresults" yaml:"hastrueresults"` // True if > 0 evaluations resulted in true.

	Results []TestSubResult `json:"results" yaml:"results"` // The sub-results for the test.
}

// For a given test, a number of sources can be identified that match the
// criteria. For example, multiple files can be identifier with a given
// filename. Each test tracks individual results for these cases.
type TestSubResult struct {
	Result     bool   `json:"result" yaml:"result"`         // The result of evaluation for an identifier source.
	Identifier string `json:"identifier" yaml:"identifier"` // The identifier for the source.
}

// Return test results for a given test. Returns an error if for
// some reason the results can not be returned.
func GetResults(d *Document, name string) (TestResult, error) {
	t, err := d.getTest(name)
	if err != nil {
		return TestResult{}, err
	}
	ret := TestResult{}
	ret.TestID = t.TestID
	ret.TestName = t.TestName
	ret.Description = t.Description
	ret.Tags = t.Tags
	if t.err != nil {
		ret.Error = fmt.Sprintf("%v", t.err)
		ret.IsError = true
		return ret, nil
	}
	ret.MasterResult = t.masterResult
	ret.HasTrueResults = t.hasTrueResults
	for _, x := range t.results {
		nr := TestSubResult{}
		nr.Result = x.result
		nr.Identifier = x.criteria.identifier
		ret.Results = append(ret.Results, nr)
	}
	return ret, nil
}

// A helper function to convert Testresult r into a slice of greppable single
// line results. Note that each line returned is not terminated with a line
// feed.
func (r *TestResult) SingleLineResults() []string {
	lns := make([]string, 0)

	rs := "[error]"
	if !r.IsError {
		if r.MasterResult {
			rs = "[true]"
		} else {
			rs = "[false]"
		}
	}
	namestr := r.TestID
	if r.TestName != "" {
		namestr = r.TestName
	}
	buf := fmt.Sprintf("master %v name:\"%v\" id:\"%v\" hastrue:%v error:\"%v\"",
		rs, namestr, r.TestID, r.HasTrueResults, r.Error)
	lns = append(lns, buf)

	for _, x := range r.Results {
		if x.Result {
			rs = "[true]"
		} else {
			rs = "[false]"
		}
		buf := fmt.Sprintf("sub %v name:\"%v\" id:\"%v\" identifier:\"%v\"",
			rs, namestr, r.TestID, x.Identifier)
		lns = append(lns, buf)
	}

	return lns
}

// A helper function to convert TestResult into a JSON string.
func (r *TestResult) JSON() string {
	buf, err := json.Marshal(r)
	if err != nil {
		fmt.Sprintf("JSON encoding error: %v", err)
	}
	return string(buf)
}

// A helper function to convert TestResult into a human readable result
// suitable for display.
func (r *TestResult) String() string {
	lns := make([]string, 0)
	if r.TestName != "" {
		lns = append(lns, fmt.Sprintf("result for \"%v\" (%v)", r.TestName, r.TestID))
	} else {
		lns = append(lns, fmt.Sprintf("result for \"%v\"", r.TestID))
	}
	if r.Description != "" {
		buf := fmt.Sprintf("\tdescription: %v", r.Description)
		lns = append(lns, buf)
	}
	if r.MasterResult {
		lns = append(lns, "\tmaster result: true")
	} else {
		buf := "\tmaster result: false"
		if r.HasTrueResults {
			buf = buf + ", has true results, failure caused by dependency"
		}
		lns = append(lns, buf)
	}
	if len(r.Tags) > 0 {
		for _, x := range r.Tags {
			lns = append(lns, fmt.Sprintf("\ttag: %v: %v", x.Key, x.Value))
		}
	}
	if r.IsError {
		buf := fmt.Sprintf("\t[error] error: %v", r.Error)
		lns = append(lns, buf)
	}
	for _, x := range r.Results {
		buf := fmt.Sprintf("\t[%v] identifier: \"%v\"", x.Result, x.Identifier)
		lns = append(lns, buf)
	}
	return strings.Join(lns, "\n")
}
<commit_msg>in TestResult JSON(), fix issue with ignored fmt.Sprintf result<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor:
// - Aaron Meihm ameihm@mozilla.com

package scribe

import (
	"encoding/json"
	"fmt"
	"strings"
)

// Describes the results of a test. The type can be marshaled into a JSON
// string as required.
type TestResult struct {
	TestID      string    `json:"testid" yaml:"testid"`                 // The identifier for the test.
	TestName    string    `json:"name" yaml:"name"`                     // Optional test name for display
	Description string    `json:"description" yaml:"description"`       // Test description
	Tags        []TestTag `json:"tags,omitempty" yaml:"tags,omitempty"` // Tags for the test.

	IsError bool   `json:"iserror" yaml:"iserror"` // True of error is encountered during evaluation.
	Error   string `json:"error" yaml:"error"`     // Error associated with test.

	MasterResult   bool `json:"masterresult" yaml:"masterresult"`     // Master result of test.
	HasTrueResults bool `json:"hastrueresults" yaml:"hastrueresults"` // True if > 0 evaluations resulted in true.

	Results []TestSubResult `json:"results" yaml:"results"` // The sub-results for the test.
}

// For a given test, a number of sources can be identified that match the
// criteria. For example, multiple files can be identifier with a given
// filename. Each test tracks individual results for these cases.
type TestSubResult struct {
	Result     bool   `json:"result" yaml:"result"`         // The result of evaluation for an identifier source.
	Identifier string `json:"identifier" yaml:"identifier"` // The identifier for the source.
}

// Return test results for a given test. Returns an error if for
// some reason the results can not be returned.
func GetResults(d *Document, name string) (TestResult, error) {
	t, err := d.getTest(name)
	if err != nil {
		return TestResult{}, err
	}
	ret := TestResult{}
	ret.TestID = t.TestID
	ret.TestName = t.TestName
	ret.Description = t.Description
	ret.Tags = t.Tags
	if t.err != nil {
		ret.Error = fmt.Sprintf("%v", t.err)
		ret.IsError = true
		return ret, nil
	}
	ret.MasterResult = t.masterResult
	ret.HasTrueResults = t.hasTrueResults
	for _, x := range t.results {
		nr := TestSubResult{}
		nr.Result = x.result
		nr.Identifier = x.criteria.identifier
		ret.Results = append(ret.Results, nr)
	}
	return ret, nil
}

// A helper function to convert Testresult r into a slice of greppable single
// line results. Note that each line returned is not terminated with a line
// feed.
func (r *TestResult) SingleLineResults() []string {
	lns := make([]string, 0)

	rs := "[error]"
	if !r.IsError {
		if r.MasterResult {
			rs = "[true]"
		} else {
			rs = "[false]"
		}
	}
	namestr := r.TestID
	if r.TestName != "" {
		namestr = r.TestName
	}
	buf := fmt.Sprintf("master %v name:\"%v\" id:\"%v\" hastrue:%v error:\"%v\"",
		rs, namestr, r.TestID, r.HasTrueResults, r.Error)
	lns = append(lns, buf)

	for _, x := range r.Results {
		if x.Result {
			rs = "[true]"
		} else {
			rs = "[false]"
		}
		buf := fmt.Sprintf("sub %v name:\"%v\" id:\"%v\" identifier:\"%v\"",
			rs, namestr, r.TestID, x.Identifier)
		lns = append(lns, buf)
	}

	return lns
}

// A helper function to convert TestResult into a JSON string.
func (r *TestResult) JSON() string {
	buf, err := json.Marshal(r)
	if err != nil {
		// If we are unable to marshal the result just return an empty document
		return "{}"
	}
	return string(buf)
}

// A helper function to convert TestResult into a human readable result
// suitable for display.
func (r *TestResult) String() string {
	lns := make([]string, 0)
	if r.TestName != "" {
		lns = append(lns, fmt.Sprintf("result for \"%v\" (%v)", r.TestName, r.TestID))
	} else {
		lns = append(lns, fmt.Sprintf("result for \"%v\"", r.TestID))
	}
	if r.Description != "" {
		buf := fmt.Sprintf("\tdescription: %v", r.Description)
		lns = append(lns, buf)
	}
	if r.MasterResult {
		lns = append(lns, "\tmaster result: true")
	} else {
		buf := "\tmaster result: false"
		if r.HasTrueResults {
			buf = buf + ", has true results, failure caused by dependency"
		}
		lns = append(lns, buf)
	}
	if len(r.Tags) > 0 {
		for _, x := range r.Tags {
			lns = append(lns, fmt.Sprintf("\ttag: %v: %v", x.Key, x.Value))
		}
	}
	if r.IsError {
		buf := fmt.Sprintf("\t[error] error: %v", r.Error)
		lns = append(lns, buf)
	}
	for _, x := range r.Results {
		buf := fmt.Sprintf("\t[%v] identifier: \"%v\"", x.Result, x.Identifier)
		lns = append(lns, buf)
	}
	return strings.Join(lns, "\n")
}
<|endoftext|>


<commit_before>package misc

var Schools = map[string][]string{
	"Banning High School": []string{
		"banning high school",
		"banning high",
		"banning",
	},
	"California Academy of Mathematics and Science": []string{
		"california academy of mathematics and science",
		"cams",
	},
	"Da Vinci Communications": []string{
		"davinci communications",
	},
	"El Segundo High School": []string{
		"es",
		"eshs",
		"el segundo",
		"gundo",
	},
	"Harbor Teacher Preparation Academy": []string{
		"htpa",
	},
	"Hawthorne High School": []string{
		"hawthorne high school",
		"hawthorne high",
		"hawthorne hs",
		"hhs",
	},
	"Hawthorne Math and Science Academy": []string{
		"hmsa",
		"hms",
	},
	"Lawndale High School": []string{
		"lawndale",
		"lawndale high",
		"lawndale high school",
		"lawndale hs",
	},
	"Leuzinger High School": []string{
		"leuzinger hs",
		"leuzinger high school",
		"leuzinger",
	},
	"Mary Star of the Sea High School": []string{
		"mary star high",
		"mary star",
	},
	"Mira Costa High School": []string{
		"costa",
		"mira costa",
		"mira costa high",
		"mira costa high school",
	},
	"North High School": []string{
		"north high",
		"north",
	},
	"Palos Verdes High School": []string{
		"pv",
		"pvhs",
		"palos verdes hs",
		"palos verdes high school",
	},
	"Palos Verdes Peninsula High School": []string{
		"pvphs",
	},
	"Redondo Union High School": []string{
		"redondo union high school",
		"redondo high",
		"redondo",
	},
	"Santa Monica High School": []string{
		"samohi",
		"smhs",
	},
	"South High School": []string{
		"south high school",
		"south high",
		"south",
		"shs",
	},
	"San Pedro High School": []string{
		"sphs",
		"san pedro",
		"san pedro high school",
	},
	"Torrance High School": []string{
		"torrance high",
	},
	"West High School": []string{
		"west",
		"west high",
		"west torrance high school",
		"west high sko",
	},
}
<commit_msg>Update schools.<commit_after>package misc

var Schools = map[string][]string{
	"Banning High School": []string{
		"banning high school",
		"banning high",
		"banning",
	},
	"California Academy of Mathematics and Science": []string{
		"california academy of mathematics and science",
		"cams",
	},
	"Da Vinci Communications": []string{
		"davinci communications",
	},
	"El Segundo High School": []string{
		"es",
		"eshs",
		"el segundo",
		"gundo",
	},
	"Glendale High School": []string{
		"glendale high school",
		"ghs",
		"glendale",
		"glendale high",
		"glendale hs",
	},
	"Harbor Teacher Preparation Academy": []string{
		"htpa",
	},
	"Hawthorne High School": []string{
		"hawthorne high school",
		"hawthorne high",
		"hawthorne hs",
		"hhs",
	},
	"Hawthorne Math and Science Academy": []string{
		"hmsa",
		"hms",
	},
	"Lawndale High School": []string{
		"lawndale",
		"lawndale high",
		"lawndale high school",
		"lawndale hs",
	},
	"Leuzinger High School": []string{
		"leuzinger hs",
		"leuzinger high school",
		"leuzinger",
	},
	"Mary Star of the Sea High School": []string{
		"mary star high",
		"mary star",
	},
	"Mira Costa High School": []string{
		"mchs",
		"costa",
		"mira costa",
		"mira costa high",
		"mira costa high school",
	},
	"North High School": []string{
		"north high",
		"north",
	},
	"Palos Verdes High School": []string{
		"pv",
		"pvhs",
		"palos verdes hs",
		"palos verdes high school",
	},
	"Palos Verdes Peninsula High School": []string{
		"pvphs",
	},
	"Redondo Union High School": []string{
		"redondo union high school",
		"redondo high",
		"redondo",
	},
	"Santa Monica High School": []string{
		"samohi",
		"smhs",
	},
	"South High School": []string{
		"south high school",
		"south high",
		"south",
		"shs",
	},
	"San Pedro High School": []string{
		"sphs",
		"san pedro",
		"san pedro high school",
	},
	"Torrance High School": []string{
		"torrance high",
	},
	"West High School": []string{
		"whs",
		"west",
		"west high",
		"west high school",
		"west torrance high school",
		"west high sko",
	},
	"West Valley High School": []string{
		"wvhs",
		"west valley",
		"west valley hs",
		"west valley high",
		"west valley high school",
	},
}
<|endoftext|>


<commit_before>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock

import (
	"errors"
	"fmt"
	"reflect"
)

// Return creates an Action that simply returns the values passed to Return as
// arguments. You should make sure that these values are correctly typed to be
// return values for the mock method.
func Return(vals ...interface{}) Action {
	return &returnAction{vals}
}

type returnAction struct {
	returnVals []interface{}
}

func (a *returnAction) Invoke(vals []interface{}) []interface{} {
	return a.returnVals
}

func (a *returnAction) CheckType(signature reflect.Type) error {
	// Check the length of the return value.
	numOut := signature.NumOut()
	numVals := len(a.returnVals)

	if numOut != numVals {
		return errors.New(
			fmt.Sprintf("Return given %d vals; expected %d.", numVals, numOut))
	}

	// Check the type of each value to be returned.
	for i, val := range a.returnVals {
		expectedType := signature.Out(i)
		actualType := reflect.TypeOf(val)

		if expectedType != actualType {
			return errors.New(
				fmt.Sprintf(
					"Return arg %d: given %v; expected %v.",
					i,
					actualType,
					expectedType))
		}
	}

	return nil
}
<commit_msg>Clarified the semantics of Return.<commit_after>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 oglemock

import (
	"errors"
	"fmt"
	"reflect"
)

// Return creates an Action that simply returns the values passed to Return as
// arguments. You should make sure that these values are correctly typed to be
// return values for the mock method.
//
// The following special cases apply. Given an argument x to return and a
// corresponding type T in the method's signature:
//
//  *  If T is any numeric type, x may be an int that is in-range for that
//     type. This facilities using raw integer constants: Return(17).
//
//  *  If T is a floating-point or complex number type, x may be a float64.
//     This facilities using raw floating-point constants: Return(17.5).
//
//  *  If T is a complex number type, x may be a complex128. This facilities
//     using raw complex constants: Return(17+2i).
//
//  *  If T is an interface type, x may be any value that implements T
//     (including nil).
//
func Return(vals ...interface{}) Action {
	return &returnAction{vals}
}

type returnAction struct {
	returnVals []interface{}
}

func (a *returnAction) Invoke(vals []interface{}) []interface{} {
	return a.returnVals
}

func (a *returnAction) CheckType(signature reflect.Type) error {
	// Check the length of the return value.
	numOut := signature.NumOut()
	numVals := len(a.returnVals)

	if numOut != numVals {
		return errors.New(
			fmt.Sprintf("Return given %d vals; expected %d.", numVals, numOut))
	}

	// Check the type of each value to be returned.
	for i, val := range a.returnVals {
		expectedType := signature.Out(i)
		actualType := reflect.TypeOf(val)

		if expectedType != actualType {
			return errors.New(
				fmt.Sprintf(
					"Return arg %d: given %v; expected %v.",
					i,
					actualType,
					expectedType))
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package job

import (
	"sort"
)

type JobOffer struct {
	Job Job

	// MachineBootIDs represents a set of machines for which this offer is valid.
	// If nil or len == 0 then all machines. Must be to be sorted.
	// MachineBootIDs started life as MachineBootIds in the datastore.
	// It cannot be changed without a migration
	MachineBootIDs []string `json:"MachineBootIds"`
}

func NewOfferFromJob(j Job, machineBootIDs []string) *JobOffer {
	return &JobOffer{
		Job:            j,
		MachineBootIDs: machineBootIDs,
	}
}

// OfferedTo returns true if job is being offered to specified machine.
func (jo *JobOffer) OfferedTo(machineBootID string) bool {
	// for backward compatibility: if not populated, assume all machines are considered
	if len(jo.MachineBootIDs) == 0 {
		return true
	}

	k := sort.SearchStrings(jo.MachineBootIDs, machineBootID)
	return k < len(jo.MachineBootIDs) && jo.MachineBootIDs[k] == machineBootID
}

type JobBid struct {
	JobName string

	// MachineBootID started life as MachineBootId in the datastore.
	// It cannot be changed without a migration.
	MachineBootID string `json:"MachineBootId"`
}

func NewBid(jobName string, machBootID string) *JobBid {
	return &JobBid{jobName, machBootID}
}
<commit_msg>fix(offer): Fix a bad comment<commit_after>package job

import (
	"sort"
)

type JobOffer struct {
	Job Job

	// MachineBootIDs represents a set of machines for which this offer is valid.
	// If nil or len == 0 then all machines. Must be sorted.
	// MachineIDs started life as MachineBootIds in the datastore.
	// It cannot be changed without a migration
	MachineBootIDs []string `json:"MachineBootIds"`
}

func NewOfferFromJob(j Job, machineBootIDs []string) *JobOffer {
	return &JobOffer{
		Job:            j,
		MachineBootIDs: machineBootIDs,
	}
}

// OfferedTo returns true if job is being offered to specified machine.
func (jo *JobOffer) OfferedTo(machineBootID string) bool {
	// for backward compatibility: if not populated, assume all machines are considered
	if len(jo.MachineBootIDs) == 0 {
		return true
	}

	k := sort.SearchStrings(jo.MachineBootIDs, machineBootID)
	return k < len(jo.MachineBootIDs) && jo.MachineBootIDs[k] == machineBootID
}

type JobBid struct {
	JobName string

	// MachineBootID started life as MachineBootId in the datastore.
	// It cannot be changed without a migration.
	MachineBootID string `json:"MachineBootId"`
}

func NewBid(jobName string, machBootID string) *JobBid {
	return &JobBid{jobName, machBootID}
}
<|endoftext|>


<commit_before>package webgo

import (
	"context"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

// urlchars is regex to validate characters in a URI parameter
// const urlchars = `([a-zA-Z0-9\*\-+._~!$()=&',;:@%]+)`
// Regex prepared based on http://stackoverflow.com/a/4669750/1359163,
// https://tools.ietf.org/html/rfc3986
// Though the current one allows invalid characters in the URI parameter, it has better performance.
const (
	urlchars            = `([^/]+)`
	urlwildcard         = `(.*)`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

type ctxkey string

const wgoCtxKey = ctxkey("webgocontext")

var l *log.Logger
var validHTTPMethods = []string{http.MethodOptions, http.MethodHead, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete}

// customResponseWriter is a custom HTTP response writer
type customResponseWriter struct {
	http.ResponseWriter
	statusCode int
	written    bool
}

//WriteHeader is the interface implementation to get HTTP response code and add it to the custom response writer
func (crw *customResponseWriter) WriteHeader(code int) {
	if crw.written == false {
		crw.statusCode = code
		crw.ResponseWriter.WriteHeader(code)
		return
	}

	l.Println(errMultiHeaderWrite)
}

// Write is the interface implementation to respond to the HTTP request,
// but check if a response was already sent.
func (crw *customResponseWriter) Write(body []byte) (int, error) {
	if crw.written {
		l.Println(errMultiWrite)
		return 0, nil
	}

	crw.written = true
	return crw.ResponseWriter.Write(body)
}

// init initializes the logging variable
func init() {
	l = log.New(os.Stdout, "", 0)
}

// Route defines a route for each API
type Route struct {
	// Name is unique identifier for the route
	Name string
	// Method is the HTTP request method/type
	Method string
	// Pattern is the URI pattern to match
	Pattern string

	// HideAccessLog if enabled will not print the basic access log to console
	HideAccessLog bool

	// FallThroughPostResponse if enabled will execute all the handlers even if a response was already sent to the client
	FallThroughPostResponse bool

	// Handler is a slice of http.HandlerFunc which can be middlewares or anything else. Though only 1 of them will be allowed to respond to client.
	// subsequent writes from the following handlers will be ignored
	Handler []http.HandlerFunc
	G       *Globals // App globals

	// uriKeys is the list of URI params
	uriKeys []string

	// uriPatternString is the pattern string which is compiled to regex object
	uriPatternString string
	// uriPattern is the compiled regex to match the URI pattern
	uriPattern *regexp.Regexp
}

// WC is the webgocontext
type WC struct {
	Params map[string]string
	Route  *Route
}

// init prepares the URIKeys, compile regex for the provided pattern
func (r *Route) init() error {
	patternString := r.Pattern

	if strings.Contains(r.Pattern, ":") {
		// uriValues is a map of URI Key and it's respective value, this is calculated per request
		key := ""
		hasKey := false
		hasWildcard := false

		for i := 0; i < len(r.Pattern); i++ {
			char := string(r.Pattern[i])

			if char == ":" {
				hasKey = true
			} else if char == "*" {
				hasWildcard = true
			} else if hasKey && char != "/" {
				key += char
			} else if hasKey && len(key) > 0 {
				if hasWildcard {
					patternString = strings.Replace(patternString, ":"+key+"*", urlwildcard, 1)
				} else {
					patternString = strings.Replace(patternString, ":"+key, urlchars, 1)
				}

				for idx, k := range r.uriKeys {
					if key == k {
						l.Fatal(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
					}
				}

				r.uriKeys = append(r.uriKeys, key)

				hasWildcard, hasKey = false, false
				key = ""
			}
		}

		if hasKey && len(key) > 0 {
			if hasWildcard {
				patternString = strings.Replace(patternString, ":"+key+"*", urlwildcard, 1)
			} else {
				patternString = strings.Replace(patternString, ":"+key, urlchars, 1)
			}

			for idx, k := range r.uriKeys {
				if key == k {
					l.Fatal(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
				}
			}
			r.uriKeys = append(r.uriKeys, key)
		}

	}

	patternString = "^" + patternString + "$"

	// compile the regex for the pattern string calculated
	reg, err := regexp.Compile(patternString)
	if err != nil {
		return err
	}

	r.uriPattern = reg
	r.uriPatternString = patternString
	return nil
}

// matchAndGet will match the given requestURI with its pattern and set its URI params accordingly
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if ok := r.uriPattern.Match([]byte(requestURI)); !ok {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)

	var uriValues map[string]string
	if len(values) > 0 {
		uriValues = make(map[string]string, len(values)-1)
		for j := 1; j < len(values); j++ {
			uriValues[r.uriKeys[j-1]] = values[j]
		}
	}

	return true, uriValues
}

// Router is the HTTP router
type Router struct {
	handlers map[string][]*Route

	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	HideAccessLog bool
	NotFound      http.HandlerFunc
}

// ServeHTTP is the required `ServeHTTP` implementation to listen to requests HTTP requests
// for the server
func (rtr *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

	startTime := time.Now()

	crw := &customResponseWriter{
		ResponseWriter: rw,
	}

	var handlers []*Route

	switch req.Method {
	case http.MethodOptions:
		handlers = rtr.optHandlers
	case http.MethodHead:
		handlers = rtr.headHandlers
	case http.MethodGet:
		handlers = rtr.getHandlers
	case http.MethodPost:
		handlers = rtr.postHandlers
	case http.MethodPut:
		handlers = rtr.putHandlers
	case http.MethodPatch:
		handlers = rtr.patchHandlers
	case http.MethodDelete:
		handlers = rtr.deleteHandlers
	}

	var params map[string]string

	ok := false

	path := req.URL.EscapedPath()
	for _, route := range handlers {
		if ok, params = route.matchAndGet(path); !ok {
			continue
		}

		// webgo context object created and is injected to the request context
		reqwc := req.WithContext(
			context.WithValue(
				req.Context(),
				wgoCtxKey,
				&WC{
					Params: params,
					Route:  route,
				},
			),
		)

		for _, handler := range route.Handler {
			if crw.written == false {
				// If there has been no write to response writer yet
				handler(crw, reqwc)
			} else if route.FallThroughPostResponse {
				// run a handler post response write, only if fall through is enabled
				handler(crw, reqwc)
			} else {
				// Do not run any more handlers if already responded and no fall through enabled
				break
			}
		}

		if rtr.HideAccessLog == false && route.HideAccessLog == false {
			endTime := time.Now()
			l.Println(
				endTime.Format("2006-01-02 15:04:05 -0700 MST")+" "+req.Method+" "+req.RequestURI+" "+endTime.Sub(startTime).String(),
				crw.statusCode,
			)
		}

		return
	}

	// serve 404 when there are no matching routes
	rtr.NotFound(rw, req)
	if rtr.HideAccessLog == false {
		endTime := time.Now()
		l.Println(
			endTime.Format("2006-01-02 15:04:05 -0700 MST")+" "+req.Method+" "+req.URL.String()+" "+endTime.Sub(startTime).String(),
			http.StatusNotFound,
		)
	}
}

// Context returns the WebgoContext injected inside the HTTP request context
func Context(r *http.Request) *WC {
	return r.Context().Value(wgoCtxKey).(*WC)
}

// InitRouter initializes Router settings
func InitRouter(routes []*Route) *Router {
	var handlers = make(map[string][]*Route, len(validHTTPMethods))

	for _, validMethod := range validHTTPMethods {
		handlers[validMethod] = []*Route{}
	}

	for idx, route := range routes {
		found := false
		for _, validMethod := range validHTTPMethods {
			if route.Method == validMethod {
				found = true
			}
		}

		if found == false {
			l.Fatal("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handler == nil || len(route.Handler) == 0 {
			l.Fatal("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			l.Fatal("Unsupported URI pattern.", route.Pattern, err)
		}

		// checking if the URI pattern is duplicated
		for i := 0; i < idx; i++ {
			rt := routes[i]

			if rt.Name == route.Name {
				l.Println("Warning: Duplicate route name(\"" + rt.Name + "\") detected. Route name should be unique.")
			}

			if rt.Method == route.Method {
				// regex pattern match
				if ok, _ := rt.matchAndGet(route.Pattern); ok {
					l.Println("Warning: Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					l.Println("Note: Only the first route to match the URI pattern would handle the request")
				}
			}
		}

		handlers[route.Method] = append(handlers[route.Method], route)
	}

	return &Router{
		handlers: handlers,

		optHandlers:    handlers[http.MethodOptions],
		headHandlers:   handlers[http.MethodHead],
		getHandlers:    handlers[http.MethodGet],
		postHandlers:   handlers[http.MethodPost],
		putHandlers:    handlers[http.MethodPut],
		patchHandlers:  handlers[http.MethodPatch],
		deleteHandlers: handlers[http.MethodDelete],

		NotFound: http.NotFound,
	}
}
<commit_msg>improved performance in routing match, for URIs without any variables<commit_after>package webgo

import (
	"context"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

// urlchars is regex to validate characters in a URI parameter
// const urlchars = `([a-zA-Z0-9\*\-+._~!$()=&',;:@%]+)`
// Regex prepared based on http://stackoverflow.com/a/4669750/1359163,
// https://tools.ietf.org/html/rfc3986
// Though the current one allows invalid characters in the URI parameter, it has better performance.
const (
	urlchars            = `([^/]+)`
	urlwildcard         = `(.*)`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

type ctxkey string

const wgoCtxKey = ctxkey("webgocontext")

var l *log.Logger
var validHTTPMethods = []string{http.MethodOptions, http.MethodHead, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete}

// customResponseWriter is a custom HTTP response writer
type customResponseWriter struct {
	http.ResponseWriter
	statusCode int
	written    bool
}

//WriteHeader is the interface implementation to get HTTP response code and add it to the custom response writer
func (crw *customResponseWriter) WriteHeader(code int) {
	if crw.written == false {
		crw.statusCode = code
		crw.ResponseWriter.WriteHeader(code)
		return
	}

	l.Println(errMultiHeaderWrite)
}

// Write is the interface implementation to respond to the HTTP request,
// but check if a response was already sent.
func (crw *customResponseWriter) Write(body []byte) (int, error) {
	if crw.written {
		l.Println(errMultiWrite)
		return 0, nil
	}

	crw.written = true
	return crw.ResponseWriter.Write(body)
}

// init initializes the logging variable
func init() {
	l = log.New(os.Stdout, "", 0)
}

// Route defines a route for each API
type Route struct {
	// Name is unique identifier for the route
	Name string
	// Method is the HTTP request method/type
	Method string
	// Pattern is the URI pattern to match
	Pattern string

	// HideAccessLog if enabled will not print the basic access log to console
	HideAccessLog bool

	// FallThroughPostResponse if enabled will execute all the handlers even if a response was already sent to the client
	FallThroughPostResponse bool

	// Handler is a slice of http.HandlerFunc which can be middlewares or anything else. Though only 1 of them will be allowed to respond to client.
	// subsequent writes from the following handlers will be ignored
	Handler []http.HandlerFunc
	G       *Globals // App globals

	// uriKeys is the list of URI params
	uriKeys []string

	// uriPatternString is the pattern string which is compiled to regex object
	uriPatternString string
	// uriPattern is the compiled regex to match the URI pattern
	uriPattern *regexp.Regexp
}

// WC is the webgocontext
type WC struct {
	Params map[string]string
	Route  *Route
}

// init prepares the URIKeys, compile regex for the provided pattern
func (r *Route) init() error {
	patternString := r.Pattern

	if strings.Contains(r.Pattern, ":") {
		// uriValues is a map of URI Key and it's respective value, this is calculated per request
		key := ""
		hasKey := false
		hasWildcard := false

		for i := 0; i < len(r.Pattern); i++ {
			char := string(r.Pattern[i])

			if char == ":" {
				hasKey = true
			} else if char == "*" {
				hasWildcard = true
			} else if hasKey && char != "/" {
				key += char
			} else if hasKey && len(key) > 0 {
				if hasWildcard {
					patternString = strings.Replace(patternString, ":"+key+"*", urlwildcard, 1)
				} else {
					patternString = strings.Replace(patternString, ":"+key, urlchars, 1)
				}

				for idx, k := range r.uriKeys {
					if key == k {
						l.Fatal(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
					}
				}

				r.uriKeys = append(r.uriKeys, key)

				hasWildcard, hasKey = false, false
				key = ""
			}
		}

		if hasKey && len(key) > 0 {
			if hasWildcard {
				patternString = strings.Replace(patternString, ":"+key+"*", urlwildcard, 1)
			} else {
				patternString = strings.Replace(patternString, ":"+key, urlchars, 1)
			}

			for idx, k := range r.uriKeys {
				if key == k {
					l.Fatal(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
				}
			}
			r.uriKeys = append(r.uriKeys, key)
		}

	}

	patternString = "^" + patternString + "$"

	// compile the regex for the pattern string calculated
	reg, err := regexp.Compile(patternString)
	if err != nil {
		return err
	}

	r.uriPattern = reg
	r.uriPatternString = patternString
	return nil
}

// matchAndGet will match the given requestURI with its pattern and set its URI params accordingly
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if (r.Pattern != requestURI) && !r.uriPattern.Match([]byte(requestURI)) {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)

	var uriValues map[string]string
	if len(values) > 0 {
		uriValues = make(map[string]string, len(values)-1)
		for j := 1; j < len(values); j++ {
			uriValues[r.uriKeys[j-1]] = values[j]
		}
	}

	return true, uriValues
}

// Router is the HTTP router
type Router struct {
	handlers map[string][]*Route

	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	HideAccessLog bool
	NotFound      http.HandlerFunc
}

// ServeHTTP is the required `ServeHTTP` implementation to listen to requests HTTP requests
// for the server
func (rtr *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

	startTime := time.Now()

	crw := &customResponseWriter{
		ResponseWriter: rw,
	}

	var handlers []*Route

	switch req.Method {
	case http.MethodOptions:
		handlers = rtr.optHandlers
	case http.MethodHead:
		handlers = rtr.headHandlers
	case http.MethodGet:
		handlers = rtr.getHandlers
	case http.MethodPost:
		handlers = rtr.postHandlers
	case http.MethodPut:
		handlers = rtr.putHandlers
	case http.MethodPatch:
		handlers = rtr.patchHandlers
	case http.MethodDelete:
		handlers = rtr.deleteHandlers
	}

	var params map[string]string

	ok := false

	path := req.URL.EscapedPath()
	for _, route := range handlers {
		if ok, params = route.matchAndGet(path); !ok {
			continue
		}

		// webgo context object created and is injected to the request context
		reqwc := req.WithContext(
			context.WithValue(
				req.Context(),
				wgoCtxKey,
				&WC{
					Params: params,
					Route:  route,
				},
			),
		)

		for _, handler := range route.Handler {
			if crw.written == false {
				// If there has been no write to response writer yet
				handler(crw, reqwc)
			} else if route.FallThroughPostResponse {
				// run a handler post response write, only if fall through is enabled
				handler(crw, reqwc)
			} else {
				// Do not run any more handlers if already responded and no fall through enabled
				break
			}
		}

		if rtr.HideAccessLog == false && route.HideAccessLog == false {
			endTime := time.Now()
			l.Println(
				endTime.Format("2006-01-02 15:04:05 -0700 MST")+" "+req.Method+" "+req.RequestURI+" "+endTime.Sub(startTime).String(),
				crw.statusCode,
			)
		}

		return
	}

	// serve 404 when there are no matching routes
	rtr.NotFound(rw, req)
	if rtr.HideAccessLog == false {
		endTime := time.Now()
		l.Println(
			endTime.Format("2006-01-02 15:04:05 -0700 MST")+" "+req.Method+" "+req.URL.String()+" "+endTime.Sub(startTime).String(),
			http.StatusNotFound,
		)
	}
}

// Context returns the WebgoContext injected inside the HTTP request context
func Context(r *http.Request) *WC {
	return r.Context().Value(wgoCtxKey).(*WC)
}

// InitRouter initializes Router settings
func InitRouter(routes []*Route) *Router {
	var handlers = make(map[string][]*Route, len(validHTTPMethods))

	for _, validMethod := range validHTTPMethods {
		handlers[validMethod] = []*Route{}
	}

	for idx, route := range routes {
		found := false
		for _, validMethod := range validHTTPMethods {
			if route.Method == validMethod {
				found = true
			}
		}

		if found == false {
			l.Fatal("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handler == nil || len(route.Handler) == 0 {
			l.Fatal("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			l.Fatal("Unsupported URI pattern.", route.Pattern, err)
		}

		// checking if the URI pattern is duplicated
		for i := 0; i < idx; i++ {
			rt := routes[i]

			if rt.Name == route.Name {
				l.Println("Warning: Duplicate route name(\"" + rt.Name + "\") detected. Route name should be unique.")
			}

			if rt.Method == route.Method {
				// regex pattern match
				if ok, _ := rt.matchAndGet(route.Pattern); ok {
					l.Println("Warning: Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					l.Println("Note: Only the first route to match the URI pattern would handle the request")
				}
			}
		}

		handlers[route.Method] = append(handlers[route.Method], route)
	}

	return &Router{
		handlers: handlers,

		optHandlers:    handlers[http.MethodOptions],
		headHandlers:   handlers[http.MethodHead],
		getHandlers:    handlers[http.MethodGet],
		postHandlers:   handlers[http.MethodPost],
		putHandlers:    handlers[http.MethodPut],
		patchHandlers:  handlers[http.MethodPatch],
		deleteHandlers: handlers[http.MethodDelete],

		NotFound: http.NotFound,
	}
}
<|endoftext|>