<commit_before>package muxy

import (
	"fmt"
	"net/http"
	"net/url"

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

// Handler is a context-aware version of net/http.Handler.
type Handler interface {
	ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
}

// HandlerFunc is an adapter to allow the use of ordinary functions as Handler.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP implements net/http.Handler. It calls h(context.TODO(), w, r).
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h(context.TODO(), w, r)
}

// ServeHTTPC implements Handler. It calls h(c, w, r).
func (h HandlerFunc) ServeHTTPC(c context.Context, w http.ResponseWriter, r *http.Request) {
	h(c, w, r)
}

// -----------------------------------------------------------------------------

// Matcher registers patterns as routes and matches requests.
type Matcher interface {
	// Route returns a Route for the given pattern.
	Route(pattern string) (*Route, error)
	// Match matches registered routes against the incoming request,
	// sets URL variables in the context and returns a context and Handler.
	Match(c context.Context, r *http.Request) (context.Context, Handler)
	// Build returns a URL for the given route and variables.
	Build(r *Route, vars map[string]string) (*url.URL, error)
}

// -----------------------------------------------------------------------------

// New creates a new Router for the given matcher.
func New(m Matcher) *Router {
	r := &Router{
		matcher:     m,
		routes:      map[*Route]string{},
		namedRoutes: map[string]*Route{},
	}
	r.router = r
	return r
}

// Router matches the URL of incoming requests against
// registered routes and calls the appropriate handler.
type Router struct {
	// matcher holds the Matcher implementation used by this router.
	matcher Matcher
	// routes maps all routes to their correspondent patterns.
	routes map[*Route]string
	// namedRoutes maps route names to their correspondent routes.
	namedRoutes map[string]*Route
	// router holds the main router referenced by subrouters.
	router *Router
	// pattern holds the pattern prefix used to create new routes.
	pattern string
	// name holds the name prefix used to create new routes.
	name string
}

// Group creates a group for the given pattern prefix. All routes registered in
// the resulting router will prepend the prefix to its pattern. For example:
//
//     // Create a new router.
//     r := muxy.New(matcher)
//     // Create a group for the routes starting with the pattern "/admin".
//     g := r.Group("/admin")
//     // Register a route in the admin group, and add handlers for two HTTP
//     // methods. These handlers will be served for the path "/admin/products".
//     g.Route("/products").Get(listProducts).Post(updateProducts)
func (r *Router) Group(pattern string) *Router {
	return &Router{
		router:  r.router,
		pattern: r.pattern + pattern,
		name:    r.name,
	}
}

// Name sets the name prefix used for new routes. All routes registered in
// the resulting router will prepend the prefix to its name.
func (r *Router) Name(name string) *Router {
	r.name = r.name + name
	return r
}

// Mount imports all routes from the given router into this one.
//
// Combined with Group() and Name(), it is possible to submount a router
// defined in a different package using pattern and name prefixes.
// For example:
//
//     // Create a new router.
//     r := muxy.New(matcher)
//     // Create a group for the routes starting with the pattern "/admin",
//     // set the name prefix as "admin:" and register all routes from the
//     // external router.
//     g := r.Group("/admin").Name("admin:").Mount(admin.Router)
func (r *Router) Mount(router *Router) *Router {
	for v, _ := range router.router.routes {
		route := r.Route(v.pattern).Name(v.name)
		for method, handler := range v.Handlers {
			route.Handle(handler, method)
		}
	}
	return r
}

// Route creates a new Route for the given pattern.
func (r *Router) Route(pattern string) *Route {
	pattern = r.pattern + pattern
	route, err := r.router.matcher.Route(pattern)
	if err != nil {
		panic(err)
	}
	route.router = r.router
	route.pattern = pattern
	route.name = r.name
	r.router.routes[route] = pattern
	return route
}

// URL returns a URL for the given route name and variables.
func (r *Router) URL(name string, vars map[string]string) *url.URL {
	if route, ok := r.router.namedRoutes[name]; ok {
		u, err := r.router.matcher.Build(route, vars)
		if err != nil {
			panic(err)
		}
		return u
	}
	return nil
}

// ServeHTTP dispatches to the handler whose pattern matches the request.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	r.ServeHTTPC(context.TODO(), w, req)
}

// ServeHTTPC is a context-aware version of ServeHTTP.
//
// It can be used by middleware to add extra context data before routing begins.
func (r *Router) ServeHTTPC(c context.Context, w http.ResponseWriter, req *http.Request) {
	if c, h := r.router.matcher.Match(c, req); h != nil {
		h.ServeHTTPC(c, w, req)
		return
	}
	http.NotFound(w, req)
}

// -----------------------------------------------------------------------------

// Route stores a URL pattern to be matched and the handler to be served
// in case of a match, optionally mapping HTTP methods to different handlers.
type Route struct {
	// router holds the router that registered this route.
	router *Router
	// pattern holds the route pattern.
	pattern string
	// name holds the route name.
	name string
	// Handlers maps request methods to the handlers that will handle them.
	Handlers map[string]Handler
}

// Name defines the route name used for URL building.
func (r *Route) Name(name string) *Route {
	r.name = r.name + name
	if _, ok := r.router.namedRoutes[r.name]; ok {
		panic(fmt.Sprintf("muxy: duplicated name %q", r.name))
	}
	r.router.namedRoutes[r.name] = r
	return r
}

// Handle sets the given handler to be served for the optional request methods.
func (r *Route) Handle(h Handler, methods ...string) *Route {
	if r.Handlers == nil {
		r.Handlers = make(map[string]Handler, len(methods))
	}
	if methods == nil {
		r.Handlers[""] = h
	} else {
		for _, m := range methods {
			r.Handlers[m] = h
		}
	}
	return r
}

// Below are convenience methods that map HTTP verbs to Handler, equivalent
// to call r.Handle(muxy.HandlerFunc(f), "METHOD-NAME").

// Delete sets the given function to be served for the request method DELETE.
func (r *Route) Delete(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "DELETE")
}

// Get sets the given function to be served for the request method GET.
func (r *Route) Get(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "GET")
}

// Head sets the given function to be served for the request method HEAD.
func (r *Route) Head(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "HEAD")
}

// Options sets the given function to be served for the request method OPTIONS.
func (r *Route) Options(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "OPTIONS")
}

// PATCH sets the given function to be served for the request method PATCH.
func (r *Route) Patch(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "PATCH")
}

// POST sets the given function to be served for the request method POST.
func (r *Route) Post(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "POST")
}

// Put sets the given function to be served for the request method PUT.
func (r *Route) Put(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "PUT")
}

// -----------------------------------------------------------------------------

// Variable is a type used to set and retrieve route variables from the context.
type Variable string

// Var returns the route variable with the given name from the context.
//
// The returned value may be empty if the variable was never set.
func Var(c context.Context, name string) string {
	s, _ := c.Value(Variable(name)).(string)
	return s
}
<commit_msg>Added back middleware support through Router.Use(); made most fields public.<commit_after>package muxy

import (
	"net/http"
	"net/url"

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

// Handler is a context-aware version of net/http.Handler.
type Handler interface {
	ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
}

// HandlerFunc is an adapter to allow the use of ordinary functions as Handler.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP implements net/http.Handler. It calls h(context.TODO(), w, r).
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h(context.TODO(), w, r)
}

// ServeHTTPC implements Handler. It calls h(c, w, r).
func (h HandlerFunc) ServeHTTPC(c context.Context, w http.ResponseWriter, r *http.Request) {
	h(c, w, r)
}

// -----------------------------------------------------------------------------

// Matcher registers patterns as routes and matches requests.
type Matcher interface {
	// Route returns a Route for the given pattern.
	Route(pattern string) (*Route, error)
	// Match matches registered routes against the incoming request,
	// sets URL variables in the context and returns a context and Handler.
	Match(c context.Context, r *http.Request) (context.Context, Handler)
	// Build returns a URL for the given route and variables.
	Build(r *Route, vars map[string]string) (*url.URL, error)
}

// -----------------------------------------------------------------------------

// Variable is a type used to set and retrieve route variables from the context.
type Variable string

// Var returns the route variable with the given name from the context.
//
// The returned value may be empty if the variable wasn't set.
func Var(c context.Context, name string) string {
	v, _ := c.Value(Variable(name)).(string)
	return v
}

// -----------------------------------------------------------------------------

// New creates a new Router for the given matcher.
func New(m Matcher) *Router {
	r := &Router{
		matcher:     m,
		Routes:      map[*Route]string{},
		NamedRoutes: map[string]*Route{},
	}
	r.Router = r
	return r
}

// Router matches the URL of incoming requests against
// registered routes and calls the appropriate handler.
type Router struct {
	// matcher holds the Matcher implementation used by this router.
	matcher Matcher
	// Router holds the main router referenced by subrouters.
	Router *Router
	// Pattern holds the pattern prefix used to create new routes.
	Pattern string
	// Noun holds the name prefix used to create new routes.
	Noun string
	// Middleware holds the middleware to apply in new routes.
	Middleware []func(Handler) Handler
	// Routes maps all routes to their correspondent patterns.
	Routes map[*Route]string
	// NamedRoutes maps route names to their correspondent routes.
	NamedRoutes map[string]*Route
}

// Use appends the given middleware to this router.
func (r *Router) Use(middleware ...func(Handler) Handler) *Router {
	r.Middleware = append(r.Middleware, middleware...)
	return r
}

// Group creates a group for the given pattern prefix. All routes registered in
// the resulting router will prepend the prefix to its pattern. For example:
//
//     // Create a new router.
//     r := muxy.New(matcher)
//     // Create a group for the routes that share pattern prefix "/admin".
//     g := r.Group("/admin")
//     // Register a route in the admin group, and add handlers for two HTTP
//     // methods. These handlers will be served for the path "/admin/products".
//     g.Route("/products").Get(listProducts).Post(updateProducts)
func (r *Router) Group(pattern string) *Router {
	return &Router{
		Router:     r.Router,
		Pattern:    r.Pattern + pattern,
		Noun:       r.Noun,
		Middleware: r.Middleware,
	}
}

// Name sets the name prefix used for new routes. All routes registered in
// the resulting router will prepend the prefix to its name.
func (r *Router) Name(name string) *Router {
	r.Noun = r.Noun + name
	return r
}

// Mount imports all routes from the given router into this one.
//
// Combined with Group() and Name(), it is possible to submount a router
// defined in a different package using pattern and name prefixes.
// For example:
//
//     // Create a new router.
//     r := muxy.New(matcher)
//     // Create a group for the routes starting with the pattern "/admin",
//     // set the name prefix as "admin:" and register all routes from the
//     // external router.
//     g := r.Group("/admin").Name("admin:").Mount(admin.Router)
func (r *Router) Mount(src *Router) *Router {
	for k, _ := range src.Routes {
		route := r.Route(k.Pattern).Name(k.Noun)
		for method, handler := range k.Handlers {
			route.Handle(handler, method)
		}
	}
	return r
}

// Route creates a new Route for the given pattern.
func (r *Router) Route(pattern string) *Route {
	route, err := r.Router.matcher.Route(r.Pattern + pattern)
	if err != nil {
		panic(err)
	}
	route.Router = r
	route.Pattern = r.Pattern + pattern
	route.Noun = r.Noun
	r.Router.Routes[route] = r.Pattern + pattern
	return route
}

// URL returns a URL for the given route name and variables.
func (r *Router) URL(name string, vars map[string]string) *url.URL {
	if route, ok := r.Router.NamedRoutes[name]; ok {
		u, err := r.Router.matcher.Build(route, vars)
		if err != nil {
			panic(err)
		}
		return u
	}
	return nil
}

// ServeHTTP dispatches to the handler whose pattern matches the request.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	r.ServeHTTPC(context.TODO(), w, req)
}

