<commit_before>// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

// Package fasthttprouter is a trie based high performance HTTP request router.
//
// A trivial example is:
//
// package main

// import (
//     "fmt"
//     "log"
//
//     "github.com/buaazp/fasthttprouter"
//     "github.com/valyala/fasthttp"
// )

// func Index(ctx *fasthttp.RequestCtx) {
//     fmt.Fprint(ctx, "Welcome!\n")
// }

// func Hello(ctx *fasthttp.RequestCtx) {
//     fmt.Fprintf(ctx, "hello, %s!\n", ctx.UserValue("name"))
// }

// func main() {
//     router := fasthttprouter.New()
//     router.GET("/", Index)
//     router.GET("/hello/:name", Hello)

//     log.Fatal(fasthttp.ListenAndServe(":8080", router.Handler))
// }
//
// The router matches incoming requests by the request method and the path.
// If a handle is registered for this path and method, the router delegates the
// request to that function.
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
// register handles, for all other methods router.Handle can be used.
//
// The registered path, against which the router matches incoming requests, can
// contain two types of parameters:
//  Syntax    Type
//  :name     named parameter
//  *name     catch-all parameter
//
// Named parameters are dynamic path segments. They match anything until the
// next '/' or the path end:
//  Path: /blog/:category/:post
//
//  Requests:
//   /blog/go/request-routers            match: category="go", post="request-routers"
//   /blog/go/request-routers/           no match, but the router would redirect
//   /blog/go/                           no match
//   /blog/go/request-routers/comments   no match
//
// Catch-all parameters match anything until the path end, including the
// directory index (the '/' before the catch-all). Since they match anything
// until the end, catch-all parameters must always be the final path element.
//  Path: /files/*filepath
//
//  Requests:
//   /files/                             match: filepath="/"
//   /files/LICENSE                      match: filepath="/LICENSE"
//   /files/templates/article.html       match: filepath="/templates/article.html"
//   /files                              no match, but the router would redirect
//
// The value of parameters is inside ctx.UserValue
// To retrieve the value of a parameter:
//  // use the name of the parameter
//  user := ps.UserValue("user")
//

package fasthttprouter

import (
	"strings"

	"github.com/valyala/fasthttp"
)

var (
	defaultContentType = []byte("text/plain; charset=utf-8")
	questionMark       = []byte("?")
)

// Router is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
	trees map[string]*node

	// Enables automatic redirection if the current route can't be matched but a
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301 for GET requests
	// and 307 for all other request methods.
	RedirectTrailingSlash bool

	// If enabled, the router tries to fix the current request path, if no
	// handle is registered for it.
	// First superfluous path elements like ../ or // are removed.
	// Afterwards the router does a case-insensitive lookup of the cleaned path.
	// If a handle can be found for this route, the router makes a redirection
	// to the corrected path with status code 301 for GET requests and 307 for
	// all other request methods.
	// For example /FOO and /..//Foo could be redirected to /foo.
	// RedirectTrailingSlash is independent of this option.
	RedirectFixedPath bool

	// If enabled, the router checks if another method is allowed for the
	// current route, if the current request can not be routed.
	// If this is the case, the request is answered with 'Method Not Allowed'
	// and HTTP status code 405.
	// If no other Method is allowed, the request is delegated to the NotFound
	// handler.
	HandleMethodNotAllowed bool

	// If enabled, the router automatically replies to OPTIONS requests.
	// Custom OPTIONS handlers take priority over automatic replies.
	HandleOPTIONS bool

	// Configurable http.Handler which is called when no matching route is
	// found. If it is not set, http.NotFound is used.
	NotFound fasthttp.RequestHandler

	// Configurable http.Handler which is called when a request
	// cannot be routed and HandleMethodNotAllowed is true.
	// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
	// The "Allow" header with allowed request methods is set before the handler
	// is called.
	MethodNotAllowed fasthttp.RequestHandler

	// Function to handle panics recovered from http handlers.
	// It should be used to generate a error page and return the http error code
	// 500 (Internal Server Error).
	// The handler can be used to keep your server from crashing because of
	// unrecovered panics.
	PanicHandler func(*fasthttp.RequestCtx, interface{})
}

