<commit_before>package proto

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

import (
	"butler/crypto"
	"butler/null"
	pb "code.google.com/p/goprotobuf/proto"
	"manhattan/util/fileutil"
)

func NewMixer(path string) *Mixer {
	version, err := ioutil.ReadFile(filepath.Join(path, "VERSION"))

	if err != nil {
		panic("Couldn't find a version for mixer:" + path + ":" + err.Error())
	}

	versionStr := strings.TrimSpace(string(version))

	_, name := filepath.Split(path)

	return &Mixer{
		Name:      pb.String(name),
		Version:   pb.String(versionStr),
		Rewriters: make([]*File, 0),
		Package:   nil,
	}
}

func (m *Mixer) Write(path string) (outputPath string, err error) {

	name := null.GetString(m.Name)
	version := null.GetString(m.Version)
	outputPath = filepath.Join(path, name+"-"+version+".mxr")

	bytes, err := pb.Marshal(m)
	if err != nil {
		return
	}

	bytes = crypto.Encrypt(bytes)

	err = os.MkdirAll(path, fileutil.DIR_PERMS)
	if err != nil {
		return
	}

	err = ioutil.WriteFile(outputPath, bytes, 0644)
	if err != nil {
		return
	}

	return
}

func (m *Mixer) Inspect(printFunctions bool) {
	println("\tRewriters:")
	for _, rewriter := range m.Rewriters {
		fmt.Printf("\t\t -- %v\n", null.GetString(rewriter.Path))
	}

	println("\tRoot Package:")
	fmt.Printf(m.packageSummary(printFunctions))
}

func (m *Mixer) Unpack(path string) {
	fullPath := filepath.Join(path, null.GetString(m.Name)+"-"+null.GetString(m.Version))
	err := os.MkdirAll(fullPath, 0755)
	if err != nil {
		panic(err.Error())
	}

	err = m.unpackFiles(filepath.Join(fullPath, "rewriters"), m.Rewriters)
	if err != nil {
		panic(err)
	}

	summary := m.packageSummary(true)
	packageFile := filepath.Join(fullPath, "package-summary.txt")

	err = ioutil.WriteFile(packageFile, []byte(summary), 0644)

	if err != nil {
		fmt.Printf("Warning : Couldn't write package summary")
	}

	fmt.Printf("* %v\n", packageFile)
}

func (m *Mixer) unpackFiles(path string, files []*File) (err error) {
	fl := &FileList{
		RootDirectory: path,
		Files:         files,
	}

	err = fl.Unpack(true)

	return
}

func (m *Mixer) packageSummary(printFunctions bool) string {
	summary := ""
	if m.Package != nil {
		summary += fmt.Sprintf("\t\t -- Name: %v\n", null.GetString(m.Package.Name))
		summary += fmt.Sprintf("\t\t -- Types: %v\n", m.Package.Types)
		summary += fmt.Sprintf("\t\t -- Dependencies:  %v\n", m.Package.Dependencies)
		if printFunctions {
			summary += fmt.Sprintf("\t\t -- Functions (%v):\n", len(m.Package.Functions))
			for _, function := range m.Package.Functions {
				summary += fmt.Sprintf("\t\t\t %v\n", function.Stub(m.Package))
			}
		}
	}
	return summary
}
<commit_msg>Updating to use fileutil.FILE_PERMS<commit_after>package proto

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

import (
	"butler/crypto"
	"butler/null"
	pb "code.google.com/p/goprotobuf/proto"
	"manhattan/util/fileutil"
)

func NewMixer(path string) *Mixer {
	version, err := ioutil.ReadFile(filepath.Join(path, "VERSION"))

	if err != nil {
		panic("Couldn't find a version for mixer:" + path + ":" + err.Error())
	}

	versionStr := strings.TrimSpace(string(version))

	_, name := filepath.Split(path)

	return &Mixer{
		Name:      pb.String(name),
		Version:   pb.String(versionStr),
		Rewriters: make([]*File, 0),
		Package:   nil,
	}
}

func (m *Mixer) Write(path string) (outputPath string, err error) {

	name := null.GetString(m.Name)
	version := null.GetString(m.Version)
	outputPath = filepath.Join(path, name+"-"+version+".mxr")

	bytes, err := pb.Marshal(m)
	if err != nil {
		return
	}

	bytes = crypto.Encrypt(bytes)

	err = os.MkdirAll(path, fileutil.DIR_PERMS)
	if err != nil {
		return
	}

	err = ioutil.WriteFile(outputPath, bytes, fileutil.FILE_PERMS)
	if err != nil {
		return
	}

	return
}

func (m *Mixer) Inspect(printFunctions bool) {
	println("\tRewriters:")
	for _, rewriter := range m.Rewriters {
		fmt.Printf("\t\t -- %v\n", null.GetString(rewriter.Path))
	}

	println("\tRoot Package:")
	fmt.Printf(m.packageSummary(printFunctions))
}

func (m *Mixer) Unpack(path string) {
	fullPath := filepath.Join(path, null.GetString(m.Name)+"-"+null.GetString(m.Version))
	err := os.MkdirAll(fullPath, 0755)
	if err != nil {
		panic(err.Error())
	}

	err = m.unpackFiles(filepath.Join(fullPath, "rewriters"), m.Rewriters)
	if err != nil {
		panic(err)
	}

	summary := m.packageSummary(true)
	packageFile := filepath.Join(fullPath, "package-summary.txt")

	err = ioutil.WriteFile(packageFile, []byte(summary), 0644)

	if err != nil {
		fmt.Printf("Warning : Couldn't write package summary")
	}

	fmt.Printf("* %v\n", packageFile)
}

func (m *Mixer) unpackFiles(path string, files []*File) (err error) {
	fl := &FileList{
		RootDirectory: path,
		Files:         files,
	}

	err = fl.Unpack(true)

	return
}

func (m *Mixer) packageSummary(printFunctions bool) string {
	summary := ""
	if m.Package != nil {
		summary += fmt.Sprintf("\t\t -- Name: %v\n", null.GetString(m.Package.Name))
		summary += fmt.Sprintf("\t\t -- Types: %v\n", m.Package.Types)
		summary += fmt.Sprintf("\t\t -- Dependencies:  %v\n", m.Package.Dependencies)
		if printFunctions {
			summary += fmt.Sprintf("\t\t -- Functions (%v):\n", len(m.Package.Functions))
			for _, function := range m.Package.Functions {
				summary += fmt.Sprintf("\t\t\t %v\n", function.Stub(m.Package))
			}
		}
	}
	return summary
}
<|endoftext|>


<commit_before>package govkbot

import (
	"log"
	"strconv"
	"strings"
	"time"
)

const (
	vkAPIURL        = "https://api.vk.com/method/"
	vkAPIVer        = "5.52"
	messagesCount   = 200
	requestInterval = 400 // 3 requests per second VK limit
)

// VKBot - bot config
type VKBot struct {
	msgRoutes        map[string]func(*Message) string
	actionRoutes     map[string]func(*Message) string
	cmdHandlers      map[string]func(*Message) string
	msgHandlers      map[string]func(*Message) string
	errorHandler     func(*Message, error)
	LastMsg          int
	markedMessages   map[int]*Message
	lastUserMessages map[int]int
	lastChatMessages map[int]int
	autoFriend       bool
}

var bot = newBot()

//API - bot API
var API = newAPI()

// SetDebug - enable/disable debug messages logging
func SetDebug(debug bool) {
	API.DEBUG = debug
}

func newBot() *VKBot {
	return &VKBot{
		msgRoutes:        make(map[string]func(*Message) string),
		actionRoutes:     make(map[string]func(*Message) string),
		markedMessages:   make(map[int]*Message),
		lastUserMessages: make(map[int]int),
		lastChatMessages: make(map[int]int),
	}
}

func newAPI() *VkAPI {
	return &VkAPI{
		Token:           "",
		URL:             vkAPIURL,
		Ver:             vkAPIVer,
		MessagesCount:   messagesCount,
		RequestInterval: requestInterval,
		DEBUG:           false,
		HTTPS:           true,
	}
}

// SetToken - set bot token
func SetToken(token string) {
	API.Token = token
}

// SetAutoFriend - enables mutual auto friending
func SetAutoFriend(af bool) {
	bot.autoFriend = af
}

// SetAPI - setup API config
func SetAPI(token string, url string, ver string) {
	SetToken(token)
	if url != "" {
		API.URL = url
	}
	if ver != "" {
		API.Ver = ver
	}
}

// SetLang - sets VK response language. Default auto. Available: en, ru, ua, be, es, fi, de, it
func SetLang(lang string) {
	API.Lang = lang
}

// HandleMessage - add substr message handler.
// Function must return string to reply or "" (if no reply)
// You can use m.Reply(string), if need more replies in handler
func HandleMessage(command string, handler func(*Message) string) {
	bot.msgRoutes[command] = handler
}

// HandleAction - add action handler.
// Function must return string to reply or "" (if no reply)
// You can use m.Reply(string), if need more replies in handler
func HandleAction(command string, handler func(*Message) string) {
	bot.actionRoutes[command] = handler
}

// HandleError - add error handler
func HandleError(handler func(*Message, error)) {
	bot.errorHandler = handler
}

// GetMessages - request unread messages from VK (more than 200)
func GetMessages() ([]*Message, error) {
	var allMessages []*Message
	lastMsg := bot.LastMsg
	offset := 0
	var err error
	var messages *Messages
	for {
		messages, err = API.GetMessages(API.MessagesCount, offset)
		if len(messages.Items) > 0 {
			if messages.Items[0].ID > lastMsg {
				lastMsg = messages.Items[0].ID
			}
		}
		allMessages = append(allMessages, messages.Items...)
		if bot.LastMsg > 0 {
			if len(messages.Items) > 0 {
				if messages.Items[len(messages.Items)-1].ID <= bot.LastMsg {
					bot.LastMsg = lastMsg
					break
				}
			} else {
				break
			}
			offset += API.MessagesCount
		} else {
			bot.LastMsg = lastMsg
			break
		}
	}
	if offset > 0 {
		API.NotifyAdmin("many messages in interval. offset: " + strconv.Itoa(offset))
	}
	return allMessages, err
}