// ServeHTTPC is a context-aware version of ServeHTTP.
//
// It can be used by middleware to add extra context data before routing begins.
func (r *Router) ServeHTTPC(c context.Context, w http.ResponseWriter, req *http.Request) {
	if c, h := r.Router.matcher.Match(c, req); h != nil {
		h.ServeHTTPC(c, w, req)
		return
	}
	http.NotFound(w, req)
}

// -----------------------------------------------------------------------------

// Route stores a URL pattern to be matched and the handler to be served
// in case of a match, optionally mapping HTTP methods to different handlers.
type Route struct {
	// Router holds the router that registered this route.
	Router *Router
	// Pattern holds the route pattern.
	Pattern string
	// Noun holds the route name.
	Noun string
	// Handlers maps request methods to the handlers that will handle them.
	Handlers map[string]Handler
}

// Name defines the route name used for URL building.
func (r *Route) Name(name string) *Route {
	r.Noun = r.Noun + name
	if _, ok := r.Router.Router.NamedRoutes[r.Noun]; ok {
		panic("muxy: duplicated name: " + r.Noun)
	}
	r.Router.Router.NamedRoutes[r.Noun] = r
	return r
}

// Handle sets the given handler to be served for the optional request methods.
func (r *Route) Handle(h Handler, methods ...string) *Route {
	for i := len(r.Router.Middleware) - 1; i >= 0; i-- {
		h = r.Router.Middleware[i](h)
	}
	if r.Handlers == nil {
		r.Handlers = make(map[string]Handler, len(methods))
	}
	if methods == nil {
		r.Handlers[""] = h
	} else {
		for _, m := range methods {
			r.Handlers[m] = h
		}
	}
	return r
}

// Below are convenience methods that map HTTP verbs to Handler, equivalent
// to call r.Handle(muxy.HandlerFunc(f), "METHOD-NAME").

// Delete sets the given function to be served for the request method DELETE.
func (r *Route) Delete(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "DELETE")
}

// Get sets the given function to be served for the request method GET.
func (r *Route) Get(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "GET")
}

// Head sets the given function to be served for the request method HEAD.
func (r *Route) Head(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "HEAD")
}

// Options sets the given function to be served for the request method OPTIONS.
func (r *Route) Options(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "OPTIONS")
}

// PATCH sets the given function to be served for the request method PATCH.
func (r *Route) Patch(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "PATCH")
}

// POST sets the given function to be served for the request method POST.
func (r *Route) Post(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "POST")
}

// Put sets the given function to be served for the request method PUT.
func (r *Route) Put(f func(context.Context, http.ResponseWriter, *http.Request)) *Route {
	return r.Handle(HandlerFunc(f), "PUT")
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/WeCanHearYou/wechy/app/handlers"
	"github.com/WeCanHearYou/wechy/app/middlewares"
	"github.com/WeCanHearYou/wechy/app/models"
	"github.com/WeCanHearYou/wechy/app/pkg/env"
	"github.com/WeCanHearYou/wechy/app/pkg/oauth"
	"github.com/WeCanHearYou/wechy/app/pkg/web"
	"github.com/WeCanHearYou/wechy/app/storage"
)

// WeCHYServices holds reference to all Wechy services
type WeCHYServices struct {
	OAuth    oauth.Service
	User     storage.User
	Tenant   storage.Tenant
	Idea     storage.Idea
	Settings *models.WeCHYSettings
}

// GetMainEngine returns main HTTP engine
func GetMainEngine(ctx *WeCHYServices) *web.Engine {
	r := web.New()

	assets := r.Group("/assets")
	{
		assets.Use(middlewares.OneYearCache())
		assets.Static("/", "dist")
	}

	auth := r.Group("/oauth")
	{
		authHandlers := handlers.OAuth(ctx.Tenant, ctx.OAuth, ctx.User)
		auth.Use(middlewares.HostChecker(env.MustGet("AUTH_ENDPOINT")))

		auth.Get("/facebook", authHandlers.Login(oauth.FacebookProvider))
		auth.Get("/facebook/callback", authHandlers.Callback(oauth.FacebookProvider))
		auth.Get("/google", authHandlers.Login(oauth.GoogleProvider))
		auth.Get("/google/callback", authHandlers.Callback(oauth.GoogleProvider))
	}

	public := r.Group("")
	{
		public.Use(middlewares.MultiTenant(ctx.Tenant))
		public.Use(middlewares.JwtGetter(ctx.User))
		public.Use(middlewares.JwtSetter())

		public.Get("/", handlers.Handlers(ctx.Idea).List())
		public.Get("/ideas/:number", handlers.Handlers(ctx.Idea).Details())
		public.Get("/logout", handlers.Logout())
		public.Get("/api/status", handlers.Status(ctx.Settings))
	}

	api := r.Group("/api")
	{
		api.Use(middlewares.MultiTenant(ctx.Tenant))
		api.Use(middlewares.JwtGetter(ctx.User))
		api.Use(middlewares.JwtSetter())
		api.Use(middlewares.IsAuthenticated())

		api.Post("/ideas", handlers.Handlers(ctx.Idea).PostIdea())
		api.Post("/ideas/:id/comments", handlers.Handlers(ctx.Idea).PostComment())
	}

	admin := r.Group("/admin")
	{
		admin.Use(middlewares.MultiTenant(ctx.Tenant))
		admin.Use(middlewares.JwtGetter(ctx.User))
		admin.Use(middlewares.JwtSetter())
		admin.Use(middlewares.IsAuthenticated())
		admin.Use(middlewares.IsAuthorized(models.RoleMember, models.RoleAdministrator))

		admin.Get("", func(ctx web.Context) error {
			return ctx.HTML(http.StatusOK, "Welcome to Admin Page :)")
		})
	}

	return r
}
<commit_msg>correct naming<commit_after>package main

import (
	"net/http"

	"github.com/WeCanHearYou/wechy/app/handlers"
	"github.com/WeCanHearYou/wechy/app/middlewares"
	"github.com/WeCanHearYou/wechy/app/models"
	"github.com/WeCanHearYou/wechy/app/pkg/env"
	"github.com/WeCanHearYou/wechy/app/pkg/oauth"
	"github.com/WeCanHearYou/wechy/app/pkg/web"
	"github.com/WeCanHearYou/wechy/app/storage"
)

// WeCHYServices holds reference to all Wechy services
type WeCHYServices struct {
	OAuth    oauth.Service
	User     storage.User
	Tenant   storage.Tenant
	Idea     storage.Idea
	Settings *models.WeCHYSettings
}

// GetMainEngine returns main HTTP engine
func GetMainEngine(ctx *WeCHYServices) *web.Engine {
	r := web.New()

	assets := r.Group("/assets")
	{
		assets.Use(middlewares.OneYearCache())
		assets.Static("/", "dist")
	}

	public := r.Group("")
	{
		public.Use(middlewares.MultiTenant(ctx.Tenant))
		public.Use(middlewares.JwtGetter(ctx.User))
		public.Use(middlewares.JwtSetter())

		public.Get("/", handlers.Handlers(ctx.Idea).List())
		public.Get("/ideas/:number", handlers.Handlers(ctx.Idea).Details())
		public.Get("/logout", handlers.Logout())
		public.Get("/api/status", handlers.Status(ctx.Settings))
	}

	private := r.Group("")
	{
		private.Use(middlewares.MultiTenant(ctx.Tenant))
		private.Use(middlewares.JwtGetter(ctx.User))
		private.Use(middlewares.JwtSetter())
		private.Use(middlewares.IsAuthenticated())

		private.Post("/api/ideas", handlers.Handlers(ctx.Idea).PostIdea())
		private.Post("/api/ideas/:id/comments", handlers.Handlers(ctx.Idea).PostComment())
	}

	auth := r.Group("/oauth")
	{
		authHandlers := handlers.OAuth(ctx.Tenant, ctx.OAuth, ctx.User)
		auth.Use(middlewares.HostChecker(env.MustGet("AUTH_ENDPOINT")))

		auth.Get("/facebook", authHandlers.Login(oauth.FacebookProvider))
		auth.Get("/facebook/callback", authHandlers.Callback(oauth.FacebookProvider))
		auth.Get("/google", authHandlers.Login(oauth.GoogleProvider))
		auth.Get("/google/callback", authHandlers.Callback(oauth.GoogleProvider))
	}

	admin := r.Group("/admin")
	{
		admin.Use(middlewares.MultiTenant(ctx.Tenant))
		admin.Use(middlewares.JwtGetter(ctx.User))
		admin.Use(middlewares.JwtSetter())
		admin.Use(middlewares.IsAuthenticated())
		admin.Use(middlewares.IsAuthorized(models.RoleMember, models.RoleAdministrator))

		admin.Get("", func(ctx web.Context) error {
			return ctx.HTML(http.StatusOK, "Welcome to Admin Page :)")
		})
	}

	return r
}
<|endoftext|>


<commit_before>package rest

import (
	"errors"
	"fmt"
	"path"
	"sort"
	"strings"
)

// Param описывает именованный параметр и его значение. В качестве ключа
// используется имя параметра (без символа параметра), а в качестве значения —
// строка из пути, соответствующая данной позиции.
//
// Я не стал использовать для параметров словарь, т.к. данный метод позволяет
// сохранить порядок следования параметров и использовать параметры с одинаковым
// именем.
type Param struct {
	Key, Value string
}

// record описывает информацию о пути, в котором есть параметры.
//
// Значение priority задает приоритет для сортировки ключей с одинаковым
// количеством параметров и формируется следующим образом: в старших восьми
// байтах содержится количество элементов с параметрами, а в младших — со
// статическими путями. Таким образом получается, что элементы с меньшим
// количеством параметров имеют более высокий приоритет и после сортировки будут
// обрабатывается позже, чем элементы с меньшим количеством параметров.
//
// Текущая реализация имеет ограничение на максимальное количество элементов
// пути — 32767. Это связано с методом хранения этого значения в свойстве
// params. В принципе, ничего не мешает просто увеличить размер до uint32 и
// поправить соответствующие места, где он определен, но мне показалось, что
// для моих задач этого более, чем достаточно.
type record struct {
	params uint16      // количество параметров в пути
	handle interface{} // обработчик запроса
	parts  []string    // путь, разобранный на составные части
}

// records описывает список путей с параметрами и поддерживает сортировку по
// флагу приоритета.
type records []*record

// поддержка методов для сортировки.
func (n records) Len() int           { return len(n) }
func (n records) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }
func (n records) Less(i, j int) bool { return n[i].params < n[j].params }

// router описывает структуру для быстрого выбора обработчиков по пути запроса.
// Поддерживает как статические пути, так и пути с параметрами.
//
// Текущая реализация не привязана к конкретным типам обработчиков и может
// хранить любые объекты в качестве таких обработчиков.
type router struct {
	// хранилище статических путей, без параметров
	// в качестве ключа используется полный путь
	static map[string]interface{}
	// хранит информацию о путях с параметрами
	// в качестве ключа используется общее количество элементов пути
	fields   map[uint16]records
	maxParts uint16 // максимальное количество частей пути в определениях
	dynamic  uint16 // самый ранний динамический параметр
}