// New returns a new initialized Router.
// Path auto-correction, including trailing slashes, is enabled by default.
func New() *Router {
	return &Router{
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      true,
		HandleMethodNotAllowed: true,
		HandleOPTIONS:          true,
	}
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (r *Router) GET(path string, handle fasthttp.RequestHandler) {
	r.Handle("GET", path, handle)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func (r *Router) HEAD(path string, handle fasthttp.RequestHandler) {
	r.Handle("HEAD", path, handle)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func (r *Router) OPTIONS(path string, handle fasthttp.RequestHandler) {
	r.Handle("OPTIONS", path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (r *Router) POST(path string, handle fasthttp.RequestHandler) {
	r.Handle("POST", path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (r *Router) PUT(path string, handle fasthttp.RequestHandler) {
	r.Handle("PUT", path, handle)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func (r *Router) PATCH(path string, handle fasthttp.RequestHandler) {
	r.Handle("PATCH", path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, handle fasthttp.RequestHandler) {
	r.Handle("DELETE", path, handle)
}

// Handle registers a new request handle with the given path and method.
//
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used.
//
// This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (r *Router) Handle(method, path string, handle fasthttp.RequestHandler) {
	if path[0] != '/' {
		panic("path must begin with '/' in path '" + path + "'")
	}

	if r.trees == nil {
		r.trees = make(map[string]*node)
	}

	root := r.trees[method]
	if root == nil {
		root = new(node)
		r.trees[method] = root
	}

	root.addRoute(path, handle)
}

// ServeFiles serves files from the given file system root.
// The path must end with "/*filepath", files are then served from the local
// path /defined/root/dir/*filepath.
// For example if root is "/etc" and *filepath is "passwd", the local file
// "/etc/passwd" would be served.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
//     router.ServeFiles("/src/*filepath", "/var/www")
func (r *Router) ServeFiles(path string, rootPath string) {
	if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
		panic("path must end with /*filepath in path '" + path + "'")
	}
	prefix := path[:len(path)-10]

	fileHandler := fasthttp.FSHandler(rootPath, strings.Count(prefix, "/"))

	r.GET(path, func(ctx *fasthttp.RequestCtx) {
		fileHandler(ctx)
	})
}

func (r *Router) recv(ctx *fasthttp.RequestCtx) {
	if rcv := recover(); rcv != nil {
		r.PanicHandler(ctx, rcv)
	}
}

// Lookup allows the manual lookup of a method + path combo.
// This is e.g. useful to build a framework around this router.
// If the path was found, it returns the handle function and the path parameter
// values. Otherwise the third return value indicates whether a redirection to
// the same path with an extra / without the trailing slash should be performed.
func (r *Router) Lookup(method, path string, ctx *fasthttp.RequestCtx) (fasthttp.RequestHandler, bool) {
	if root := r.trees[method]; root != nil {
		return root.getValue(path, ctx)
	}
	return nil, false
}

func (r *Router) allowed(path, reqMethod string) (allow string) {
	if path == "*" || path == "/*" { // server-wide
		for method := range r.trees {
			if method == "OPTIONS" {
				continue
			}

			// add request method to list of allowed methods
			if len(allow) == 0 {
				allow = method
			} else {
				allow += ", " + method
			}
		}
	} else { // specific path
		for method := range r.trees {
			// Skip the requested method - we already tried this one
			if method == reqMethod || method == "OPTIONS" {
				continue
			}

			handle, _ := r.trees[method].getValue(path, nil)
			if handle != nil {
				// add request method to list of allowed methods
				if len(allow) == 0 {
					allow = method
				} else {
					allow += ", " + method
				}
			}
		}
	}
	if len(allow) > 0 {
		allow += ", OPTIONS"
	}
	return
}

// Handler makes the router implement the fasthttp.ListenAndServe interface.
func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
	if r.PanicHandler != nil {
		defer r.recv(ctx)
	}

	path := string(ctx.Path())
	method := string(ctx.Method())
	if root := r.trees[method]; root != nil {
		if f, tsr := root.getValue(path, ctx); f != nil {
			f(ctx)
			return
		} else if method != "CONNECT" && path != "/" {
			code := 301 // Permanent redirect, request with GET method
			if method != "GET" {
				// Temporary redirect, request with same method
				// As of Go 1.3, Go does not support status code 308.
				code = 307
			}

			if tsr && r.RedirectTrailingSlash {
				var uri string
				if len(path) > 1 && path[len(path)-1] == '/' {
					uri = path[:len(path)-1]
				} else {
					uri = path + "/"
				}
				ctx.Redirect(uri, code)
				return
			}

			// Try to fix the request path
			if r.RedirectFixedPath {
				fixedPath, found := root.findCaseInsensitivePath(
					CleanPath(path),
					r.RedirectTrailingSlash,
				)

				if found {
					queryBuf := ctx.URI().QueryString()
					if len(queryBuf) > 0 {
						fixedPath = append(fixedPath, questionMark...)
						fixedPath = append(fixedPath, queryBuf...)
					}
					uri := string(fixedPath)
					ctx.Redirect(uri, code)
					return
				}
			}
		}
	}

	if method == "OPTIONS" {
		// Handle OPTIONS requests
		if r.HandleOPTIONS {
			if allow := r.allowed(path, method); len(allow) > 0 {
				ctx.Response.Header.Set("Allow", allow)
				return
			}
		}
	} else {
		// Handle 405
		if r.HandleMethodNotAllowed {
			if allow := r.allowed(path, method); len(allow) > 0 {
				ctx.Response.Header.Set("Allow", allow)
				if r.MethodNotAllowed != nil {
					r.MethodNotAllowed(ctx)
				} else {
					ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
					ctx.SetContentTypeBytes(defaultContentType)
					ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
				}
				return
			}
		}
	}

	// Handle 404
	if r.NotFound != nil {
		r.NotFound(ctx)
	} else {
		ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound),
			fasthttp.StatusNotFound)
	}
}
<commit_msg>Fix query string on trailing slash redirect<commit_after>// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

// Package fasthttprouter is a trie based high performance HTTP request router.
//
// A trivial example is:
//
// package main

// import (
//     "fmt"
//     "log"
//
//     "github.com/buaazp/fasthttprouter"
//     "github.com/valyala/fasthttp"
// )

// func Index(ctx *fasthttp.RequestCtx) {
//     fmt.Fprint(ctx, "Welcome!\n")
// }

// func Hello(ctx *fasthttp.RequestCtx) {
//     fmt.Fprintf(ctx, "hello, %s!\n", ctx.UserValue("name"))
// }

// func main() {
//     router := fasthttprouter.New()
//     router.GET("/", Index)
//     router.GET("/hello/:name", Hello)

//     log.Fatal(fasthttp.ListenAndServe(":8080", router.Handler))
// }
//
// The router matches incoming requests by the request method and the path.
// If a handle is registered for this path and method, the router delegates the
// request to that function.
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
// register handles, for all other methods router.Handle can be used.
//
// The registered path, against which the router matches incoming requests, can
// contain two types of parameters:
//  Syntax    Type
//  :name     named parameter
//  *name     catch-all parameter
//
// Named parameters are dynamic path segments. They match anything until the
// next '/' or the path end:
//  Path: /blog/:category/:post
//
//  Requests:
//   /blog/go/request-routers            match: category="go", post="request-routers"
//   /blog/go/request-routers/           no match, but the router would redirect
//   /blog/go/                           no match
//   /blog/go/request-routers/comments   no match
//
// Catch-all parameters match anything until the path end, including the
// directory index (the '/' before the catch-all). Since they match anything
// until the end, catch-all parameters must always be the final path element.
//  Path: /files/*filepath
//
//  Requests:
//   /files/                             match: filepath="/"
//   /files/LICENSE                      match: filepath="/LICENSE"
//   /files/templates/article.html       match: filepath="/templates/article.html"
//   /files                              no match, but the router would redirect
//
// The value of parameters is inside ctx.UserValue
// To retrieve the value of a parameter:
//  // use the name of the parameter
//  user := ps.UserValue("user")
//

package fasthttprouter

import (
	"strings"

	"github.com/valyala/fasthttp"
)

var (
	defaultContentType = []byte("text/plain; charset=utf-8")
	questionMark       = []byte("?")
)

// Router is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
	trees map[string]*node

	// Enables automatic redirection if the current route can't be matched but a
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301 for GET requests
	// and 307 for all other request methods.
	RedirectTrailingSlash bool

	// If enabled, the router tries to fix the current request path, if no
	// handle is registered for it.
	// First superfluous path elements like ../ or // are removed.
	// Afterwards the router does a case-insensitive lookup of the cleaned path.
	// If a handle can be found for this route, the router makes a redirection
	// to the corrected path with status code 301 for GET requests and 307 for
	// all other request methods.
	// For example /FOO and /..//Foo could be redirected to /foo.
	// RedirectTrailingSlash is independent of this option.
	RedirectFixedPath bool

	// If enabled, the router checks if another method is allowed for the
	// current route, if the current request can not be routed.
	// If this is the case, the request is answered with 'Method Not Allowed'
	// and HTTP status code 405.
	// If no other Method is allowed, the request is delegated to the NotFound
	// handler.
	HandleMethodNotAllowed bool

	// If enabled, the router automatically replies to OPTIONS requests.
	// Custom OPTIONS handlers take priority over automatic replies.
	HandleOPTIONS bool

	// Configurable http.Handler which is called when no matching route is
	// found. If it is not set, http.NotFound is used.
	NotFound fasthttp.RequestHandler

	// Configurable http.Handler which is called when a request
	// cannot be routed and HandleMethodNotAllowed is true.
	// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
	// The "Allow" header with allowed request methods is set before the handler
	// is called.
	MethodNotAllowed fasthttp.RequestHandler

	// Function to handle panics recovered from http handlers.
	// It should be used to generate a error page and return the http error code
	// 500 (Internal Server Error).
	// The handler can be used to keep your server from crashing because of
	// unrecovered panics.
	PanicHandler func(*fasthttp.RequestCtx, interface{})
}

// New returns a new initialized Router.
// Path auto-correction, including trailing slashes, is enabled by default.
func New() *Router {
	return &Router{
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      true,
		HandleMethodNotAllowed: true,
		HandleOPTIONS:          true,
	}
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (r *Router) GET(path string, handle fasthttp.RequestHandler) {
	r.Handle("GET", path, handle)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func (r *Router) HEAD(path string, handle fasthttp.RequestHandler) {
	r.Handle("HEAD", path, handle)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func (r *Router) OPTIONS(path string, handle fasthttp.RequestHandler) {
	r.Handle("OPTIONS", path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (r *Router) POST(path string, handle fasthttp.RequestHandler) {
	r.Handle("POST", path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (r *Router) PUT(path string, handle fasthttp.RequestHandler) {
	r.Handle("PUT", path, handle)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func (r *Router) PATCH(path string, handle fasthttp.RequestHandler) {
	r.Handle("PATCH", path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, handle fasthttp.RequestHandler) {
	r.Handle("DELETE", path, handle)
}

// Handle registers a new request handle with the given path and method.
//
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used.
//
// This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (r *Router) Handle(method, path string, handle fasthttp.RequestHandler) {
	if path[0] != '/' {
		panic("path must begin with '/' in path '" + path + "'")
	}

	if r.trees == nil {
		r.trees = make(map[string]*node)
	}

	root := r.trees[method]
	if root == nil {
		root = new(node)
		r.trees[method] = root
	}

	root.addRoute(path, handle)
}

// ServeFiles serves files from the given file system root.
// The path must end with "/*filepath", files are then served from the local
// path /defined/root/dir/*filepath.
// For example if root is "/etc" and *filepath is "passwd", the local file
// "/etc/passwd" would be served.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
//     router.ServeFiles("/src/*filepath", "/var/www")
func (r *Router) ServeFiles(path string, rootPath string) {
	if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
		panic("path must end with /*filepath in path '" + path + "'")
	}
	prefix := path[:len(path)-10]

	fileHandler := fasthttp.FSHandler(rootPath, strings.Count(prefix, "/"))

	r.GET(path, func(ctx *fasthttp.RequestCtx) {
		fileHandler(ctx)
	})
}

func (r *Router) recv(ctx *fasthttp.RequestCtx) {
	if rcv := recover(); rcv != nil {
		r.PanicHandler(ctx, rcv)
	}
}

// Lookup allows the manual lookup of a method + path combo.
// This is e.g. useful to build a framework around this router.
// If the path was found, it returns the handle function and the path parameter
// values. Otherwise the third return value indicates whether a redirection to
// the same path with an extra / without the trailing slash should be performed.
func (r *Router) Lookup(method, path string, ctx *fasthttp.RequestCtx) (fasthttp.RequestHandler, bool) {
	if root := r.trees[method]; root != nil {
		return root.getValue(path, ctx)
	}
	return nil, false
}

func (r *Router) allowed(path, reqMethod string) (allow string) {
	if path == "*" || path == "/*" { // server-wide
		for method := range r.trees {
			if method == "OPTIONS" {
				continue
			}

			// add request method to list of allowed methods
			if len(allow) == 0 {
				allow = method
			} else {
				allow += ", " + method
			}
		}
	} else { // specific path
		for method := range r.trees {
			// Skip the requested method - we already tried this one
			if method == reqMethod || method == "OPTIONS" {
				continue
			}

			handle, _ := r.trees[method].getValue(path, nil)
			if handle != nil {
				// add request method to list of allowed methods
				if len(allow) == 0 {
					allow = method
				} else {
					allow += ", " + method
				}
			}
		}
	}
	if len(allow) > 0 {
		allow += ", OPTIONS"
	}
	return
}

// Handler makes the router implement the fasthttp.ListenAndServe interface.
func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
	if r.PanicHandler != nil {
		defer r.recv(ctx)
	}

	path := string(ctx.Path())
	method := string(ctx.Method())
	if root := r.trees[method]; root != nil {
		if f, tsr := root.getValue(path, ctx); f != nil {
			f(ctx)
			return
		} else if method != "CONNECT" && path != "/" {
			code := 301 // Permanent redirect, request with GET method
			if method != "GET" {
				// Temporary redirect, request with same method
				// As of Go 1.3, Go does not support status code 308.
				code = 307
			}

			if tsr && r.RedirectTrailingSlash {
				var uri string
				if len(path) > 1 && path[len(path)-1] == '/' {
					uri = path[:len(path)-1]
				} else {
					uri = path + "/"
				}

				if len(ctx.URI().QueryString()) > 0 {
					uri += "?" + string(ctx.QueryArgs().QueryString())
				}

				ctx.Redirect(uri, code)
				return
			}

			// Try to fix the request path
			if r.RedirectFixedPath {
				fixedPath, found := root.findCaseInsensitivePath(
					CleanPath(path),
					r.RedirectTrailingSlash,
				)

				if found {
					queryBuf := ctx.URI().QueryString()
					if len(queryBuf) > 0 {
						fixedPath = append(fixedPath, questionMark...)
						fixedPath = append(fixedPath, queryBuf...)
					}
					uri := string(fixedPath)
					ctx.Redirect(uri, code)
					return
				}
			}
		}
	}

	if method == "OPTIONS" {
		// Handle OPTIONS requests
		if r.HandleOPTIONS {
			if allow := r.allowed(path, method); len(allow) > 0 {
				ctx.Response.Header.Set("Allow", allow)
				return
			}
		}
	} else {
		// Handle 405
		if r.HandleMethodNotAllowed {
			if allow := r.allowed(path, method); len(allow) > 0 {
				ctx.Response.Header.Set("Allow", allow)
				if r.MethodNotAllowed != nil {
					r.MethodNotAllowed(ctx)
				} else {
					ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
					ctx.SetContentTypeBytes(defaultContentType)
					ctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed))
				}
				return
			}
		}
	}

	// Handle 404
	if r.NotFound != nil {
		r.NotFound(ctx)
	} else {
		ctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound),
			fasthttp.StatusNotFound)
	}
}
<|endoftext|>


<commit_before>package conductor

import (
	"context"
	"net/http"
	"regexp"
	"strconv"
	"sync"
)

type routeKey string

const routeParamsKey routeKey = "github.com/ascarter/conductor/RouteParamsKey"

// newContextWithRegexpMatch creates context with regular expression matches
func newContextWithRouteParams(ctx context.Context, params RouteParams) context.Context {
	return context.WithValue(ctx, routeParamsKey, params)
}

// RouteParamsFromContext returns a map of params and values extracted from the route match.
func RouteParamsFromContext(ctx context.Context) (RouteParams, bool) {
	params, ok := ctx.Value(routeParamsKey).(RouteParams)
	return params, ok
}

type RouteParams map[string]string

// A route is a RegExp pattern and the handler to call.
type route struct {
	re *regexp.Regexp
	h  http.Handler
}

func newRoute(p string, h http.Handler) *route {
	// TODO: Fix up pattern...
	return &route{h: h, re: regexp.MustCompile(p)}
}

func (r *route) GetParams(path string) RouteParams {
	keys := r.re.SubexpNames()
	values := r.re.FindStringSubmatch(path)

	params := RouteParams{}
	for i, k := range keys {
		if k == "" {
			k = strconv.Itoa(i)
		}
		params[k] = values[i]
	}

	return params
}

// Match checks if p matches route
func (r *route) Match(p string) bool {
	return r.re.MatchString(p)
}

// A RouterMux is an HTTP request multiplexer for URL patterns.
// It matches URL of each incoming request against a list of registered patterns
// and calls the handler for best pattern match.
//
// Patterns are static matches, parameterized patterns, or regular expressions.
// Longer patterns take precedence over shorter ones. If there are patterns that
// match both "/images\/.*" and "/images/thumbnails\/.*", the path "/images/thumbnails"
// would use the later handler.
//
// Patterns may optionally begin with a host name, restricting matches to URLs on that
// host only. Host specific patterns take precedence over general patterns.
//
// Patterns can take the following forms:
//	`/posts`
//	`/posts/`
//	`/posts/:id`
//	`/posts/(\d+)`
//	`/posts/(?<id>\d+)`
//	`host/posts`
//
// RouterMux follows the general approach used by http.ServeMux.
type RouterMux struct {
	mu     sync.RWMutex
	routes map[string]*route
	hosts  bool
}

// NewRouterMux allocates and returns a new RouterMux.
func NewRouterMux() *RouterMux {
	return &RouterMux{}
}

// match finds the best pattern for the method and path.
func (mux *RouterMux) match(method, path string) (h http.Handler, pattern string) {
	var n = 0
	for k, v := range mux.routes {
		if !v.Match(path) {
			continue
		}

		if h == nil || len(k) > n {
			n = len(k)
			h = v.h
			pattern = k
		}
	}

	return
}

// Handler returns the handler to use for the given request, consulting r.Host, r.Method,
// and r.URL.Path. It always returns a non-nil handler.
//
// Handler also returns the registered pattern that matches the request.
//
// If there is no registered handler that applies to the request, Handler returns
// a ``page not found'' handler and an empty pattern.
func (mux *RouterMux) Handler(r *http.Request) (h http.Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// Host-specific pattern takes precedence over generic ones
	if mux.hosts {
		h, pattern = mux.match(r.Method, r.Host+r.URL.Path)
	}

	// If no host match, match generic patterns
	if h == nil {
		h, pattern = mux.match(r.Method, r.URL.Path)
	}

	// No handler matches
	if h == nil {
		h, pattern = http.NotFoundHandler(), ""
	}

	return
}

// ServeHTTP dispatches request to the handler whose pattern most closely matches the request URL.
func (mux *RouterMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h, pattern := mux.Handler(r)
	route, ok := mux.routes[pattern]
	if ok {
		ctx := newContextWithRouteParams(r.Context(), route.GetParams(r.URL.Path))
		r = r.WithContext(ctx)
	}
	h.ServeHTTP(w, r)
}

// Handle registers the handler for a give pattern.
//
// If the handler already exists for pattern or the expression does not compile,
// Handle panics.
func (mux *RouterMux) Handle(pattern string, handler http.Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	// Verify parameters
	if pattern == "" {
		panic("mux: invalid pattern " + pattern)
	}

	if handler == nil {
		panic("mux: nil handler")
	}

	if _, ok := mux.routes[pattern]; ok {
		panic("mux: multiple registrations for " + pattern)
	}

	if mux.routes == nil {
		mux.routes = make(map[string]*route)
	}

	mux.routes[pattern] = newRoute(pattern, handler)

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

// HandleFunc registers the handler function for the given pattern.
func (mux *RouterMux) HandleFunc(pattern string, handler http.HandlerFunc) {
	mux.Handle(pattern, http.HandlerFunc(handler))
}

// A Router is an http.Handler that can route requests with a stack of middleware components.
type Router struct {
	mux        *http.ServeMux
	rmux       *RouterMux
	components []Component
}

// NewRouter returns a new Router instance
func NewRouter() *Router {
	router := Router{mux: http.NewServeMux(), rmux: NewRouterMux()}

	// Take advantage of ServeMux URL normalization and then send
	// to RouterMux for dispatch
	router.mux.Handle("/", router.rmux)

	return &router
}

// Use adds a Component to the middleware stack for an Router
func (router *Router) Use(component ...Component) {
	router.components = append(router.components, component...)
}

// Handle registers the handler for the given pattern.
func (router *Router) Handle(pattern string, handler http.Handler) {
	for i := range router.components {
		handler = router.components[len(router.components)-1-i].Next(handler)
	}

	router.rmux.Handle(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern.
func (router *Router) HandleFunc(pattern string, handler http.HandlerFunc) {
	router.Handle(pattern, http.HandlerFunc(handler))
}

// ServeHTTP dispatches the request to the internal ServeMux for URL normalization then
// is passed to the internal RouterMux for dispatch.
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.mux.ServeHTTP(w, r)
}
<commit_msg>Handle subtree patterns<commit_after>package conductor

import (
	"context"
	"net/http"
	"regexp"
	"strconv"
	"sync"
)

type routeKey string

const routeParamsKey routeKey = "github.com/ascarter/conductor/RouteParamsKey"

// newContextWithRegexpMatch creates context with regular expression matches
func newContextWithRouteParams(ctx context.Context, params RouteParams) context.Context {
	return context.WithValue(ctx, routeParamsKey, params)
}

// RouteParamsFromContext returns a map of params and values extracted from the route match.
func RouteParamsFromContext(ctx context.Context) (RouteParams, bool) {
	params, ok := ctx.Value(routeParamsKey).(RouteParams)
	return params, ok
}

// RouteParams is a map of param names to values that are matched with a pattern to a path.
// Param ID's are expected to be unique.
type RouteParams map[string]string

// A route is a RegExp pattern and the handler to call.
type route struct {
	re *regexp.Regexp
	h  http.Handler
}

func newRoute(p string, h http.Handler) *route {
	n := len(p)
	pattern := p

	// TODO: Replace parameters

	if p[n-1] != '/' {
		if p[n-1] != '$' {
			// Terminate for exact match
			pattern += "$"
		}
	} else {
		// Subtree - match anything below
		pattern += ".*"
	}

	return &route{h: h, re: regexp.MustCompile(pattern)}
}

func (r *route) GetParams(path string) RouteParams {
	keys := r.re.SubexpNames()
	values := r.re.FindStringSubmatch(path)

	params := RouteParams{}
	for i, k := range keys {
		if k == "" {
			k = strconv.Itoa(i)
		}
		params[k] = values[i]
	}

	return params
}

// Match checks if p matches route
func (r *route) Match(p string) bool {
	return r.re.MatchString(p)
}

// A RouterMux is an HTTP request multiplexer for URL patterns.
// It matches URL of each incoming request against a list of registered patterns
// and calls the handler for best pattern match.
//
// Patterns are static matches, parameterized patterns, or regular expressions.
// Longer patterns take precedence over shorter ones. If there are patterns that
// match both "/images\/.*" and "/images/thumbnails\/.*", the path "/images/thumbnails"
// would use the later handler.
//
// Patterns may optionally begin with a host name, restricting matches to URLs on that
// host only. Host specific patterns take precedence over general patterns.
//
// Patterns can take the following forms:
//	`/posts`
//	`/posts/`
//	`/posts/:id`
//	`/posts/(\d+)`
//	`/posts/(?<id>\d+)`
//	`host/posts`
//
// RouterMux follows the general approach used by http.ServeMux.
type RouterMux struct {
	mu     sync.RWMutex
	routes map[string]*route
	hosts  bool
}

// NewRouterMux allocates and returns a new RouterMux.
func NewRouterMux() *RouterMux {
	return &RouterMux{}
}

// match finds the best pattern for the method and path.
func (mux *RouterMux) match(method, path string) (h http.Handler, pattern string) {
	var n = 0
	for k, v := range mux.routes {
		if !v.Match(path) {
			continue
		}

		if h == nil || len(k) > n {
			n = len(k)
			h = v.h
			pattern = k
		}
	}

	return
}

// Handler returns the handler to use for the given request, consulting r.Host, r.Method,
// and r.URL.Path. It always returns a non-nil handler.
//
// Handler also returns the registered pattern that matches the request.
//
// If there is no registered handler that applies to the request, Handler returns
// a ``page not found'' handler and an empty pattern.
func (mux *RouterMux) Handler(r *http.Request) (h http.Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// Host-specific pattern takes precedence over generic ones
	if mux.hosts {
		h, pattern = mux.match(r.Method, r.Host+r.URL.Path)
	}

	// If no host match, match generic patterns
	if h == nil {
		h, pattern = mux.match(r.Method, r.URL.Path)
	}

	// No handler matches
	if h == nil {
		h, pattern = http.NotFoundHandler(), ""
	}

	return
}

// ServeHTTP dispatches request to the handler whose pattern most closely matches the request URL.
func (mux *RouterMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h, pattern := mux.Handler(r)
	route, ok := mux.routes[pattern]
	if ok {
		ctx := newContextWithRouteParams(r.Context(), route.GetParams(r.URL.Path))
		r = r.WithContext(ctx)
	}
	h.ServeHTTP(w, r)
}

// Handle registers the handler for a give pattern.
//
// If the handler already exists for pattern or the expression does not compile,
// Handle panics.
func (mux *RouterMux) Handle(pattern string, handler http.Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	// Verify parameters
	if pattern == "" {
		panic("mux: invalid pattern " + pattern)
	}

	if handler == nil {
		panic("mux: nil handler")
	}

	if _, ok := mux.routes[pattern]; ok {
		panic("mux: multiple registrations for " + pattern)
	}

	if mux.routes == nil {
		mux.routes = make(map[string]*route)
	}

	mux.routes[pattern] = newRoute(pattern, handler)

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

// HandleFunc registers the handler function for the given pattern.
func (mux *RouterMux) HandleFunc(pattern string, handler http.HandlerFunc) {
	mux.Handle(pattern, http.HandlerFunc(handler))
}

// A Router is an http.Handler that can route requests with a stack of middleware components.
type Router struct {
	mux        *http.ServeMux
	rmux       *RouterMux
	components []Component
}

// NewRouter returns a new Router instance
func NewRouter() *Router {
	router := Router{mux: http.NewServeMux(), rmux: NewRouterMux()}

	// Take advantage of ServeMux URL normalization and then send
	// to RouterMux for dispatch
	router.mux.Handle("/", router.rmux)

	return &router
}

// Use adds a Component to the middleware stack for an Router
func (router *Router) Use(component ...Component) {
	router.components = append(router.components, component...)
}

// Handle registers the handler for the given pattern.
func (router *Router) Handle(pattern string, handler http.Handler) {
	for i := range router.components {
		handler = router.components[len(router.components)-1-i].Next(handler)
	}

	router.rmux.Handle(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern.
func (router *Router) HandleFunc(pattern string, handler http.HandlerFunc) {
	router.Handle(pattern, http.HandlerFunc(handler))
}

// ServeHTTP dispatches the request to the internal ServeMux for URL normalization then
// is passed to the internal RouterMux for dispatch.
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.mux.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package impulse

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
)

type SampleFlag uint8

const (
	SampleAssociatedWithHeader SampleFlag = 1 << iota
	Quality16Bit
	StereoSample
	Loop
	SustainLoop
	Compressed
	PingPongLoop
	PingPongSustainLoop
)

type VibratoWaveform uint8

const (
	SineWave VibratoWaveform = iota
	RampDown
	SquareWave
	Random
)

type rawSample struct {
	MagicString        [4]byte
	DOSFilename        [12]byte
	_                  byte
	GvL, Flg, Vol      uint8
	SampleName         [26]byte
	Cvt, DfP           uint8
	Length             uint32
	LoopBegin          uint32
	LoopEnd            uint32
	C5Speed            uint32
	SusLoopBegin       uint32
	SusLoopEnd         uint32
	SamplePointer      uint32
	ViS, ViD, ViR, ViT uint8
}

// Sample is an Impulse Tracker sample.
type Sample struct {
	Filename         string // max 11 bytes
	GlobalVolume     uint8  // range 0->64
	Flags            SampleFlag
	DefaultVolume    uint8  // range 0->64
	Name             string // max 26 bytes
	Signed           bool
	DefaultPan       uint8 // range 0->64
	DefaultPanOn     bool
	Length           uint32
	LoopBegin        uint32
	LoopEnd          uint32
	Speed            uint32 // range 0->9999999
	SustainLoopBegin uint32
	SustainLoopEnd   uint32
	VibratoSpeed     uint8 // range 0->64
	VibratoDepth     uint8 // range 0->64
	VibratoWaveform  VibratoWaveform
	VibratoRate      uint8
	Data             []byte // PCM audio data
}

func sampleFromRaw(raw *rawSample, r io.ReadSeeker) (*Sample, error) {
	s := Sample{
		Filename:         string(bytes.Trim(raw.DOSFilename[:], "\x00")),
		GlobalVolume:     raw.GvL,
		Flags:            SampleFlag(raw.Flg),
		DefaultVolume:    raw.Vol,
		Name:             string(bytes.Trim(raw.SampleName[:], "\x00")),
		Signed:           raw.Cvt&0x01 != 0,
		DefaultPan:       raw.DfP & 0x4f,
		DefaultPanOn:     raw.DfP&0x80 != 0,
		Length:           raw.Length,
		LoopBegin:        raw.LoopBegin,
		LoopEnd:          raw.LoopEnd,
		Speed:            raw.C5Speed,
		SustainLoopBegin: raw.SusLoopBegin,
		SustainLoopEnd:   raw.SusLoopEnd,
		VibratoSpeed:     raw.ViS,
		VibratoDepth:     raw.ViD,
		VibratoRate:      raw.ViR,
		VibratoWaveform:  VibratoWaveform(raw.ViT),
	}

	if s.Flags&Quality16Bit != 0 {
		s.Data = make([]byte, s.Length*2)
	} else {
		s.Data = make([]byte, s.Length)
	}

	if _, err := r.Seek(int64(raw.SamplePointer), 0); err != nil {
		return nil, err
	}
	if _, err := r.Read(s.Data); err != nil {
		return nil, err
	}

	return &s, nil
}

// ReadSample reads a Sample in ITS format from r.
func ReadSample(r io.ReadSeeker) (*Sample, error) {
	raw := new(rawSample)
	if err := binary.Read(r, binary.LittleEndian, raw); err != nil {
		return nil, err
	}
	if string(raw.MagicString[:]) != "IMPS" {
		return nil, errors.New("data is not Impulse Tracker sample")
	}
	return sampleFromRaw(raw, r)
}

// Write writes the Sample to w in ITS format.
func (s *Sample) Write(w io.Writer) error {
	raw := &rawSample{
		MagicString:   [4]byte{'I', 'M', 'P', 'S'},
		GvL:           s.GlobalVolume,
		Flg:           uint8(s.Flags),
		Vol:           s.DefaultVolume,
		DfP:           s.DefaultPan,
		Length:        s.Length,
		LoopBegin:     s.LoopBegin,
		LoopEnd:       s.LoopEnd,
		C5Speed:       s.Speed,
		SusLoopBegin:  s.SustainLoopBegin,
		SusLoopEnd:    s.SustainLoopEnd,
		SamplePointer: 0x0050,
		ViS:           s.VibratoSpeed,
		ViD:           s.VibratoDepth,
		ViR:           s.VibratoRate,
		ViT:           uint8(s.VibratoWaveform),
	}
	for i := range raw.DOSFilename {
		if i < len(s.Filename) {
			raw.DOSFilename[i] = s.Filename[i]
		}
	}
	for i := range raw.SampleName {
		if i < len(s.Name) {
			raw.SampleName[i] = s.Name[i]
		}
	}
	if s.Signed {
		raw.Cvt = 0x01
	}
	if s.DefaultPanOn {
		raw.DfP |= 0x80
	}

	if err := binary.Write(w, binary.LittleEndian, raw); err != nil {
		return err
	}
	if err := binary.Write(w, binary.LittleEndian, s.Data); err != nil {
		return err
	}

	return nil
}
<commit_msg>Fix order of sample flags<commit_after>package impulse

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
)

type SampleFlag uint8

const (
	SampleAssociatedWithHeader SampleFlag = 1 << iota
	Quality16Bit
	StereoSample
	Compressed
	Loop
	SustainLoop
	PingPongLoop
	PingPongSustainLoop
)

type VibratoWaveform uint8

const (
	SineWave VibratoWaveform = iota
	RampDown
	SquareWave
	Random
)

type rawSample struct {
	MagicString        [4]byte
	DOSFilename        [12]byte
	_                  byte
	GvL, Flg, Vol      uint8
	SampleName         [26]byte
	Cvt, DfP           uint8
	Length             uint32
	LoopBegin          uint32
	LoopEnd            uint32
	C5Speed            uint32
	SusLoopBegin       uint32
	SusLoopEnd         uint32
	SamplePointer      uint32
	ViS, ViD, ViR, ViT uint8
}

// Sample is an Impulse Tracker sample.
type Sample struct {
	Filename         string // max 11 bytes
	GlobalVolume     uint8  // range 0->64
	Flags            SampleFlag
	DefaultVolume    uint8  // range 0->64
	Name             string // max 26 bytes
	Signed           bool
	DefaultPan       uint8 // range 0->64
	DefaultPanOn     bool
	Length           uint32
	LoopBegin        uint32
	LoopEnd          uint32
	Speed            uint32 // range 0->9999999
	SustainLoopBegin uint32
	SustainLoopEnd   uint32
	VibratoSpeed     uint8 // range 0->64
	VibratoDepth     uint8 // range 0->64
	VibratoWaveform  VibratoWaveform
	VibratoRate      uint8
	Data             []byte // PCM audio data
}

func sampleFromRaw(raw *rawSample, r io.ReadSeeker) (*Sample, error) {
	s := Sample{
		Filename:         string(bytes.Trim(raw.DOSFilename[:], "\x00")),
		GlobalVolume:     raw.GvL,
		Flags:            SampleFlag(raw.Flg),
		DefaultVolume:    raw.Vol,
		Name:             string(bytes.Trim(raw.SampleName[:], "\x00")),
		Signed:           raw.Cvt&0x01 != 0,
		DefaultPan:       raw.DfP & 0x4f,
		DefaultPanOn:     raw.DfP&0x80 != 0,
		Length:           raw.Length,
		LoopBegin:        raw.LoopBegin,
		LoopEnd:          raw.LoopEnd,
		Speed:            raw.C5Speed,
		SustainLoopBegin: raw.SusLoopBegin,
		SustainLoopEnd:   raw.SusLoopEnd,
		VibratoSpeed:     raw.ViS,
		VibratoDepth:     raw.ViD,
		VibratoRate:      raw.ViR,
		VibratoWaveform:  VibratoWaveform(raw.ViT),
	}

	if s.Flags&Quality16Bit != 0 {
		s.Data = make([]byte, s.Length*2)
	} else {
		s.Data = make([]byte, s.Length)
	}

	if _, err := r.Seek(int64(raw.SamplePointer), 0); err != nil {
		return nil, err
	}
	if _, err := r.Read(s.Data); err != nil {
		return nil, err
	}

	return &s, nil
}

// ReadSample reads a Sample in ITS format from r.
func ReadSample(r io.ReadSeeker) (*Sample, error) {
	raw := new(rawSample)
	if err := binary.Read(r, binary.LittleEndian, raw); err != nil {
		return nil, err
	}
	if string(raw.MagicString[:]) != "IMPS" {
		return nil, errors.New("data is not Impulse Tracker sample")
	}
	return sampleFromRaw(raw, r)
}

// Write writes the Sample to w in ITS format.
func (s *Sample) Write(w io.Writer) error {
	raw := &rawSample{
		MagicString:   [4]byte{'I', 'M', 'P', 'S'},
		GvL:           s.GlobalVolume,
		Flg:           uint8(s.Flags),
		Vol:           s.DefaultVolume,
		DfP:           s.DefaultPan,
		Length:        s.Length,
		LoopBegin:     s.LoopBegin,
		LoopEnd:       s.LoopEnd,
		C5Speed:       s.Speed,
		SusLoopBegin:  s.SustainLoopBegin,
		SusLoopEnd:    s.SustainLoopEnd,
		SamplePointer: 0x0050,
		ViS:           s.VibratoSpeed,
		ViD:           s.VibratoDepth,
		ViR:           s.VibratoRate,
		ViT:           uint8(s.VibratoWaveform),
	}
	for i := range raw.DOSFilename {
		if i < len(s.Filename) {
			raw.DOSFilename[i] = s.Filename[i]
		}
	}
	for i := range raw.SampleName {
		if i < len(s.Name) {
			raw.SampleName[i] = s.Name[i]
		}
	}
	if s.Signed {
		raw.Cvt = 0x01
	}
	if s.DefaultPanOn {
		raw.DfP |= 0x80
	}

	if err := binary.Write(w, binary.LittleEndian, raw); err != nil {
		return err
	}
	if err := binary.Write(w, binary.LittleEndian, s.Data); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell

// Screen represents the physical (or emulated) screen.
// This can be a terminal window or a physical console.  Platforms implement
// this differently.
type Screen interface {
	// Init initializes the screen for use.
	Init() error

	// Fini finalizes the screen also releasing resources.
	Fini()

	// Clear erases the screen.  The contents of any screen buffers
	// will also be cleared.  This has the logical effect of
	// filling the screen with spaces, using the global default style.
	Clear()

	// Fill fills the screen with the given character and style.
	Fill(rune, Style)

	// SetCell is an older API, and will be removed.  Please use
	// SetContent instead; SetCell is implemented in terms of SetContent.
	SetCell(x int, y int, style Style, ch ...rune)

	// GetContent returns the contents at the given location.  If the
	// coordinates are out of range, then the values will be 0, nil,
	// StyleDefault.  Note that the contents returned are logical contents
	// and may not actually be what is displayed, but rather are what will
	// be displayed if Show() or Sync() is called.  The width is the width
	// in screen cells; most often this will be 1, but some East Asian
	// characters require two cells.
	GetContent(x, y int) (mainc rune, combc []rune, style Style, width int)

	// SetContent sets the contents of the given cell location.  If
	// the coordinates are out of range, then the operation is ignored.
	//
	// The first rune is the primary non-zero width rune.  The array
	// that follows is a possible list of combining characters to append,
	// and will usually be nil (no combining characters.)
	//
	// The results are not displayd until Show() or Sync() is called.
	//
	// Note that wide (East Asian full width) runes occupy two cells,
	// and attempts to place character at next cell to the right will have
	// undefined effects.  Wide runes that are printed in the
	// last column will be replaced with a single width space on output.
	SetContent(x int, y int, mainc rune, combc []rune, style Style)

	// SetStyle sets the default style to use when clearing the screen
	// or when StyleDefault is specified.  If it is also StyleDefault,
	// then whatever system/terminal default is relevant will be used.
	SetStyle(style Style)

	// ShowCursor is used to display the cursor at a given location.
	// If the coordinates -1, -1 are given or are otherwise outside the
	// dimensions of the screen, the cursor will be hidden.
	ShowCursor(x int, y int)

	// HideCursor is used to hide the cursor.  Its an alias for
	// ShowCursor(-1, -1).sim
	HideCursor()

	// SetCursorStyle is used to set the cursor style.  If the style
	// is not supported (or cursor styles are not supported at all),
	// then this will have no effect.
	SetCursorStyle(CursorStyle)

	// Size returns the screen size as width, height.  This changes in
	// response to a call to Clear or Flush.
	Size() (width, height int)

	// ChannelEvents is an infinite loop that waits for an event and
	// channels it into the user provided channel ch.  Closing the
	// quit channel and calling the Fini method are cancellation
	// signals.  When a cancellation signal is received the method
	// returns after closing ch.
	//
	// This method should be used as a goroutine.
	//
	// NOTE: PollEvent should not be called while this method is running.
	ChannelEvents(ch chan<- Event, quit <-chan struct{})

	// PollEvent waits for events to arrive.  Main application loops
	// must spin on this to prevent the application from stalling.
	// Furthermore, this will return nil if the Screen is finalized.
	PollEvent() Event

	// HasPendingEvent returns true if PollEvent would return an event
	// without blocking.  If the screen is stopped and PollEvent would
	// return nil, then the return value from this function is unspecified.
	// The purpose of this function is to allow multiple events to be collected
	// at once, to minimize screen redraws.
	HasPendingEvent() bool

	// PostEvent tries to post an event into the event stream.  This
	// can fail if the event queue is full.  In that case, the event
	// is dropped, and ErrEventQFull is returned.
	PostEvent(ev Event) error

	// Deprecated: PostEventWait is unsafe, and will be removed
	// in the future.
	//
	// PostEventWait is like PostEvent, but if the queue is full, it
	// blocks until there is space in the queue, making delivery
	// reliable.  However, it is VERY important that this function
	// never be called from within whatever event loop is polling
	// with PollEvent(), otherwise a deadlock may arise.
	//
	// For this reason, when using this function, the use of a
	// Goroutine is recommended to ensure no deadlock can occur.
	PostEventWait(ev Event)

	// EnableMouse enables the mouse.  (If your terminal supports it.)
	// If no flags are specified, then all events are reported, if the
	// terminal supports them.
	EnableMouse(...MouseFlags)

	// DisableMouse disables the mouse.
	DisableMouse()

	// EnablePaste enables bracketed paste mode, if supported.
	EnablePaste()

	// DisablePaste disables bracketed paste mode.
	DisablePaste()

	// HasMouse returns true if the terminal (apparently) supports a
	// mouse.  Note that the a return value of true doesn't guarantee that
	// a mouse/pointing device is present; a false return definitely
	// indicates no mouse support is available.
	HasMouse() bool

	// Colors returns the number of colors.  All colors are assumed to
	// use the ANSI color map.  If a terminal is monochrome, it will
	// return 0.
	Colors() int

	// Show makes all the content changes made using SetContent() visible
	// on the display.
	//
	// It does so in the most efficient and least visually disruptive
	// manner possible.
	Show()

	// Sync works like Show(), but it updates every visible cell on the
	// physical display, assuming that it is not synchronized with any
	// internal model.  This may be both expensive and visually jarring,
	// so it should only be used when believed to actually be necessary.
	//
	// Typically this is called as a result of a user-requested redraw
	// (e.g. to clear up on screen corruption caused by some other program),
	// or during a resize event.
	Sync()

	// CharacterSet returns information about the character set.
	// This isn't the full locale, but it does give us the input/output
	// character set.  Note that this is just for diagnostic purposes,
	// we normally translate input/output to/from UTF-8, regardless of
	// what the user's environment is.
	CharacterSet() string

	// RegisterRuneFallback adds a fallback for runes that are not
	// part of the character set -- for example one could register
	// o as a fallback for ø.  This should be done cautiously for
	// characters that might be displayed ordinarily in language
	// specific text -- characters that could change the meaning of
	// of written text would be dangerous.  The intention here is to
	// facilitate fallback characters in pseudo-graphical applications.
	//
	// If the terminal has fallbacks already in place via an alternate
	// character set, those are used in preference.  Also, standard
	// fallbacks for graphical characters in the ACSC terminfo string
	// are registered implicitly.
	//
	// The display string should be the same width as original rune.
	// This makes it possible to register two character replacements
	// for full width East Asian characters, for example.
	//
	// It is recommended that replacement strings consist only of
	// 7-bit ASCII, since other characters may not display everywhere.
	RegisterRuneFallback(r rune, subst string)

	// UnregisterRuneFallback unmaps a replacement.  It will unmap
	// the implicit ASCII replacements for alternate characters as well.
	// When an unmapped char needs to be displayed, but no suitable
	// glyph is available, '?' is emitted instead.  It is not possible
	// to "disable" the use of alternate characters that are supported
	// by your terminal except by changing the terminal database.
	UnregisterRuneFallback(r rune)

	// CanDisplay returns true if the given rune can be displayed on
	// this screen.  Note that this is a best guess effort -- whether
	// your fonts support the character or not may be questionable.
	// Mostly this is for folks who work outside of Unicode.
	//
	// If checkFallbacks is true, then if any (possibly imperfect)
	// fallbacks are registered, this will return true.  This will
	// also return true if the terminal can replace the glyph with
	// one that is visually indistinguishable from the one requested.
	CanDisplay(r rune, checkFallbacks bool) bool

	// Resize does nothing, since its generally not possible to
	// ask a screen to resize, but it allows the Screen to implement
	// the View interface.
	Resize(int, int, int, int)

	// HasKey returns true if the keyboard is believed to have the
	// key.  In some cases a keyboard may have keys with this name
	// but no support for them, while in others a key may be reported
	// as supported but not actually be usable (such as some emulators
	// that hijack certain keys).  Its best not to depend to strictly
	// on this function, but it can be used for hinting when building
	// menus, displayed hot-keys, etc.  Note that KeyRune (literal
	// runes) is always true.
	HasKey(Key) bool

	// Suspend pauses input and output processing.  It also restores the
	// terminal settings to what they were when the application started.
	// This can be used to, for example, run a sub-shell.
	Suspend() error

	// Resume resumes after Suspend().
	Resume() error

	// Beep attempts to sound an OS-dependent audible alert and returns an error
	// when unsuccessful.
	Beep() error
}

// NewScreen returns a default Screen suitable for the user's terminal
// environment.
func NewScreen() (Screen, error) {
	// Windows is happier if we try for a console screen first.
	if s, _ := NewConsoleScreen(); s != nil {
		return s, nil
	} else if s, e := NewTerminfoScreen(); s != nil {
		return s, nil
	} else {
		return nil, e
	}
}

// MouseFlags are options to modify the handling of mouse events.
// Actual events can be or'd together.
type MouseFlags int

const (
	MouseButtonEvents = MouseFlags(1) // Click events only
	MouseDragEvents   = MouseFlags(2) // Click-drag events (includes button events)
	MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
)

// CursorStyle represents a given cursor style, which can include the shape and
// whether the cursor blinks or is solid.  Support for changing these is not universal.
type CursorStyle int

const (
	CursorStyleDefault = CursorStyle(iota) // The default
	CursorStyleBlinkingBlock
	CursorStyleSteadyBlock
	CursorStyleBlinkingUnderline
	CursorStyleSteadyUnderline
	CursorStyleBlinkingBar
	CursorStyleSteadyBar
)<commit_msg>Some minor language fixups.<commit_after>// Copyright 2022 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell

// Screen represents the physical (or emulated) screen.
// This can be a terminal window or a physical console.  Platforms implement
// this differently.
type Screen interface {
	// Init initializes the screen for use.
	Init() error

	// Fini finalizes the screen also releasing resources.
	Fini()

	// Clear erases the screen.  The contents of any screen buffers
	// will also be cleared.  This has the logical effect of
	// filling the screen with spaces, using the global default style.
	Clear()

	// Fill fills the screen with the given character and style.
	Fill(rune, Style)

	// SetCell is an older API, and will be removed.  Please use
	// SetContent instead; SetCell is implemented in terms of SetContent.
	SetCell(x int, y int, style Style, ch ...rune)

	// GetContent returns the contents at the given location.  If the
	// coordinates are out of range, then the values will be 0, nil,
	// StyleDefault.  Note that the contents returned are logical contents
	// and may not actually be what is displayed, but rather are what will
	// be displayed if Show() or Sync() is called.  The width is the width
	// in screen cells; most often this will be 1, but some East Asian
	// characters require two cells.
	GetContent(x, y int) (primary rune, combining []rune, style Style, width int)

	// SetContent sets the contents of the given cell location.  If
	// the coordinates are out of range, then the operation is ignored.
	//
	// The first rune is the primary non-zero width rune.  The array
	// that follows is a possible list of combining characters to append,
	// and will usually be nil (no combining characters.)
	//
	// The results are not displayed until Show() or Sync() is called.
	//
	// Note that wide (East Asian full width) runes occupy two cells,
	// and attempts to place character at next cell to the right will have
	// undefined effects.  Wide runes that are printed in the
	// last column will be replaced with a single width space on output.
	SetContent(x int, y int, primary rune, combining []rune, style Style)

	// SetStyle sets the default style to use when clearing the screen
	// or when StyleDefault is specified.  If it is also StyleDefault,
	// then whatever system/terminal default is relevant will be used.
	SetStyle(style Style)

	// ShowCursor is used to display the cursor at a given location.
	// If the coordinates -1, -1 are given or are otherwise outside the
	// dimensions of the screen, the cursor will be hidden.
	ShowCursor(x int, y int)

	// HideCursor is used to hide the cursor.  It's an alias for
	// ShowCursor(-1, -1).sim
	HideCursor()

	// SetCursorStyle is used to set the cursor style.  If the style
	// is not supported (or cursor styles are not supported at all),
	// then this will have no effect.
	SetCursorStyle(CursorStyle)

	// Size returns the screen size as width, height.  This changes in
	// response to a call to Clear or Flush.
	Size() (width, height int)

	// ChannelEvents is an infinite loop that waits for an event and
	// channels it into the user provided channel ch.  Closing the
	// quit channel and calling the Fini method are cancellation
	// signals.  When a cancellation signal is received the method
	// returns after closing ch.
	//
	// This method should be used as a goroutine.
	//
	// NOTE: PollEvent should not be called while this method is running.
	ChannelEvents(ch chan<- Event, quit <-chan struct{})

	// PollEvent waits for events to arrive.  Main application loops
	// must spin on this to prevent the application from stalling.
	// Furthermore, this will return nil if the Screen is finalized.
	PollEvent() Event

	// HasPendingEvent returns true if PollEvent would return an event
	// without blocking.  If the screen is stopped and PollEvent would
	// return nil, then the return value from this function is unspecified.
	// The purpose of this function is to allow multiple events to be collected
	// at once, to minimize screen redraws.
	HasPendingEvent() bool

	// PostEvent tries to post an event into the event stream.  This
	// can fail if the event queue is full.  In that case, the event
	// is dropped, and ErrEventQFull is returned.
	PostEvent(ev Event) error

	// Deprecated: PostEventWait is unsafe, and will be removed
	// in the future.
	//
	// PostEventWait is like PostEvent, but if the queue is full, it
	// blocks until there is space in the queue, making delivery
	// reliable.  However, it is VERY important that this function
	// never be called from within whatever event loop is polling
	// with PollEvent(), otherwise a deadlock may arise.
	//
	// For this reason, when using this function, the use of a
	// Goroutine is recommended to ensure no deadlock can occur.
	PostEventWait(ev Event)

	// EnableMouse enables the mouse.  (If your terminal supports it.)
	// If no flags are specified, then all events are reported, if the
	// terminal supports them.
	EnableMouse(...MouseFlags)

	// DisableMouse disables the mouse.
	DisableMouse()

	// EnablePaste enables bracketed paste mode, if supported.
	EnablePaste()

	// DisablePaste disables bracketed paste mode.
	DisablePaste()

	// HasMouse returns true if the terminal (apparently) supports a
	// mouse.  Note that the return value of true doesn't guarantee that
	// a mouse/pointing device is present; a false return definitely
	// indicates no mouse support is available.
	HasMouse() bool

	// Colors returns the number of colors.  All colors are assumed to
	// use the ANSI color map.  If a terminal is monochrome, it will
	// return 0.
	Colors() int

	// Show makes all the content changes made using SetContent() visible
	// on the display.
	//
	// It does so in the most efficient and least visually disruptive
	// manner possible.
	Show()

	// Sync works like Show(), but it updates every visible cell on the
	// physical display, assuming that it is not synchronized with any
	// internal model.  This may be both expensive and visually jarring,
	// so it should only be used when believed to actually be necessary.
	//
	// Typically, this is called as a result of a user-requested redraw
	// (e.g. to clear up on-screen corruption caused by some other program),
	// or during a resize event.
	Sync()

	// CharacterSet returns information about the character set.
	// This isn't the full locale, but it does give us the input/output
	// character set.  Note that this is just for diagnostic purposes,
	// we normally translate input/output to/from UTF-8, regardless of
	// what the user's environment is.
	CharacterSet() string

	// RegisterRuneFallback adds a fallback for runes that are not
	// part of the character set -- for example one could register
	// o as a fallback for ø.  This should be done cautiously for
	// characters that might be displayed ordinarily in language
	// specific text -- characters that could change the meaning of
	// written text would be dangerous.  The intention here is to
	// facilitate fallback characters in pseudo-graphical applications.
	//
	// If the terminal has fallbacks already in place via an alternate
	// character set, those are used in preference.  Also, standard
	// fallbacks for graphical characters in the alternate character set
	// terminfo string are registered implicitly.
	//
	// The display string should be the same width as original rune.
	// This makes it possible to register two character replacements
	// for full width East Asian characters, for example.
	//
	// It is recommended that replacement strings consist only of
	// 7-bit ASCII, since other characters may not display everywhere.
	RegisterRuneFallback(r rune, subst string)

	// UnregisterRuneFallback unmaps a replacement.  It will unmap
	// the implicit ASCII replacements for alternate characters as well.
	// When an unmapped char needs to be displayed, but no suitable
	// glyph is available, '?' is emitted instead.  It is not possible
	// to "disable" the use of alternate characters that are supported
	// by your terminal except by changing the terminal database.
	UnregisterRuneFallback(r rune)

	// CanDisplay returns true if the given rune can be displayed on
	// this screen.  Note that this is a best-guess effort -- whether
	// your fonts support the character or not may be questionable.
	// Mostly this is for folks who work outside of Unicode.
	//
	// If checkFallbacks is true, then if any (possibly imperfect)
	// fallbacks are registered, this will return true.  This will
	// also return true if the terminal can replace the glyph with
	// one that is visually indistinguishable from the one requested.
	CanDisplay(r rune, checkFallbacks bool) bool

	// Resize does nothing, since it's generally not possible to
	// ask a screen to resize, but it allows the Screen to implement
	// the View interface.
	Resize(int, int, int, int)

	// HasKey returns true if the keyboard is believed to have the
	// key.  In some cases a keyboard may have keys with this name
	// but no support for them, while in others a key may be reported
	// as supported but not actually be usable (such as some emulators
	// that hijack certain keys).  Its best not to depend to strictly
	// on this function, but it can be used for hinting when building
	// menus, displayed hot-keys, etc.  Note that KeyRune (literal
	// runes) is always true.
	HasKey(Key) bool

	// Suspend pauses input and output processing.  It also restores the
	// terminal settings to what they were when the application started.
	// This can be used to, for example, run a sub-shell.
	Suspend() error

	// Resume resumes after Suspend().
	Resume() error

	// Beep attempts to sound an OS-dependent audible alert and returns an error
	// when unsuccessful.
	Beep() error
}

// NewScreen returns a default Screen suitable for the user's terminal
// environment.
func NewScreen() (Screen, error) {
	// Windows is happier if we try for a console screen first.
	if s, _ := NewConsoleScreen(); s != nil {
		return s, nil
	} else if s, e := NewTerminfoScreen(); s != nil {
		return s, nil
	} else {
		return nil, e
	}
}

// MouseFlags are options to modify the handling of mouse events.
// Actual events can be ORed together.
type MouseFlags int

const (
	MouseButtonEvents = MouseFlags(1) // Click events only
	MouseDragEvents   = MouseFlags(2) // Click-drag events (includes button events)
	MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
)

// CursorStyle represents a given cursor style, which can include the shape and
// whether the cursor blinks or is solid.  Support for changing this is not universal.
type CursorStyle int

const (
	CursorStyleDefault = CursorStyle(iota) // The default
	CursorStyleBlinkingBlock
	CursorStyleSteadyBlock
	CursorStyleBlinkingUnderline
	CursorStyleSteadyUnderline
	CursorStyleBlinkingBar
	CursorStyleSteadyBar
)
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux
// +build !js
// +build !android
// +build !ios

package oto

// #cgo darwin        LDFLAGS: -framework OpenAL
// #cgo freebsd linux LDFLAGS: -lopenal
//
// #ifdef __APPLE__
// #include <OpenAL/al.h>
// #include <OpenAL/alc.h>
// #else
// #include <AL/al.h>
// #include <AL/alc.h>
// #endif
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"
)

// As x/mobile/exp/audio/al is broken on macOS (https://github.com/golang/go/issues/15075),
// and that doesn't support FreeBSD, use OpenAL directly here.

const (
	maxBufferNum = 8
)

type player struct {
	// alContext represents a pointer to ALCcontext. The type is uintptr since the value
	// can be 0x18 on macOS, which is invalid as a pointer value, and this might cause
	// GC errors.
	alContext      uintptr
	alDevice       uintptr
	alSource       C.ALuint
	alBuffers      []C.ALuint
	sampleRate     int
	isClosed       bool
	alFormat       C.ALenum
	maxWrittenSize int
	writtenSize    int
	bufferSizes    []int
}

func alFormat(channelNum, bytesPerSample int) C.ALenum {
	switch {
	case channelNum == 1 && bytesPerSample == 1:
		return C.AL_FORMAT_MONO8
	case channelNum == 1 && bytesPerSample == 2:
		return C.AL_FORMAT_MONO16
	case channelNum == 2 && bytesPerSample == 1:
		return C.AL_FORMAT_STEREO8
	case channelNum == 2 && bytesPerSample == 2:
		return C.AL_FORMAT_STEREO16
	}
	panic(fmt.Sprintf("oto: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample))
}

func getError(device uintptr) error {
	c := C.alcGetError((*C.struct_ALCdevice_struct)(unsafe.Pointer(device)))
	switch c {
	case C.ALC_NO_ERROR:
		return nil
	case C.ALC_INVALID_DEVICE:
		return errors.New("OpenAL error: invalid device")
	case C.ALC_INVALID_CONTEXT:
		return errors.New("OpenAL error: invalid context")
	case C.ALC_INVALID_ENUM:
		return errors.New("OpenAL error: invalid enum")
	case C.ALC_INVALID_VALUE:
		return errors.New("OpenAL error: invalid value")
	case C.ALC_OUT_OF_MEMORY:
		return errors.New("OpenAL error: out of memory")
	default:
		return fmt.Errorf("OpenAL error: code %d", c)
	}
}

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	name := C.alGetString(C.ALC_DEFAULT_DEVICE_SPECIFIER)
	d := uintptr(unsafe.Pointer(C.alcOpenDevice((*C.ALCchar)(name))))
	if d == 0 {
		return nil, fmt.Errorf("oto: alcOpenDevice must not return null")
	}
	c := uintptr(unsafe.Pointer(C.alcCreateContext((*C.struct_ALCdevice_struct)(unsafe.Pointer(d)), nil)))
	if c == 0 {
		return nil, fmt.Errorf("oto: alcCreateContext must not return null")
	}
	// Don't check getError until making the current context is done.
	// Linux might fail this check even though it succeeds (hajimehoshi/ebiten#204).
	C.alcMakeContextCurrent((*C.struct_ALCcontext_struct)(unsafe.Pointer(c)))
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: Activate: %v", err)
	}
	s := C.ALuint(0)
	C.alGenSources(1, &s)
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: NewSource: %v", err)
	}
	p := &player{
		alContext:      c,
		alDevice:       d,
		alSource:       s,
		alBuffers:      []C.ALuint{},
		sampleRate:     sampleRate,
		alFormat:       alFormat(channelNum, bytesPerSample),
		maxWrittenSize: bufferSizeInBytes,
	}
	runtime.SetFinalizer(p, (*player).Close)

	bs := make([]C.ALuint, maxBufferNum)
	C.alGenBuffers(maxBufferNum, &bs[0])
	const bufferSize = 1024
	emptyBytes := make([]byte, bufferSize)
	for _, b := range bs {
		// Note that the third argument of only the first buffer is used.
		C.alBufferData(b, p.alFormat, unsafe.Pointer(&emptyBytes[0]), C.ALsizei(len(emptyBytes)), C.ALsizei(p.sampleRate))
		p.alBuffers = append(p.alBuffers, b)
	}
	C.alSourcePlay(p.alSource)
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: Play: %v", err)
	}
	return p, nil
}

func (p *player) Write(data []byte) (int, error) {
	if err := getError(p.alDevice); err != nil {
		return 0, fmt.Errorf("oto: starting Write: %v", err)
	}
	processedNum := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_PROCESSED, &processedNum)
	if 0 < processedNum {
		bufs := make([]C.ALuint, processedNum)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bufs)), &bufs[0])
		if err := getError(p.alDevice); err != nil {
			return 0, fmt.Errorf("oto: UnqueueBuffers: %v", err)
		}
		p.alBuffers = append(p.alBuffers, bufs...)
		for i := 0; i < len(bufs); i++ {
			p.writtenSize -= p.bufferSizes[0]
			p.bufferSizes = p.bufferSizes[1:]
		}
	}

	if len(p.alBuffers) == 0 {
		// This can happen (hajimehoshi/ebiten#207)
		return 0, nil
	}
	buf := p.alBuffers[0]
	p.alBuffers = p.alBuffers[1:]
	n := min(len(data), p.maxWrittenSize-p.writtenSize)
	if n <= 0 {
		return 0, nil
	}
	C.alBufferData(buf, p.alFormat, unsafe.Pointer(&data[0]), C.ALsizei(n), C.ALsizei(p.sampleRate))
	C.alSourceQueueBuffers(p.alSource, 1, &buf)
	if err := getError(p.alDevice); err != nil {
		return 0, fmt.Errorf("oto: QueueBuffer: %v", err)
	}
	p.writtenSize += n
	p.bufferSizes = append(p.bufferSizes, n)

	state := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_SOURCE_STATE, &state)
	if state == C.AL_STOPPED || state == C.AL_INITIAL {
		C.alSourceRewind(p.alSource)
		C.alSourcePlay(p.alSource)
		if err := getError(p.alDevice); err != nil {
			return 0, fmt.Errorf("oto: Rewind or Play: %v", err)
		}
	}

	return n, nil
}

func (p *player) Close() error {
	if err := getError(p.alDevice); err != nil {
		return fmt.Errorf("oto: starting Close: %v", err)
	}
	if p.isClosed {
		return nil
	}
	var bs []C.ALuint
	C.alSourceRewind(p.alSource)
	C.alSourcePlay(p.alSource)
	n := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_QUEUED, &n)
	if 0 < n {
		bs = make([]C.ALuint, n)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bs)), &bs[0])
		p.alBuffers = append(p.alBuffers, bs...)
	}
	C.alcCloseDevice((*C.struct_ALCdevice_struct)(unsafe.Pointer(p.alDevice)))
	p.isClosed = true
	if err := getError(p.alDevice); err != nil {
		return fmt.Errorf("oto: CloseDevice: %v", err)
	}
	runtime.SetFinalizer(p, nil)
	return nil
}
<commit_msg>openal: Adjust lower buffer sizes<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux
// +build !js
// +build !android
// +build !ios