func sendError(msg *Message, err error) {
	if bot.errorHandler != nil {
		bot.errorHandler(msg, err)
	} else {
		log.Fatalf("VKBot error: %+v\n", err.Error())
	}

}

//RouteAction routes an action
func RouteAction(m *Message) (replies []string, err error) {
	if m.Action != "" {
		debugPrint("route action: %+v\n", m.Action)
		for k, v := range bot.actionRoutes {
			if m.Action == k {
				bot.markedMessages[m.ID] = m
				msg := v(m)
				if msg != "" {
					replies = append(replies, msg)
				}
			}
		}
	}
	return replies, nil
}

// RouteMessage routes single message
func RouteMessage(m *Message) (replies []string, err error) {
	message := strings.TrimSpace(strings.ToLower(m.Body))
	if HasPrefix(message, "/ ") {
		message = "/" + TrimPrefix(message, "/ ")
	}
	if m.Action != "" {
		replies, err = RouteAction(m)
		return replies, err
	}
	marked := false
	for k, v := range bot.msgRoutes {
		if HasPrefix(message, k) {
			msg := v(m)
			if msg != "" {
				marked = true
				_, ok := bot.markedMessages[m.ID]
				if ok {
					delete(bot.markedMessages, m.ID)
				}
				replies = append(replies, msg)
			} else {
				if !marked {
					bot.markedMessages[m.ID] = m
				}
			}
		}
	}
	return replies, nil
}

// RouteMessages routes inbound messages
func RouteMessages(messages []*Message) (result map[*Message][]string) {
	result = make(map[*Message][]string)
	for _, m := range messages {
		//if m.ID <= bot.LastMsg {
		//	break
		//}
		if m.ReadState == 0 {
			replies, err := RouteMessage(m)
			if err != nil {
				sendError(m, err)
			}
			if len(replies) > 0 {
				result[m] = replies
			}
		}
	}
	return result
}

// MainRoute - main router func. Working cycle Listen.
func MainRoute() {
	bot.markedMessages = make(map[int]*Message)
	messages, err := GetMessages()
	if err != nil {
		sendError(nil, err)
	}
	replies := RouteMessages(messages)
	for m, msgs := range replies {
		for _, msg := range msgs {
			if msg != "" {
				_, err = m.Reply(msg)
				if err != nil {
					sendError(m, err)
				}
			}
		}
	}

	for _, m := range bot.markedMessages {
		m.MarkAsRead()
	}
}

// Listen - start server
func Listen(token string, url string, ver string, adminID int) {
	SetAPI(token, url, ver)
	API.AdminID = adminID
	u, err := API.Me()
	if err != nil {
		sendError(nil, err)
	}
	API.UID = u.ID

	go friendReceiver()

	c := time.Tick(3 * time.Second)
	for range c {
		MainRoute()
	}
}

// CheckFriends checking friend invites and mathes and deletes mutual
func CheckFriends() {
	uids, _ := API.GetFriendRequests(false)
	if len(uids) > 0 {
		for _, uid := range uids {
			API.AddFriend(uid)
			for k, v := range bot.actionRoutes {
				if k == "friend_add" {
					m := Message{Action: "friend_add", UserID: uid}
					v(&m)
				}
			}
		}
	}
	uids, _ = API.GetFriendRequests(true)
	if len(uids) > 0 {
		for _, uid := range uids {
			API.DeleteFriend(uid)
			for k, v := range bot.actionRoutes {
				if k == "friend_delete" {
					m := Message{Action: "friend_delete", UserID: uid}
					v(&m)
				}
			}
		}
	}
}

func friendReceiver() {
	CheckFriends()
	c := time.Tick(30 * time.Second)
	for range c {
		CheckFriends()
	}
}

// NotifyAdmin - notify AdminID by VK
func NotifyAdmin(msg string) error {
	return API.NotifyAdmin(msg)
}
<commit_msg>log message if error<commit_after>package govkbot

import (
	"log"
	"strconv"
	"strings"
	"time"
)

const (
	vkAPIURL        = "https://api.vk.com/method/"
	vkAPIVer        = "5.52"
	messagesCount   = 200
	requestInterval = 400 // 3 requests per second VK limit
)

// VKBot - bot config
type VKBot struct {
	msgRoutes        map[string]func(*Message) string
	actionRoutes     map[string]func(*Message) string
	cmdHandlers      map[string]func(*Message) string
	msgHandlers      map[string]func(*Message) string
	errorHandler     func(*Message, error)
	LastMsg          int
	markedMessages   map[int]*Message
	lastUserMessages map[int]int
	lastChatMessages map[int]int
	autoFriend       bool
}

var bot = newBot()

//API - bot API
var API = newAPI()

// SetDebug - enable/disable debug messages logging
func SetDebug(debug bool) {
	API.DEBUG = debug
}

func newBot() *VKBot {
	return &VKBot{
		msgRoutes:        make(map[string]func(*Message) string),
		actionRoutes:     make(map[string]func(*Message) string),
		markedMessages:   make(map[int]*Message),
		lastUserMessages: make(map[int]int),
		lastChatMessages: make(map[int]int),
	}
}

func newAPI() *VkAPI {
	return &VkAPI{
		Token:           "",
		URL:             vkAPIURL,
		Ver:             vkAPIVer,
		MessagesCount:   messagesCount,
		RequestInterval: requestInterval,
		DEBUG:           false,
		HTTPS:           true,
	}
}

// SetToken - set bot token
func SetToken(token string) {
	API.Token = token
}

// SetAutoFriend - enables mutual auto friending
func SetAutoFriend(af bool) {
	bot.autoFriend = af
}

// SetAPI - setup API config
func SetAPI(token string, url string, ver string) {
	SetToken(token)
	if url != "" {
		API.URL = url
	}
	if ver != "" {
		API.Ver = ver
	}
}

// SetLang - sets VK response language. Default auto. Available: en, ru, ua, be, es, fi, de, it
func SetLang(lang string) {
	API.Lang = lang
}

// HandleMessage - add substr message handler.
// Function must return string to reply or "" (if no reply)
// You can use m.Reply(string), if need more replies in handler
func HandleMessage(command string, handler func(*Message) string) {
	bot.msgRoutes[command] = handler
}

// HandleAction - add action handler.
// Function must return string to reply or "" (if no reply)
// You can use m.Reply(string), if need more replies in handler
func HandleAction(command string, handler func(*Message) string) {
	bot.actionRoutes[command] = handler
}

// HandleError - add error handler
func HandleError(handler func(*Message, error)) {
	bot.errorHandler = handler
}

// GetMessages - request unread messages from VK (more than 200)
func GetMessages() ([]*Message, error) {
	var allMessages []*Message
	lastMsg := bot.LastMsg
	offset := 0
	var err error
	var messages *Messages
	for {
		messages, err = API.GetMessages(API.MessagesCount, offset)
		if len(messages.Items) > 0 {
			if messages.Items[0].ID > lastMsg {
				lastMsg = messages.Items[0].ID
			}
		}
		allMessages = append(allMessages, messages.Items...)
		if bot.LastMsg > 0 {
			if len(messages.Items) > 0 {
				if messages.Items[len(messages.Items)-1].ID <= bot.LastMsg {
					bot.LastMsg = lastMsg
					break
				}
			} else {
				break
			}
			offset += API.MessagesCount
		} else {
			bot.LastMsg = lastMsg
			break
		}
	}
	if offset > 0 {
		API.NotifyAdmin("many messages in interval. offset: " + strconv.Itoa(offset))
	}
	return allMessages, err
}

func sendError(msg *Message, err error) {
	if bot.errorHandler != nil {
		bot.errorHandler(msg, err)
	} else {
		log.Fatalf("VKBot error: %+v\n", err.Error())
	}

}

//RouteAction routes an action
func RouteAction(m *Message) (replies []string, err error) {
	if m.Action != "" {
		debugPrint("route action: %+v\n", m.Action)
		for k, v := range bot.actionRoutes {
			if m.Action == k {
				bot.markedMessages[m.ID] = m
				msg := v(m)
				if msg != "" {
					replies = append(replies, msg)
				}
			}
		}
	}
	return replies, nil
}

// RouteMessage routes single message
func RouteMessage(m *Message) (replies []string, err error) {
	message := strings.TrimSpace(strings.ToLower(m.Body))
	if HasPrefix(message, "/ ") {
		message = "/" + TrimPrefix(message, "/ ")
	}
	if m.Action != "" {
		replies, err = RouteAction(m)
		return replies, err
	}
	marked := false
	for k, v := range bot.msgRoutes {
		if HasPrefix(message, k) {
			msg := v(m)
			if msg != "" {
				marked = true
				_, ok := bot.markedMessages[m.ID]
				if ok {
					delete(bot.markedMessages, m.ID)
				}
				replies = append(replies, msg)
			} else {
				if !marked {
					bot.markedMessages[m.ID] = m
				}
			}
		}
	}
	return replies, nil
}

// RouteMessages routes inbound messages
func RouteMessages(messages []*Message) (result map[*Message][]string) {
	result = make(map[*Message][]string)
	for _, m := range messages {
		//if m.ID <= bot.LastMsg {
		//	break
		//}
		if m.ReadState == 0 {
			replies, err := RouteMessage(m)
			if err != nil {
				sendError(m, err)
			}
			if len(replies) > 0 {
				result[m] = replies
			}
		}
	}
	return result
}