// add добавляет описание нового пути запроса и ассоциирует его с указанным
// обработчиком запроса. Возвращает ошибку, если количество частей пути больше
// 32767. В качестве флага для определения именованных параметров используется
// символ ':' и '*' для завершающего параметра, который "забирает" в себя весь
// оставшийся путь.
func (r *router) add(url string, handle interface{}) error {
	parts := split(url) // нормализуем путь и разбиваем его на части
	// проверяем, что количество получившихся частей не превышает поддерживаемое
	// количество.
	length := len(parts)
	if length > (1<<15 - 1) {
		return fmt.Errorf("path parts overflow: %d", len(parts))
	}
	var dynamic uint16 // считаем количество параметров
	for i, value := range parts {
		// if len(value) > 0 { после нормализации не должно быть пустых элементов
		switch value[0] {
		case byte('*'):
			if i != length-1 {
				return errors.New("catch-all parameter must be last")
			}
			dynamic |= 1 << 15 // взводим флаг *-параметра
			if r.dynamic == 0 || r.dynamic > uint16(i+1) {
				// есть динамический параметр — сохраняем его позицию
				r.dynamic = uint16(i + 1)
			}
		case byte(':'):
			dynamic++ // увеличиваем счетчик параметров
		}
		// }
	}
	// в пути нет параметров — добавляем в статические обработчики
	if dynamic == 0 {
		if r.static == nil {
			// инициализируем статику, если не сделали этого раньше
			r.static = make(map[string]interface{})
		}
		r.static[strings.Join(parts, "/")] = handle
		return nil
	}
	level := uint16(length) // всего элементов пути
	if r.maxParts < level {
		// запоминаем максимальное количество определенных параметров
		r.maxParts = level
	}
	if r.fields == nil {
		// инициализируем динамические пути, если не сделали этого раньше
		r.fields = make(map[uint16]records)
	}
	// в пути есть динамические параметры — добавляем в список с параметрами
	record := &record{
		params: dynamic,
		handle: handle, // обработчик запроса
		parts:  parts,  // части пути
	}
	// сохраняем в массиве обработчиков с таким же количеством параметров
	r.fields[level] = append(r.fields[level], record)
	sort.Stable(r.fields[level]) // сортируем по количеству параметров
	return nil
}

// lookup возвращает обработчик и список именованных параметров с их значениям.
// Символ параметра из имени при этом изымается. Если подходящего обработчика не
// найдено, то возвращается nil.
func (r *router) lookup(url string) (interface{}, []Param) {
	parts := split(url) // нормализуем путь и разбиваем его на части
	// сначала ищем среди статических путей
	// если статические пути не определены, то пропускаем проверку
	if r.static != nil {
		if handle, ok := r.static[strings.Join(parts, "/")]; ok {
			return handle, nil
		}
	}
	// если пути с параметрами не определены, то пропускаем проверку
	if r.fields == nil {
		return nil, nil
	}
	length := uint16(len(parts))
	var total uint16
	if length > r.maxParts {
		// если нет динамических параметров, то ничего и не подойдет
		if r.dynamic == 0 {
			return nil, nil
		}
		total = r.maxParts
	} else {
		total = length
	}
	// запрашиваем список обработчиков для такого же количества элементов пути
	for l := total; l > 0; l-- {
		if l < r.dynamic {
			// больше нет динамических параметров дальше
			break
		}
		records := r.fields[l]
		if len(records) == 0 {
			continue // обработчики для такого пути не зарегистрированы
		}
	nextRecord:
		// перебираем все записи с обработчиками
		for _, record := range records {
			if l < length && record.params>>15 != 1 {
				continue // игнорируем, если последний параметр не со звездочкой
			}
			var params []Param // сбрасываем предыдущие значения, если они были
		params:
			// перебираем все части пути, заданные в обработчике
			for i, part := range record.parts {
				// if len(part) > 0 { // это параметр?
				switch part[0] {
				case byte('*'):
					params = append(params, Param{
						Key:   part[1:],
						Value: strings.Join(parts[i:], "/"),
					})
					break params //
				case byte(':'):
					params = append(params, Param{
						Key:   part[1:],
						Value: parts[i],
					})
					continue // переходим к следующему элементу пути
				}
				// }
				// статическая часть пути не совпадает с запрашиваемой
				if part != parts[i] {
					continue nextRecord // переходим к следующему обработчику
				}
			}
			return record.handle, params // возвращаем обработчик и параметры
		}
	}
	return nil, nil // ничего подходящего не нашли
}

// split нормализует путь и возвращает его в виде частей.
func split(url string) []string {
	return strings.Split(strings.Trim(path.Clean(url), "/"), "/")
}
<commit_msg>комментарии<commit_after>package rest

import (
	"errors"
	"fmt"
	"path"
	"sort"
	"strings"
)

// Param описывает именованный параметр и его значение. В качестве ключа
// используется имя параметра (без символа параметра), а в качестве значения —
// строка из пути, соответствующая данной позиции.
//
// Я не стал использовать для параметров словарь, т.к. данный метод позволяет
// сохранить порядок следования параметров и использовать параметры с одинаковым
// именем.
type Param struct {
	Key, Value string
}

// record описывает информацию о пути, в котором есть параметры.
//
// Значение priority задает приоритет для сортировки ключей с одинаковым
// количеством параметров и формируется следующим образом: в старших восьми
// байтах содержится количество элементов с параметрами, а в младших — со
// статическими путями. Таким образом получается, что элементы с меньшим
// количеством параметров имеют более высокий приоритет и после сортировки будут
// обрабатывается позже, чем элементы с меньшим количеством параметров.
//
// Текущая реализация имеет ограничение на максимальное количество элементов
// пути — 32767. Это связано с методом хранения этого значения в свойстве
// params. В принципе, ничего не мешает просто увеличить размер до uint32 и
// поправить соответствующие места, где он определен, но мне показалось, что
// для моих задач этого более, чем достаточно.
type record struct {
	// флаги параметров (количество параметров и старший бит — есть динамический)
	params uint16
	// путь, разобранный на составные части
	parts []string
	// обработчик запроса
	handle interface{}
}

// records описывает список путей с параметрами и поддерживает сортировку по
// флагу приоритета.
type records []*record

// поддержка методов для сортировки.
func (n records) Len() int           { return len(n) }
func (n records) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }
func (n records) Less(i, j int) bool { return n[i].params < n[j].params }

// router описывает структуру для быстрого выбора обработчиков по пути запроса.
// Поддерживает как статические пути, так и пути с параметрами.
//
// Текущая реализация не привязана к конкретным типам обработчиков и может
// хранить любые объекты в качестве таких обработчиков.
type router struct {
	// хранилище статических путей, без параметров, в качестве ключа
	// используется полный путь
	static map[string]interface{}
	// хранит информацию о путях с параметрами, в качестве ключа используется
	// общее количество элементов пути
	fields map[uint16]records
	// максимальное количество частей пути во всех определениях
	maxParts uint16
	// позиция, в которой встречается самый ранний динамический параметр
	dynamic uint16
}

// add добавляет описание нового пути запроса и ассоциирует его с указанным
// обработчиком запроса. Возвращает ошибку, если количество частей пути больше
// 32767. В качестве флага для определения именованных параметров используется
// символ ':' и '*' для завершающего параметра, который "забирает" в себя весь
// оставшийся путь.
func (r *router) add(url string, handle interface{}) error {
	parts := split(url) // нормализуем путь и разбиваем его на части
	// проверяем, что количество получившихся частей не превышает поддерживаемое
	// количество.
	length := len(parts)
	if length > (1<<15 - 1) {
		return fmt.Errorf("path parts overflow: %d", len(parts))
	}
	var dynamic uint16 // считаем количество параметров
	for i, value := range parts {
		// if len(value) > 0 { после нормализации не должно быть пустых элементов
		switch value[0] {
		case byte('*'):
			// такой параметр должен быть самым последним в определении путей
			if i != length-1 {
				return errors.New("catch-all parameter must be last")
			}
			dynamic |= 1 << 15 // взводим флаг *-параметра
			if r.dynamic == 0 || r.dynamic > uint16(i+1) {
				// это самый ранний динамический параметр, который нам
				// встретился — сохраняем его позицию
				r.dynamic = uint16(i + 1)
			}
		case byte(':'):
			dynamic++ // увеличиваем счетчик параметров
		}
		// }
	}
	// в пути нет параметров — добавляем в статические обработчики
	if dynamic == 0 {
		if r.static == nil {
			// инициализируем статику, если не сделали этого раньше
			r.static = make(map[string]interface{})
		}
		r.static[strings.Join(parts, "/")] = handle
		return nil
	}
	level := uint16(length) // всего элементов пути
	if r.maxParts < level {
		// запоминаем максимальное количество определенных параметров
		r.maxParts = level
	}
	if r.fields == nil {
		// инициализируем динамические пути, если не сделали этого раньше
		r.fields = make(map[uint16]records)
	}
	// в пути есть динамические параметры — добавляем в список с параметрами
	record := &record{
		params: dynamic,
		handle: handle, // обработчик запроса
		parts:  parts,  // части пути
	}
	// сохраняем в массиве обработчиков с таким же количеством параметров
	r.fields[level] = append(r.fields[level], record)
	sort.Stable(r.fields[level]) // сортируем по количеству параметров
	return nil
}

// lookup возвращает обработчик и список именованных параметров с их значениям.
// Символ параметра из имени при этом изымается. Если подходящего обработчика не
// найдено, то возвращается nil.
func (r *router) lookup(url string) (interface{}, []Param) {
	parts := split(url) // нормализуем путь и разбиваем его на части
	// сначала ищем среди статических путей
	// если статические пути не определены, то пропускаем проверку
	if r.static != nil {
		if handle, ok := r.static[strings.Join(parts, "/")]; ok {
			return handle, nil
		}
	}
	// если пути с параметрами не определены, то заканчиваем проверку
	if r.fields == nil {
		return nil, nil
	}
	length := uint16(len(parts)) // вычисляем количество элементов пути
	// наши определения могут быть короче, если используются параметры со '*'
	// поэтому вычисляем с какой длины начинать
	var total uint16
	// если длина запроса больше максимальной длины определений, то нужно
	// замахиваться на меньшее...
	if length > r.maxParts {
		// если нет динамических параметров, то ничего и не подойдет,
		// потому что наш запрос явно длиннее
		if r.dynamic == 0 {
			return nil, nil
		}
		total = r.maxParts // начнем с максимального определения пути
	} else {
		total = length // наш запрос короче самого длинного определения
	}
	// запрашиваем список обработчиков для такого же количества элементов пути
	for l := total; l > 0; l-- {
		// проверяем, что на этом уровне динамические пути еще встречаются
		if l < r.dynamic {
			break // больше нет динамических параметров дальше
		}
		records := r.fields[l] // получаем определения путей для данной длины
		if len(records) == 0 {
			// обработчики для такой длины пути не зарегистрированы
			// переходим к более короткому пути
			continue
		}
	nextRecord:
		// обработчики есть — перебираем все записи с ними
		for _, record := range records {
			// если наш путь длиннее обработчика, а он не содержит динамического
			// параметра, то точно нам не подойдет
			if l < length && record.params>>15 != 1 {
				continue
			}
			// здесь мы будем собирать значения параметров к данному запросу
			// если ранее они были не пустые от другого обработчика, то
			// сбрасываем их
			var params []Param
		params:
			// перебираем все части пути, заданные в обработчике
			for i, part := range record.parts {
				// if len(part) > 0 { // это параметр?
				switch part[0] {
				case byte(':'): // это одиночный параметр
					params = append(params, Param{
						Key:   part[1:], // имя будет без ':'
						Value: parts[i], // значением берем элемент пути
					})
					continue // переходим к следующему элементу пути
				case byte('*'): // это параметр, который заберет все
					params = append(params, Param{
						Key: part[1:], // исключаем '*' из имени
						// добавляем весь оставшийся путь
						Value: strings.Join(parts[i:], "/"),
					})
					break params // больше ловить нечего — нашли
				}
				// }
				// статическая часть пути не совпадает с запрашиваемой
				if part != parts[i] {
					// переходим к следующему обработчику
					continue nextRecord
				}
			}
			// возвращаем найденный обработчик и заполненные параметры
			return record.handle, params
		}
	}
	// сюда мы попадаем, если так ничего подходящего и не нашли
	return nil, nil
}