package oto

// #cgo darwin        LDFLAGS: -framework OpenAL
// #cgo freebsd linux LDFLAGS: -lopenal
//
// #ifdef __APPLE__
// #include <OpenAL/al.h>
// #include <OpenAL/alc.h>
// #else
// #include <AL/al.h>
// #include <AL/alc.h>
// #endif
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"
)

// As x/mobile/exp/audio/al is broken on macOS (https://github.com/golang/go/issues/15075),
// and that doesn't support FreeBSD, use OpenAL directly here.

type player struct {
	// alContext represents a pointer to ALCcontext. The type is uintptr since the value
	// can be 0x18 on macOS, which is invalid as a pointer value, and this might cause
	// GC errors.
	alContext        uintptr
	alDevice         uintptr
	alSource         C.ALuint
	sampleRate       int
	isClosed         bool
	alFormat         C.ALenum
	lowerBufferUnits []C.ALuint
	upperBuffer      []uint8
	upperBufferSize  int
}

func alFormat(channelNum, bytesPerSample int) C.ALenum {
	switch {
	case channelNum == 1 && bytesPerSample == 1:
		return C.AL_FORMAT_MONO8
	case channelNum == 1 && bytesPerSample == 2:
		return C.AL_FORMAT_MONO16
	case channelNum == 2 && bytesPerSample == 1:
		return C.AL_FORMAT_STEREO8
	case channelNum == 2 && bytesPerSample == 2:
		return C.AL_FORMAT_STEREO16
	}
	panic(fmt.Sprintf("oto: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample))
}

func getError(device uintptr) error {
	c := C.alcGetError((*C.struct_ALCdevice_struct)(unsafe.Pointer(device)))
	switch c {
	case C.ALC_NO_ERROR:
		return nil
	case C.ALC_INVALID_DEVICE:
		return errors.New("OpenAL error: invalid device")
	case C.ALC_INVALID_CONTEXT:
		return errors.New("OpenAL error: invalid context")
	case C.ALC_INVALID_ENUM:
		return errors.New("OpenAL error: invalid enum")
	case C.ALC_INVALID_VALUE:
		return errors.New("OpenAL error: invalid value")
	case C.ALC_OUT_OF_MEMORY:
		return errors.New("OpenAL error: out of memory")
	default:
		return fmt.Errorf("OpenAL error: code %d", c)
	}
}

const lowerBufferSize = 1024

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	name := C.alGetString(C.ALC_DEFAULT_DEVICE_SPECIFIER)
	d := uintptr(unsafe.Pointer(C.alcOpenDevice((*C.ALCchar)(name))))
	if d == 0 {
		return nil, fmt.Errorf("oto: alcOpenDevice must not return null")
	}
	c := uintptr(unsafe.Pointer(C.alcCreateContext((*C.struct_ALCdevice_struct)(unsafe.Pointer(d)), nil)))
	if c == 0 {
		return nil, fmt.Errorf("oto: alcCreateContext must not return null")
	}
	// Don't check getError until making the current context is done.
	// Linux might fail this check even though it succeeds (hajimehoshi/ebiten#204).
	C.alcMakeContextCurrent((*C.struct_ALCcontext_struct)(unsafe.Pointer(c)))
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: Activate: %v", err)
	}
	s := C.ALuint(0)
	C.alGenSources(1, &s)
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: NewSource: %v", err)
	}
	u, l := bufferSizes(bufferSizeInBytes)
	p := &player{
		alContext:        c,
		alDevice:         d,
		alSource:         s,
		sampleRate:       sampleRate,
		alFormat:         alFormat(channelNum, bytesPerSample),
		lowerBufferUnits: make([]C.ALuint, l),
		upperBufferSize:  u,
	}
	runtime.SetFinalizer(p, (*player).Close)

	C.alGenBuffers(C.ALsizei(len(p.lowerBufferUnits)), &p.lowerBufferUnits[0])
	emptyBytes := make([]byte, lowerBufferUnitSize)
	for _, b := range p.lowerBufferUnits {
		// Note that the third argument of only the first buffer is used.
		C.alBufferData(b, p.alFormat, unsafe.Pointer(&emptyBytes[0]), C.ALsizei(lowerBufferUnitSize), C.ALsizei(p.sampleRate))
	}
	C.alSourcePlay(p.alSource)
	if err := getError(d); err != nil {
		return nil, fmt.Errorf("oto: Play: %v", err)
	}
	return p, nil
}