// MainRoute - main router func. Working cycle Listen.
func MainRoute() {
	bot.markedMessages = make(map[int]*Message)
	messages, err := GetMessages()
	if err != nil {
		sendError(nil, err)
	}
	replies := RouteMessages(messages)
	for m, msgs := range replies {
		for _, msg := range msgs {
			if msg != "" {
				_, err = m.Reply(msg)
				if err != nil {
					log.Printf("Error sending message: '%+v'\n", msg)
					sendError(m, err)
				}
			}
		}
	}

	for _, m := range bot.markedMessages {
		m.MarkAsRead()
	}
}

// Listen - start server
func Listen(token string, url string, ver string, adminID int) {
	SetAPI(token, url, ver)
	API.AdminID = adminID
	u, err := API.Me()
	if err != nil {
		sendError(nil, err)
	}
	API.UID = u.ID

	go friendReceiver()

	c := time.Tick(3 * time.Second)
	for range c {
		MainRoute()
	}
}

// CheckFriends checking friend invites and mathes and deletes mutual
func CheckFriends() {
	uids, _ := API.GetFriendRequests(false)
	if len(uids) > 0 {
		for _, uid := range uids {
			API.AddFriend(uid)
			for k, v := range bot.actionRoutes {
				if k == "friend_add" {
					m := Message{Action: "friend_add", UserID: uid}
					v(&m)
				}
			}
		}
	}
	uids, _ = API.GetFriendRequests(true)
	if len(uids) > 0 {
		for _, uid := range uids {
			API.DeleteFriend(uid)
			for k, v := range bot.actionRoutes {
				if k == "friend_delete" {
					m := Message{Action: "friend_delete", UserID: uid}
					v(&m)
				}
			}
		}
	}
}

func friendReceiver() {
	CheckFriends()
	c := time.Tick(30 * time.Second)
	for range c {
		CheckFriends()
	}
}

// NotifyAdmin - notify AdminID by VK
func NotifyAdmin(msg string) error {
	return API.NotifyAdmin(msg)
}
<|endoftext|>


<commit_before>package webgo

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// 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         = `(.*)`
	trailingSlash       = `[\/]?`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

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

type ctxkey string

const wgoCtxKey = ctxkey("webgocontext")

// 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 {
		warnLogger.Println(errMultiHeaderWrite)
		return
	}

	crw.statusCode = code
	crw.ResponseWriter.WriteHeader(code)
}

// 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 {
		warnLogger.Println(errMultiWrite)
		return 0, nil
	}

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

// 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
	// TrailingSlash if set to true, the URI will be matched with or without
	// a trailing slash. Note: It does not *do* a redirect.
	TrailingSlash bool

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

	// Handlers 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
	Handlers []http.HandlerFunc

	// uriKeys is the list of URI parameter variables available for this route
	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
}

// 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 {
				regexPattern := ""
				patternKey := ""
				if hasWildcard {
					patternKey = fmt.Sprintf(":%s*", key)
					regexPattern = urlwildcard
				} else {
					patternKey = fmt.Sprintf(":%s", key)
					regexPattern = urlchars
				}

				patternString = strings.Replace(patternString, patternKey, regexPattern, 1)

				for idx, k := range r.uriKeys {
					if key == k {
						errLogger.Fatalln(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 {
			regexPattern := ""
			patternKey := ""
			if hasWildcard {
				patternKey = fmt.Sprintf(":%s*", key)
				regexPattern = urlwildcard
			} else {
				patternKey = fmt.Sprintf(":%s", key)
				regexPattern = urlchars
			}

			patternString = strings.Replace(patternString, patternKey, regexPattern, 1)

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

	}

	if r.TrailingSlash {
		patternString = fmt.Sprintf("^%s%s$", patternString, trailingSlash)
	} else {
		patternString = fmt.Sprintf("^%s$", 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 returns if the request URI matches the pattern defined in a Route as well as
// all the URI parameters configured for the route.
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if r.Pattern == requestURI {
		return true, nil
	}

	if !r.uriPattern.Match([]byte(requestURI)) {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)
	if len(values) == 0 {
		return true, nil
	}

	uriValues := make(map[string]string, len(values)-1)
	for i := 1; i < len(values); i++ {
		uriValues[r.uriKeys[i-1]] = values[i]
	}
	return true, uriValues

}

// Router is the HTTP router
type Router struct {
	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	// NotFound is the generic handler for 404 resource not found response
	NotFound http.HandlerFunc
	// AppContext holds all the app specific context which is to be injected into all HTTP
	// request context
	AppContext map[string]interface{}

	// config has all the app config
	config       *Config
	serveHandler http.HandlerFunc
	// httpServer is the server handler for the active HTTP server
	httpServer *http.Server
	// httpsServer is the server handler for the active HTTPS server
	httpsServer *http.Server
}

func (rtr *Router) serve(rw http.ResponseWriter, req *http.Request) {
	var rr []*Route

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

	var route *Route
	ok := false
	params := make(map[string]string, 0)
	path := req.URL.EscapedPath()
	for _, r := range rr {
		if ok, params = r.matchAndGet(path); ok {
			route = r
			break
		}
	}

	if !ok {
		// serve 404 when there are no matching routes
		rtr.NotFound(rw, req)
		return
	}

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

	for _, handler := range route.Handlers {
		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
		}
	}
}

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

// Use adds a middleware layer
func (rtr *Router) Use(f func(http.ResponseWriter, *http.Request, http.HandlerFunc)) {
	srv := rtr.serveHandler
	rtr.serveHandler = func(rw http.ResponseWriter, req *http.Request) {
		f(rw, req, srv)
	}
}

// NewRouter initializes returns a new router instance with all the configurations and routes set
func NewRouter(cfg *Config, routes []*Route) *Router {
	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 {
			errLogger.Fatalln("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handlers == nil || len(route.Handlers) == 0 {
			errLogger.Fatalln("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			errLogger.Fatalln("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 {
				warnLogger.Println("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 {
					warnLogger.Println("Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					infoLogger.Println("Only the first route to match the URI pattern would handle the request")
				}
			}
		}

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

	r := &Router{
		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,
		AppContext: make(map[string]interface{}, 0),
		config:     cfg,
	}
	// setting the default serve handler
	r.serveHandler = r.serve

	return r
}
<commit_msg>updated route init to reduce cyclo complexity.<commit_after>package webgo

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// 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         = `(.*)`
	trailingSlash       = `[\/]?`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

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

type ctxkey string

const wgoCtxKey = ctxkey("webgocontext")

// 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 {
		warnLogger.Println(errMultiHeaderWrite)
		return
	}

	crw.statusCode = code
	crw.ResponseWriter.WriteHeader(code)
}

// 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 {
		warnLogger.Println(errMultiWrite)
		return 0, nil
	}

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

// 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
	// TrailingSlash if set to true, the URI will be matched with or without
	// a trailing slash. Note: It does not *do* a redirect.
	TrailingSlash bool

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

	// Handlers 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
	Handlers []http.HandlerFunc

	// uriKeys is the list of URI parameter variables available for this route
	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
}

// computerPatternStr computes the pattern string required for the route's regex.
// It also adds the URI parameter key to the route's `keys` field
func (r *Route) computerPatternStr(patternString string, hasWildcard bool, key string) string {
	regexPattern := ""
	patternKey := ""
	if hasWildcard {
		patternKey = fmt.Sprintf(":%s*", key)
		regexPattern = urlwildcard
	} else {
		patternKey = fmt.Sprintf(":%s", key)
		regexPattern = urlchars
	}

	patternString = strings.Replace(patternString, patternKey, regexPattern, 1)

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

	r.uriKeys = append(r.uriKeys, key)
	return patternString
}

// 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 {
				patternString = r.computerPatternStr(patternString, hasWildcard, key)
				hasWildcard, hasKey = false, false
				key = ""
			}
		}

		if hasKey && len(key) > 0 {
			patternString = r.computerPatternStr(patternString, hasWildcard, key)
		}

	}

	if r.TrailingSlash {
		patternString = fmt.Sprintf("^%s%s$", patternString, trailingSlash)
	} else {
		patternString = fmt.Sprintf("^%s$", 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 returns if the request URI matches the pattern defined in a Route as well as
// all the URI parameters configured for the route.
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if r.Pattern == requestURI {
		return true, nil
	}

	if !r.uriPattern.Match([]byte(requestURI)) {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)
	if len(values) == 0 {
		return true, nil
	}

	uriValues := make(map[string]string, len(values)-1)
	for i := 1; i < len(values); i++ {
		uriValues[r.uriKeys[i-1]] = values[i]
	}
	return true, uriValues

}

// Router is the HTTP router
type Router struct {
	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	// NotFound is the generic handler for 404 resource not found response
	NotFound http.HandlerFunc
	// AppContext holds all the app specific context which is to be injected into all HTTP
	// request context
	AppContext map[string]interface{}

	// config has all the app config
	config       *Config
	serveHandler http.HandlerFunc
	// httpServer is the server handler for the active HTTP server
	httpServer *http.Server
	// httpsServer is the server handler for the active HTTPS server
	httpsServer *http.Server
}

func (rtr *Router) serve(rw http.ResponseWriter, req *http.Request) {
	var rr []*Route

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

	var route *Route
	ok := false
	params := make(map[string]string, 0)
	path := req.URL.EscapedPath()
	for _, r := range rr {
		if ok, params = r.matchAndGet(path); ok {
			route = r
			break
		}
	}

	if !ok {
		// serve 404 when there are no matching routes
		rtr.NotFound(rw, req)
		return
	}

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

	for _, handler := range route.Handlers {
		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
		}
	}
}

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