// split нормализует путь и возвращает его в виде частей.
func split(url string) []string {
	return strings.Split(strings.Trim(path.Clean(url), "/"), "/")
}
<|endoftext|>


<commit_before>package main

import (
	// "encoding/json"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/gin-gonic/gin"
	"github.com/hashicorp/vault/helper/uuid"
	"log"
	"net/http"
)

type registration struct {
	Email string `form:"email" json:"email" binding:"required"`
}

type User struct {
	Email string `form:"email" json:"email" binding:"required"`
}

// GET /user endpoint
// This function returns the user object of the user
// making the request
func getUser(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"user": "bjorn"})
}

// POST /register endpoint
// the register function takes an email as the only input parameter and generates a UUID that it returns to the user
func register(c *gin.Context) {
	var json registration

	if c.BindJSON(&json) == nil {
		redisConn := pool.Get()
		defer redisConn.Close()

		redisReply, redisError := redis.Bool(redisConn.Do("EXISTS", json.Email))
		if redisError != nil {
			log.Print("Error reading redis data '%s'", redisError)
		}
		if redisReply == true {
			c.JSON(http.StatusConflict, gin.H{
				"status": "user already exists",
				"email":  json.Email})
			return
		}

		apiToken, tokenErr := createVaultToken(vaultclient, json.Email)
		if tokenErr != nil {
			log.Print("Error creating vault token '%s'", tokenErr)
			return
		}

		// TODO Move this part of registration into verification
		// We don't need to save the apitoken in our database if the email
		// has not been verified yet
		// Also make sure to set this to valid: true during verification
		_, redisError = redisConn.Do("HMSET", apiToken, "email", json.Email, "valid", false)
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		verificationToken := uuid.GenerateUUID()

		_, redisError = redisConn.Do("HMSET", verificationToken, "valid", true, "email", json.Email, "apiToken", apiToken)
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		_, redisError = redisConn.Do("SET", json.Email, "true")
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		go sendVerificationEmail(json.Email, verificationToken)

		c.JSON(http.StatusOK, gin.H{
			"status":       "user registered",
			"email":        json.Email,
			"email_status": "awaiting verification"})
		return
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"status": "invalid request json"})
	}
}

// GET /verify endpoint
// This function is used for email verification
// Where the clickable link provided to the user via email
// is handled
func verifyToken(c *gin.Context) {
	var st StopwatchToken
	token := c.Param("token")
	verToken, verTokenError := verifyRegistrationToken(token, &st)
	if verTokenError == nil {
		fmt.Sprintf("here we are %s", verToken.Email)
		go sendTokenEmail(verToken.Email, verToken.ApiToken)
		c.JSON(http.StatusOK, gin.H{
			"status":           "email verified",
			"api_token_status": fmt.Sprintf("email sent to %s", verToken.Email)})
	} else {
		c.JSON(http.StatusUnauthorized, gin.H{
			"status": "error verifying email",
			"error":  fmt.Sprintf("%s", verTokenError)})
	}
}
<commit_msg>adding more todo<commit_after>package main

import (
	// "encoding/json"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/gin-gonic/gin"
	"github.com/hashicorp/vault/helper/uuid"
	"log"
	"net/http"
)

type registration struct {
	Email string `form:"email" json:"email" binding:"required"`
}

type User struct {
	Email string `form:"email" json:"email" binding:"required"`
}

// GET /user endpoint
// This function returns the user object of the user
// making the request
func getUser(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"user": "bjorn"})
}

// POST /register endpoint
// the register function takes an email as the only input parameter and generates a UUID that it returns to the user
func register(c *gin.Context) {
	var json registration

	if c.BindJSON(&json) == nil {
		redisConn := pool.Get()
		defer redisConn.Close()

		redisReply, redisError := redis.Bool(redisConn.Do("EXISTS", json.Email))
		if redisError != nil {
			log.Print("Error reading redis data '%s'", redisError)
		}
		if redisReply == true {
			c.JSON(http.StatusConflict, gin.H{
				"status": "user already exists",
				"email":  json.Email})
			return
		}

		apiToken, tokenErr := createVaultToken(vaultclient, json.Email)
		if tokenErr != nil {
			log.Print("Error creating vault token '%s'", tokenErr)
			return
		}

		// TODO Move this part of registration into verification
		// We don't need to save the apitoken in our database if the email
		// has not been verified yet
		// Also make sure to set this to valid: true during verification
		// Actually on second thought, move everything that doesn't need
		// to happen before verification into verification itself
		// - generating api token
		// - writing data in redis, determine which data to write?
		_, redisError = redisConn.Do("HMSET", apiToken, "email", json.Email, "valid", false)
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		verificationToken := uuid.GenerateUUID()

		_, redisError = redisConn.Do("HMSET", verificationToken, "valid", true, "email", json.Email, "apiToken", apiToken)
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		_, redisError = redisConn.Do("SET", json.Email, "true")
		if redisError != nil {
			log.Print("Error inserting redis data '%s'", redisError)
			return
		}

		go sendVerificationEmail(json.Email, verificationToken)

		c.JSON(http.StatusOK, gin.H{
			"status":       "user registered",
			"email":        json.Email,
			"email_status": "awaiting verification"})
		return
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"status": "invalid request json"})
	}
}

// GET /verify endpoint
// This function is used for email verification
// Where the clickable link provided to the user via email
// is handled
func verifyToken(c *gin.Context) {
	var st StopwatchToken
	token := c.Param("token")
	verToken, verTokenError := verifyRegistrationToken(token, &st)
	if verTokenError == nil {
		fmt.Sprintf("here we are %s", verToken.Email)
		go sendTokenEmail(verToken.Email, verToken.ApiToken)
		c.JSON(http.StatusOK, gin.H{
			"status":           "email verified",
			"api_token_status": fmt.Sprintf("email sent to %s", verToken.Email)})
	} else {
		c.JSON(http.StatusUnauthorized, gin.H{
			"status": "error verifying email",
			"error":  fmt.Sprintf("%s", verTokenError)})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gin-gonic/gin"
	csh_auth "github.com/liam-middlebrook/csh-auth"
	log "github.com/sirupsen/logrus"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"net/http"
	"time"
)

func protectedProfile(c *gin.Context) {
	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}
	c.String(http.StatusOK, "uid %s email %s name %s uuid %s", claims.UserInfo.Username, claims.UserInfo.Email, claims.UserInfo.FullName, claims.UserInfo.Subject)
}

func index(c *gin.Context) {
	c.Redirect(http.StatusFound, "/upload")
}

func action(c *gin.Context) {
	plug := GetPlug()
	url := S3PresignPlug(plug)

	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}
	log.WithFields(log.Fields{
		"uid":           claims.UserInfo.Username,
		"plug_id":       plug.ID,
		"plug_s3id":     plug.S3ID,
		"presigned_uri": url.String(),
	}).Info("Presigned URI Generated")
	c.Redirect(http.StatusFound, url.String())
}

func upload(c *gin.Context) {
	plug := Plug{}

	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}

	plug.Owner = claims.UserInfo.Username
	plug.ViewsRemaining = 100

	if !DecrementCredits(plug.Owner, 1) {
		c.String(http.StatusPaymentRequired, "Get More Credits!")
		return
	}

	file, err := c.FormFile("file")
	if err != nil {
		log.Error(err)
	}
	log.Info(file.Filename)
	data, err := file.Open()
	if err != nil {
		log.Error(err)
	}
	defer data.Close()
	imageData, _, err := image.DecodeConfig(data)
	if err != nil {
		log.Error(err)
	}
	data.Seek(0, 0)
	if imageData.Width == 728 && imageData.Height == 200 {
		mime := getMime(data)
		data.Seek(0, 0)

		plug.S3ID = time.Now().Format("2006/01/02/150405") + "-" + plug.Owner + "-" + file.Filename
		S3AddFile(plug, data, mime)

		MakePlug(plug)
	} else {
		log.Error("invalid file dimensions")
	}

	c.Data(http.StatusOK, "text/html", []byte(`

Uploaded a Plug!

Take a look at what you uploaded! (This does not count towards the views for your Plug!)

			<img src="`+S3PresignPlug(plug).String()+`">

	`))
	log.WithFields(log.Fields{
		"uid":       claims.UserInfo.Username,
		"plug_id":   plug.ID,
		"plug_s3id": plug.S3ID,
	}).Info("Uploaded new Plug!")
}

func upload_view(c *gin.Context) {
	c.Data(http.StatusOK, "text/html", []byte(`

Upload a Plug!

You will lose 1 drink credit in exchange for a 100 view-limit plug!

			<form action="/upload" method="post" enctype="multipart/form-data">
				<input type="file" name="file" id="file">
				<input type="submit" value="Upload" name="submit">
			</form>

	`))
}

func getMime(data io.Reader) string {
	buffer := make([]byte, 512)
	n, err := data.Read(buffer)
	if err != nil {
		log.Error(err)
	}
	return http.DetectContentType(buffer[:n])
}
<commit_msg>upload rulez<commit_after>package main

import (
	"github.com/gin-gonic/gin"
	csh_auth "github.com/liam-middlebrook/csh-auth"
	log "github.com/sirupsen/logrus"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"net/http"
	"time"
)

func protectedProfile(c *gin.Context) {
	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}
	c.String(http.StatusOK, "uid %s email %s name %s uuid %s", claims.UserInfo.Username, claims.UserInfo.Email, claims.UserInfo.FullName, claims.UserInfo.Subject)
}

func index(c *gin.Context) {
	c.Redirect(http.StatusFound, "/upload")
}

func action(c *gin.Context) {
	plug := GetPlug()
	url := S3PresignPlug(plug)

	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}
	log.WithFields(log.Fields{
		"uid":           claims.UserInfo.Username,
		"plug_id":       plug.ID,
		"plug_s3id":     plug.S3ID,
		"presigned_uri": url.String(),
	}).Info("Presigned URI Generated")
	c.Redirect(http.StatusFound, url.String())
}

func upload(c *gin.Context) {
	plug := Plug{}

	claims, ok := c.Value(csh_auth.AuthKey).(csh_auth.CSHClaims)
	if !ok {
		log.Fatal("error finding claims")
		return
	}

	plug.Owner = claims.UserInfo.Username
	plug.ViewsRemaining = 100

	if !DecrementCredits(plug.Owner, 1) {
		c.String(http.StatusPaymentRequired, "Get More Credits!")
		return
	}

	file, err := c.FormFile("file")
	if err != nil {
		log.Error(err)
	}
	log.Info(file.Filename)
	data, err := file.Open()
	if err != nil {
		log.Error(err)
	}
	defer data.Close()
	imageData, _, err := image.DecodeConfig(data)
	if err != nil {
		log.Error(err)
	}
	data.Seek(0, 0)
	if imageData.Width == 728 && imageData.Height == 200 {
		mime := getMime(data)
		data.Seek(0, 0)

		plug.S3ID = time.Now().Format("2006/01/02/150405") + "-" + plug.Owner + "-" + file.Filename
		S3AddFile(plug, data, mime)

		MakePlug(plug)
	} else {
		log.Error("invalid file dimensions")
	}

	c.Data(http.StatusOK, "text/html", []byte(`

Uploaded a Plug!

Take a look at what you uploaded! (This does not count towards the views for your Plug!)

			<img src="`+S3PresignPlug(plug).String()+`">

	`))
	log.WithFields(log.Fields{
		"uid":       claims.UserInfo.Username,
		"plug_id":   plug.ID,
		"plug_s3id": plug.S3ID,
	}).Info("Uploaded new Plug!")
}