func (p *player) Write(data []byte) (int, error) {
	if err := getError(p.alDevice); err != nil {
		return 0, fmt.Errorf("oto: starting Write: %v", err)
	}
	n := min(len(data), p.upperBufferSize-len(p.upperBuffer))
	p.upperBuffer = append(p.upperBuffer, data[:n]...)
	for len(p.upperBuffer) >= lowerBufferUnitSize {
		pn := C.ALint(0)
		C.alGetSourcei(p.alSource, C.AL_BUFFERS_PROCESSED, &pn)
		if pn > 0 {
			bufs := make([]C.ALuint, pn)
			C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bufs)), &bufs[0])
			if err := getError(p.alDevice); err != nil {
				return 0, fmt.Errorf("oto: UnqueueBuffers: %v", err)
			}
			p.lowerBufferUnits = append(p.lowerBufferUnits, bufs...)
		}
		if len(p.lowerBufferUnits) == 0 {
			break
		}
		lowerBufferUnit := p.lowerBufferUnits[0]
		p.lowerBufferUnits = p.lowerBufferUnits[1:]
		C.alBufferData(lowerBufferUnit, p.alFormat, unsafe.Pointer(&p.upperBuffer[0]), C.ALsizei(lowerBufferUnitSize), C.ALsizei(p.sampleRate))
		C.alSourceQueueBuffers(p.alSource, 1, &lowerBufferUnit)
		if err := getError(p.alDevice); err != nil {
			return 0, fmt.Errorf("oto: QueueBuffer: %v", err)
		}
		state := C.ALint(0)
		C.alGetSourcei(p.alSource, C.AL_SOURCE_STATE, &state)
		if state == C.AL_STOPPED || state == C.AL_INITIAL {
			C.alSourceRewind(p.alSource)
			C.alSourcePlay(p.alSource)
			if err := getError(p.alDevice); err != nil {
				return 0, fmt.Errorf("oto: Rewind or Play: %v", err)
			}
		}
		p.upperBuffer = p.upperBuffer[lowerBufferUnitSize:]
	}
	return n, nil
}