// Use adds a middleware layer
func (rtr *Router) Use(f func(http.ResponseWriter, *http.Request, http.HandlerFunc)) {
	srv := rtr.serveHandler
	rtr.serveHandler = func(rw http.ResponseWriter, req *http.Request) {
		f(rw, req, srv)
	}
}

// NewRouter initializes returns a new router instance with all the configurations and routes set
func NewRouter(cfg *Config, routes []*Route) *Router {
	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 {
			errLogger.Fatalln("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handlers == nil || len(route.Handlers) == 0 {
			errLogger.Fatalln("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			errLogger.Fatalln("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 {
				warnLogger.Println("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 {
					warnLogger.Println("Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					infoLogger.Println("Only the first route to match the URI pattern would handle the request")
				}
			}
		}

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

	r := &Router{
		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,
		AppContext: make(map[string]interface{}, 0),
		config:     cfg,
	}
	// setting the default serve handler
	r.serveHandler = r.serve

	return r
}
<|endoftext|>


<commit_before>package air

import (
	"strings"
	"unsafe"
)

// router is a registry of all registered routes.
type router struct {
	tree      *node
	maxParams int
}

// routerSingleton is the singleton of the `router`.
var routerSingleton = &router{
	tree: &node{
		handlers: map[string]Handler{},
	},
}

// register registers a new route for the method and the path with the matching
// h in the r with the optional route-level gases..
func (r *router) register(method, path string, h Handler, gases ...Gas) {
	if path != "/" && hasLastSlash(path) {
		path = path[:len(path)-1]
	}

	if path == "" {
		panic("air: the path cannot be empty")
	} else if path[0] != '/' {
		panic("air: the path must start with the /")
	} else if strings.Contains(path, "//") {
		panic("air: the path cannot have the //")
	} else if strings.Count(path, ":") > 1 {
		ps := strings.Split(path, "/")
		for _, p := range ps {
			if strings.Count(p, ":") > 1 {
				panic("air: adjacent params in the path must " +
					"be separated by the /")
			}
		}
	} else if strings.Contains(path, "*") {
		if strings.Count(path, "*") > 1 {
			panic("air: only one * is allowed in the path")
		} else if path[len(path)-1] != '*' {
			panic("air: the * can only appear at the end of the " +
				"path")
		} else if strings.Contains(
			path[strings.LastIndex(path, "/"):],
			":",
		) {
			panic("air: adjacent param and the * in the path " +
				"must be separated by the /")
		}
	}

	nh := func(req *Request, res *Response) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(req, res)
	}

	paramNames := []string{}

	for i, l := 0, len(path); i < l; i++ {
		if path[i] == ':' {
			j := i + 1

			r.insert(method, path[:i], nil, staticKind, nil)

			for ; i < l && path[i] != '/'; i++ {
			}

			paramName := path[j:i]

			for _, pn := range paramNames {
				if pn == paramName {
					panic("air: the path cannot have " +
						"duplicate param names")
				}
			}

			paramNames = append(paramNames, paramName)
			path = path[:j] + path[i:]

			if i, l = j, len(path); i == l {
				r.insert(method, path, nh, paramKind, paramNames)
				return
			}

			r.insert(method, path[:i], nil, paramKind, paramNames)
		} else if path[i] == '*' {
			r.insert(method, path[:i], nil, staticKind, nil)
			paramNames = append(paramNames, "*")
			r.insert(method, path[:i+1], nh, anyKind, paramNames)
			return
		}
	}

	r.insert(method, path, nh, staticKind, paramNames)
}

// insert inserts a new route into the `tree` of the r.
func (r *router) insert(
	method,
	path string,
	h Handler,
	nk uint8,
	paramNames []string,
) {
	if l := len(paramNames); l > r.maxParams {
		r.maxParams = l
	}

	cn := r.tree // Current node as the root of the `tree` of the r

	var (
		s   = path // Search
		nn  *node  // Next node
		sl  int    // Search length
		pl  int    // Prefix length
		ll  int    // LCP length
		max int    // Max number of sl and pl
	)

	for {
		sl = len(s)
		pl = len(cn.prefix)
		ll = 0

		max = pl
		if sl < max {
			max = sl
		}

		for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
		}

		if ll == 0 {
			// At root node
			cn.label = s[0]
			cn.prefix = s
			if h != nil {
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			}
		} else if ll < pl {
			// Split node
			nn = &node{
				kind:       cn.kind,
				label:      cn.prefix[ll],
				prefix:     cn.prefix[ll:],
				handlers:   cn.handlers,
				parent:     cn,
				children:   cn.children,
				paramNames: cn.paramNames,
			}

			// Reset parent node
			cn.kind = staticKind
			cn.label = cn.prefix[0]
			cn.prefix = cn.prefix[:ll]
			cn.children = nil
			cn.handlers = map[string]Handler{}
			cn.paramNames = nil
			cn.children = append(cn.children, nn)

			if ll == sl {
				// At parent node
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			} else {
				// Create child node
				nn = &node{
					kind:       nk,
					label:      s[ll],
					prefix:     s[ll:],
					handlers:   map[string]Handler{},
					parent:     cn,
					paramNames: paramNames,
				}
				nn.handlers[method] = h
				cn.children = append(cn.children, nn)
			}
		} else if ll < sl {
			s = s[ll:]

			if nn = cn.childByLabel(s[0]); nn != nil {
				// Go deeper
				cn = nn
				continue
			}

			// Create child node
			nn = &node{
				kind:       nk,
				label:      s[0],
				prefix:     s,
				handlers:   map[string]Handler{},
				parent:     cn,
				paramNames: paramNames,
			}
			nn.handlers[method] = h
			cn.children = append(cn.children, nn)
		} else if h != nil {
			// Node already exists
			cn.handlers[method] = h
			cn.paramNames = paramNames
		}

		return
	}
}

// route returns a handler registered for the req.
func (r *router) route(req *Request) Handler {
	cn := r.tree // Current node as root of the `tree` of the r

	var (
		s   = pathClean(req.URL.Path)        // Search
		nn  *node                            // Next node
		nk  uint8                            // Next kind
		sn  *node                            // Saved node
		ss  string                           // Saved search
		sl  int                              // Search length
		pl  int                              // Prefix length
		ll  int                              // LCP length
		max int                              // Max number of sl and pl
		si  int                              // Start index
		pvs = make([]string, 0, r.maxParams) // Param values
	)

	// Search order: static > param > any
	for {
		if s == "" {
			break
		}

		pl = 0
		ll = 0

		if cn.label != ':' {
			sl = len(s)
			pl = len(cn.prefix)

			max = pl
			if sl < max {
				max = sl
			}

			for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
			}
		}

		if ll != pl {
			goto Struggle
		}

		if s = s[ll:]; s == "" {
			break
		}

		// Static node
		if nn = cn.child(s[0], staticKind); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = paramKind
				sn = cn
				ss = s
			}

			cn = nn

			continue
		}

		// Param node
	Param:
		if nn = cn.childByKind(paramKind); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = anyKind
				sn = cn
				ss = s
			}

			cn = nn

			for si = 0; si < len(s) && s[si] != '/'; si++ {
			}

			pvs = append(pvs, unescape(s[:si]))
			s = s[si:]

			continue
		}

		// Any node
	Any:
		if cn = cn.childByKind(anyKind); cn != nil {
			if hasLastSlash(req.URL.Path) {
				si = len(req.URL.Path) - 1
				for ; si > 0 && req.URL.Path[si] == '/'; si-- {
				}
				s += req.URL.Path[si+1:]
			}

			if len(pvs) < len(cn.paramNames) {
				pvs = append(pvs, unescape(s))
			} else {
				pvs[len(cn.paramNames)-1] = unescape(s)
			}

			break
		}

		// Struggle for the former node
	Struggle:
		if sn != nil {
			cn = sn
			sn = nil
			s = ss

			switch nk {
			case paramKind:
				goto Param
			case anyKind:
				goto Any
			}
		}

		return NotFoundHandler
	}

	if handler := cn.handlers[req.Method]; handler != nil {
		for i := range pvs {
			req.PathParams[cn.paramNames[i]] = pvs[i]
		}
		return handler
	} else if len(cn.handlers) != 0 {
		return MethodNotAllowedHandler
	}

	return NotFoundHandler
}

// hasLastSlash reports whether the s has the last '/'.
func hasLastSlash(s string) bool {
	return len(s) > 0 && s[len(s)-1] == '/'
}

// pathWithoutParamNames returns a path from the p without the param names.
func pathWithoutParamNames(p string) string {
	for i, l := 0, len(p); i < l; i++ {
		if p[i] == ':' {
			j := i + 1

			for ; i < l && p[i] != '/'; i++ {
			}

			p = p[:j] + p[i:]
			i, l = j, len(p)

			if i == l {
				break
			}
		}
	}
	return p
}

// pathClean returns a clean path from the p.
func pathClean(p string) string {
	if p == "" {
		return "/"
	}

	b := make([]byte, 0, len(p))

	i, l := 0, len(p)
	if p[0] == '/' {
		i = 1
	}

	for i < l {
		if p[i] == '/' {
			i++
		} else {
			b = append(b, '/')
			for ; i < l && p[i] != '/'; i++ {
				b = append(b, p[i])
			}
		}
	}

	return *(*string)(unsafe.Pointer(&b))
}

// unescape return a normal string unescaped from the s.
func unescape(s string) string {
	// Count the %, check that they're well-formed.
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '%' {
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]
				if len(s) > 3 {
					s = s[:3]
				}
				return ""
			}
			i += 2
		}
	}

	if n == 0 {
		return s
	}

	t := make([]byte, len(s)-2*n)
	for i, j := 0, 0; i < len(s); i++ {
		switch s[i] {
		case '%':
			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
			j++
			i += 2
		case '+':
			t[j] = ' '
			j++
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

// ishex reports whether the c is hex.
func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}
	return false
}