func upload_view(c *gin.Context) {
	c.Data(http.StatusOK, "text/html", []byte(`

Upload a Plug!

You will lose 1 drink credit in exchange for a 100 view-limit plug!

Plugs must be 728x200 pixels and in PNG, JPG, or GIF format!

			<form action="/upload" method="post" enctype="multipart/form-data">
				<input type="file" name="file" id="file">
				<input type="submit" value="Upload" name="submit">
			</form>

	`))
}

func getMime(data io.Reader) string {
	buffer := make([]byte, 512)
	n, err := data.Read(buffer)
	if err != nil {
		log.Error(err)
	}
	return http.DetectContentType(buffer[:n])
}
<|endoftext|>


<commit_before>// Copyright 2015 The etcd Authors
//
// 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 command

import (
	"fmt"

	"github.com/coreos/etcd/clientv3"
	"github.com/spf13/cobra"
)

var (
	delPrefix  bool
	delPrevKV  bool
	delFromKey bool
)

// NewDelCommand returns the cobra command for "del".
func NewDelCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "del [options] <key> [range_end]",
		Short: "Removes the specified key or range of keys [key, range_end)",
		Run:   delCommandFunc,
	}

	cmd.Flags().BoolVar(&delPrefix, "prefix", false, "delete keys with matching prefix")
	cmd.Flags().BoolVar(&delPrevKV, "prev-kv", false, "return deleted key-value pairs")
	cmd.Flags().BoolVar(&delFromKey, "from-key", false, "delete keys that are greater than or equal to the given key using byte compare")
	return cmd
}

// delCommandFunc executes the "del" command.
func delCommandFunc(cmd *cobra.Command, args []string) {
	key, opts := getDelOp(cmd, args)
	ctx, cancel := commandCtx(cmd)
	resp, err := mustClientFromCmd(cmd).Delete(ctx, key, opts...)
	cancel()
	if err != nil {
		ExitWithError(ExitError, err)
	}
	display.Del(*resp)
}

func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) {
	if len(args) == 0 || len(args) > 2 {
		ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end."))
	}

	if delPrefix && delFromKey {
		ExitWithError(ExitBadArgs, fmt.Errorf("`--prefix` and `--from-key` cannot be set at the same time, choose one."))
	}

	opts := []clientv3.OpOption{}
	key := args[0]
	if len(args) > 1 {
		if delPrefix || delFromKey {
			ExitWithError(ExitBadArgs, fmt.Errorf("too many arguments, only accept one argument when `--prefix` or `--from-key` is set."))
		}
		opts = append(opts, clientv3.WithRange(args[1]))
	}

	if delPrefix {
		opts = append(opts, clientv3.WithPrefix())
	}
	if delPrevKV {
		opts = append(opts, clientv3.WithPrevKV())
	}

	if delFromKey {
		if len(key) == 0 {
			key = "\x00"
		}
		opts = append(opts, clientv3.WithFromKey())
	}

	return key, opts
}
<commit_msg>ctlv3: support del all keys by '--prefix'<commit_after>// Copyright 2015 The etcd Authors
//
// 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 command

import (
	"fmt"

	"github.com/coreos/etcd/clientv3"
	"github.com/spf13/cobra"
)

var (
	delPrefix  bool
	delPrevKV  bool
	delFromKey bool
)

// NewDelCommand returns the cobra command for "del".
func NewDelCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "del [options] <key> [range_end]",
		Short: "Removes the specified key or range of keys [key, range_end)",
		Run:   delCommandFunc,
	}

	cmd.Flags().BoolVar(&delPrefix, "prefix", false, "delete keys with matching prefix")
	cmd.Flags().BoolVar(&delPrevKV, "prev-kv", false, "return deleted key-value pairs")
	cmd.Flags().BoolVar(&delFromKey, "from-key", false, "delete keys that are greater than or equal to the given key using byte compare")
	return cmd
}

// delCommandFunc executes the "del" command.
func delCommandFunc(cmd *cobra.Command, args []string) {
	key, opts := getDelOp(cmd, args)
	ctx, cancel := commandCtx(cmd)
	resp, err := mustClientFromCmd(cmd).Delete(ctx, key, opts...)
	cancel()
	if err != nil {
		ExitWithError(ExitError, err)
	}
	display.Del(*resp)
}

func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) {
	if len(args) == 0 || len(args) > 2 {
		ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end."))
	}

	if delPrefix && delFromKey {
		ExitWithError(ExitBadArgs, fmt.Errorf("`--prefix` and `--from-key` cannot be set at the same time, choose one."))
	}

	opts := []clientv3.OpOption{}
	key := args[0]
	if len(args) > 1 {
		if delPrefix || delFromKey {
			ExitWithError(ExitBadArgs, fmt.Errorf("too many arguments, only accept one argument when `--prefix` or `--from-key` is set."))
		}
		opts = append(opts, clientv3.WithRange(args[1]))
	}

	if delPrefix {
		if len(key) == 0 {
			key = "\x00"
			opts = append(opts, clientv3.WithFromKey())
		} else {
			opts = append(opts, clientv3.WithPrefix())
		}
	}
	if delPrevKV {
		opts = append(opts, clientv3.WithPrevKV())
	}

	if delFromKey {
		if len(key) == 0 {
			key = "\x00"
		}
		opts = append(opts, clientv3.WithFromKey())
	}

	return key, opts
}
<|endoftext|>


<commit_before>
package main