func (p *player) Close() error {
	if err := getError(p.alDevice); err != nil {
		return fmt.Errorf("oto: starting Close: %v", err)
	}
	if p.isClosed {
		return nil
	}
	var bs []C.ALuint
	C.alSourceRewind(p.alSource)
	C.alSourcePlay(p.alSource)
	n := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_QUEUED, &n)
	if 0 < n {
		bs = make([]C.ALuint, n)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bs)), &bs[0])
		p.lowerBufferUnits = append(p.lowerBufferUnits, bs...)
	}
	C.alcCloseDevice((*C.struct_ALCdevice_struct)(unsafe.Pointer(p.alDevice)))
	p.isClosed = true
	if err := getError(p.alDevice); err != nil {
		return fmt.Errorf("oto: CloseDevice: %v", err)
	}
	runtime.SetFinalizer(p, nil)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Gosl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package plt

import (
	"bytes"

	"github.com/cpmech/gosl/io"
)

// 'A' holds "arguments" to configure plots, including "style" data for shapes (e.g. polygons)
type A struct {

	// plot and basic options
	C      string  // color
	M      string  // marker
	Ls     string  // linestyle
	Lw     float64 // linewidth; -1 => default
	Ms     int     // marker size; -1 => default
	L      string  // label
	Me     int     // mark-every; -1 => default
	Z      int     // z-order
	Mec    string  // marker edge color
	Mew    float64 // marker edge width
	Void   bool    // void marker => markeredgecolor='C', markerfacecolor='none'
	NoClip bool    // turn clipping off

	// shapes
	Fc     string  // shapes: face color
	Ec     string  // shapes: edge color
	Scale  float64 // shapes: scale information
	Style  string  // shapes: style information
	Closed bool    // shapes: closed shape

	// text and extra arguments
	Ha      string  // horizontal alignment; e.g. 'center'
	Va      string  // vertical alignment; e.g. 'center'
	Rot     float64 // rotation
	Fsz     float64 // font size
	FszLbl  float64 // font size of labels
	FszLeg  float64 // font size of legend
	FszXtck float64 // font size of x-ticks
	FszYtck float64 // font size of y-ticks
	HideL   bool    // hide left frame border
	HideR   bool    // hide right frame border
	HideB   bool    // hide bottom frame border
	HideT   bool    // hide top frame border

	// legend
	LegLoc   string    // legend: location
	LegNcol  int       // legend: number of columns
	LegHlen  float64   // legend: handle length
	LegFrame bool      // legend: frame on
	LegOut   bool      // legend: outside
	LegOutX  []float64 // legend: normalised coordinates to put legend outside frame

	// colors for contours or histograms
	Colors []string // contour or histogram: colors

	// contours
	Ulevels   []float64 // contour: levels
	UcmapIdx  int       // contour: colormap index
	UnumFmt   string    // contour: number format; e.g. "%g" or "%.2f"
	UnoLines  bool      // contour: do not add lines on top of filled contour
	UnoLabels bool      // contour: do not add labels
	UnoInline bool      // contour: do not draw labels 'inline'
	UnoCbar   bool      // contour: do not add colorbar
	UcbarLbl  string    // contour: colorbar label
	UselectV  float64   // contour: selected value
	UselectC  string    // contour: color to mark selected level. empty means no selected line
	UselectLw float64   // contour: zero level linewidth

	// Histograms
	Htype    string // histogram: type; e.g. "bar"
	Hstacked bool   // histogram: stacked
	Hvoid    bool   // histogram: not filled
	Hnbins   int    // histogram: number of bins
	Hnormed  bool   // histogram: normed
}