// unhex returns the normal byte from the hex char c.
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

// node is the node of the radix tree.
type node struct {
	kind       uint8
	label      byte
	prefix     string
	handlers   map[string]Handler
	parent     *node
	children   []*node
	paramNames []string
}

// node kinds
const (
	staticKind uint8 = iota
	paramKind
	anyKind
)

// child returns a child `node` of the n by the label and the kind.
func (n *node) child(label byte, kind uint8) *node {
	for _, c := range n.children {
		if c.label == label && c.kind == kind {
			return c
		}
	}
	return nil
}

// childByLabel returns a child `node` of the n by the l.
func (n *node) childByLabel(l byte) *node {
	for _, c := range n.children {
		if c.label == l {
			return c
		}
	}
	return nil
}

// childByKind returns a child `node` of the n by the k.
func (n *node) childByKind(k uint8) *node {
	for _, c := range n.children {
		if c.kind == k {
			return c
		}
	}
	return nil
}
<commit_msg>feat: add `nodeKind`<commit_after>package air

import (
	"strings"
	"unsafe"
)

// router is a registry of all registered routes.
type router struct {
	tree      *node
	maxParams int
}

// routerSingleton is the singleton of the `router`.
var routerSingleton = &router{
	tree: &node{
		handlers: map[string]Handler{},
	},
}

// register registers a new route for the method and the path with the matching
// h in the r with the optional route-level gases..
func (r *router) register(method, path string, h Handler, gases ...Gas) {
	if path != "/" && hasLastSlash(path) {
		path = path[:len(path)-1]
	}

	if path == "" {
		panic("air: the path cannot be empty")
	} else if path[0] != '/' {
		panic("air: the path must start with the /")
	} else if strings.Contains(path, "//") {
		panic("air: the path cannot have the //")
	} else if strings.Count(path, ":") > 1 {
		ps := strings.Split(path, "/")
		for _, p := range ps {
			if strings.Count(p, ":") > 1 {
				panic("air: adjacent params in the path must " +
					"be separated by the /")
			}
		}
	} else if strings.Contains(path, "*") {
		if strings.Count(path, "*") > 1 {
			panic("air: only one * is allowed in the path")
		} else if path[len(path)-1] != '*' {
			panic("air: the * can only appear at the end of the " +
				"path")
		} else if strings.Contains(
			path[strings.LastIndex(path, "/"):],
			":",
		) {
			panic("air: adjacent param and the * in the path " +
				"must be separated by the /")
		}
	}

	nh := func(req *Request, res *Response) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(req, res)
	}

	paramNames := []string{}

	for i, l := 0, len(path); i < l; i++ {
		if path[i] == ':' {
			j := i + 1

			r.insert(method, path[:i], nil, static, nil)

			for ; i < l && path[i] != '/'; i++ {
			}

			paramName := path[j:i]

			for _, pn := range paramNames {
				if pn == paramName {
					panic("air: the path cannot have " +
						"duplicate param names")
				}
			}

			paramNames = append(paramNames, paramName)
			path = path[:j] + path[i:]

			if i, l = j, len(path); i == l {
				r.insert(method, path, nh, param, paramNames)
				return
			}

			r.insert(method, path[:i], nil, param, paramNames)
		} else if path[i] == '*' {
			r.insert(method, path[:i], nil, static, nil)
			paramNames = append(paramNames, "*")
			r.insert(method, path[:i+1], nh, any, paramNames)
			return
		}
	}

	r.insert(method, path, nh, static, paramNames)
}

// insert inserts a new route into the `tree` of the r.
func (r *router) insert(
	method,
	path string,
	h Handler,
	nk nodeKind,
	paramNames []string,
) {
	if l := len(paramNames); l > r.maxParams {
		r.maxParams = l
	}

	cn := r.tree // Current node as the root of the `tree` of the r

	var (
		s   = path // Search
		nn  *node  // Next node
		sl  int    // Search length
		pl  int    // Prefix length
		ll  int    // LCP length
		max int    // Max number of sl and pl
	)

	for {
		sl = len(s)
		pl = len(cn.prefix)
		ll = 0

		max = pl
		if sl < max {
			max = sl
		}

		for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
		}

		if ll == 0 {
			// At root node
			cn.label = s[0]
			cn.prefix = s
			if h != nil {
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			}
		} else if ll < pl {
			// Split node
			nn = &node{
				kind:       cn.kind,
				label:      cn.prefix[ll],
				prefix:     cn.prefix[ll:],
				handlers:   cn.handlers,
				parent:     cn,
				children:   cn.children,
				paramNames: cn.paramNames,
			}

			// Reset parent node
			cn.kind = static
			cn.label = cn.prefix[0]
			cn.prefix = cn.prefix[:ll]
			cn.children = nil
			cn.handlers = map[string]Handler{}
			cn.paramNames = nil
			cn.children = append(cn.children, nn)

			if ll == sl {
				// At parent node
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			} else {
				// Create child node
				nn = &node{
					kind:       nk,
					label:      s[ll],
					prefix:     s[ll:],
					handlers:   map[string]Handler{},
					parent:     cn,
					paramNames: paramNames,
				}
				nn.handlers[method] = h
				cn.children = append(cn.children, nn)
			}
		} else if ll < sl {
			s = s[ll:]

			if nn = cn.childByLabel(s[0]); nn != nil {
				// Go deeper
				cn = nn
				continue
			}

			// Create child node
			nn = &node{
				kind:       nk,
				label:      s[0],
				prefix:     s,
				handlers:   map[string]Handler{},
				parent:     cn,
				paramNames: paramNames,
			}
			nn.handlers[method] = h
			cn.children = append(cn.children, nn)
		} else if h != nil {
			// Node already exists
			cn.handlers[method] = h
			cn.paramNames = paramNames
		}

		return
	}
}

// route returns a handler registered for the req.
func (r *router) route(req *Request) Handler {
	cn := r.tree // Current node as root of the `tree` of the r

	var (
		s   = pathClean(req.URL.Path)        // Search
		nn  *node                            // Next node
		nk  nodeKind                         // Next kind
		sn  *node                            // Saved node
		ss  string                           // Saved search
		sl  int                              // Search length
		pl  int                              // Prefix length
		ll  int                              // LCP length
		max int                              // Max number of sl and pl
		si  int                              // Start index
		pvs = make([]string, 0, r.maxParams) // Param values
	)

	// Search order: static > param > any
	for {
		if s == "" {
			break
		}

		pl = 0
		ll = 0

		if cn.label != ':' {
			sl = len(s)
			pl = len(cn.prefix)

			max = pl
			if sl < max {
				max = sl
			}

			for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
			}
		}

		if ll != pl {
			goto Struggle
		}

		if s = s[ll:]; s == "" {
			break
		}

		// Static node
		if nn = cn.child(s[0], static); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = param
				sn = cn
				ss = s
			}

			cn = nn

			continue
		}

		// Param node
	Param:
		if nn = cn.childByKind(param); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = any
				sn = cn
				ss = s
			}

			cn = nn

			for si = 0; si < len(s) && s[si] != '/'; si++ {
			}

			pvs = append(pvs, unescape(s[:si]))
			s = s[si:]

			continue
		}

		// Any node
	Any:
		if cn = cn.childByKind(any); cn != nil {
			if hasLastSlash(req.URL.Path) {
				si = len(req.URL.Path) - 1
				for ; si > 0 && req.URL.Path[si] == '/'; si-- {
				}
				s += req.URL.Path[si+1:]
			}

			if len(pvs) < len(cn.paramNames) {
				pvs = append(pvs, unescape(s))
			} else {
				pvs[len(cn.paramNames)-1] = unescape(s)
			}

			break
		}

		// Struggle for the former node
	Struggle:
		if sn != nil {
			cn = sn
			sn = nil
			s = ss

			switch nk {
			case param:
				goto Param
			case any:
				goto Any
			}
		}

		return NotFoundHandler
	}

	if handler := cn.handlers[req.Method]; handler != nil {
		for i := range pvs {
			req.PathParams[cn.paramNames[i]] = pvs[i]
		}
		return handler
	} else if len(cn.handlers) != 0 {
		return MethodNotAllowedHandler
	}

	return NotFoundHandler
}

// hasLastSlash reports whether the s has the last '/'.
func hasLastSlash(s string) bool {
	return len(s) > 0 && s[len(s)-1] == '/'
}

// pathWithoutParamNames returns a path from the p without the param names.
func pathWithoutParamNames(p string) string {
	for i, l := 0, len(p); i < l; i++ {
		if p[i] == ':' {
			j := i + 1

			for ; i < l && p[i] != '/'; i++ {
			}

			p = p[:j] + p[i:]
			i, l = j, len(p)

			if i == l {
				break
			}
		}
	}
	return p
}

// pathClean returns a clean path from the p.
func pathClean(p string) string {
	if p == "" {
		return "/"
	}

	b := make([]byte, 0, len(p))

	i, l := 0, len(p)
	if p[0] == '/' {
		i = 1
	}

	for i < l {
		if p[i] == '/' {
			i++
		} else {
			b = append(b, '/')
			for ; i < l && p[i] != '/'; i++ {
				b = append(b, p[i])
			}
		}
	}

	return *(*string)(unsafe.Pointer(&b))
}

// unescape return a normal string unescaped from the s.
func unescape(s string) string {
	// Count the %, check that they're well-formed.
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '%' {
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]
				if len(s) > 3 {
					s = s[:3]
				}
				return ""
			}
			i += 2
		}
	}

	if n == 0 {
		return s
	}

	t := make([]byte, len(s)-2*n)
	for i, j := 0, 0; i < len(s); i++ {
		switch s[i] {
		case '%':
			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
			j++
			i += 2
		case '+':
			t[j] = ' '
			j++
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

// ishex reports whether the c is hex.
func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}
	return false
}