import (
	"fmt"
	"strings"
	"strconv"
	"errors"
	"encoding/json"
	"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

type Employee struct {
	Name string `json:"name"`
	EmployeeId int `json:"employeeId"`
	Project string `json:"project"`
}

type Customer struct {
	Name string `json:"name"`
	CustomerId int `json:"customerId"`
}
type Project struct {
	Name string `json:"name"`
	ProjectId int `json:"projectId"`
	CustomerOf string `json:"customerOf"`
	StartTime string `json:"startDate"`
	EndTime string `json:"endDate"`
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {
    fmt.Println("Init is running " + function)
    return nil,nil
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {

    fmt.Println("Invoke is running " + function)

    if function =="initEmployee"{
		return t.initEmployee(stub,args)
	}else if function =="getEmployee"{
		return t.getEmployee(stub,args)
	}else if function =="initCustomer"{
		return t.initCustomer(stub,args)
	}else if function =="getCustomer"{
		return t.getCustomer(stub,args)
	}else if function =="initProject"{
		return t.initProject(stub,args)
	}else if function =="getProject"{
		return t.getProject(stub,args)
	}
	return nil,errors.New("Received unknown function invocation")
}

func (t *SimpleChaincode) initProject(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){

	if len(args) != 3 {
		return nil,errors.New("Incorrect number of arguments. Expecting 3")
	}
	// ==== Input sanitation ====
	fmt.Println("- start initProject")
	if len(args[0]) <= 0 {
		return nil,errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil,errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil,errors.New("3rd argument must be a non-empty string")
	}
	if len(args[3]) <= 0 {
		return nil,errors.New("4th argument must be a non-empty string")
	}
	if len(args[4]) <= 0 {
		return nil,errors.New("5th argument must be a non-empty string")
	}

	projectName := args[0]
	customerOf := strings.ToLower(args[2])
	projectId, err := strconv.Atoi(args[1])
	projectIdAsString := args[1]
	startDate:= args[3]
	endDate:= args[4]
	if err != nil {
		return nil,errors.New("2nd argument must be a numeric string")
	}

	projectAsBytes, err := stub.GetState(projectIdAsString)
	if err != nil {
		return nil,err
	} else if projectAsBytes != nil {
		fmt.Println("This project already exists: " + projectIdAsString)
		return nil, errors.New("This project already exists "+projectIdAsString)
	}

	project:= Project{projectName,projectId,customerOf,startDate,endDate}

	projectJSONasBytes, err := json.Marshal(project)

	if err != nil {
		return nil,err
	}
	err = stub.PutState(projectIdAsString, projectJSONasBytes)

	if err != nil {
		return nil,err
	}

	// composite key creation for searching projects according to customer
	/*
	indexName := "customerOf"
	customerOfIndexKey, err := stub.CreateCompositeKey(indexName, []string{customerOf, projectIdAsString})
	if err != nil {
		return nil,err
	}

	value := []byte{0x00}
	stub.PutState(customerOfIndexKey, value)
    */
	fmt.Println("- end initProject")
	return nil,nil
}

func (t *SimpleChaincode) getProject(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	projectId := args[0]
	project, err := stub.GetState(projectId)
	if err != nil {
		return nil,err
	}

	return project,nil
}

func (t *SimpleChaincode) getCustomer(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	customerId := args[0]
	customer, err := stub.GetState(customerId)
	if err != nil {
		return nil,err
	}

	return customer,nil
}

func (t *SimpleChaincode) initCustomer(stub shim.ChaincodeStubInterface,args []string) ([]byte,error) {
		if len(args) != 2{
			return nil,errors.New("Incorrect number of arguments. Expecting 2")
		}
			// ==== Input sanitation ====
		fmt.Println("- start initCustomer")
		if len(args[0]) <= 0 {
			return nil,errors.New("1st argument must be a non-empty string")
		}
		if len(args[1]) <= 0 {
			return nil,errors.New("2nd argument must be a non-empty string")
		}

		customerName := args[0]
		customerId, err := strconv.Atoi(args[1])
		customerIdAsString := args[1]
		if err != nil {
			return nil,errors.New("2nd argument must be a numeric string")
		}
		customerAsBytes, err := stub.GetState(customerIdAsString)
		if err != nil {
			 return nil,err
		} else if customerAsBytes != nil {
			fmt.Println("This customer already exists: " + customerIdAsString)
			return nil,errors.New("This customer already exists: "+customerIdAsString)
		}

		customer:= Customer{customerName,customerId}

		customerJSONasBytes, err := json.Marshal(customer)

		if err != nil {
			return nil,err
		}
		err = stub.PutState(customerIdAsString, customerJSONasBytes)

		if err != nil {
			return nil,err
		}

		fmt.Println("- end initCustomer")
		return nil,nil
}

func (t *SimpleChaincode) initEmployee(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){

	if len(args) != 3 {
		return nil,errors.New("Incorrect number of arguments. Expecting 3")
	}
	// ==== Input sanitation ====
	fmt.Println("- start initEmployee")
	if len(args[0]) <= 0 {
		return nil,errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil,errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil,errors.New("3rd argument must be a non-empty string")
	}

	employeeName := args[0]
	project := strings.ToLower(args[2])
	employeeId, err := strconv.Atoi(args[1])
	employeeIdAsString := args[1]
	if err != nil {
		return nil,errors.New("2nd argument must be a numeric string")
	}

	employeeAsBytes, err := stub.GetState(employeeIdAsString)
	if err != nil {
		return nil,err
	} else if employeeAsBytes != nil {
		fmt.Println("This employee already exists: " + employeeIdAsString)
		return []byte("duplicate"),errors.New("This employee already exists: "+employeeIdAsString)
	}

	employee:= Employee{employeeName,employeeId,project}
	fmt.Println(employee)
	employeeJSONasBytes, err := json.Marshal(employee)
	fmt.Println(employeeJSONasBytes)
	if err != nil {
		return nil,err
	}
	err = stub.PutState(employeeIdAsString, employeeJSONasBytes)

	if err != nil {
		return nil,err
	}
	employee_temp,_ := stub.GetState(employeeIdAsString)
	fmt.Println(employee_temp)
	// composite key to get employees by project

	/*
	indexName := "project"
	projectIndexKey, err := stub.CreateCompositeKey(indexName, []string{project, employeeIdAsString})
	if err != nil {
		return nil,err
	}

	value := []byte{0x00}
	stub.PutState(projectIndexKey, value)
	*/
	fmt.Println("- end initEmployee")
	return nil,nil
}

func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {
    fmt.Println("Query is running " + function)

    if function =="getEmployee"{
		return t.getEmployee(stub,args)
	}else if function =="getCustomer"{
		return t.getCustomer(stub,args)
	}else if function =="getProject"{
		return t.getProject(stub,args)
	}

	return nil,errors.New("Received unknown function query")
}

func (t *SimpleChaincode) getEmployee(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	fmt.Printf("getEmployee called")
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	employeeId := args[0]
	employee, err := stub.GetState(employeeId)
	fmt.Println(employee)
	if err != nil {
		return nil,err
	}

	return employee,nil
}

func main() {
    	err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}
<commit_msg>added return values to some functions<commit_after>
package main

import (
	"fmt"
	"strings"
	"strconv"
	"errors"
	"encoding/json"
	"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

type Employee struct {
	Name string `json:"name"`
	EmployeeId int `json:"employeeId"`
	Project string `json:"project"`
}

type Customer struct {
	Name string `json:"name"`
	CustomerId int `json:"customerId"`
}
type Project struct {
	Name string `json:"name"`
	ProjectId int `json:"projectId"`
	CustomerOf string `json:"customerOf"`
	StartTime string `json:"startDate"`
	EndTime string `json:"endDate"`
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {
    fmt.Println("Init is running " + function)
    return nil,nil
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {

    fmt.Println("Invoke is running " + function)

    if function =="initEmployee"{
		return t.initEmployee(stub,args)
	}else if function =="getEmployee"{
		return t.getEmployee(stub,args)
	}else if function =="initCustomer"{
		return t.initCustomer(stub,args)
	}else if function =="getCustomer"{
		return t.getCustomer(stub,args)
	}else if function =="initProject"{
		return t.initProject(stub,args)
	}else if function =="getProject"{
		return t.getProject(stub,args)
	}
	return nil,errors.New("Received unknown function invocation")
}

func (t *SimpleChaincode) initProject(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){

	if len(args) != 5 {
		return nil,errors.New("Incorrect number of arguments. Expecting 5")
	}
	// ==== Input sanitation ====
	fmt.Println("- start initProject")
	if len(args[0]) <= 0 {
		return nil,errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil,errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil,errors.New("3rd argument must be a non-empty string")
	}
	if len(args[3]) <= 0 {
		return nil,errors.New("4th argument must be a non-empty string")
	}
	if len(args[4]) <= 0 {
		return nil,errors.New("5th argument must be a non-empty string")
	}

	projectName := args[0]
	customerOf := strings.ToLower(args[2])
	projectId, err := strconv.Atoi(args[1])
	projectIdAsString := args[1]
	startDate:= args[3]
	endDate:= args[4]
	if err != nil {
		return nil,errors.New("2nd argument must be a numeric string")
	}

	projectAsBytes, err := stub.GetState(projectIdAsString)
	if err != nil {
		return nil,err
	} else if projectAsBytes != nil {
		fmt.Println("This project already exists: " + projectIdAsString)
		return nil, errors.New("This project already exists "+projectIdAsString)
	}

	project:= Project{projectName,projectId,customerOf,startDate,endDate}

	projectJSONasBytes, err := json.Marshal(project)

	if err != nil {
		return nil,err
	}
	err = stub.PutState(projectIdAsString, projectJSONasBytes)

	if err != nil {
		return nil,err
	}

	// composite key creation for searching projects according to customer
	/*
	indexName := "customerOf"
	customerOfIndexKey, err := stub.CreateCompositeKey(indexName, []string{customerOf, projectIdAsString})
	if err != nil {
		return nil,err
	}

	value := []byte{0x00}
	stub.PutState(customerOfIndexKey, value)
    */
	fmt.Println("- end initProject")
	return nil,nil
}

func (t *SimpleChaincode) getProject(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	projectId := args[0]
	project, err := stub.GetState(projectId)
	if err != nil {
		return nil,err
	}

	return project,nil
}

func (t *SimpleChaincode) getCustomer(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	customerId := args[0]
	customer, err := stub.GetState(customerId)
	if err != nil {
		return nil,err
	}

	return customer,nil
}

func (t *SimpleChaincode) initCustomer(stub shim.ChaincodeStubInterface,args []string) ([]byte,error) {
		if len(args) != 2{
			return nil,errors.New("Incorrect number of arguments. Expecting 2")
		}
			// ==== Input sanitation ====
		fmt.Println("- start initCustomer")
		if len(args[0]) <= 0 {
			return nil,errors.New("1st argument must be a non-empty string")
		}
		if len(args[1]) <= 0 {
			return nil,errors.New("2nd argument must be a non-empty string")
		}

		customerName := args[0]
		customerId, err := strconv.Atoi(args[1])
		customerIdAsString := args[1]
		if err != nil {
			return nil,errors.New("2nd argument must be a numeric string")
		}
		customerAsBytes, err := stub.GetState(customerIdAsString)
		if err != nil {
			 return nil,err
		} else if customerAsBytes != nil {
			fmt.Println("This customer already exists: " + customerIdAsString)
			return nil,errors.New("This customer already exists: "+customerIdAsString)
		}

		customer:= Customer{customerName,customerId}

		customerJSONasBytes, err := json.Marshal(customer)

		if err != nil {
			return nil,err
		}
		err = stub.PutState(customerIdAsString, customerJSONasBytes)

		if err != nil {
			return nil,err
		}

		fmt.Println("- end initCustomer")
		return nil,nil
}

func (t *SimpleChaincode) initEmployee(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){

	if len(args) != 3 {
		return nil,errors.New("Incorrect number of arguments. Expecting 3")
	}
	// ==== Input sanitation ====
	fmt.Println("- start initEmployee")
	if len(args[0]) <= 0 {
		return nil,errors.New("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return nil,errors.New("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return nil,errors.New("3rd argument must be a non-empty string")
	}

	employeeName := args[0]
	project := strings.ToLower(args[2])
	employeeId, err := strconv.Atoi(args[1])
	employeeIdAsString := args[1]
	if err != nil {
		return nil,errors.New("2nd argument must be a numeric string")
	}

	employeeAsBytes, err := stub.GetState(employeeIdAsString)
	if err != nil {
		return nil,err
	} else if employeeAsBytes != nil {
		fmt.Println("This employee already exists: " + employeeIdAsString)
		return []byte("duplicate"),errors.New("This employee already exists: "+employeeIdAsString)
	}

	employee:= Employee{employeeName,employeeId,project}
	fmt.Println(employee)
	employeeJSONasBytes, err := json.Marshal(employee)
	fmt.Println(employeeJSONasBytes)
	if err != nil {
		return nil,err
	}
	err = stub.PutState(employeeIdAsString, employeeJSONasBytes)

	if err != nil {
		return nil,err
	}
	employee_temp,_ := stub.GetState(employeeIdAsString)
	fmt.Println(employee_temp)
	// composite key to get employees by project

	/*
	indexName := "project"
	projectIndexKey, err := stub.CreateCompositeKey(indexName, []string{project, employeeIdAsString})
	if err != nil {
		return nil,err
	}

	value := []byte{0x00}
	stub.PutState(projectIndexKey, value)
	*/
	fmt.Println("- end initEmployee")
	return nil,nil
}

func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte,error) {
    fmt.Println("Query is running " + function)

    if function =="getEmployee"{
		return t.getEmployee(stub,args)
	}else if function =="getCustomer"{
		return t.getCustomer(stub,args)
	}else if function =="getProject"{
		return t.getProject(stub,args)
	}

	return nil,errors.New("Received unknown function query")
}

func (t *SimpleChaincode) getEmployee(stub shim.ChaincodeStubInterface, args []string) ([]byte,error){
	fmt.Printf("getEmployee called")
	if len(args) !=1{
		return nil,errors.New("Incorrect number of arguments. Expecting 1")
	}
	employeeId := args[0]
	employee, err := stub.GetState(employeeId)
	fmt.Println(employee)
	if err != nil {
		return nil,err
	}

	return employee,nil
}

func main() {
    	err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}
<|endoftext|>


<commit_before>/*
Package runner runs distinct commands in parallel goroutines while queueing
indistinct commands so they're never run twice at the same time (but all get run eventually).

Simply send your command to runner and let it do the rest!

Example usage:

  package main

  import "github.com/modcloth/queued-command-runner"

  import (
  	"fmt"
  	"os"
  	"os/exec"
  )

  func main() {
  	fmt.Println("Running a command now.")

  	pwd := os.Getenv("PWD")

  	cmd := exec.Command("ls", "-la", pwd)
  	cmd.Stdout = os.Stdout
  	cmd.Stderr = os.Stderr

  	runner.Run(&cmd)

  	WaitOnRunner:
  	for {
  		select {
  		case <-runner.Done:
  			break WaitOnRunner
		case err := <-runner.Errors:
			fmt.Printf("Uh oh, got an error: %q\n", err)
  		}
  	}

  	os.Exit(0)
  }
*/
package runner

import (
	"fmt"
	"os/exec"
	"strings"
	"sync"
)

import (
	structures "github.com/hishboy/gocommons/lang"
)

// tm for "Treasure Map"
var tm = make(map[string]*runner)
var tmLock = &sync.Mutex{}

/*
Done is qcr's exit channel - if you use qcr, you MUST wait on Done to ensure
your commands get run.  This can be accomplished by including the following at
the bottom of main():

  <-runner.Done

*/
var Done = make(chan bool)

/*
Error is the channel that qcr will use to report any errors that occur.
*/
var Errors = make(chan *QCRError)

/*
QCRError is a custom error type that includes CommandStr, the command args of
the command that failed.
*/
type QCRError struct {
	CommandStr string
	Key        string
	error
}

type runner struct {
	queue *structures.Queue
	*sync.Mutex
	key string
}

func (r *runner) start() {
	for {
		r.Lock()
		cmd := r.queue.Poll()
		if cmd == nil {
			destroyRunner(r)
			break
		} else {
			cmd := cmd.(*exec.Cmd)

			r.Unlock()

			if err := cmd.Run(); err != nil {
				Errors <- &QCRError{
					error:      err,
					CommandStr: r.key,
				}
				fmt.Printf("OOPS, qcr encountered an error for %q: %q\n", r.key, err)
			}
		}
	}
}

/*
Command is a small wrapper for *exec.Cmd so that a custom key may be specified.  If no key
is specified (i.e. Key == ""), key is defaulted to the following:

	key = strings.Join(cmd.Cmd.Args, " ")
*/
type Command struct {
	Key string
	Cmd *exec.Cmd
}

func (r *runner) enqueue(cmd *exec.Cmd) {
	r.Lock()
	defer r.Unlock()
	r.queue.Push(cmd)
}

//Run runs your command.
func Run(cmd *Command) {
	tmLock.Lock()
	defer tmLock.Unlock()

	if cmd.Key == "" {
		cmd.Key = strings.Join(cmd.Cmd.Args, " ")
	}

	key := cmd.Key

	if tm[key] == nil {
		tm[key] = newRunner(cmd)
		go tm[key].start()
	} else {
		tm[key].enqueue(cmd.Cmd)
	}
}

func newRunner(cmd *Command) *runner {
	q := structures.NewQueue()
	q.Push(cmd.Cmd)

	ret := &runner{
		key:   cmd.Key,
		Mutex: &sync.Mutex{},
		queue: q,
	}
	return ret
}

func destroyRunner(r *runner) {
	tmLock.Lock()
	defer tmLock.Unlock()

	if r.queue.Len() != 0 {
		panic("HOW THE HELL DID YOU GET HERE?!?!")
	}

	delete(tm, r.key)
	if len(tm) == 0 {
		Done <- true
	}
}
<commit_msg>We shouldn't be printing things unexpectedly<commit_after>/*
Package runner runs distinct commands in parallel goroutines while queueing
indistinct commands so they're never run twice at the same time (but all get run eventually).

Simply send your command to runner and let it do the rest!

Example usage:

  package main

  import "github.com/modcloth/queued-command-runner"

  import (
  	"fmt"
  	"os"
  	"os/exec"
  )

  func main() {
  	fmt.Println("Running a command now.")

  	pwd := os.Getenv("PWD")

  	cmd := exec.Command("ls", "-la", pwd)
  	cmd.Stdout = os.Stdout
  	cmd.Stderr = os.Stderr

  	runner.Run(&cmd)

  	WaitOnRunner:
  	for {
  		select {
  		case <-runner.Done:
  			break WaitOnRunner
		case err := <-runner.Errors:
			fmt.Printf("Uh oh, got an error: %q\n", err)
  		}
  	}

  	os.Exit(0)
  }
*/
package runner

import (
	"os/exec"
	"strings"
	"sync"
)

import (
	structures "github.com/hishboy/gocommons/lang"
)

// tm for "Treasure Map"
var tm = make(map[string]*runner)
var tmLock = &sync.Mutex{}

/*
Done is qcr's exit channel - if you use qcr, you MUST wait on Done to ensure
your commands get run.  This can be accomplished by including the following at
the bottom of main():

  <-runner.Done

*/
var Done = make(chan bool)

/*
Error is the channel that qcr will use to report any errors that occur.
*/
var Errors = make(chan *QCRError)

/*
QCRError is a custom error type that includes CommandStr, the command args of
the command that failed.
*/
type QCRError struct {
	CommandStr string
	Key        string
	error
}

type runner struct {
	queue *structures.Queue
	*sync.Mutex
	key string
}

func (r *runner) start() {
	for {
		r.Lock()
		cmd := r.queue.Poll()
		if cmd == nil {
			destroyRunner(r)
			break
		} else {
			cmd := cmd.(*exec.Cmd)

			r.Unlock()

			if err := cmd.Run(); err != nil {
				Errors <- &QCRError{
					error:      err,
					CommandStr: r.key,
				}
			}
		}
	}
}

/*
Command is a small wrapper for *exec.Cmd so that a custom key may be specified.  If no key
is specified (i.e. Key == ""), key is defaulted to the following:

	key = strings.Join(cmd.Cmd.Args, " ")
*/
type Command struct {
	Key string
	Cmd *exec.Cmd
}

func (r *runner) enqueue(cmd *exec.Cmd) {
	r.Lock()
	defer r.Unlock()
	r.queue.Push(cmd)
}

//Run runs your command.
func Run(cmd *Command) {
	tmLock.Lock()
	defer tmLock.Unlock()

	if cmd.Key == "" {
		cmd.Key = strings.Join(cmd.Cmd.Args, " ")
	}

	key := cmd.Key

	if tm[key] == nil {
		tm[key] = newRunner(cmd)
		go tm[key].start()
	} else {
		tm[key].enqueue(cmd.Cmd)
	}
}

func newRunner(cmd *Command) *runner {
	q := structures.NewQueue()
	q.Push(cmd.Cmd)

	ret := &runner{
		key:   cmd.Key,
		Mutex: &sync.Mutex{},
		queue: q,
	}
	return ret
}

func destroyRunner(r *runner) {
	tmLock.Lock()
	defer tmLock.Unlock()

	if r.queue.Len() != 0 {
		panic("HOW THE HELL DID YOU GET HERE?!?!")
	}

	delete(tm, r.key)
	if len(tm) == 0 {
		Done <- true
	}
}
<|endoftext|>


<commit_before>package lazytest

import (
	"fmt"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type testQueue struct {
	tests []Batch
}

const (
	RunnerIdle int32 = iota
	RunnerBusy
)

type TestStatus int8

const (
	StatusPending TestStatus = iota
	StatusSkipped
	StatusFailed
	StatusPanicked
	StatusPassed
)

type TestReport struct {
	Name    string
	Package string
	Status  TestStatus
	Message string
}

var (
	runnerDone   chan struct{} = make(chan struct{})
	runnerStatus int32
	mux          sync.Mutex
	queue        *testQueue = &testQueue{}
	rep          chan Report
)

type Report []TestReport

func Runner(batch chan Batch) chan Report {
	rep = make(chan Report, 50)
	go queueTests(batch, rep)
	return rep
}

func (t *testQueue) run() {
	packageTests := make(map[string][]string)
	for _, test := range t.tests {
		if _, ok := packageTests[test.Package]; !ok {
			packageTests[test.Package] = make([]string, 0)
		}
		packageTests[test.Package] = append(packageTests[test.Package], regexp.QuoteMeta(test.TestName))
	}
	for pkg, tests := range packageTests {
		testRegexp := fmt.Sprintf("'(%s)'", strings.Join(tests, "|"))

		cmd := exec.Command("go", "test", pkg, "-run", testRegexp)
		out, err := cmd.CombinedOutput()
		if err != nil {
			log(err.Error())
		}
		log(string(out))
	}
	atomic.StoreInt32(&runnerStatus, RunnerIdle)
	runnerDone <- struct{}{}
}

func queueTests(batch chan Batch, rep chan Report) {
	block := make(chan struct{})
	var delay *time.Timer
	for {
		select {
		case b := <-batch:
			mux.Lock()
			if delay == nil {
				delay = time.NewTimer(time.Second * 2)
				go func(d *time.Timer) {
					<-d.C
					block <- struct{}{}
				}(delay)
			}
			if queue.tests == nil {
				queue.tests = make([]Batch, 0)
			}
			queue.tests = append(queue.tests, b)
			mux.Unlock()

		case <-block:
			mux.Lock()
			if atomic.CompareAndSwapInt32(&runnerStatus, RunnerIdle, RunnerBusy) {
				delay = nil
				go queue.run()
				queue = &testQueue{}
			}
			mux.Unlock()

		case <-runnerDone:
			mux.Lock()
			if delay == nil && len(queue.tests) > 0 {
				atomic.StoreInt32(&runnerStatus, RunnerBusy)
				go queue.run()
				queue = &testQueue{}
			}
			mux.Unlock()
		}
	}
}
<commit_msg>Added friendly message on file changes<commit_after>package lazytest

import (
	"fmt"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type testQueue struct {
	tests []Batch
}

const (
	RunnerIdle int32 = iota
	RunnerBusy
)

type TestStatus int8

const (
	StatusPending TestStatus = iota
	StatusSkipped
	StatusFailed
	StatusPanicked
	StatusPassed
)

type TestReport struct {
	Name    string
	Package string
	Status  TestStatus
	Message string
}

var (
	runnerDone   chan struct{} = make(chan struct{})
	runnerStatus int32
	mux          sync.Mutex
	queue        *testQueue = &testQueue{}
	rep          chan Report
)

type Report []TestReport

func Runner(batch chan Batch) chan Report {
	rep = make(chan Report, 50)
	go queueTests(batch, rep)
	return rep
}

func (t *testQueue) run() {
	packageTests := make(map[string][]string)
	for _, test := range t.tests {
		if _, ok := packageTests[test.Package]; !ok {
			packageTests[test.Package] = make([]string, 0)
		}
		packageTests[test.Package] = append(packageTests[test.Package], regexp.QuoteMeta(test.TestName))
	}
	for pkg, tests := range packageTests {
		testRegexp := fmt.Sprintf("'(%s)'", strings.Join(tests, "|"))

		cmd := exec.Command("go", "test", pkg, "-run", testRegexp)
		out, err := cmd.CombinedOutput()
		if err != nil {
			log(err.Error())
		}
		log(string(out))
	}
	atomic.StoreInt32(&runnerStatus, RunnerIdle)
	runnerDone <- struct{}{}
}

func queueTests(batch chan Batch, rep chan Report) {
	block := make(chan struct{})
	var delay *time.Timer
	for {
		select {
		case b := <-batch:
			mux.Lock()
			if delay == nil {
				log("Filechange detected, running tests...")

				delay = time.NewTimer(time.Second * 2)
				go func(d *time.Timer) {
					<-d.C
					block <- struct{}{}
				}(delay)
			}
			if queue.tests == nil {
				queue.tests = make([]Batch, 0)
			}
			queue.tests = append(queue.tests, b)
			mux.Unlock()

		case <-block:
			mux.Lock()
			if atomic.CompareAndSwapInt32(&runnerStatus, RunnerIdle, RunnerBusy) {
				delay = nil
				go queue.run()
				queue = &testQueue{}
			}
			mux.Unlock()

		case <-runnerDone:
			mux.Lock()
			if delay == nil && len(queue.tests) > 0 {
				atomic.StoreInt32(&runnerStatus, RunnerBusy)
				go queue.run()
				queue = &testQueue{}
			}
			mux.Unlock()
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
    "bufio"
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "path"
    "regexp"
    "strings"
    "text/template"
    "time"
)

type Script struct {
    Name               string
    Help               string
    Desc               string
    ParamDefs          []ScriptDef `json:"-"`
    OutputDefs         []ScriptDef `json:"-"`
    Path               string
    *template.Template `json:"-"`
    ParsedTs           int64
}

type ScriptDef struct {
    Name       string
    Type       string
    DefaultStr string
    Default    interface{}
    Desc       string
}

// Make a `Script` out of the bash script at `scriptPath` with contents
// `source`. The script should be a regular bash script formatted as a
// `text/template.Template`. Optionally, a description, params, and outputs
// may be defined in the leading comments of the script. The format for these
// are as follows:
//
//     # @desc <text>
//     # @param <pname> (int|float|string|unsafe|bool) `<default>` <pdesc>
//     # @output <vname> (a|w)
//
// @desc
//     desc entries get appended to `Script.Desc`.
// @param
//     param entries define paramters useable as `text/template` vars within
//     the script. Use `int` for integers, `float` for floats, `string` for
//     shell-escaped strings, `bool` for booleans, and `unsafe` for unescaped
//     strings.
// @output
//     output entries define simple output vars to write within the script.
//     Use `a` for an append var and `w` for an overwrite var. Write to
//     these vars like so:
//         echo 'hi' >&$vname
//     Clear an append var like so:
//         echo 'vname' >&$_clear
//
// Scripts may also set a timeout at any time like so:
//     echo 100 >&$_timeout   # timeout 100 seconds from now
//     echo 0 >&$_timeout     # disable timeout (default)
// If a script times out, it is sent a kill signal.
func newScript(scriptPath string, source []byte) (*Script, error) {
    script := &Script{
        Name:       path.Base(scriptPath),
        Path:       scriptPath,
        ParamDefs:  make([]ScriptDef, 0),
        OutputDefs: make([]ScriptDef, 0),
    }

    // Define regexes
    descRe := regexp.MustCompile(`(?m)^#\s+@desc\s*(.*)$`)
    paramRe := regexp.MustCompile(fmt.Sprintf(
        `(?m)^#\s+@param\s+([^\s]+)\s+(int|float|string|bool|unsafe)\s+%s([^%s]*)%s\s+(.*)$`, "`", "`", "`"))
    outputRe := regexp.MustCompile(`(?m)^#\s+@output\s+([^\s]+)\s+(a|w)$`)

    // Read source line by line
    reader := bufio.NewReader(bytes.NewBuffer(source))
    var templateBuf bytes.Buffer
    var helpBuf bytes.Buffer
    afterLeadingComments := false
    for {
        line, readErr := reader.ReadString('\n')
        if readErr == io.EOF {
            // End of source
            break
        } else if !afterLeadingComments && !strings.HasPrefix(line, "#") {
            // End of leading comments
            // Insert _clear, _timeout, and output var fds
            if _, writeErr := templateBuf.WriteString("_clear=3\n_timeout=4\n"); writeErr != nil {
                return nil, writeErr
            }
            for outputDefIdx, outputDef := range script.OutputDefs {
                if _, writeErr := templateBuf.WriteString(fmt.Sprintf("%s=%d\n", outputDef.Name, 5+outputDefIdx)); writeErr != nil {
                    return nil, writeErr
                }
            }
            afterLeadingComments = true
        } else if readErr != nil {
            // Some other error reading source
            errLog.Printf("reader.ReadString err=%v\n", readErr)
            return nil, readErr
        }
        if _, writeErr := templateBuf.WriteString(line); writeErr != nil {
            return nil, writeErr
        }
        matchedEntry := true
        if afterLeadingComments {
            // After leading comments, so do nothing
            continue
        } else if matches := descRe.FindStringSubmatch(line); len(matches) > 0 {
            // Matched a @desc entry
            script.Desc += strings.TrimSpace(matches[1])
        } else if matches := paramRe.FindStringSubmatch(line); len(matches) > 0 {
            // Matched a @param entry
            paramDef := &ScriptDef{
                Name:       matches[1],
                Type:       matches[2],
                DefaultStr: matches[3],
                Desc:       matches[4],
            }
            if valErr := paramDef.makeDefault(); valErr != nil {
                errLog.Printf("paramDef.makeDefault err=%v\n", valErr)
                return nil, valErr
            }
            script.ParamDefs = append(script.ParamDefs, *paramDef)
        } else if matches := outputRe.FindStringSubmatch(line); len(matches) > 0 {
            // Mached an @output entry
            script.OutputDefs = append(script.OutputDefs, ScriptDef{
                Name: matches[1],
                Type: matches[2],
            })
        } else {
            matchedEntry = false
        }
        if matchedEntry {
            helpBuf.WriteString(line)
        }
    }

    // Make help
    script.Help = helpBuf.String()

    // Compile template
    if tpl, tplErr := template.New(script.Name).Parse(templateBuf.String()); tplErr != nil {
        return nil, tplErr
    } else {
        script.Template = tpl
    }

    // Done!
    script.ParsedTs = time.Now().Unix()
    return script, nil
}

// Given a `map[string]string` of input params `iparams`, return a
// `map[string]interface{}` of type-normalized params. Params missing from
// `iparams` are set to their default values.
func (self *Script) normalizeParams(iparams map[string]string) (map[string]interface{}, error) {
    oparams := make(map[string]interface{})
    for _, def := range self.ParamDefs {
        if ival, exists := iparams[def.Name]; exists {
            if oval, err := def.toInterfaceVal(ival); err != nil {
                return nil, err
            } else {
                oparams[def.Name] = oval
            }
        } else {
            oparams[def.Name] = def.Default
        }
    }
    return oparams, nil
}

// Return the index of the output with name `name`. Return -1 if no such
// output exists.
func (self *Script) getOutputIdxByName(name string) int {
    for i, def := range self.OutputDefs {
        if def.Name == name {
            return i
        }
    }
    return -1
}

// Set `ScriptDef.Default` to the JSON-decoded version of
// `ScriptDef.DefaultStr`
func (self *ScriptDef) makeDefault() error {
    if def, err := self.toInterfaceVal(self.DefaultStr); err != nil {
        return err
    } else {
        self.Default = def
    }
    return nil
}

// Return the JSON-decoded form of `in`. For `unsafe` and `string` types,
// double quotes are added if `in` does not begin with a double quote. For
// `string` the value is passed through `escapeShellArg`. `int` and `float`
// types are both JSON-decoded as floats, but `int` is casted to an integer
// afterwards. `bool` is JSON-decoded as a bool.
func (self *ScriptDef) toInterfaceVal(in string) (interface{}, error) {
    var v interface{}
    if self.Type == "int" || self.Type == "float" {
        v = float64(0)
    } else if self.Type == "string" || self.Type == "unsafe" {
        v = ""
        if !strings.HasPrefix(in, `"`) {
            in = fmt.Sprintf(`"%s"`, in)
        }
    } else if self.Type == "bool" {
        v = false
    } else {
        return nil, errors.New("Unexpected Type")
    }
    err := json.Unmarshal([]byte(in), &v)
    if err != nil {
        return nil, err
    }
    if self.Type == "int" {
        return int(v.(float64)), nil
    } else if self.Type == "string" {
        return escapeShellArg(v.(string)), nil
    }
    return v, nil
}
<commit_msg>Make input error more useful<commit_after>package main

import (
    "bufio"
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "path"
    "regexp"
    "strings"
    "text/template"
    "time"
)

type Script struct {
    Name               string
    Help               string
    Desc               string
    ParamDefs          []ScriptDef `json:"-"`
    OutputDefs         []ScriptDef `json:"-"`
    Path               string
    *template.Template `json:"-"`
    ParsedTs           int64
}

type ScriptDef struct {
    Name       string
    Type       string
    DefaultStr string
    Default    interface{}
    Desc       string
}

// Make a `Script` out of the bash script at `scriptPath` with contents
// `source`. The script should be a regular bash script formatted as a
// `text/template.Template`. Optionally, a description, params, and outputs
// may be defined in the leading comments of the script. The format for these
// are as follows:
//
//     # @desc <text>
//     # @param <pname> (int|float|string|unsafe|bool) `<default>` <pdesc>
//     # @output <vname> (a|w)
//
// @desc
//     desc entries get appended to `Script.Desc`.
// @param
//     param entries define paramters useable as `text/template` vars within
//     the script. Use `int` for integers, `float` for floats, `string` for
//     shell-escaped strings, `bool` for booleans, and `unsafe` for unescaped
//     strings.
// @output
//     output entries define simple output vars to write within the script.
//     Use `a` for an append var and `w` for an overwrite var. Write to
//     these vars like so:
//         echo 'hi' >&$vname
//     Clear an append var like so:
//         echo 'vname' >&$_clear
//
// Scripts may also set a timeout at any time like so:
//     echo 100 >&$_timeout   # timeout 100 seconds from now
//     echo 0 >&$_timeout     # disable timeout (default)
// If a script times out, it is sent a kill signal.
func newScript(scriptPath string, source []byte) (*Script, error) {
    script := &Script{
        Name:       path.Base(scriptPath),
        Path:       scriptPath,
        ParamDefs:  make([]ScriptDef, 0),
        OutputDefs: make([]ScriptDef, 0),
    }

    // Define regexes
    descRe := regexp.MustCompile(`(?m)^#\s+@desc\s*(.*)$`)
    paramRe := regexp.MustCompile(fmt.Sprintf(
        `(?m)^#\s+@param\s+([^\s]+)\s+(int|float|string|bool|unsafe)\s+%s([^%s]*)%s\s+(.*)$`, "`", "`", "`"))
    outputRe := regexp.MustCompile(`(?m)^#\s+@output\s+([^\s]+)\s+(a|w)$`)

    // Read source line by line
    reader := bufio.NewReader(bytes.NewBuffer(source))
    var templateBuf bytes.Buffer
    var helpBuf bytes.Buffer
    afterLeadingComments := false
    for {
        line, readErr := reader.ReadString('\n')
        if readErr == io.EOF {
            // End of source
            break
        } else if !afterLeadingComments && !strings.HasPrefix(line, "#") {
            // End of leading comments
            // Insert _clear, _timeout, and output var fds
            if _, writeErr := templateBuf.WriteString("_clear=3\n_timeout=4\n"); writeErr != nil {
                return nil, writeErr
            }
            for outputDefIdx, outputDef := range script.OutputDefs {
                if _, writeErr := templateBuf.WriteString(fmt.Sprintf("%s=%d\n", outputDef.Name, 5+outputDefIdx)); writeErr != nil {
                    return nil, writeErr
                }
            }
            afterLeadingComments = true
        } else if readErr != nil {
            // Some other error reading source
            errLog.Printf("reader.ReadString err=%v\n", readErr)
            return nil, readErr
        }
        if _, writeErr := templateBuf.WriteString(line); writeErr != nil {
            return nil, writeErr
        }
        matchedEntry := true
        if afterLeadingComments {
            // After leading comments, so do nothing
            continue
        } else if matches := descRe.FindStringSubmatch(line); len(matches) > 0 {
            // Matched a @desc entry
            script.Desc += strings.TrimSpace(matches[1])
        } else if matches := paramRe.FindStringSubmatch(line); len(matches) > 0 {
            // Matched a @param entry
            paramDef := &ScriptDef{
                Name:       matches[1],
                Type:       matches[2],
                DefaultStr: matches[3],
                Desc:       matches[4],
            }
            if valErr := paramDef.makeDefault(); valErr != nil {
                errLog.Printf("paramDef.makeDefault err=%v\n", valErr)
                return nil, valErr
            }
            script.ParamDefs = append(script.ParamDefs, *paramDef)
        } else if matches := outputRe.FindStringSubmatch(line); len(matches) > 0 {
            // Mached an @output entry
            script.OutputDefs = append(script.OutputDefs, ScriptDef{
                Name: matches[1],
                Type: matches[2],
            })
        } else {
            matchedEntry = false
        }
        if matchedEntry {
            helpBuf.WriteString(line)
        }
    }

    // Make help
    script.Help = helpBuf.String()

    // Compile template
    if tpl, tplErr := template.New(script.Name).Parse(templateBuf.String()); tplErr != nil {
        return nil, tplErr
    } else {
        script.Template = tpl
    }

    // Done!
    script.ParsedTs = time.Now().Unix()
    return script, nil
}

// Given a `map[string]string` of input params `iparams`, return a
// `map[string]interface{}` of type-normalized params. Params missing from
// `iparams` are set to their default values.
func (self *Script) normalizeParams(iparams map[string]string) (map[string]interface{}, error) {
    oparams := make(map[string]interface{})
    for _, def := range self.ParamDefs {
        if ival, exists := iparams[def.Name]; exists {
            if oval, err := def.toInterfaceVal(ival); err != nil {
                return nil, err
            } else {
                oparams[def.Name] = oval
            }
        } else {
            oparams[def.Name] = def.Default
        }
    }
    return oparams, nil
}

// Return the index of the output with name `name`. Return -1 if no such
// output exists.
func (self *Script) getOutputIdxByName(name string) int {
    for i, def := range self.OutputDefs {
        if def.Name == name {
            return i
        }
    }
    return -1
}

// Set `ScriptDef.Default` to the JSON-decoded version of
// `ScriptDef.DefaultStr`
func (self *ScriptDef) makeDefault() error {
    if def, err := self.toInterfaceVal(self.DefaultStr); err != nil {
        return err
    } else {
        self.Default = def
    }
    return nil
}

// Return the JSON-decoded form of `in`. For `unsafe` and `string` types,
// double quotes are added if `in` does not begin with a double quote. For
// `string` the value is passed through `escapeShellArg`. `int` and `float`
// types are both JSON-decoded as floats, but `int` is casted to an integer
// afterwards. `bool` is JSON-decoded as a bool.
func (self *ScriptDef) toInterfaceVal(in string) (interface{}, error) {
    var v interface{}
    if self.Type == "int" || self.Type == "float" {
        v = float64(0)
    } else if self.Type == "string" || self.Type == "unsafe" {
        v = ""
        if !strings.HasPrefix(in, `"`) {
            in = fmt.Sprintf(`"%s"`, in)
        }
    } else if self.Type == "bool" {
        v = false
    } else {
        return nil, errors.New(fmt.Sprintf("Unrecognized type %s for %s", self.Type, self.Name))
    }
    err := json.Unmarshal([]byte(in), &v)
    if err != nil {
        return nil, errors.New(fmt.Sprintf("Unable to parse `%s` as %s (%s)", in, self.Name, self.Type))
    }
    if self.Type == "int" {
        return int(v.(float64)), nil
    } else if self.Type == "string" {
        return escapeShellArg(v.(string)), nil
    }
    return v, nil
}
<|endoftext|>