// String returns a string representation of arguments
func (o A) String(forHistogram bool) (l string) {

	// plot and basic options
	addToCmd(&l, o.C != "", io.Sf("color='%s'", o.C))
	addToCmd(&l, o.M != "", io.Sf("marker='%s'", o.M))
	addToCmd(&l, o.Ls != "", io.Sf("ls='%s'", o.Ls))
	addToCmd(&l, o.Lw > 0, io.Sf("lw=%g", o.Lw))
	addToCmd(&l, o.Ms > 0, io.Sf("ms=%d", o.Ms))
	addToCmd(&l, o.L != "", io.Sf("label='%s'", o.L))
	addToCmd(&l, o.Me > 0, io.Sf("markevery=%d", o.Me))
	addToCmd(&l, o.Z > 0, io.Sf("zorder=%d", o.Z))
	addToCmd(&l, o.Mec != "", io.Sf("markeredgecolor='%s'", o.Mec))
	addToCmd(&l, o.Mew > 0, io.Sf("mew=%g", o.Mew))
	addToCmd(&l, o.Void, "markerfacecolor='none'")
	addToCmd(&l, o.Void && o.Mec == "", io.Sf("markeredgecolor='%s'", o.C))
	addToCmd(&l, o.NoClip, "clip_on=0")

	// shapes
	addToCmd(&l, o.Fc != "", io.Sf("facecolor='%s'", o.Fc))
	addToCmd(&l, o.Ec != "", io.Sf("edgecolor='%s'", o.Ec))

	// text and extra arguments
	addToCmd(&l, o.Ha != "", io.Sf("ha='%s'", o.Ha))
	addToCmd(&l, o.Va != "", io.Sf("va='%s'", o.Va))
	addToCmd(&l, o.Fsz > 0, io.Sf("fontsize=%g", o.Fsz))

	// histograms
	if forHistogram {
		addToCmd(&l, len(o.Colors) > 0, io.Sf("color=%s", strings2list(o.Colors)))
		addToCmd(&l, len(o.Htype) > 0, io.Sf("histtype='%s'", o.Htype))
		addToCmd(&l, o.Hstacked, "stacked=1")
		addToCmd(&l, o.Hvoid, "fill=0")
		addToCmd(&l, o.Hnbins > 0, io.Sf("bins=%d", o.Hnbins))
		addToCmd(&l, o.Hnormed, "normed=1")
	}
	return
}