// unhex returns the normal byte from the hex char c.
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

// node is the node of the radix tree.
type node struct {
	kind       nodeKind
	label      byte
	prefix     string
	handlers   map[string]Handler
	parent     *node
	children   []*node
	paramNames []string
}

// nodeKind is a kind of the `node`.
type nodeKind uint8

// node kinds
const (
	static nodeKind = iota
	param
	any
)

// child returns a child `node` of the n by the label and the kind.
func (n *node) child(label byte, kind nodeKind) *node {
	for _, c := range n.children {
		if c.label == label && c.kind == kind {
			return c
		}
	}
	return nil
}

// childByLabel returns a child `node` of the n by the l.
func (n *node) childByLabel(l byte) *node {
	for _, c := range n.children {
		if c.label == l {
			return c
		}
	}
	return nil
}

// childByKind returns a child `node` of the n by the k.
func (n *node) childByKind(k nodeKind) *node {
	for _, c := range n.children {
		if c.kind == k {
			return c
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package amber

import (
	"os"
	"fmt"
	"strings"
	"regexp"
	"net/http"
	"reflect"
	"runtime"
)

type route struct {
	pattern				string
	method				string
	isController 	bool
	cName					string
	cAction				string
	handler 			Handler
	router 				*router
	regex 				*regexp.Regexp
}

type router struct {
	namedRoutes 	map[string]*route
	routes				[]*route
}

func newRouter() *router {
	return &router{namedRoutes: make(map[string]*route)}
}

func (r *router) AddRoute(method string, pattern string, handler Handler) *route {
	route := newRoute(method, pattern, handler, r)
	r.routes = append(r.routes, route)

	if route.isController {
		route.Name(cName + "#" + cAction)
	}

	return route
}

func (r *router) searchRoute(method string, request string) (*route, Param) {
	for i, route := range r.routes {
		matches := route.regex.FindStringSubmatch(request)
		if route.method == method || method == "Any" {
			if len(matches) > 0 && matches[0] == request {
				params := make(Param)
				for i := 1; i < len(matches); i++ {
					params[route.regex.SubexpNames()[i]] = matches[i]
				}
				return r.routes[i], params
			}
		}
	}
	return nil, nil
}

func (r *router) getReverseUrl(name string, param []interface{}) string {
	route := getNamedRoute
	nParam := len(param)
	if route != nil {
		i := -1
		regex := regexp.MustCompile("{.*}")
		url := regex.ReplaceAllStringFunc(route.pattern, func(str string) string {
			i = i + 1
			if i <= nParam - 1 {
				return param[i].(string)
			} else {
				return ""
			}
		})

		return url
	}

	return ""
}

func (r *router) getNamedRoute(name string) *route {
	return r.namedRoutes[strings.ToLower(name)]
}

func (r *route) Name(name string) {
	r.router.namedRoutes[strings.ToLower(name)] = r
}

func newRoute(method string, pattern string, handler Handler, router *router) *route {
	regex := regexp.MustCompile("{[a-zA-Z0-9]+}")
	regexPattern := regex.ReplaceAllStringFunc(pattern, func(s string) string {
		return fmt.Sprintf("(?P<%s>[a-z]+)", s[1:len(s)-1])
	})

	cName := ""
	cAction := ""
	isController := false

	if isControllerHandler(handler) {
		var fn *runtime.Func
	  if fn = runtime.FuncForPC(reflect.ValueOf(handler).Pointer()); fn == nil {
			logger.Println("Failed to add route. Can't fetch controller function")
			return nil
	  }

	  isController = true
		token := strings.Split(fn.Name(), ".")
		cName = token[1][2:len(token[1])-1]
		cAction = token[2]
	}

	return &route{pattern, method, isController, cName, cAction, handler, router, regexp.MustCompile(regexPattern)}
}

func servePublic(rw http.ResponseWriter, req *http.Request) {
	var file http.File
	var err error
	var stat os.FileInfo
	fname := req.URL.Path[len("/public/"):]

	if !strings.HasPrefix(fname, ".") {
		if file, err = http.Dir("public").Open(fname); err == nil {
			if stat, err = file.Stat(); err == nil {
				if !stat.IsDir() {
					http.ServeContent(rw, req, req.URL.Path, stat.ModTime(), file)
					file.Close()
					return
				}
			}
		}
	}

	http.NotFound(rw, req)
}<commit_msg>fixed some minor errors<commit_after>package amber

import (
	"os"
	"fmt"
	"strings"
	"regexp"
	"net/http"
	"reflect"
	"runtime"
)

type route struct {
	pattern				string
	method				string
	isController 	bool
	cName					string
	cAction				string
	handler 			Handler
	router 				*router
	regex 				*regexp.Regexp
}

type router struct {
	namedRoutes 	map[string]*route
	routes				[]*route
}

func newRouter() *router {
	return &router{namedRoutes: make(map[string]*route)}
}

func (r *router) AddRoute(method string, pattern string, handler Handler) *route {
	route := newRoute(method, pattern, handler, r)
	r.routes = append(r.routes, route)

	if route.isController {
		route.Name(route.cName + "#" + route.cAction)
	}

	return route
}

func (r *router) searchRoute(method string, request string) (*route, Param) {
	for i, route := range r.routes {
		matches := route.regex.FindStringSubmatch(request)
		if route.method == method || method == "Any" {
			if len(matches) > 0 && matches[0] == request {
				params := make(Param)
				for i := 1; i < len(matches); i++ {
					params[route.regex.SubexpNames()[i]] = matches[i]
				}
				return r.routes[i], params
			}
		}
	}
	return nil, nil
}

func (r *router) getReverseUrl(name string, param []interface{}) string {
	route := r.getNamedRoute(name)
	nParam := len(param)
	if route != nil {
		i := -1
		regex := regexp.MustCompile("{.*}")
		url := regex.ReplaceAllStringFunc(route.pattern, func(str string) string {
			i = i + 1
			if i <= nParam - 1 {
				return param[i].(string)
			} else {
				return ""
			}
		})

		return url
	}

	return ""
}

func (r *router) getNamedRoute(name string) *route {
	return r.namedRoutes[strings.ToLower(name)]
}

func (r *route) Name(name string) {
	r.router.namedRoutes[strings.ToLower(name)] = r
}

func newRoute(method string, pattern string, handler Handler, router *router) *route {
	regex := regexp.MustCompile("{[a-zA-Z0-9]+}")
	regexPattern := regex.ReplaceAllStringFunc(pattern, func(s string) string {
		return fmt.Sprintf("(?P<%s>[a-z]+)", s[1:len(s)-1])
	})

	cName := ""
	cAction := ""
	isController := false

	if isControllerHandler(handler) {
		var fn *runtime.Func
	  if fn = runtime.FuncForPC(reflect.ValueOf(handler).Pointer()); fn == nil {
			logger.Println("Failed to add route. Can't fetch controller function")
			return nil
	  }

	  isController = true
		token := strings.Split(fn.Name(), ".")
		cName = token[1][2:len(token[1])-1]
		cAction = token[2]
	}

	return &route{pattern, method, isController, cName, cAction, handler, router, regexp.MustCompile(regexPattern)}
}

func servePublic(rw http.ResponseWriter, req *http.Request) {
	var file http.File
	var err error
	var stat os.FileInfo
	fname := req.URL.Path[len("/public/"):]

	if !strings.HasPrefix(fname, ".") {
		if file, err = http.Dir("public").Open(fname); err == nil {
			if stat, err = file.Stat(); err == nil {
				if !stat.IsDir() {
					http.ServeContent(rw, req, req.URL.Path, stat.ModTime(), file)
					file.Close()
					return
				}
			}
		}
	}

	http.NotFound(rw, req)
}<|endoftext|>


<commit_before>package typhon

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/monzo/terrors"
)

type Router interface {
	// OPTIONS is a shortcut for Register("OPTIONS", svc).
	OPTIONS(path string, svc Service)
	// GET is a shortcut for Register("GET", svc).
	GET(path string, svc Service)
	// HEAD is a shortcut for Register("HEAD", svc).
	HEAD(path string, svc Service)
	// POST is a shortcut for Register("POST", svc).
	POST(path string, svc Service)
	// PUT is a shortcut for Register("PUT", svc).
	PUT(path string, svc Service)
	// DELETE is a shortcut for Register("DELETE", svc).
	DELETE(path string, svc Service)
	// TRACE is a shortcut for Register("TRACE", svc).
	TRACE(path string, svc Service)
	// Register associates a Service with a method and path.
	Register(method, path string, svc Service)
	// Lookup returns the Service and extracted path parameters for the HTTP method and path.
	Lookup(method, path string) (svc Service, params map[string]string, ok bool)
	// Serve returns a Service which will route inbound requests to the enclosed routes.
	Serve() Service
	// Params returns extracted URL parameters, assuming the request has been routed and has captured parameters.
	Params(req Request) map[string]string
}

type router struct {
	impl *httprouter.Router
}

func NewRouter() Router {
	return &router{
		impl: httprouter.New()}
}

func (r *router) Register(method, path string, svc Service) {
	// Forgive me.
	r.impl.Handle(method, path, func(rw_ http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
		rw := rw_.(*routerRw)
		rw.svc = svc
	})
}

func (r *router) Lookup(method, path string) (Service, map[string]string, bool) {
	hf, params_, _ := r.impl.Lookup(method, path)
	if hf == nil {
		return nil, nil, false
	}

	params := make(map[string]string, len(params_))
	for _, p := range params_ {
		params[p.Key] = p.Value
	}

	rw := routerRw{}
	hf(&rw, nil, nil)
	return rw.svc, params, true
}

func (r *router) Serve() Service {
	return func(req Request) Response {
		svc, _, ok := r.Lookup(req.Method, req.URL.Path)
		if !ok {
			txt := fmt.Sprintf("No handler for %s %s", req.Method, req.URL.Path)
			rsp := NewResponse(req)
			rsp.Error = terrors.NotFound("no_handler", txt, nil)
			return rsp
		}
		return svc(req)
	}
}

func (r *router) Params(req Request) map[string]string {
	_, params, _ := r.Lookup(req.Method, req.URL.Path)
	return params
}

// Sugar
func (r *router) OPTIONS(path string, svc Service) { r.Register("OPTIONS", path, svc) }
func (r *router) GET(path string, svc Service)     { r.Register("GET", path, svc) }
func (r *router) HEAD(path string, svc Service)    { r.Register("HEAD", path, svc) }
func (r *router) POST(path string, svc Service)    { r.Register("POST", path, svc) }
func (r *router) PUT(path string, svc Service)     { r.Register("PUT", path, svc) }
func (r *router) DELETE(path string, svc Service)  { r.Register("DELETE", path, svc) }
func (r *router) TRACE(path string, svc Service)   { r.Register("TRACE", path, svc) }

// I'm sorry, dear reader, I really am. To do this properly is more work than I have the appetite for right now.
//
// Future me will remove this horrific cruft and provide a URL router that acts on Services directly, without needing
// the kabuki of a fake Handler and ResponseWriter.
//
// As it is, here's the fake ResponseWriter.
type routerRw struct {
	svc Service
}

func (r *routerRw) Header() http.Header         { return nil }
func (r *routerRw) WriteHeader(_ int)           {}
func (r *routerRw) Write(_ []byte) (int, error) { return 0, nil }
<commit_msg>Switch router internals to use labstack/echo<commit_after>package typhon

import (
	"fmt"
	"sync"

	"github.com/labstack/echo"
	"github.com/monzo/terrors"
)

type Router interface {
	// OPTIONS is a shortcut for Register("OPTIONS", svc).
	OPTIONS(path string, svc Service)
	// GET is a shortcut for Register("GET", svc).
	GET(path string, svc Service)
	// HEAD is a shortcut for Register("HEAD", svc).
	HEAD(path string, svc Service)
	// POST is a shortcut for Register("POST", svc).
	POST(path string, svc Service)
	// PUT is a shortcut for Register("PUT", svc).
	PUT(path string, svc Service)
	// DELETE is a shortcut for Register("DELETE", svc).
	DELETE(path string, svc Service)
	// TRACE is a shortcut for Register("TRACE", svc).
	TRACE(path string, svc Service)
	// Register associates a Service with a method and path.
	Register(method, path string, svc Service)
	// Lookup returns the Service and extracted path parameters for the HTTP method and path.
	Lookup(method, path string) (svc Service, params map[string]string, ok bool)
	// Serve returns a Service which will route inbound requests to the enclosed routes.
	Serve() Service
	// Params returns extracted URL parameters, assuming the request has been routed and has captured parameters.
	Params(req Request) map[string]string
}

type router struct {
	e    *echo.Echo
	r    *echo.Router
	svcs map[string]Service
	m    sync.RWMutex
}

// NewRouter vends a new implementation of Router
func NewRouter() Router {
	e := echo.New()
	return &router{
		e:    e,
		r:    echo.NewRouter(e),
		svcs: make(map[string]Service, 10)}
}

func (r *router) identityHandler(c echo.Context) error {
	return nil
}

func (r *router) Register(method, path string, svc Service) {
	r.m.Lock()
	defer r.m.Unlock()
	r.r.Add(method, path, r.identityHandler)
	r.svcs[method+path] = svc
}

func (r *router) Lookup(method, path string) (Service, map[string]string, bool) {
	c := r.e.AcquireContext()
	defer r.e.ReleaseContext(c)
	c.Reset(nil, nil)
	c.SetPath("") // Annoyingly, this isn't done as part of Reset()

	r.m.RLock()
	r.r.Find(method, path, c)
	if c.Path() == "" {
		r.m.RUnlock()
		return nil, nil, false
	}
	svc := r.svcs[method+c.Path()]
	r.m.RUnlock()

	if svc == nil {
		return nil, nil, false
	}

	names := c.ParamNames()
	params := make(map[string]string, len(names))
	for _, name := range names {
		params[name] = c.Param(name)
	}
	return svc, params, true

	// hf, params_, _ := r.impl.Lookup(method, path)
	// if hf == nil {
	// 	return nil, nil, false
	// }
	//
	// params := make(map[string]string, len(params_))
	// for _, p := range params_ {
	// 	params[p.Key] = p.Value
	// }
	//
	// rw := routerRw{}
	// hf(&rw, nil, nil)
	// return rw.svc, params, true
}

func (r *router) Serve() Service {
	return func(req Request) Response {
		svc, _, ok := r.Lookup(req.Method, req.URL.Path)
		if !ok {
			txt := fmt.Sprintf("No handler for %s %s", req.Method, req.URL.Path)
			rsp := NewResponse(req)
			rsp.Error = terrors.NotFound("no_handler", txt, nil)
			return rsp
		}
		return svc(req)
	}
}

func (r *router) Params(req Request) map[string]string {
	_, params, _ := r.Lookup(req.Method, req.URL.Path)
	return params
}

// Sugar
func (r *router) OPTIONS(path string, svc Service) { r.Register("OPTIONS", path, svc) }
func (r *router) GET(path string, svc Service)     { r.Register("GET", path, svc) }
func (r *router) HEAD(path string, svc Service)    { r.Register("HEAD", path, svc) }
func (r *router) POST(path string, svc Service)    { r.Register("POST", path, svc) }
func (r *router) PUT(path string, svc Service)     { r.Register("PUT", path, svc) }
func (r *router) DELETE(path string, svc Service)  { r.Register("DELETE", path, svc) }
func (r *router) TRACE(path string, svc Service)   { r.Register("TRACE", path, svc) }
<|endoftext|>


<commit_before>package tachyon

import (
	"sync"
	"time"
)

type RunResult struct {
	Task    *Task
	Result  *Result
	Runtime time.Duration
}

type Runner struct {
	env       *Environment
	plays     []*Play
	wait      sync.WaitGroup
	to_notify map[string]struct{}
	async     chan *AsyncAction
	report    Reporter

	Results []RunResult
	Runtime time.Duration
}

func NewRunner(env *Environment, plays []*Play) *Runner {
	r := &Runner{
		env:       env,
		plays:     plays,
		to_notify: make(map[string]struct{}),
		async:     make(chan *AsyncAction),
		report:    env.report,
	}

	go r.handleAsync()

	return r
}

func (r *Runner) SetReport(rep Reporter) {
	r.report = rep
}

func (r *Runner) AddNotify(n string) {
	r.to_notify[n] = struct{}{}
}

func (r *Runner) ShouldRunHandler(name string) bool {
	_, ok := r.to_notify[name]

	return ok
}

func (r *Runner) AsyncChannel() chan *AsyncAction {
	return r.async
}

func (r *Runner) Run(env *Environment) error {
	start := time.Now()
	defer func() {
		r.Runtime = time.Since(start)
	}()

	r.report.StartTasks(r)

	for _, play := range r.plays {
		fs := NewFutureScope(play.Vars)

		for _, task := range play.Tasks {
			err := r.runTask(env, play, task, fs, fs)
			if err != nil {
				return err
			}
		}

		r.Results = append(r.Results, fs.Results()...)
	}

	r.report.FinishTasks(r)

	r.wait.Wait()

	r.report.StartHandlers(r)

	for _, play := range r.plays {
		fs := NewFutureScope(play.Vars)

		for _, task := range play.Handlers {
			if r.ShouldRunHandler(task.Name()) {
				err := r.runTask(env, play, task, fs, fs)

				if err != nil {
					return err
				}
			}
		}

		fs.Wait()
	}

	r.report.FinishHandlers(r)

	return nil
}

func RunAdhocTask(cmd, args string) (*Result, error) {
	env := NewEnv(NewNestedScope(nil), &Config{})
	defer env.Cleanup()

	task := AdhocTask(cmd, args)

	str, err := ExpandVars(env.Vars, task.Args())
	if err != nil {
		return nil, err
	}

	obj, err := MakeCommand(env.Vars, task, str)
	if err != nil {
		return nil, err
	}

	ce := &CommandEnv{env, env.Paths}

	return obj.Run(ce, str)
}

type PriorityScope struct {
	task Vars
	rest Scope
}

func (p *PriorityScope) Get(key string) (Value, bool) {
	if p.task != nil {
		if v, ok := p.task[key]; ok {
			return Any(v), true
		}
	}

	return p.rest.Get(key)
}

func (p *PriorityScope) Set(key string, val interface{}) {
	p.rest.Set(key, val)
}

func boolify(str string) bool {
	switch str {
	case "", "false", "no":
		return false
	default:
		return true
	}
}

type ModuleRun struct {
	Play        *Play
	Task        *Task
	Module      *Module
	Runner      *Runner
	Scope       Scope
	FutureScope *FutureScope
}

func (m *ModuleRun) Run(env *CommandEnv, args string) (*Result, error) {
	for _, task := range m.Module.ModTasks {
		ns := NewNestedScope(m.Scope)
		sm, err := ParseSimpleMap(ns, args)
		if err != nil {
			return nil, err
		}

		for k, v := range sm {
			ns.Set(k, v)
		}

		m.Runner.runTask(env.Env, m.Play, task, ns, m.FutureScope)
	}

	return NewResult(true), nil
}

func (r *Runner) runTask(env *Environment, play *Play, task *Task, s Scope, fs *FutureScope) error {
	ps := &PriorityScope{task.IncludeVars, s}

	start := time.Now()

	if when := task.When(); when != "" {
		when, err := ExpandVars(ps, when)

		if err != nil {
			return err
		}

		if !boolify(when) {
			return nil
		}
	}

	if items := task.Items(); items != nil {
		var results []*Result

		anyChanged := false

		for _, item := range items {
			ns := NewNestedScope(ps)
			ns.Set("item", item)

			str, err := ExpandVars(ns, task.Args())

			if err != nil {
				return err
			}

			cmd, err := MakeCommand(ns, task, str)

			if err != nil {
				return err
			}

			r.report.StartTask(task, cmd, str)

			ce := &CommandEnv{env, task.Paths}

			res, err := cmd.Run(ce, str)

			if err != nil {
				res = NewResult(false)
				res.Data.Set("failed", true)
				res.Data.Set("error", err.Error())
			}

			if res.Changed {
				anyChanged = true
			}

			results = append(results, res)
		}

		res := NewResult(anyChanged)
		res.Data.Set("items", len(items))
		res.Data.Set("results", results)

		if name := task.Register(); name != "" {
			fs.Set(name, res)
		}

		runtime := time.Since(start)

		r.Results = append(r.Results, RunResult{task, res, runtime})

		r.report.FinishTask(task, res)

		for _, x := range task.Notify() {
			r.AddNotify(x)
		}

		return nil
	}

	str, err := ExpandVars(ps, task.Args())

	if err != nil {
		return err
	}

	var cmd Command

	if mod, ok := play.Modules[task.Command()]; ok {
		cmd = &ModuleRun{
			Play:   play,
			Task:   task,
			Module: mod,
			Runner: r,
			Scope:  s,
		}
	} else {
		cmd, err = MakeCommand(ps, task, str)

		if err != nil {
			return err
		}
	}

	r.report.StartTask(task, cmd, str)

	ce := &CommandEnv{env, task.Paths}

	if name := task.Future(); name != "" {
		future := NewFuture(start, task, func() (*Result, error) {
			return cmd.Run(ce, str)
		})

		fs.AddFuture(name, future)

		return nil
	}

	if task.Async() {
		asyncAction := &AsyncAction{Task: task}
		asyncAction.Init(r)

		go func() {
			asyncAction.Finish(cmd.Run(ce, str))
		}()
	} else {
		res, err := cmd.Run(ce, str)

		if name := task.Register(); name != "" {
			fs.Set(name, res)
		}

		runtime := time.Since(start)

		if err != nil {
			res = NewResult(false)
			res.Data.Set("failed", true)
			res.Data.Set("error", err.Error())
		}

		r.Results = append(r.Results, RunResult{task, res, runtime})

		r.report.FinishTask(task, res)

		if err == nil {
			for _, x := range task.Notify() {
				r.AddNotify(x)
			}
		}
	}

	return err
}
<commit_msg>refactor with_items usage out<commit_after>package tachyon

import (
	"sync"
	"time"
)

type RunResult struct {
	Task    *Task
	Result  *Result
	Runtime time.Duration
}

type Runner struct {
	env       *Environment
	plays     []*Play
	wait      sync.WaitGroup
	to_notify map[string]struct{}
	async     chan *AsyncAction
	report    Reporter

	Results []RunResult
	Runtime time.Duration
}

func NewRunner(env *Environment, plays []*Play) *Runner {
	r := &Runner{
		env:       env,
		plays:     plays,
		to_notify: make(map[string]struct{}),
		async:     make(chan *AsyncAction),
		report:    env.report,
	}

	go r.handleAsync()

	return r
}

func (r *Runner) SetReport(rep Reporter) {
	r.report = rep
}

func (r *Runner) AddNotify(n string) {
	r.to_notify[n] = struct{}{}
}

func (r *Runner) ShouldRunHandler(name string) bool {
	_, ok := r.to_notify[name]

	return ok
}

func (r *Runner) AsyncChannel() chan *AsyncAction {
	return r.async
}

func (r *Runner) Run(env *Environment) error {
	start := time.Now()
	defer func() {
		r.Runtime = time.Since(start)
	}()

	r.report.StartTasks(r)

	for _, play := range r.plays {
		fs := NewFutureScope(play.Vars)

		for _, task := range play.Tasks {
			err := r.runTask(env, play, task, fs, fs)
			if err != nil {
				return err
			}
		}

		r.Results = append(r.Results, fs.Results()...)
	}

	r.report.FinishTasks(r)

	r.wait.Wait()

	r.report.StartHandlers(r)

	for _, play := range r.plays {
		fs := NewFutureScope(play.Vars)

		for _, task := range play.Handlers {
			if r.ShouldRunHandler(task.Name()) {
				err := r.runTask(env, play, task, fs, fs)

				if err != nil {
					return err
				}
			}
		}

		fs.Wait()
	}

	r.report.FinishHandlers(r)

	return nil
}

func RunAdhocTask(cmd, args string) (*Result, error) {
	env := NewEnv(NewNestedScope(nil), &Config{})
	defer env.Cleanup()

	task := AdhocTask(cmd, args)

	str, err := ExpandVars(env.Vars, task.Args())
	if err != nil {
		return nil, err
	}

	obj, err := MakeCommand(env.Vars, task, str)
	if err != nil {
		return nil, err
	}

	ce := &CommandEnv{env, env.Paths}

	return obj.Run(ce, str)
}

type PriorityScope struct {
	task Vars
	rest Scope
}

func (p *PriorityScope) Get(key string) (Value, bool) {
	if p.task != nil {
		if v, ok := p.task[key]; ok {
			return Any(v), true
		}
	}

	return p.rest.Get(key)
}

func (p *PriorityScope) Set(key string, val interface{}) {
	p.rest.Set(key, val)
}

func boolify(str string) bool {
	switch str {
	case "", "false", "no":
		return false
	default:
		return true
	}
}

type ModuleRun struct {
	Play        *Play
	Task        *Task
	Module      *Module
	Runner      *Runner
	Scope       Scope
	FutureScope *FutureScope
}

func (m *ModuleRun) Run(env *CommandEnv, args string) (*Result, error) {
	for _, task := range m.Module.ModTasks {
		ns := NewNestedScope(m.Scope)
		sm, err := ParseSimpleMap(ns, args)
		if err != nil {
			return nil, err
		}

		for k, v := range sm {
			ns.Set(k, v)
		}

		m.Runner.runTask(env.Env, m.Play, task, ns, m.FutureScope)
	}

	return NewResult(true), nil
}

func (r *Runner) runTaskItems(env *Environment, play *Play, task *Task, s Scope, fs *FutureScope, start time.Time) error {
	var results []*Result

	anyChanged := false

	for _, item := range task.Items() {
		ns := NewNestedScope(s)
		ns.Set("item", item)

		str, err := ExpandVars(ns, task.Args())

		if err != nil {
			return err
		}

		cmd, err := MakeCommand(ns, task, str)

		if err != nil {
			return err
		}

		r.report.StartTask(task, cmd, str)

		ce := &CommandEnv{env, task.Paths}

		res, err := cmd.Run(ce, str)

		if err != nil {
			res = NewResult(false)
			res.Data.Set("failed", true)
			res.Data.Set("error", err.Error())
		}

		if res.Changed {
			anyChanged = true
		}

		results = append(results, res)
	}

	res := NewResult(anyChanged)
	res.Data.Set("items", len(task.Items()))
	res.Data.Set("results", results)

	if name := task.Register(); name != "" {
		fs.Set(name, res)
	}

	runtime := time.Since(start)

	r.Results = append(r.Results, RunResult{task, res, runtime})

	r.report.FinishTask(task, res)

	for _, x := range task.Notify() {
		r.AddNotify(x)
	}

	return nil
}

func (r *Runner) runTask(env *Environment, play *Play, task *Task, s Scope, fs *FutureScope) error {
	ps := &PriorityScope{task.IncludeVars, s}

	start := time.Now()

	if when := task.When(); when != "" {
		when, err := ExpandVars(ps, when)

		if err != nil {
			return err
		}

		if !boolify(when) {
			return nil
		}
	}

	if items := task.Items(); items != nil {
		return r.runTaskItems(env, play, task, s, fs, start)
	}

	str, err := ExpandVars(ps, task.Args())

	if err != nil {
		return err
	}

	var cmd Command

	if mod, ok := play.Modules[task.Command()]; ok {
		cmd = &ModuleRun{
			Play:   play,
			Task:   task,
			Module: mod,
			Runner: r,
			Scope:  s,
		}
	} else {
		cmd, err = MakeCommand(ps, task, str)

		if err != nil {
			return err
		}
	}

	r.report.StartTask(task, cmd, str)

	ce := &CommandEnv{env, task.Paths}

	if name := task.Future(); name != "" {
		future := NewFuture(start, task, func() (*Result, error) {
			return cmd.Run(ce, str)
		})

		fs.AddFuture(name, future)

		return nil
	}

	if task.Async() {
		asyncAction := &AsyncAction{Task: task}
		asyncAction.Init(r)

		go func() {
			asyncAction.Finish(cmd.Run(ce, str))
		}()
	} else {
		res, err := cmd.Run(ce, str)

		if name := task.Register(); name != "" {
			fs.Set(name, res)
		}

		runtime := time.Since(start)

		if err != nil {
			res = NewResult(false)
			res.Data.Set("failed", true)
			res.Data.Set("error", err.Error())
		}

		r.Results = append(r.Results, RunResult{task, res, runtime})

		r.report.FinishTask(task, res)

		if err == nil {
			for _, x := range task.Notify() {
				r.AddNotify(x)
			}
		}
	}

	return err
}
<|endoftext|>