// addToCmd adds new option to list of commands separated with commas
func addToCmd(line *string, condition bool, delta string) {
	if condition {
		if len(*line) > 0 {
			*line += ","
		}
		*line += delta
	}
}

// updateBufferWithArgsAndClose updates buffer with arguments and close with ")\n". See updateBufferWithArgs too.
func updateBufferAndClose(buf *bytes.Buffer, args *A, forHistogram bool) {
	if buf == nil {
		return
	}
	if args == nil {
		io.Ff(buf, ")\n")
		return
	}
	txt := args.String(forHistogram)
	if txt == "" {
		io.Ff(buf, ")\n")
		return
	}
	io.Ff(buf, ", "+txt+")\n")
}

// floats2list converts slice of floats to string representing a Python list
func floats2list(vals []float64) (l string) {
	l = "["
	for i, v := range vals {
		if i > 0 {
			l += ","
		}
		l += io.Sf("%g", v)
	}
	l += "]"
	return
}

// strings2list converts slice of strings to string representing a Python list
func strings2list(vals []string) (l string) {
	l = "["
	for i, v := range vals {
		if i > 0 {
			l += ","
		}
		l += io.Sf("'%s'", v)
	}
	l += "]"
	return
}

// getHideList returns a string representing the "spines-to-remove" list in Python
func getHideList(args *A) (l string) {
	if args == nil {
		return
	}
	if args.HideL || args.HideR || args.HideB || args.HideT {
		c := ""
		addToCmd(&c, args.HideL, "'left'")
		addToCmd(&c, args.HideR, "'right'")
		addToCmd(&c, args.HideB, "'bottom'")
		addToCmd(&c, args.HideT, "'top'")
		l = "[" + c + "]"
	}
	return
}

// argsLeg returns legend arguments
func argsLeg(args *A) (loc string, ncol int, hlen, fsz float64, frame int, out int, outX string) {
	loc = "'best'"
	ncol = 1
	hlen = 3.0
	fsz = 8.0
	frame = 0
	out = 0
	outX = "[0.0, 1.02, 1.0, 0.102]"
	if args == nil {
		return
	}
	if args.LegLoc != "" {
		loc = io.Sf("'%s'", args.LegLoc)
	}
	if args.LegNcol > 0 {
		ncol = args.LegNcol
	}
	if args.LegHlen > 0 {
		hlen = args.LegHlen
	}
	if args.FszLeg > 0 {
		fsz = args.FszLeg
	}
	if args.LegFrame {
		frame = 1
	}
	if args.LegOut {
		out = 1
	}
	if len(args.LegOutX) == 4 {
		outX = io.Sf("[%g, %g, %g, %g]", args.LegOutX[0], args.LegOutX[1], args.LegOutX[2], args.LegOutX[3])
	}
	return
}

// argsFsz allocates args if nil, and sets default fontsizes
func argsFsz(args *A) (txt, lbl, leg, xtck, ytck float64) {
	txt, lbl, leg, xtck, ytck = 11, 10, 9, 8, 8
	if args == nil {
		return
	}
	if args.Fsz > 0 {
		txt = args.Fsz
	}
	if args.FszLbl > 0 {
		lbl = args.FszLbl
	}
	if args.FszLeg > 0 {
		leg = args.FszLeg
	}
	if args.FszXtck > 0 {
		xtck = args.FszXtck
	}
	if args.FszYtck > 0 {
		ytck = args.FszYtck
	}
	return
}

// argsContour allocates args if nil, sets default parameters, and return formatted arguments
func argsContour(in *A) (out *A, colors, levels string) {
	out = in
	if out == nil {
		out = new(A)
	}
	if out.UnumFmt == "" {
		out.UnumFmt = "%g"
	}
	if out.UselectLw < 0.01 {
		out.UselectLw = 3.0
	}
	if out.Lw < 0.01 {
		out.Lw = 1.0
	}
	if out.Fsz < 0.01 {
		out.Fsz = 10.0
	}
	if len(out.Colors) > 0 {
		colors = io.Sf(",colors=%s", strings2list(out.Colors))
	} else {
		colors = io.Sf(",cmap=getCmap(%d)", out.UcmapIdx)
	}
	if len(out.Ulevels) > 0 {
		levels = io.Sf(",levels=%s", floats2list(out.Ulevels))
	}
	return
}

// pyBool converts Go bool to Python bool
func pyBool(flag bool) int {
	if flag {
		return 1
	}
	return 0
}
<commit_msg>Add FigFraction argument to plt<commit_after>// Copyright 2016 The Gosl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package plt

import (
	"bytes"

	"github.com/cpmech/gosl/io"
)

// 'A' holds "arguments" to configure plots, including "style" data for shapes (e.g. polygons)
type A struct {

	// plot and basic options
	C      string  // color
	M      string  // marker
	Ls     string  // linestyle
	Lw     float64 // linewidth; -1 => default
	Ms     int     // marker size; -1 => default
	L      string  // label
	Me     int     // mark-every; -1 => default
	Z      int     // z-order
	Mec    string  // marker edge color
	Mew    float64 // marker edge width
	Void   bool    // void marker => markeredgecolor='C', markerfacecolor='none'
	NoClip bool    // turn clipping off

	// shapes
	Fc     string  // shapes: face color
	Ec     string  // shapes: edge color
	Scale  float64 // shapes: scale information
	Style  string  // shapes: style information
	Closed bool    // shapes: closed shape

	// text and extra arguments
	Ha      string  // horizontal alignment; e.g. 'center'
	Va      string  // vertical alignment; e.g. 'center'
	Rot     float64 // rotation
	Fsz     float64 // font size
	FszLbl  float64 // font size of labels
	FszLeg  float64 // font size of legend
	FszXtck float64 // font size of x-ticks
	FszYtck float64 // font size of y-ticks
	HideL   bool    // hide left frame border
	HideR   bool    // hide right frame border
	HideB   bool    // hide bottom frame border
	HideT   bool    // hide top frame border

	// other options
	FigFraction bool // the given x-y coordinates correspond to figure coords  "xycoords='figure fraction'") }

	// legend
	LegLoc   string    // legend: location
	LegNcol  int       // legend: number of columns
	LegHlen  float64   // legend: handle length
	LegFrame bool      // legend: frame on
	LegOut   bool      // legend: outside
	LegOutX  []float64 // legend: normalised coordinates to put legend outside frame

	// colors for contours or histograms
	Colors []string // contour or histogram: colors

	// contours
	Ulevels   []float64 // contour: levels
	UcmapIdx  int       // contour: colormap index
	UnumFmt   string    // contour: number format; e.g. "%g" or "%.2f"
	UnoLines  bool      // contour: do not add lines on top of filled contour
	UnoLabels bool      // contour: do not add labels
	UnoInline bool      // contour: do not draw labels 'inline'
	UnoCbar   bool      // contour: do not add colorbar
	UcbarLbl  string    // contour: colorbar label
	UselectV  float64   // contour: selected value
	UselectC  string    // contour: color to mark selected level. empty means no selected line
	UselectLw float64   // contour: zero level linewidth

	// Histograms
	Htype    string // histogram: type; e.g. "bar"
	Hstacked bool   // histogram: stacked
	Hvoid    bool   // histogram: not filled
	Hnbins   int    // histogram: number of bins
	Hnormed  bool   // histogram: normed
}

// String returns a string representation of arguments
func (o A) String(forHistogram bool) (l string) {

	// plot and basic options
	addToCmd(&l, o.C != "", io.Sf("color='%s'", o.C))
	addToCmd(&l, o.M != "", io.Sf("marker='%s'", o.M))
	addToCmd(&l, o.Ls != "", io.Sf("ls='%s'", o.Ls))
	addToCmd(&l, o.Lw > 0, io.Sf("lw=%g", o.Lw))
	addToCmd(&l, o.Ms > 0, io.Sf("ms=%d", o.Ms))
	addToCmd(&l, o.L != "", io.Sf("label='%s'", o.L))
	addToCmd(&l, o.Me > 0, io.Sf("markevery=%d", o.Me))
	addToCmd(&l, o.Z > 0, io.Sf("zorder=%d", o.Z))
	addToCmd(&l, o.Mec != "", io.Sf("markeredgecolor='%s'", o.Mec))
	addToCmd(&l, o.Mew > 0, io.Sf("mew=%g", o.Mew))
	addToCmd(&l, o.Void, "markerfacecolor='none'")
	addToCmd(&l, o.Void && o.Mec == "", io.Sf("markeredgecolor='%s'", o.C))
	addToCmd(&l, o.NoClip, "clip_on=0")

	// shapes
	addToCmd(&l, o.Fc != "", io.Sf("facecolor='%s'", o.Fc))
	addToCmd(&l, o.Ec != "", io.Sf("edgecolor='%s'", o.Ec))

	// text and extra arguments
	addToCmd(&l, o.Ha != "", io.Sf("ha='%s'", o.Ha))
	addToCmd(&l, o.Va != "", io.Sf("va='%s'", o.Va))
	addToCmd(&l, o.Fsz > 0, io.Sf("fontsize=%g", o.Fsz))

	// other options
	addToCmd(&l, o.FigFraction, "xycoords='figure fraction'")

	// histograms
	if forHistogram {
		addToCmd(&l, len(o.Colors) > 0, io.Sf("color=%s", strings2list(o.Colors)))
		addToCmd(&l, len(o.Htype) > 0, io.Sf("histtype='%s'", o.Htype))
		addToCmd(&l, o.Hstacked, "stacked=1")
		addToCmd(&l, o.Hvoid, "fill=0")
		addToCmd(&l, o.Hnbins > 0, io.Sf("bins=%d", o.Hnbins))
		addToCmd(&l, o.Hnormed, "normed=1")
	}
	return
}

// addToCmd adds new option to list of commands separated with commas
func addToCmd(line *string, condition bool, delta string) {
	if condition {
		if len(*line) > 0 {
			*line += ","
		}
		*line += delta
	}
}

// updateBufferWithArgsAndClose updates buffer with arguments and close with ")\n". See updateBufferWithArgs too.
func updateBufferAndClose(buf *bytes.Buffer, args *A, forHistogram bool) {
	if buf == nil {
		return
	}
	if args == nil {
		io.Ff(buf, ")\n")
		return
	}
	txt := args.String(forHistogram)
	if txt == "" {
		io.Ff(buf, ")\n")
		return
	}
	io.Ff(buf, ", "+txt+")\n")
}

// floats2list converts slice of floats to string representing a Python list
func floats2list(vals []float64) (l string) {
	l = "["
	for i, v := range vals {
		if i > 0 {
			l += ","
		}
		l += io.Sf("%g", v)
	}
	l += "]"
	return
}

// strings2list converts slice of strings to string representing a Python list
func strings2list(vals []string) (l string) {
	l = "["
	for i, v := range vals {
		if i > 0 {
			l += ","
		}
		l += io.Sf("'%s'", v)
	}
	l += "]"
	return
}

// getHideList returns a string representing the "spines-to-remove" list in Python
func getHideList(args *A) (l string) {
	if args == nil {
		return
	}
	if args.HideL || args.HideR || args.HideB || args.HideT {
		c := ""
		addToCmd(&c, args.HideL, "'left'")
		addToCmd(&c, args.HideR, "'right'")
		addToCmd(&c, args.HideB, "'bottom'")
		addToCmd(&c, args.HideT, "'top'")
		l = "[" + c + "]"
	}
	return
}

// argsLeg returns legend arguments
func argsLeg(args *A) (loc string, ncol int, hlen, fsz float64, frame int, out int, outX string) {
	loc = "'best'"
	ncol = 1
	hlen = 3.0
	fsz = 8.0
	frame = 0
	out = 0
	outX = "[0.0, 1.02, 1.0, 0.102]"
	if args == nil {
		return
	}
	if args.LegLoc != "" {
		loc = io.Sf("'%s'", args.LegLoc)
	}
	if args.LegNcol > 0 {
		ncol = args.LegNcol
	}
	if args.LegHlen > 0 {
		hlen = args.LegHlen
	}
	if args.FszLeg > 0 {
		fsz = args.FszLeg
	}
	if args.LegFrame {
		frame = 1
	}
	if args.LegOut {
		out = 1
	}
	if len(args.LegOutX) == 4 {
		outX = io.Sf("[%g, %g, %g, %g]", args.LegOutX[0], args.LegOutX[1], args.LegOutX[2], args.LegOutX[3])
	}
	return
}

// argsFsz allocates args if nil, and sets default fontsizes
func argsFsz(args *A) (txt, lbl, leg, xtck, ytck float64) {
	txt, lbl, leg, xtck, ytck = 11, 10, 9, 8, 8
	if args == nil {
		return
	}
	if args.Fsz > 0 {
		txt = args.Fsz
	}
	if args.FszLbl > 0 {
		lbl = args.FszLbl
	}
	if args.FszLeg > 0 {
		leg = args.FszLeg
	}
	if args.FszXtck > 0 {
		xtck = args.FszXtck
	}
	if args.FszYtck > 0 {
		ytck = args.FszYtck
	}
	return
}

// argsContour allocates args if nil, sets default parameters, and return formatted arguments
func argsContour(in *A) (out *A, colors, levels string) {
	out = in
	if out == nil {
		out = new(A)
	}
	if out.UnumFmt == "" {
		out.UnumFmt = "%g"
	}
	if out.UselectLw < 0.01 {
		out.UselectLw = 3.0
	}
	if out.Lw < 0.01 {
		out.Lw = 1.0
	}
	if out.Fsz < 0.01 {
		out.Fsz = 10.0
	}
	if len(out.Colors) > 0 {
		colors = io.Sf(",colors=%s", strings2list(out.Colors))
	} else {
		colors = io.Sf(",cmap=getCmap(%d)", out.UcmapIdx)
	}
	if len(out.Ulevels) > 0 {
		levels = io.Sf(",levels=%s", floats2list(out.Ulevels))
	}
	return
}

// pyBool converts Go bool to Python bool
func pyBool(flag bool) int {
	if flag {
		return 1
	}
	return 0
}
<|endoftext|>