<commit_before>package wmenu

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//Menu is used to display options to a user.
//A user can then select options and Menu will validate the response and perform the correct action.
type Menu struct {
	question        string
	defaultFunction func(Option)
	options         []Option
	ui              wlog.UI
	multiSeperator  string
	multiFunction   func([]Option)
	loopOnInvalid   bool
	clear           bool
}

//NewMenu creates a menu with a wlog.UI as the writer.
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeperator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(wlog.None, optionColor, wlog.None, questionColor, errorColor, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
//This is checked when LoopOnInvalid is activated.
//Meaning if an error occured then it will clear the screen before asking again.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeperator sets the seperator to use when multiple options are valid responses.
//Default value is a space.
func (m *Menu) SetSeperator(sep string) {
	m.multiSeperator = sep
}

//LoopOnInvalid is used if an invalid option was given then it will prompt the user again.
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds an option to the menu for the user to select from.
//title is the string the user will select
//isDefault is whether this option is a default option (IE when no options are selected).
//function is what is called when only this option is selected.
//If function is nil then it will default to the menu's Action.
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action to use in certain scenarios.
//If the selected option (by default or user selected) does not have a function applied to it this will be called.
//If there are no default options and no option was selected this will be called with an option that has an ID of -1.
func (m *Menu) Action(function func(Option)) {
	m.defaultFunction = function
}

//MultipleAction is called when multiple options are selected (by default or user selected).
//If this is set then it uses the seperator string specified by SetSeperator (Default is a space) to seperate the responses.
//If this is not set then it is implied that the menu only allows for one option to be selected.
func (m *Menu) MultipleAction(function func([]Option)) {
	m.multiFunction = function
}

//Run is used to execute the menu.
//It will print to options and question to the screen.
//It will only clear the screen if ClearOnMenuRun is activated.
//This will validate all responses.
//Errors are of type MenuError.
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Option
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print things to screen
		m.print()
		//step 2 get and validate response
		opt, err := m.ask()
		if err != nil {
			if m.loopOnInvalid {
				if m.clear {
					Clear()
				}
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Option{ID: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
	return nil
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.ID, opt.Text))
	}
	m.ui.Info(m.question)
}

func (m *Menu) ask() ([]Option, error) {
	reader := bufio.NewReader(os.Stdin)
	res, _ := reader.ReadString('\n')
	res = strings.Replace(res, "\r", "", -1) //this will only be useful under windows
	res = strings.Replace(res, "\n", "", -1)

	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		if m.checkOptAndFunc(opt) {
			return nil, newMenuError(ErrNoResponse, "")
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeperator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "")
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response)
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response))
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response))
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Option
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

func (m *Menu) getDefault() []Option {
	var opt []Option
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}

//make sure that there is an action available to be called in certain cases
func (m *Menu) checkOptAndFunc(opt []Option) bool {
	return ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 0 && m.multiFunction == nil))
}
<commit_msg>Add package comment<commit_after>//Package wmenu creates menus for cli programs.
//It uses wlog for it's interface with the command line.
//It uses os.Stdin, os.Stdout, and os.Stderr with concurrency by default.
//wmenu allows you to change the color of the different parts of the menu.
package wmenu

//TODO add options to change where wlog writes to
import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//Menu is used to display options to a user.
//A user can then select options and Menu will validate the response and perform the correct action.
type Menu struct {
	question        string
	defaultFunction func(Option)
	options         []Option
	ui              wlog.UI
	multiSeperator  string
	multiFunction   func([]Option)
	loopOnInvalid   bool
	clear           bool
}

//NewMenu creates a menu with a wlog.UI as the writer.
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeperator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(wlog.None, optionColor, wlog.None, questionColor, errorColor, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
//This is checked when LoopOnInvalid is activated.
//Meaning if an error occured then it will clear the screen before asking again.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeperator sets the seperator to use when multiple options are valid responses.
//Default value is a space.
func (m *Menu) SetSeperator(sep string) {
	m.multiSeperator = sep
}

//LoopOnInvalid is used if an invalid option was given then it will prompt the user again.
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds an option to the menu for the user to select from.
//title is the string the user will select
//isDefault is whether this option is a default option (IE when no options are selected).
//function is what is called when only this option is selected.
//If function is nil then it will default to the menu's Action.
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action to use in certain scenarios.
//If the selected option (by default or user selected) does not have a function applied to it this will be called.
//If there are no default options and no option was selected this will be called with an option that has an ID of -1.
func (m *Menu) Action(function func(Option)) {
	m.defaultFunction = function
}

//MultipleAction is called when multiple options are selected (by default or user selected).
//If this is set then it uses the seperator string specified by SetSeperator (Default is a space) to seperate the responses.
//If this is not set then it is implied that the menu only allows for one option to be selected.
func (m *Menu) MultipleAction(function func([]Option)) {
	m.multiFunction = function
}

//Run is used to execute the menu.
//It will print to options and question to the screen.
//It will only clear the screen if ClearOnMenuRun is activated.
//This will validate all responses.
//Errors are of type MenuError.
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Option
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print things to screen
		m.print()
		//step 2 get and validate response
		opt, err := m.ask()
		if err != nil {
			if m.loopOnInvalid {
				if m.clear {
					Clear()
				}
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Option{ID: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
	return nil
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.ID, opt.Text))
	}
	m.ui.Info(m.question)
}

func (m *Menu) ask() ([]Option, error) {
	reader := bufio.NewReader(os.Stdin)
	res, _ := reader.ReadString('\n')
	res = strings.Replace(res, "\r", "", -1) //this will only be useful under windows
	res = strings.Replace(res, "\n", "", -1)

	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		if m.checkOptAndFunc(opt) {
			return nil, newMenuError(ErrNoResponse, "")
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeperator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "")
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response)
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response))
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response))
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Option
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

func (m *Menu) getDefault() []Option {
	var opt []Option
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}

//make sure that there is an action available to be called in certain cases
func (m *Menu) checkOptAndFunc(opt []Option) bool {
	return ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 0 && m.multiFunction == nil))
}
<|endoftext|>


<commit_before>package router

import (
	"net/http"
	"sync"

	"github.com/kataras/iris/context"
	"github.com/kataras/iris/core/errors"
)

// Router is the "director".
// Caller should provide a request handler (router implementation or root handler).
// Router is responsible to build the received request handler and run it
// to serve requests, based on the received context.Pool.
//
// User can refresh the router with `RefreshRouter` whenever a route's field is changed by him.
type Router struct {
	mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter,
	// not indeed but we don't to risk its usage by third-parties.
	requestHandler RequestHandler   // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
	mainHandler    http.HandlerFunc // init-accessible
	wrapperFunc    func(http.ResponseWriter, *http.Request, http.HandlerFunc)

	cPool          *context.Pool // used on RefreshRouter
	routesProvider RoutesProvider
}

// NewRouter returns a new empty Router.
func NewRouter() *Router { return &Router{} }

// RefreshRouter re-builds the router. Should be called when a route's state
// changed (i.e Method changed at serve-time).
func (router *Router) RefreshRouter() error {
	return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider)
}

// BuildRouter builds the router based on
// the context factory (explicit pool in this case),
// the request handler which manages how the main handler will multiplexes the routes
// provided by the third parameter, routerProvider (it's the api builder in this case) and
// its wrapper.
//
// Use of RefreshRouter to re-build the router if needed.
func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error {

	if requestHandler == nil {
		return errors.New("router: request handler is nil")
	}

	if cPool == nil {
		return errors.New("router: context pool is nil")
	}

	// build the handler using the routesProvider
	if err := requestHandler.Build(routesProvider); err != nil {
		return err
	}

	router.mu.Lock()
	defer router.mu.Unlock()

	// store these for RefreshRouter's needs.
	router.cPool = cPool
	router.requestHandler = requestHandler
	router.routesProvider = routesProvider

	// the important
	router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
		ctx := cPool.Acquire(w, r)
		router.requestHandler.HandleRequest(ctx)
		cPool.Release(ctx)
	}

	if router.wrapperFunc != nil { // if wrapper used then attach that as the router service
		router.mainHandler = NewWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
	}

	return nil
}

// Downgrade "downgrades", alters the router supervisor service(Router.mainHandler)
//  algorithm to a custom one,
// be aware to change the global variables of 'ParamStart' and 'ParamWildcardStart'.
// can be used to implement a custom proxy or
// a custom router which should work with raw ResponseWriter, *Request
// instead of the Context(which agaiin, can be retrieved by the Cramework's context pool).
//
// Note: Downgrade will by-pass the Wrapper, the caller is responsible for everything.
// Downgrade is thread-safe.
func (router *Router) Downgrade(newMainHandler http.HandlerFunc) {
	router.mu.Lock()
	router.mainHandler = newMainHandler
	router.mu.Unlock()
}

// Downgraded returns true if this router is downgraded.
func (router *Router) Downgraded() bool {
	return router.mainHandler != nil && router.requestHandler == nil
}

// WrapperFunc is used as an expected input parameter signature
// for the WrapRouter. It's a "low-level" signature which is compatible
// with the net/http.
// It's being used to run or no run the router based on a custom logic.
type WrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRouter http.HandlerFunc)

// WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
//
// Developers can add more than one wrappers,
// those wrappers' execution comes from last to first.
// That means that the second wrapper will wrap the first, and so on.
//
// Before build.
func (router *Router) WrapRouter(wrapperFunc WrapperFunc) {
	router.mu.Lock()
	defer router.mu.Unlock()

	if wrapperFunc == nil {
		return
	}

	if router.wrapperFunc != nil {
		// wrap into one function, from bottom to top, end to begin.
		nextWrapper := wrapperFunc
		prevWrapper := router.wrapperFunc
		wrapperFunc = func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
			if next != nil {
				nexthttpFunc := http.HandlerFunc(func(_w http.ResponseWriter, _r *http.Request) {
					prevWrapper(_w, _r, next)
				})
				nextWrapper(w, r, nexthttpFunc)
			}
		}
	}

	router.wrapperFunc = wrapperFunc
}

// ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper.
func (router *Router) ServeHTTPC(ctx context.Context) {
	router.requestHandler.HandleRequest(ctx)
}

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.mainHandler(w, r)
}

type wrapper struct {
	router      http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change.
	wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
}

// NewWrapper returns a new http.Handler wrapped by the 'wrapperFunc'
// the "next" is the final "wrapped" input parameter.
//
// Application is responsible to make it to work on more than one wrappers
// via composition or func clojure.
func NewWrapper(wrapperFunc func(w http.ResponseWriter, r *http.Request, routerNext http.HandlerFunc), wrapped http.HandlerFunc) http.Handler {
	return &wrapper{
		wrapperFunc: wrapperFunc,
		router:      wrapped,
	}
}

func (wr *wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	wr.wrapperFunc(w, r, wr.router)
}
<commit_msg>fix a typo<commit_after>package router

import (
	"net/http"
	"sync"

	"github.com/kataras/iris/context"
	"github.com/kataras/iris/core/errors"
)

// Router is the "director".
// Caller should provide a request handler (router implementation or root handler).
// Router is responsible to build the received request handler and run it
// to serve requests, based on the received context.Pool.
//
// User can refresh the router with `RefreshRouter` whenever a route's field is changed by him.
type Router struct {
	mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter,
	// not indeed but we don't to risk its usage by third-parties.
	requestHandler RequestHandler   // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
	mainHandler    http.HandlerFunc // init-accessible
	wrapperFunc    func(http.ResponseWriter, *http.Request, http.HandlerFunc)

	cPool          *context.Pool // used on RefreshRouter
	routesProvider RoutesProvider
}

// NewRouter returns a new empty Router.
func NewRouter() *Router { return &Router{} }

// RefreshRouter re-builds the router. Should be called when a route's state
// changed (i.e Method changed at serve-time).
func (router *Router) RefreshRouter() error {
	return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider)
}

// BuildRouter builds the router based on
// the context factory (explicit pool in this case),
// the request handler which manages how the main handler will multiplexes the routes
// provided by the third parameter, routerProvider (it's the api builder in this case) and
// its wrapper.
//
// Use of RefreshRouter to re-build the router if needed.
func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error {

	if requestHandler == nil {
		return errors.New("router: request handler is nil")
	}

	if cPool == nil {
		return errors.New("router: context pool is nil")
	}

	// build the handler using the routesProvider
	if err := requestHandler.Build(routesProvider); err != nil {
		return err
	}

	router.mu.Lock()
	defer router.mu.Unlock()

	// store these for RefreshRouter's needs.
	router.cPool = cPool
	router.requestHandler = requestHandler
	router.routesProvider = routesProvider

	// the important
	router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
		ctx := cPool.Acquire(w, r)
		router.requestHandler.HandleRequest(ctx)
		cPool.Release(ctx)
	}

	if router.wrapperFunc != nil { // if wrapper used then attach that as the router service
		router.mainHandler = NewWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
	}

	return nil
}

// Downgrade "downgrades", alters the router supervisor service(Router.mainHandler)
//  algorithm to a custom one,
// be aware to change the global variables of 'ParamStart' and 'ParamWildcardStart'.
// can be used to implement a custom proxy or
// a custom router which should work with raw ResponseWriter, *Request
// instead of the Context(which again, can be retrieved by the Cramework's context pool).
//
// Note: Downgrade will by-pass the Wrapper, the caller is responsible for everything.
// Downgrade is thread-safe.
func (router *Router) Downgrade(newMainHandler http.HandlerFunc) {
	router.mu.Lock()
	router.mainHandler = newMainHandler
	router.mu.Unlock()
}

// Downgraded returns true if this router is downgraded.
func (router *Router) Downgraded() bool {
	return router.mainHandler != nil && router.requestHandler == nil
}

// WrapperFunc is used as an expected input parameter signature
// for the WrapRouter. It's a "low-level" signature which is compatible
// with the net/http.
// It's being used to run or no run the router based on a custom logic.
type WrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRouter http.HandlerFunc)

// WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
//
// Developers can add more than one wrappers,
// those wrappers' execution comes from last to first.
// That means that the second wrapper will wrap the first, and so on.
//
// Before build.
func (router *Router) WrapRouter(wrapperFunc WrapperFunc) {
	router.mu.Lock()
	defer router.mu.Unlock()

	if wrapperFunc == nil {
		return
	}

	if router.wrapperFunc != nil {
		// wrap into one function, from bottom to top, end to begin.
		nextWrapper := wrapperFunc
		prevWrapper := router.wrapperFunc
		wrapperFunc = func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
			if next != nil {
				nexthttpFunc := http.HandlerFunc(func(_w http.ResponseWriter, _r *http.Request) {
					prevWrapper(_w, _r, next)
				})
				nextWrapper(w, r, nexthttpFunc)
			}
		}
	}

	router.wrapperFunc = wrapperFunc
}

// ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper.
func (router *Router) ServeHTTPC(ctx context.Context) {
	router.requestHandler.HandleRequest(ctx)
}

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.mainHandler(w, r)
}

type wrapper struct {
	router      http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change.
	wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
}

// NewWrapper returns a new http.Handler wrapped by the 'wrapperFunc'
// the "next" is the final "wrapped" input parameter.
//
// Application is responsible to make it to work on more than one wrappers
// via composition or func clojure.
func NewWrapper(wrapperFunc func(w http.ResponseWriter, r *http.Request, routerNext http.HandlerFunc), wrapped http.HandlerFunc) http.Handler {
	return &wrapper{
		wrapperFunc: wrapperFunc,
		router:      wrapped,
	}
}

func (wr *wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	wr.wrapperFunc(w, r, wr.router)
}
<|endoftext|>


<commit_before>package formatter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	"golang.org/x/crypto/ssh/terminal"
)

const (
	nocolor = 0
	red     = 31
	green   = 32
	yellow  = 33
	blue    = 36
	gray    = 37

	defaultSourceField = "_source"
)

const defaultTimestampFormat = time.RFC3339

var (
	baseTimestamp time.Time
)

func init() {
	baseTimestamp = time.Now()
}

// TextFormatter formats logs into text
type TextFormatter struct {
	logrus.TextFormatter
	isTerminal       bool
	terminalInitOnce sync.Once

	SourceField  string
	FormatValue  func(value interface{}) string
	FormatCaller func(caller *runtime.Frame) string
}

// New creates new formatter
func New() *TextFormatter {
	return &TextFormatter{}
}

func (f *TextFormatter) init(entry *logrus.Entry) {
	if entry.Logger != nil {
		f.isTerminal = checkIfTerminal(entry.Logger.Out)
	}
	if f.FormatValue == nil {
		f.FormatValue = DefaultFormat
	}
	if f.FormatCaller == nil {
		f.FormatCaller = CallerFormat
	}
	if f.SourceField == "" {
		f.SourceField = defaultSourceField
	}
}

// Format renders a single log entry
func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b *bytes.Buffer
	keys := make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		keys = append(keys, k)
	}

	if !f.DisableSorting {
		sort.Strings(keys)
	}
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	prefixFieldClashes(entry.Data)

	f.terminalInitOnce.Do(func() { f.init(entry) })

	isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = defaultTimestampFormat
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat)
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}
	if entry.HasCaller() && entry.Data["_source"] == nil {
		f.appendKeyValue(b, f.SourceField, f.FormatCaller(entry.Caller))
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
	var levelColor int
	switch entry.Level {
	case logrus.DebugLevel:
		levelColor = gray
	case logrus.WarnLevel:
		levelColor = yellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())[0:4]

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
		f.appendValue(b, v)
	}
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
	if b.Len() > 0 {
		b.WriteByte(' ')
	}
	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	b.WriteString(f.FormatValue(value))
}

// DefaultFormat format value as text with fmt.Sprintf
func DefaultFormat(value interface{}) string {
	s, ok := value.(string)
	if !ok {
		return fmt.Sprintf("%+v", value)
	}

	if len(s) == 0 || NeedsQuoting(s) {
		return fmt.Sprintf("%q", s)
	}

	return s
}

// JSONFormat format value as json
func JSONFormat(value interface{}) string {
	s, ok := value.(string)
	if !ok {
		data, _ := json.Marshal(value)
		return string(data)
	}

	return s
}

// CallerFormat format entry caller as text with fmt.Sprintf
func CallerFormat(caller *runtime.Frame) string {
	i := strings.LastIndex(caller.File, "/")
	i = strings.LastIndex(caller.File[:i], "/")
	return fmt.Sprintf("%s:%d", caller.File[i+1:], caller.Line)
}

// NeedsQuoting checks quoting needed for text
func NeedsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
			return true
		}
	}
	return false
}

func checkIfTerminal(w io.Writer) bool {
	switch v := w.(type) {
	case *os.File:
		return terminal.IsTerminal(int(v.Fd()))
	default:
		return false
	}
}

func prefixFieldClashes(data logrus.Fields) {
	if t, ok := data["time"]; ok {
		data["fields.time"] = t
	}

	if m, ok := data["msg"]; ok {
		data["fields.msg"] = m
	}

	if l, ok := data["level"]; ok {
		data["fields.level"] = l
	}
}
<commit_msg>"golang.org/x/crypto/ssh/terminal" is deprecated<commit_after>package formatter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
	"golang.org/x/term"
)

const (
	nocolor = 0
	red     = 31
	green   = 32
	yellow  = 33
	blue    = 36
	gray    = 37

	defaultSourceField = "_source"
)

const defaultTimestampFormat = time.RFC3339

var (
	baseTimestamp time.Time
)

func init() {
	baseTimestamp = time.Now()
}

// TextFormatter formats logs into text
type TextFormatter struct {
	logrus.TextFormatter
	isTerminal       bool
	terminalInitOnce sync.Once

	SourceField  string
	FormatValue  func(value interface{}) string
	FormatCaller func(caller *runtime.Frame) string
}

// New creates new formatter
func New() *TextFormatter {
	return &TextFormatter{}
}

func (f *TextFormatter) init(entry *logrus.Entry) {
	if entry.Logger != nil {
		f.isTerminal = checkIfTerminal(entry.Logger.Out)
	}
	if f.FormatValue == nil {
		f.FormatValue = DefaultFormat
	}
	if f.FormatCaller == nil {
		f.FormatCaller = CallerFormat
	}
	if f.SourceField == "" {
		f.SourceField = defaultSourceField
	}
}

// Format renders a single log entry
func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b *bytes.Buffer
	keys := make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		keys = append(keys, k)
	}

	if !f.DisableSorting {
		sort.Strings(keys)
	}
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	prefixFieldClashes(entry.Data)

	f.terminalInitOnce.Do(func() { f.init(entry) })

	isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = defaultTimestampFormat
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat)
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}
	if entry.HasCaller() && entry.Data["_source"] == nil {
		f.appendKeyValue(b, f.SourceField, f.FormatCaller(entry.Caller))
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
	var levelColor int
	switch entry.Level {
	case logrus.DebugLevel:
		levelColor = gray
	case logrus.WarnLevel:
		levelColor = yellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())[0:4]

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
		f.appendValue(b, v)
	}
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
	if b.Len() > 0 {
		b.WriteByte(' ')
	}
	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	b.WriteString(f.FormatValue(value))
}

// DefaultFormat format value as text with fmt.Sprintf
func DefaultFormat(value interface{}) string {
	s, ok := value.(string)
	if !ok {
		return fmt.Sprintf("%+v", value)
	}

	if len(s) == 0 || NeedsQuoting(s) {
		return fmt.Sprintf("%q", s)
	}

	return s
}

// JSONFormat format value as json
func JSONFormat(value interface{}) string {
	s, ok := value.(string)
	if !ok {
		data, _ := json.Marshal(value)
		return string(data)
	}

	return s
}

// CallerFormat format entry caller as text with fmt.Sprintf
func CallerFormat(caller *runtime.Frame) string {
	i := strings.LastIndex(caller.File, "/")
	i = strings.LastIndex(caller.File[:i], "/")
	return fmt.Sprintf("%s:%d", caller.File[i+1:], caller.Line)
}

// NeedsQuoting checks quoting needed for text
func NeedsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
			return true
		}
	}
	return false
}

func checkIfTerminal(w io.Writer) bool {
	switch v := w.(type) {
	case *os.File:
		return term.IsTerminal(int(v.Fd()))
	default:
		return false
	}
}

func prefixFieldClashes(data logrus.Fields) {
	if t, ok := data["time"]; ok {
		data["fields.time"] = t
	}

	if m, ok := data["msg"]; ok {
		data["fields.msg"] = m
	}

	if l, ok := data["level"]; ok {
		data["fields.level"] = l
	}
}
<|endoftext|>


<commit_before>package ui

import (
	"github.com/gotk3/gotk3/gtk"
	"github.com/leanovate/microtools/logging"
	"github.com/pkg/errors"
	"github.com/untoldwind/trustless/api"
)

type destroyable interface {
	gtk.IWidget

	Destroy()
}

type secretPropertiesDisplay struct {
	*gtk.ScrolledWindow
	grid    *gtk.Grid
	widgets []destroyable
	rows    int
	logger  logging.Logger
}

func newSecretPropertiesDisplay(logger logging.Logger) (*secretPropertiesDisplay, error) {
	scrolledWindow, err := gtk.ScrolledWindowNew(nil, nil)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create scrolled window")
	}
	grid, err := gtk.GridNew()
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create grid")
	}

	w := &secretPropertiesDisplay{
		ScrolledWindow: scrolledWindow,
		grid:           grid,
		logger:         logger.WithField("package", "ui").WithField("component", "secretPropertiesDisplay"),
	}

	w.grid.SetOrientation(gtk.ORIENTATION_HORIZONTAL)
	w.grid.SetBorderWidth(5)
	w.grid.SetColumnSpacing(5)
	w.grid.SetRowSpacing(5)
	w.Add(grid)

	return w, nil
}

func (w *secretPropertiesDisplay) display(secretVersion *api.SecretVersion) {
	w.destroyAllChildren()

	w.renderUrls(secretVersion.URLs)

	knownNames := w.renderProperties(api.SecretProperties, secretVersion.Properties)

	var unknownPropertyDefs api.SecretPropertyList
	for name := range secretVersion.Properties {
		if _, ok := knownNames[name]; ok {
			continue
		}
		unknownPropertyDefs = append(unknownPropertyDefs, api.SecretProperty{
			Name:    name,
			Display: name,
		})
	}
	unknownPropertyDefs.Sort()

	w.renderProperties(unknownPropertyDefs, secretVersion.Properties)

	w.ShowAll()
}

func (w *secretPropertiesDisplay) renderUrls(urls []string) {
	if len(urls) == 0 {
		return
	}
	label, err := gtk.LabelNew("URLs")
	if err != nil {
		w.logger.ErrorErr(err)
		return
	}
	label.SetHAlign(gtk.ALIGN_START)
	label.SetVAlign(gtk.ALIGN_START)
	w.widgets = append(w.widgets, label)
	w.grid.Attach(label, 0, w.rows, 1, 1)

	for _, url := range urls {
		urlLabel, err := newUrlLabel(w.logger, url)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		urlLabel.SetHExpand(true)
		urlLabel.SetHAlign(gtk.ALIGN_START)

		w.widgets = append(w.widgets, urlLabel)
		w.grid.Attach(urlLabel, 1, w.rows, 1, 1)
		w.rows++
	}
}

func (w *secretPropertiesDisplay) renderProperties(propertyDefs api.SecretPropertyList, properties map[string]string) map[string]bool {
	renderedNames := map[string]bool{}
	for _, propertyDef := range propertyDefs {
		value, ok := properties[propertyDef.Name]
		if !ok {
			continue
		}
		renderedNames[propertyDef.Name] = true
		label, err := gtk.LabelNew(propertyDef.Display)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		label.SetHAlign(gtk.ALIGN_START)
		label.SetVAlign(gtk.ALIGN_START)
		w.widgets = append(w.widgets, label)
		w.grid.Attach(label, 0, w.rows, 1, 1)

		valueDisplay, err := newSecretValueDisplay(value, propertyDef.Blurred, w.logger)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		valueDisplay.SetHExpand(true)
		w.widgets = append(w.widgets, valueDisplay)
		w.grid.Attach(valueDisplay, 1, w.rows, 1, 1)

		w.rows++
	}

	return renderedNames
}

func (w *secretPropertiesDisplay) destroyAllChildren() {
	for _, widget := range w.widgets {
		w.grid.Remove(widget)
		widget.Destroy()
	}
	w.widgets = w.widgets[:0]
	w.rows = 0
}
<commit_msg>Cleanup dangling signal handles (kind of a workaround<commit_after>package ui

import (
	"github.com/gotk3/gotk3/gtk"
	"github.com/leanovate/microtools/logging"
	"github.com/pkg/errors"
	"github.com/untoldwind/trustless/api"
)

type destroyable interface {
	gtk.IWidget

	Destroy()
}

type secretPropertiesDisplay struct {
	*gtk.ScrolledWindow
	grid    *gtk.Grid
	widgets []destroyable
	rows    int
	logger  logging.Logger
}

func newSecretPropertiesDisplay(logger logging.Logger) (*secretPropertiesDisplay, error) {
	scrolledWindow, err := gtk.ScrolledWindowNew(nil, nil)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create scrolled window")
	}
	grid, err := gtk.GridNew()
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create grid")
	}

	w := &secretPropertiesDisplay{
		ScrolledWindow: scrolledWindow,
		grid:           grid,
		logger:         logger.WithField("package", "ui").WithField("component", "secretPropertiesDisplay"),
	}

	w.grid.SetOrientation(gtk.ORIENTATION_HORIZONTAL)
	w.grid.SetBorderWidth(5)
	w.grid.SetColumnSpacing(5)
	w.grid.SetRowSpacing(5)
	w.Add(grid)

	return w, nil
}

func (w *secretPropertiesDisplay) display(secretVersion *api.SecretVersion) {
	w.destroyAllChildren()

	w.renderUrls(secretVersion.URLs)

	knownNames := w.renderProperties(api.SecretProperties, secretVersion.Properties)

	var unknownPropertyDefs api.SecretPropertyList
	for name := range secretVersion.Properties {
		if _, ok := knownNames[name]; ok {
			continue
		}
		unknownPropertyDefs = append(unknownPropertyDefs, api.SecretProperty{
			Name:    name,
			Display: name,
		})
	}
	unknownPropertyDefs.Sort()

	w.renderProperties(unknownPropertyDefs, secretVersion.Properties)

	w.ShowAll()
}

func (w *secretPropertiesDisplay) renderUrls(urls []string) {
	if len(urls) == 0 {
		return
	}
	label, err := gtk.LabelNew("URLs")
	if err != nil {
		w.logger.ErrorErr(err)
		return
	}
	label.SetHAlign(gtk.ALIGN_START)
	label.SetVAlign(gtk.ALIGN_START)
	w.widgets = append(w.widgets, label)
	w.grid.Attach(label, 0, w.rows, 1, 1)

	for _, url := range urls {
		urlLabel, err := newUrlLabel(w.logger, url)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		urlLabel.SetHExpand(true)
		urlLabel.SetHAlign(gtk.ALIGN_START)

		w.widgets = append(w.widgets, urlLabel)
		w.grid.Attach(urlLabel, 1, w.rows, 1, 1)
		w.rows++
	}
}

func (w *secretPropertiesDisplay) renderProperties(propertyDefs api.SecretPropertyList, properties map[string]string) map[string]bool {
	renderedNames := map[string]bool{}
	for _, propertyDef := range propertyDefs {
		value, ok := properties[propertyDef.Name]
		if !ok {
			continue
		}
		renderedNames[propertyDef.Name] = true
		label, err := gtk.LabelNew(propertyDef.Display)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		label.SetHAlign(gtk.ALIGN_START)
		label.SetVAlign(gtk.ALIGN_START)
		w.widgets = append(w.widgets, label)
		w.grid.Attach(label, 0, w.rows, 1, 1)

		valueDisplay, err := newSecretValueDisplay(value, propertyDef.Blurred, w.logger)
		if err != nil {
			w.logger.ErrorErr(err)
			continue
		}
		valueDisplay.SetHExpand(true)
		w.widgets = append(w.widgets, valueDisplay)
		w.grid.Attach(valueDisplay, 1, w.rows, 1, 1)

		w.rows++
	}

	return renderedNames
}

func (w *secretPropertiesDisplay) destroyAllChildren() {
	for _, widget := range w.widgets {
		w.grid.Remove(widget)
		widget.Destroy()
	}
	w.widgets = nil
	w.rows = 0
}
<|endoftext|>


<commit_before>package server

import "github.com/xgfone/go-tools/atomics"

var (
	shutdowned     = atomics.NewBool()
	shouldShutdown = make(chan bool, 1)
)

// RunForever runs for ever.
func RunForever() {
	if shutdowned.Get() {
		panic("The server has been shutdowned")
	}
	<-shouldShutdown
}

// Shutdown shutdowns the server gracefully.
func Shutdown() {
	shutdowned.SetTrue()
	shouldShutdown <- true
}

// IsShutdowned returns whether the server has been shutdowned.
func IsShutdowned() bool {
	return shutdowned.Get()
}
<commit_msg>Add lifecycle.Manager for shutdown<commit_after>package server

import (
	"github.com/xgfone/go-tools/atomics"
	"github.com/xgfone/go-tools/lifecycle"
)

var (
	manager        = lifecycle.GetDefaultManager()
	shutdowned     = atomics.NewBool()
	shouldShutdown = make(chan bool, 1)
)

// RunForever runs for ever.
func RunForever() {
	if shutdowned.Get() {
		panic("The server has been shutdowned")
	}
	<-shouldShutdown
}

// Shutdown shutdowns the server gracefully.
func Shutdown() {
	shutdowned.SetTrue()
	manager.Stop()
	shouldShutdown <- true
}

// IsShutdowned returns whether the server has been shutdowned.
func IsShutdowned() bool {
	return shutdowned.Get()
}

// RegisterManager replaces the default lifecycle manager.
// The default manager is the default global manager in the package lifecycle.
func RegisterManager(m *lifecycle.Manager) {
	manager = m
}
<|endoftext|>


<commit_before>package collaboration

import (
	socialapimodels "socialapi/models"
	"socialapi/workers/collaboration/models"
	"strconv"
	"strings"
	"time"

	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/klient"

	"github.com/koding/bongo"
	"github.com/koding/kite"

	"labix.org/v2/mgo"
)

// DeleteDriveDoc deletes the file from google drive
func (c *Controller) DeleteDriveDoc(ping *models.Ping) error {
	return c.deleteFile(ping.FileId)
}

// EndPrivateMessage stops the collaboration session
func (c *Controller) EndPrivateMessage(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	id, err := strconv.ParseInt(ping.ChannelId, 10, 64)
	if err != nil {
		return nil
	}

	// fetch the channel
	channel := socialapimodels.NewChannel()
	if err := channel.ById(id); err != nil {
		// if channel is not there, do not do anyting
		if err == bongo.RecordNotFound {
			return nil
		}

		return err
	}

	// delete the channel
	return channel.Delete()
}

// UnshareVM removes the users from JMachine document
func (c *Controller) UnshareVM(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnshareMachineByUid(ws.MachineUID)
}

// RemoveUsersFromMachine removes the collaboraters from the host machine
func (c *Controller) RemoveUsersFromMachine(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	m, err := modelhelper.GetMachineByUid(ws.MachineUID)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the machine is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	// Get the klient.
	klientRef, err := klient.ConnectTimeout(c.kite, m.QueryString, time.Second*10)
	if err != nil {
		if err == klient.ErrDialingFailed || err == kite.ErrNoKitesAvailable {
			c.log.Error(
				"[%s] Klient is not registered to Kontrol. Err: %s",
				m.QueryString,
				err,
			)

			return nil // if the machine is not open, we cant do anything
		}

		return err
	}
	defer klientRef.Close()

	type args struct {
		Username string

		// we are not gonna use this propery here, just for reference
		Permanent bool
	}

	var iterErr error
	for _, user := range m.Users {
		// do not unshare from owner user
		if user.Sudo && user.Owner {
			continue
		}

		// fetch user for its username
		u, err := modelhelper.GetUserById(user.Id.Hex())
		if err != nil {
			c.log.Error(err.Error())

			// if we cant find the regarding user, do not do anything
			if err == mgo.ErrNotFound {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}

		param := args{
			Username: u.Name,
		}

		_, err = klientRef.Client.Tell("klient.unshare", param)
		if err != nil {
			c.log.Error(err.Error())

			// those are so error prone, force klient side not to change the API
			// or make them exported to some other package?
			if strings.Contains(err.Error(), "user is permanent") {
				continue
			}

			if strings.Contains(err.Error(), "user is not in the shared list") {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}
	}

	// iterErr will be nil if we dont encounter to any error in iter
	return iterErr

}
<commit_msg>Socialapi: unset social channel id from workspace while deleting the socialapi channel<commit_after>package collaboration

import (
	socialapimodels "socialapi/models"
	"socialapi/workers/collaboration/models"
	"strconv"
	"strings"
	"time"

	"koding/db/mongodb/modelhelper"
	"koding/kites/kloud/klient"

	"github.com/koding/bongo"
	"github.com/koding/kite"

	"labix.org/v2/mgo"
)

// DeleteDriveDoc deletes the file from google drive
func (c *Controller) DeleteDriveDoc(ping *models.Ping) error {

	return c.deleteFile(ping.FileId)
}

// EndPrivateMessage stops the collaboration session
func (c *Controller) EndPrivateMessage(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	id, err := strconv.ParseInt(ping.ChannelId, 10, 64)
	if err != nil {
		return nil
	}

	// fetch the channel
	channel := socialapimodels.NewChannel()
	if err := channel.ById(id); err != nil {
		// if channel is not there, do not do anyting
		if err == bongo.RecordNotFound {
			return nil
		}

		return err
	}

	// delete the channel
	err = channel.Delete()
	if err != nil {
		return err
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnsetSocialChannelFromWorkspace(ws.ObjectId)
}

// UnshareVM removes the users from JMachine document
func (c *Controller) UnshareVM(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	return modelhelper.UnshareMachineByUid(ws.MachineUID)
}

// RemoveUsersFromMachine removes the collaboraters from the host machine
func (c *Controller) RemoveUsersFromMachine(ping *models.Ping) error {
	// if channel id is nil, there is nothing to do
	if ping.ChannelId == "" {
		return nil
	}

	ws, err := modelhelper.GetWorkspaceByChannelId(ping.ChannelId)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the workspace is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	m, err := modelhelper.GetMachineByUid(ws.MachineUID)
	if err != nil && err != mgo.ErrNotFound {
		return err
	}

	// if the machine is not there, nothing to do
	if err == mgo.ErrNotFound {
		return nil
	}

	// Get the klient.
	klientRef, err := klient.ConnectTimeout(c.kite, m.QueryString, time.Second*10)
	if err != nil {
		if err == klient.ErrDialingFailed || err == kite.ErrNoKitesAvailable {
			c.log.Error(
				"[%s] Klient is not registered to Kontrol. Err: %s",
				m.QueryString,
				err,
			)

			return nil // if the machine is not open, we cant do anything
		}

		return err
	}
	defer klientRef.Close()

	type args struct {
		Username string

		// we are not gonna use this propery here, just for reference
		Permanent bool
	}

	var iterErr error
	for _, user := range m.Users {
		// do not unshare from owner user
		if user.Sudo && user.Owner {
			continue
		}

		// fetch user for its username
		u, err := modelhelper.GetUserById(user.Id.Hex())
		if err != nil {
			c.log.Error(err.Error())

			// if we cant find the regarding user, do not do anything
			if err == mgo.ErrNotFound {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}

		param := args{
			Username: u.Name,
		}

		_, err = klientRef.Client.Tell("klient.unshare", param)
		if err != nil {
			c.log.Error(err.Error())

			// those are so error prone, force klient side not to change the API
			// or make them exported to some other package?
			if strings.Contains(err.Error(), "user is permanent") {
				continue
			}

			if strings.Contains(err.Error(), "user is not in the shared list") {
				continue
			}

			iterErr = err

			continue // do not stop iterating, unshare from others
		}
	}

	// iterErr will be nil if we dont encounter to any error in iter
	return iterErr

}
<|endoftext|>


<commit_before>package version

const Version = "0.1.2"
<commit_msg>chore(release): bump version to 0.1.2+git<commit_after>package version

const Version = "0.1.2+git"
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"unsafe"

	"github.com/andreluzz/cas-xog/constant"
)

//BytesToString convert an array of bytes to a string
func BytesToString(b []byte) string {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := reflect.StringHeader{Data: bh.Data, Len: bh.Len}
	return *(*string)(unsafe.Pointer(&sh))
}

//ValidateFolder creates the folder structure if it do not exists
func ValidateFolder(folder string) error {
	if folder != "" && folder[len(folder)-1:] == "/" {
		folder = folder[0 : len(folder)-1]
	}
	_, dirErr := os.Stat(folder)
	if os.IsNotExist(dirErr) {
		err := os.MkdirAll(folder, os.ModePerm)
		if err != nil {
			return err
		}
	}
	return nil
}

//GetOutputDebug formats the string from the debug when it has errors or warnings
func GetOutputDebug(code, debug string) string {
	if code != constant.OutputSuccess {
		return "| Debug: " + debug
	}
	return debug
}

//GetStatusColorFromOutput returns the status and color from the output struct
func GetStatusColorFromOutput(code string) (string, string) {
	switch code {
	case constant.OutputSuccess:
		return "success", "green"
	case constant.OutputWarning:
		return "warning", "yellow"
	case constant.OutputError:
		return "error  ", "red"
	}
	return "", ""
}

//GetActionLabel returns the properly formatted string according to the constant action
func GetActionLabel(action string) string {
	switch action {
	case constant.Read:
		return "Read"
	case constant.Write:
		return "Write"
	case constant.Migrate:
		return "Create"
	}
	return ""
}

//RightPad insert a defined number of characters on the right of the string
func RightPad(s, padStr string, length int) string {
	var padCountInt int
	padCountInt = 1 + ((length - len(padStr)) / len(padStr))
	var retStr = s + strings.Repeat(padStr, padCountInt)
	return retStr[:length]
}

//GetPathFolder returns only the folders without filename and extension
func GetPathFolder(path string) string {
	folder := ""

	re := regexp.MustCompile(`.*[/\\]`)
	match := re.FindStringSubmatch(path)

	if len(match) > 0 {
		folder = match[0]
		matchInit, _ := regexp.MatchString(`^[/\\]`, path)

		if !matchInit {
			folder = "/" + folder
		}
	}

	return folder
}

//GetPathWithoutExtension returns the folders and filename without the file extension
func GetPathWithoutExtension(path string) string {
	extIndex := strings.LastIndex(path, ".")
	return path[:extIndex]
}

//GetExtension returns the file extension
func GetExtension(path string) string {
	extIndex := strings.LastIndex(path, ".")
	return path[extIndex:]
}

//GetDirectFolder returns the file closest folder
func GetDirectFolder(path string) string {
	extIndex := strings.LastIndex(path, GetPathSeparator())
	folder := path[:extIndex]
	extIndex = strings.LastIndex(folder, GetPathSeparator())
	return folder[extIndex+1:]
}

//GetPathSeparator returns the folder separator by OS
func GetPathSeparator() string {
	return string(os.PathSeparator)
}

//ReplacePathSeparatorByOS returns path with right folder separator by OS
func ReplacePathSeparatorByOS(path string) string {
	var result string
	if runtime.GOOS == "windows" {
		result = strings.Replace(path, "/", GetPathSeparator(), -1)
	} else {
		result = strings.Replace(path, "\\", GetPathSeparator(), -1)
	}
	return result
}

//JSONEscapeText avoid escaping caracters like <, > and & from json byte array
func JSONAvoidEscapeText(data []byte) []byte {
	data = bytes.Replace(data, []byte("\\u003c"), []byte("<"), -1)
	data = bytes.Replace(data, []byte("\\u003e"), []byte(">"), -1)
	data = bytes.Replace(data, []byte("\\u0026"), []byte("&"), -1)
	return data
}
<commit_msg>Refactoring<commit_after>package util

import (
	"bytes"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"unsafe"

	"github.com/andreluzz/cas-xog/constant"
)

//BytesToString convert an array of bytes to a string
func BytesToString(b []byte) string {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := reflect.StringHeader{Data: bh.Data, Len: bh.Len}
	return *(*string)(unsafe.Pointer(&sh))
}

//ValidateFolder creates the folder structure if it do not exists
func ValidateFolder(folder string) error {
	if folder != "" && folder[len(folder)-1:] == "/" {
		folder = folder[0 : len(folder)-1]
	}
	_, dirErr := os.Stat(folder)
	if os.IsNotExist(dirErr) {
		err := os.MkdirAll(folder, os.ModePerm)
		if err != nil {
			return err
		}
	}
	return nil
}

//GetOutputDebug formats the string from the debug when it has errors or warnings
func GetOutputDebug(code, debug string) string {
	if code != constant.OutputSuccess {
		return "| Debug: " + debug
	}
	return debug
}

//GetStatusColorFromOutput returns the status and color from the output struct
func GetStatusColorFromOutput(code string) (string, string) {
	switch code {
	case constant.OutputSuccess:
		return "success", "green"
	case constant.OutputWarning:
		return "warning", "yellow"
	case constant.OutputError:
		return "error  ", "red"
	}
	return "", ""
}

//GetActionLabel returns the properly formatted string according to the constant action
func GetActionLabel(action string) string {
	switch action {
	case constant.Read:
		return "Read"
	case constant.Write:
		return "Write"
	case constant.Migrate:
		return "Create"
	}
	return ""
}

//RightPad insert a defined number of characters on the right of the string
func RightPad(s, padStr string, length int) string {
	var padCountInt int
	padCountInt = 1 + ((length - len(padStr)) / len(padStr))
	var retStr = s + strings.Repeat(padStr, padCountInt)
	return retStr[:length]
}

//GetPathFolder returns only the folders without filename and extension
func GetPathFolder(path string) string {
	folder := ""

	re := regexp.MustCompile(`.*[/\\]`)
	match := re.FindStringSubmatch(path)

	if len(match) > 0 {
		folder = match[0]
		matchInit, _ := regexp.MatchString(`^[/\\]`, path)

		if !matchInit {
			folder = "/" + folder
		}
	}

	return folder
}

//GetPathWithoutExtension returns the folders and filename without the file extension
func GetPathWithoutExtension(path string) string {
	extIndex := strings.LastIndex(path, ".")
	return path[:extIndex]
}

//GetExtension returns the file extension
func GetExtension(path string) string {
	extIndex := strings.LastIndex(path, ".")
	return path[extIndex:]
}

//GetDirectFolder returns the file closest folder
func GetDirectFolder(path string) string {
	extIndex := strings.LastIndex(path, GetPathSeparator())
	folder := path[:extIndex]
	extIndex = strings.LastIndex(folder, GetPathSeparator())
	return folder[extIndex+1:]
}

//GetPathSeparator returns the folder separator by OS
func GetPathSeparator() string {
	return string(os.PathSeparator)
}

//ReplacePathSeparatorByOS returns path with right folder separator by OS
func ReplacePathSeparatorByOS(path string) string {
	var result string
	if runtime.GOOS == "windows" {
		result = strings.Replace(path, "/", GetPathSeparator(), -1)
	} else {
		result = strings.Replace(path, "\\", GetPathSeparator(), -1)
	}
	return result
}

//JSONAvoidEscapeText avoid escaping caracters like <, > and & from json byte array
func JSONAvoidEscapeText(data []byte) []byte {
	data = bytes.Replace(data, []byte("\\u003c"), []byte("<"), -1)
	data = bytes.Replace(data, []byte("\\u003e"), []byte(">"), -1)
	data = bytes.Replace(data, []byte("\\u0026"), []byte("&"), -1)
	return data
}
<|endoftext|>


<commit_before>package cli

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/blang/semver"
	"github.com/spf13/cobra"

	"github.com/datawire/dlib/dtime"
	"github.com/datawire/telepresence2/pkg/client"
)

const checkDuration = 24 * time.Hour
const checkURL = "https://s3.amazonaws.com/datawire-static-files/tel2"
const binaryName = "telepresence"

type updateChecker struct {
	NextCheck map[string]time.Time `json:"next_check"`
	url       string
	cacheFile string
}

// newUpdateChecker returns a new update checker, possibly initialized from the users cache.
func newUpdateChecker(url string) (*updateChecker, error) {
	cache, err := client.CacheDir()
	if err != nil {
		return nil, err
	}
	ts := &updateChecker{url: url, cacheFile: filepath.Join(cache, "update-checks.json")}

	js, err := ioutil.ReadFile(ts.cacheFile)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
		ts.NextCheck = make(map[string]time.Time)
		return ts, nil
	}
	if err = json.Unmarshal(js, ts); err != nil {
		return nil, err
	}
	return ts, nil
}

// updateCheck performs an update check for the telepresence binary on the current os/arch and
// prints a message on stdout if an update is available
func updateCheck(cmd *cobra.Command, _ []string) error {
	uc, err := newUpdateChecker(fmt.Sprintf("%s/%s/%s/stable.txt", checkURL, runtime.GOOS, runtime.GOARCH))
	if err != nil || !uc.timeToCheck() {
		return err
	}

	ourVersion := client.Semver()
	update, ok := uc.updateAvailable(&ourVersion, cmd.ErrOrStderr())
	if !ok {
		// Failed to read from remote server. Next attempt is due in an hour
		return uc.storeNextCheck(time.Hour)
	}
	if update != nil {
		fmt.Fprintf(cmd.OutOrStdout(), "An update of %s from version %s to %s is available. Download it from %s/%s/%s/%s/%s\n",
			binaryName, &ourVersion, update,
			checkURL, runtime.GOOS, runtime.GOARCH, update, binaryName)
	}
	return uc.storeNextCheck(checkDuration)
}

func (uc *updateChecker) storeNextCheck(d time.Duration) error {
	uc.NextCheck[uc.url] = dtime.Now().Add(d)
	js, err := json.MarshalIndent(uc, "", "  ")
	if err != nil {
		// Internal error. The updateChecker struct cannot be marshalled.
		panic(err)
	}
	if err = ioutil.WriteFile(uc.cacheFile, js, 0600); err != nil {
		err = fmt.Errorf("unable to write update check cache %s: %v", uc.cacheFile, err)
	}
	return err
}

func (uc *updateChecker) updateAvailable(currentVersion *semver.Version, errOut io.Writer) (*semver.Version, bool) {
	resp, err := http.Get(uc.url)
	if err != nil {
		// silently ignore connection failures
		return nil, false
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// silently ignore failure to read response body
		return nil, false
	}
	vs := strings.TrimSpace(string(body))
	lastVersion, err := semver.Parse(vs)
	if err != nil {
		// The version found remotely is invalid. Not fatal, but inform the user.
		fmt.Fprintf(errOut, "Update checker was unable to parse version %q returned from %s: %v\n", vs, uc.url, err)
		return nil, false
	}
	if currentVersion.LT(lastVersion) {
		return &lastVersion, true
	}
	return nil, true
}

func (uc *updateChecker) timeToCheck() bool {
	ts, ok := uc.NextCheck[uc.url]
	return !ok || dtime.Now().After(ts)
}
<commit_msg>Let update checker use the cloud app and point to the docs site<commit_after>package cli

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/blang/semver"
	"github.com/spf13/cobra"

	"github.com/datawire/dlib/dtime"
	"github.com/datawire/telepresence2/pkg/client"
)

const checkDuration = 24 * time.Hour
const binaryName = "telepresence"

type updateChecker struct {
	NextCheck map[string]time.Time `json:"next_check"`
	url       string
	cacheFile string
}

// newUpdateChecker returns a new update checker, possibly initialized from the users cache.
func newUpdateChecker(url string) (*updateChecker, error) {
	cache, err := client.CacheDir()
	if err != nil {
		return nil, err
	}
	ts := &updateChecker{url: url, cacheFile: filepath.Join(cache, "update-checks.json")}

	js, err := ioutil.ReadFile(ts.cacheFile)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
		ts.NextCheck = make(map[string]time.Time)
		return ts, nil
	}
	if err = json.Unmarshal(js, ts); err != nil {
		return nil, err
	}
	return ts, nil
}

// updateCheck performs an update check for the telepresence binary on the current os/arch and
// prints a message on stdout if an update is available
func updateCheck(cmd *cobra.Command, _ []string) error {
	env, err := client.LoadEnv(cmd.Context())
	if err != nil {
		return err
	}
	uc, err := newUpdateChecker(fmt.Sprintf("https://%s/download/tel2/%s/%s/stable.txt", env.SystemAHost, runtime.GOOS, runtime.GOARCH))
	if err != nil || !uc.timeToCheck() {
		return err
	}

	ourVersion := client.Semver()
	update, ok := uc.updateAvailable(&ourVersion, cmd.ErrOrStderr())
	if !ok {
		// Failed to read from remote server. Next attempt is due in an hour
		return uc.storeNextCheck(time.Hour)
	}
	if update != nil {
		fmt.Fprintf(cmd.OutOrStdout(), "An update of %s from version %s to %s is available. Please visit https://%s/docs/latest/ for more info.\n",
			binaryName, &ourVersion, update,
			env.SystemAHost)
	}
	return uc.storeNextCheck(checkDuration)
}

func (uc *updateChecker) storeNextCheck(d time.Duration) error {
	uc.NextCheck[uc.url] = dtime.Now().Add(d)
	js, err := json.MarshalIndent(uc, "", "  ")
	if err != nil {
		// Internal error. The updateChecker struct cannot be marshalled.
		panic(err)
	}
	if err = ioutil.WriteFile(uc.cacheFile, js, 0600); err != nil {
		err = fmt.Errorf("unable to write update check cache %s: %v", uc.cacheFile, err)
	}
	return err
}

func (uc *updateChecker) updateAvailable(currentVersion *semver.Version, errOut io.Writer) (*semver.Version, bool) {
	resp, err := http.Get(uc.url)
	if err != nil {
		// silently ignore connection failures
		return nil, false
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// silently ignore failure to read response body
		return nil, false
	}
	vs := strings.TrimSpace(string(body))
	lastVersion, err := semver.Parse(vs)
	if err != nil {
		// The version found remotely is invalid. Not fatal, but inform the user.
		fmt.Fprintf(errOut, "Update checker was unable to parse version %q returned from %s: %v\n", vs, uc.url, err)
		return nil, false
	}
	if currentVersion.LT(lastVersion) {
		return &lastVersion, true
	}
	return nil, true
}

func (uc *updateChecker) timeToCheck() bool {
	ts, ok := uc.NextCheck[uc.url]
	return !ok || dtime.Now().After(ts)
}
<|endoftext|>


<commit_before>package runtime

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"

	"github.com/containerd/containerd"
	"github.com/containerd/go-cni"
	"github.com/opencontainers/runtime-spec/specs-go"
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 github.com/containerd/go-cni.CNI

// CNINetworkConfig provides configuration for CNINetwork to override the
// defaults.
//
type CNINetworkConfig struct {
	// BridgeName is the name that the bridge set up in the current network
	// namespace to connect the veth's to.
	//
	BridgeName string

	// NetworkName is the virtual name used to identify the managed network.
	//
	NetworkName string

	// Subnet is the subnet (in CIDR notation) which the veths should be
	// added to.
	//
	Subnet string
}

const (
	// fileStoreWorkDir is a default directory used for storing
	// container-related files
	//
	fileStoreWorkDir = "/tmp"

	// binariesDir corresponds to the directory where CNI plugins have their
	// binaries in.
	//
	binariesDir = "/usr/local/concourse/bin"
)

var (
	// defaultNameServers is the default set of nameservers used.
	//
	defaultNameServers = []string{"8.8.8.8"}

	// defaultCNINetworkConfig is the default configuration for the CNI network
	// created to put concourse containers into.
	//
	defaultCNINetworkConfig = CNINetworkConfig{
		BridgeName:  "concourse0",
		NetworkName: "concourse",
		Subnet:      "10.80.0.0/16",
	}
)

func (c CNINetworkConfig) ToJSON() string {
	const networksConfListFormat = `{
  "cniVersion": "0.4.0",
  "name": "%s",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "%s",
      "isGateway": true,
      "ipMasq": true,
      "ipam": {
        "type": "host-local",
        "subnet": "%s",
        "routes": [
          {
            "dst": "0.0.0.0/0"
          }
        ]
      }
    },
    {
      "type": "firewall"
    }
  ]
}`

	return fmt.Sprintf(networksConfListFormat,
		c.NetworkName, c.BridgeName, c.Subnet,
	)
}

// CNINetworkOpt defines a functional option that when applied, modifies the
// configuration of a CNINetwork.
//
type CNINetworkOpt func(n *cniNetwork)

// WithCNIBinariesDir is the directory where the binaries necessary for setting
// up the network live.
//
func WithCNIBinariesDir(dir string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.binariesDir = dir
	}
}

// WithNameServers sets the set of nameservers to be configured for the
// /etc/resolv.conf inside the containers.
//
func WithNameServers(nameservers []string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.nameServers = nameservers
	}
}

// WithCNIClient is an implementor of the CNI interface for reaching out to CNI
// plugins.
//
func WithCNIClient(c cni.CNI) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.client = c
	}
}

// WithCNINetworkConfig provides a custom CNINetworkConfig to be used by the CNI
// client at startup time.
//
func WithCNINetworkConfig(c CNINetworkConfig) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.config = c
	}
}

// WithCNIFileStore changes the default FileStore used to store files that
// belong to network configurations for containers.
//
func WithCNIFileStore(f FileStore) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.store = f
	}
}

// WithRestrictedNetworks defines the network ranges that containers will be restricted
// from accessing.
func WithRestrictedNetworks(restrictedNetworks []string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.restrictedNetworks = restrictedNetworks
	}
}

type cniNetwork struct {
	client             cni.CNI
	store              FileStore
	config             CNINetworkConfig
	nameServers        []string
	binariesDir        string
	restrictedNetworks []string
}

var _ Network = (*cniNetwork)(nil)

func NewCNINetwork(opts ...CNINetworkOpt) (*cniNetwork, error) {
	var err error

	n := &cniNetwork{
		binariesDir: binariesDir,
		config:      defaultCNINetworkConfig,
		nameServers: defaultNameServers,
	}

	for _, opt := range opts {
		opt(n)
	}

	if n.store == nil {
		n.store = NewFileStore(fileStoreWorkDir)
	}

	if n.client == nil {
		n.client, err = cni.New(cni.WithPluginDir([]string{n.binariesDir}))
		if err != nil {
			return nil, fmt.Errorf("cni init: %w", err)
		}

		err = n.client.Load(
			cni.WithConfListBytes([]byte(n.config.ToJSON())),
			cni.WithLoNetwork,
		)
		if err != nil {
			return nil, fmt.Errorf("cni configuration loading: %w", err)
		}
	}

	err = n.setupRestrictedNetworks()
	if err != nil {
		return nil, fmt.Errorf("setup restricted networks failed: %w", err)
	}

	return n, nil
}

func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {
	if handle == "" {
		return nil, ErrInvalidInput("empty handle")
	}

	etcHosts, err := n.store.Create(
		filepath.Join(handle, "/hosts"),
		[]byte("127.0.0.1 localhost"),
	)
	if err != nil {
		return nil, fmt.Errorf("creating /etc/hosts: %w", err)
	}

	resolvConf, err := n.store.Create(
		filepath.Join(handle, "/resolv.conf"),
		n.generateResolvConfContents(),
	)
	if err != nil {
		return nil, fmt.Errorf("creating /etc/resolv.conf: %w", err)
	}

	return []specs.Mount{
		{
			Destination: "/etc/hosts",
			Type:        "bind",
			Source:      etcHosts,
			Options:     []string{"bind", "rw"},
		}, {
			Destination: "/etc/resolv.conf",
			Type:        "bind",
			Source:      resolvConf,
			Options:     []string{"bind", "rw"},
		},
	}, nil
}

func (n cniNetwork) setupRestrictedNetworks() error {
	err := createIptablesChain()
	if err != nil {
		return err
	}

	for _, network := range n.restrictedNetworks {
		err = createRejectRules(network, "CNI-ADMIN")
		if err != nil {
			return err
		}
	}
	return nil
}

// Create chain - ignore error if it already exists
func createIptablesChain() error {
	cmd := exec.Command("iptables", "-t", "filter", "-N", "CNI-ADMIN")
	_, err := cmd.Output()

	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			// Ignore error if Chain already exists
			if string(exitErr.Stderr) != "iptables: Chain already exists.\n" {
				return fmt.Errorf("failed to create iptables chain CNI-ADMIN: %s", string(exitErr.Stderr))
			}
		} else {
			return fmt.Errorf("failed to create iptables chain CNI-ADMIN: %w", err)
		}
	}

	return nil
}

// Check reject rule - check if rule exists in chain, otherwise clear chain and add rule
func createRejectRules(restrictedNetwork string, targetChain string) error {
	// Check if reject rule exists
	cmd := exec.Command("iptables", "-C", targetChain, "-d", restrictedNetwork, "-j", "REJECT")
	err := cmd.Run()
	if err != nil {
		// Delete all rules in chain
		cmd = exec.Command("iptables", "-F", targetChain)
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("failed to flush chain: %w", err)
		}
		// Append reject rule
		cmd = exec.Command("iptables", "-A", targetChain, "-d", restrictedNetwork, "-j", "REJECT")
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("failed to create REJECT rule: %w", err)
		}

	}
	return nil
}

func (n cniNetwork) generateResolvConfContents() []byte {
	contents := ""
	for _, n := range n.nameServers {
		contents = contents + "nameserver " + n + "\n"
	}

	return []byte(contents)
}

func (n cniNetwork) Add(ctx context.Context, task containerd.Task) error {
	if task == nil {
		return ErrInvalidInput("nil task")
	}

	id, netns := netId(task), netNsPath(task)

	_, err := n.client.Setup(ctx, id, netns)
	if err != nil {
		return fmt.Errorf("cni net setup: %w", err)
	}

	return nil
}

func (n cniNetwork) Remove(ctx context.Context, task containerd.Task) error {
	if task == nil {
		return ErrInvalidInput("nil task")
	}

	id, netns := netId(task), netNsPath(task)

	err := n.client.Remove(ctx, id, netns)
	if err != nil {
		return fmt.Errorf("cni net teardown: %w", err)
	}

	return nil
}

func netId(task containerd.Task) string {
	return task.ID()
}

func netNsPath(task containerd.Task) string {
	return fmt.Sprintf("/proc/%d/ns/net", task.Pid())
}
<commit_msg>containerd: pass custom admin chain name to firewall plugin<commit_after>package runtime

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"

	"github.com/containerd/containerd"
	"github.com/containerd/go-cni"
	"github.com/opencontainers/runtime-spec/specs-go"
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 github.com/containerd/go-cni.CNI

// CNINetworkConfig provides configuration for CNINetwork to override the
// defaults.
//
type CNINetworkConfig struct {
	// BridgeName is the name that the bridge set up in the current network
	// namespace to connect the veth's to.
	//
	BridgeName string

	// NetworkName is the virtual name used to identify the managed network.
	//
	NetworkName string

	// Subnet is the subnet (in CIDR notation) which the veths should be
	// added to.
	//
	Subnet string
}

const (
	// fileStoreWorkDir is a default directory used for storing
	// container-related files
	//
	fileStoreWorkDir = "/tmp"

	// binariesDir corresponds to the directory where CNI plugins have their
	// binaries in.
	//
	binariesDir = "/usr/local/concourse/bin"

	ipTablesAdminChainName = "CONCOURSE-OPERATOR"
)

var (
	// defaultNameServers is the default set of nameservers used.
	//
	defaultNameServers = []string{"8.8.8.8"}

	// defaultCNINetworkConfig is the default configuration for the CNI network
	// created to put concourse containers into.
	//
	defaultCNINetworkConfig = CNINetworkConfig{
		BridgeName:  "concourse0",
		NetworkName: "concourse",
		Subnet:      "10.80.0.0/16",
	}
)

func (c CNINetworkConfig) ToJSON() string {
	const networksConfListFormat = `{
  "cniVersion": "0.4.0",
  "name": "%s",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "%s",
      "isGateway": true,
      "ipMasq": true,
      "ipam": {
        "type": "host-local",
        "subnet": "%s",
        "routes": [
          {
            "dst": "0.0.0.0/0"
          }
        ]
      }
    },
    {
      "type": "firewall",
	  "iptablesAdminChainName": "%s"
    }
  ]
}`

	return fmt.Sprintf(networksConfListFormat,
		c.NetworkName, c.BridgeName, c.Subnet, ipTablesAdminChainName,
	)
}

// CNINetworkOpt defines a functional option that when applied, modifies the
// configuration of a CNINetwork.
//
type CNINetworkOpt func(n *cniNetwork)

// WithCNIBinariesDir is the directory where the binaries necessary for setting
// up the network live.
//
func WithCNIBinariesDir(dir string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.binariesDir = dir
	}
}

// WithNameServers sets the set of nameservers to be configured for the
// /etc/resolv.conf inside the containers.
//
func WithNameServers(nameservers []string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.nameServers = nameservers
	}
}

// WithCNIClient is an implementor of the CNI interface for reaching out to CNI
// plugins.
//
func WithCNIClient(c cni.CNI) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.client = c
	}
}

// WithCNINetworkConfig provides a custom CNINetworkConfig to be used by the CNI
// client at startup time.
//
func WithCNINetworkConfig(c CNINetworkConfig) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.config = c
	}
}

// WithCNIFileStore changes the default FileStore used to store files that
// belong to network configurations for containers.
//
func WithCNIFileStore(f FileStore) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.store = f
	}
}

// WithRestrictedNetworks defines the network ranges that containers will be restricted
// from accessing.
func WithRestrictedNetworks(restrictedNetworks []string) CNINetworkOpt {
	return func(n *cniNetwork) {
		n.restrictedNetworks = restrictedNetworks
	}
}

type cniNetwork struct {
	client             cni.CNI
	store              FileStore
	config             CNINetworkConfig
	nameServers        []string
	binariesDir        string
	restrictedNetworks []string
}

var _ Network = (*cniNetwork)(nil)

func NewCNINetwork(opts ...CNINetworkOpt) (*cniNetwork, error) {
	var err error

	n := &cniNetwork{
		binariesDir: binariesDir,
		config:      defaultCNINetworkConfig,
		nameServers: defaultNameServers,
	}

	for _, opt := range opts {
		opt(n)
	}

	if n.store == nil {
		n.store = NewFileStore(fileStoreWorkDir)
	}

	if n.client == nil {
		n.client, err = cni.New(cni.WithPluginDir([]string{n.binariesDir}))
		if err != nil {
			return nil, fmt.Errorf("cni init: %w", err)
		}

		err = n.client.Load(
			cni.WithConfListBytes([]byte(n.config.ToJSON())),
			cni.WithLoNetwork,
		)
		if err != nil {
			return nil, fmt.Errorf("cni configuration loading: %w", err)
		}
	}

	err = n.setupRestrictedNetworks()
	if err != nil {
		return nil, fmt.Errorf("setup restricted networks failed: %w", err)
	}

	return n, nil
}

func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {
	if handle == "" {
		return nil, ErrInvalidInput("empty handle")
	}

	etcHosts, err := n.store.Create(
		filepath.Join(handle, "/hosts"),
		[]byte("127.0.0.1 localhost"),
	)
	if err != nil {
		return nil, fmt.Errorf("creating /etc/hosts: %w", err)
	}

	resolvConf, err := n.store.Create(
		filepath.Join(handle, "/resolv.conf"),
		n.generateResolvConfContents(),
	)
	if err != nil {
		return nil, fmt.Errorf("creating /etc/resolv.conf: %w", err)
	}

	return []specs.Mount{
		{
			Destination: "/etc/hosts",
			Type:        "bind",
			Source:      etcHosts,
			Options:     []string{"bind", "rw"},
		}, {
			Destination: "/etc/resolv.conf",
			Type:        "bind",
			Source:      resolvConf,
			Options:     []string{"bind", "rw"},
		},
	}, nil
}

func (n cniNetwork) setupRestrictedNetworks() error {
	err := createIptablesChain(ipTablesAdminChainName)
	if err != nil {
		return err
	}

	for _, network := range n.restrictedNetworks {
		err = createRejectRules(network, ipTablesAdminChainName)
		if err != nil {
			return err
		}
	}
	return nil
}

// Create chain - ignore error if it already exists
func createIptablesChain(chainName string) error {
	cmd := exec.Command("iptables", "-t", "filter", "-N", chainName)
	_, err := cmd.Output()

	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			// Ignore error if Chain already exists
			if string(exitErr.Stderr) != "iptables: Chain already exists.\n" {
				return fmt.Errorf("failed to create iptables chain %s: %s", chainName, string(exitErr.Stderr))
			}
		} else {
			return fmt.Errorf("failed to create iptables chain %s: %w", chainName, err)
		}
	}

	return nil
}

// Check reject rule - check if rule exists in chain, otherwise clear chain and add rule
func createRejectRules(restrictedNetwork string, chainName string) error {
	// Check if reject rule exists
	cmd := exec.Command("iptables", "-C", chainName, "-d", restrictedNetwork, "-j", "REJECT")
	err := cmd.Run()
	if err != nil {
		// Delete all rules in chain
		cmd = exec.Command("iptables", "-F", chainName)
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("failed to flush chain: %w", err)
		}
		// Append reject rule
		cmd = exec.Command("iptables", "-A", chainName, "-d", restrictedNetwork, "-j", "REJECT")
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("failed to create REJECT rule: %w", err)
		}

	}
	return nil
}

func (n cniNetwork) generateResolvConfContents() []byte {
	contents := ""
	for _, n := range n.nameServers {
		contents = contents + "nameserver " + n + "\n"
	}

	return []byte(contents)
}

func (n cniNetwork) Add(ctx context.Context, task containerd.Task) error {
	if task == nil {
		return ErrInvalidInput("nil task")
	}

	id, netns := netId(task), netNsPath(task)

	_, err := n.client.Setup(ctx, id, netns)
	if err != nil {
		return fmt.Errorf("cni net setup: %w", err)
	}

	return nil
}

func (n cniNetwork) Remove(ctx context.Context, task containerd.Task) error {
	if task == nil {
		return ErrInvalidInput("nil task")
	}

	id, netns := netId(task), netNsPath(task)

	err := n.client.Remove(ctx, id, netns)
	if err != nil {
		return fmt.Errorf("cni net teardown: %w", err)
	}

	return nil
}

func netId(task containerd.Task) string {
	return task.ID()
}

func netNsPath(task containerd.Task) string {
	return fmt.Sprintf("/proc/%d/ns/net", task.Pid())
}
<|endoftext|>


<commit_before>package opts

// BarChart
type BarChart struct {
	// Name of stack. On the same category axis, the series with the
	// same stack name would be put on top of each other.
	Stack string

	// The gap between bars between different series, is a percent value like '30%',
	// which means 30% of the bar width.
	// Set barGap as '-100%' can overlap bars that belong to different series,
	// which is useful when making a series of bar be background.
	// In a single coordinate system, this attribute is shared by multiple 'bar' series.
	// This attribute should be set on the last 'bar' series in the coordinate system,
	// then it will be adopted by all 'bar' series in the coordinate system.
	BarGap string

	// The bar gap of a single series, defaults to be 20% of the category gap,
	// can be set as a fixed value.
	// In a single coordinate system, this attribute is shared by multiple 'bar' series.
	// This attribute should be set on the last 'bar' series in the coordinate system,
	// then it will be adopted by all 'bar' series in the coordinate system.
	BarCategoryGap string

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// BarData
type BarData struct {
	// The name of data item.
	Name string `json:"name,omitempty"`

	// The value of a single data item.
	Value interface{} `json:"value,omitempty"`

	// The style setting of the text label in a single bar.
	*Label `json:"label,omitempty"`

	// ItemStyle settings in this series data.
	*ItemStyle `json:"itemStyle,omitempty"`

	// Tooltip settings in this series data.
	*Tooltip `json:"tooltip,omitempty"`
}

// BoxPlotData
type BoxPlotData struct {
	// The name of data item.
	Name string `json:"name,omitempty"`

	// The value of a single data item.
	Value interface{} `json:"value,omitempty"`

	// The style setting of the text label in a single bar.
	*Label `json:"label,omitempty"`

	// ItemStyle settings in this series data.
	*ItemStyle `json:"itemStyle,omitempty"`

	// Emphasis settings in this series data.
	*Emphasis `json:"emphasis,omitempty"`

	// Tooltip settings in this series data.
	*Tooltip `json:"tooltip,omitempty"`
}

// EffectScatterData
type EffectScatterData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// FunnelData
type FunnelData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GaugeData
type GaugeData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GraphOpts is the option set for graph chart.
//type GraphChart struct {
//	//图的布局。可选：
//	// "none" 不采用任何布局，使用节点中提供的 x， y 作为节点的位置。
//	// "circular" 采用环形布局
//	// "force" 采用力引导布局
//	Layout string
//	// "force", "circular" 布局详细配置项
//	Force GraphForce
//	// 是否开启鼠标缩放和平移漫游。默认不开启。
//	Roam bool
//	// 是否在鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点
//	FocusNodeAdjacency bool
//	//
//	Categories []GraphCategory
//}

// HeatMapChart is the option set for a heatmap chart.
type HeatMapChart struct {
	//使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	//使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
}

// LineChart is the options set for a line chart.
type LineChart struct {
	// 数据堆叠，同个类目轴上系列配置相同的 stack 值可以堆叠放置
	Stack string
	// 曲线是否平滑
	Smooth bool
	// 是否使用阶梯图
	Step bool
	// 使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	// 使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
	// 是否连接空数据。
	ConnectNulls bool
}

// LineData
type LineData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
	// Symbol
	Symbol string `json:"symbol,omitempty"`
	// SymbolSize
	SymbolSize int `json:"symbolSize,omitempty"`

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// LiquidChart
type LiquidChart struct {
	// 水球图形状，可选
	// "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Shape string
	// 是否显示水球轮廓
	IsShowOutline bool
	// 是否停止动画
	IsWaveAnimation bool
}

// LiquidData
// reference https://github.com/ecomfe/echarts-liquidfill
type LiquidData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// PieChart is the option set for a pie chart.
type PieChart struct {
	// 是否展示成南丁格尔图，通过半径区分数据大小。可选择两种模式：
	// 1."radius": 扇区圆心角展现数据的百分比，半径展现数据的大小。
	// 2."area": 所有扇区圆心角相同，仅通过半径展现数据大小。
	RoseType string
	// 饼图的中心（圆心）坐标，数组的第一项是横坐标，第二项是纵坐标。
	// 支持设置成百分比，设置成百分比时第一项是相对于容器宽度，第二项是相对于容器高度
	// 使用示例
	// 设置成绝对的像素值: center: [400, 300]
	// 设置成相对的百分比: center: ['50%', '50%']
	// 默认 ["50%", "50%"]
	Center interface{}
	// 饼图的半径。可以为如下类型：
	// 1.number：直接指定外半径值。
	// 2.string：例如，'20%'，表示外半径为可视区尺寸（容器高宽中较小一项）的 20% 长度。
	// 3.Array.<number|string>：数组的第一项是内半径，第二项是外半径。
	// 每一项遵从上述 number string 的描述。
	// 默认 [0, "75%"]
	Radius interface{}
}

// PieData
type PieData struct {
	Name       string      `json:"name,omitempty"`
	Value      interface{} `json:"value,omitempty"`
	Selected   bool        `json:"selected,omitempty"`
	*Label     `json:"label,omitempty"`
	*ItemStyle `json:"itemStyle,omitempty"`
	*Tooltip   `json:"tooltip,omitempty"`
}

// ScatterChart is the option set for a scatter chart.
type ScatterChart struct {
	// 使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	// 使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
}

// Bar3DChart is the option set for a 3D bar chart.
type Bar3DChart struct {
	Shading string
}

// WordCloudChart is the option set for a word cloud chart.
type WordCloudChart struct {
	// shape of WordCloud
	// Optional: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow"
	Shape string
	// range of font size
	SizeRange []float32
	// range of font rotation angle
	RotationRange []float32
}

// SankeyLink represents relationship between two data nodes.
type SankeyLink struct {
	// 边的源节点名称的字符串，也支持使用数字表示源节点的索引
	Source interface{} `json:"source,omitempty"`
	// 边的目标节点名称的字符串，也支持使用数字表示源节点的索引
	Target interface{} `json:"target,omitempty"`
	// 边的数值，可以在力引导布局中用于映射到边的长度
	Value float32 `json:"value,omitempty"`
}

// SankeyNode represents a data node.
type SankeyNode struct {
	// 数据项名称
	Name string `json:"name,omitempty"`
	// 数据项值
	Value string `json:"value,omitempty"`
}

// ThemeRiverChartItem
type ThemeRiverData struct {
}

// RadarChartItem
type RadarData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// KlineData
type KlineData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// ScatterData
type ScatterData struct {
	// Name
	Name string `json:"name,omitempty"`

	// Value
	Value interface{} `json:"value,omitempty"`

	// Symbol
	Symbol string `json:"symbol,omitempty"`

	// SymbolSize
	SymbolSize int `json:"symbolSize,omitempty"`

	// SymbolRotate
	SymbolRotate int `json:"symbolRotate,omitempty"`

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// MapData
type MapData struct {
}

// HeatMapData
type HeatMapData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// WordCloudData
type WordCloudData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GeoData
type GeoData struct {
}

// ParallelData
type ParallelData struct {
}
<commit_msg>refactor map.go MapData. (#100)<commit_after>package opts

// BarChart
type BarChart struct {
	// Name of stack. On the same category axis, the series with the
	// same stack name would be put on top of each other.
	Stack string

	// The gap between bars between different series, is a percent value like '30%',
	// which means 30% of the bar width.
	// Set barGap as '-100%' can overlap bars that belong to different series,
	// which is useful when making a series of bar be background.
	// In a single coordinate system, this attribute is shared by multiple 'bar' series.
	// This attribute should be set on the last 'bar' series in the coordinate system,
	// then it will be adopted by all 'bar' series in the coordinate system.
	BarGap string

	// The bar gap of a single series, defaults to be 20% of the category gap,
	// can be set as a fixed value.
	// In a single coordinate system, this attribute is shared by multiple 'bar' series.
	// This attribute should be set on the last 'bar' series in the coordinate system,
	// then it will be adopted by all 'bar' series in the coordinate system.
	BarCategoryGap string

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// BarData
type BarData struct {
	// The name of data item.
	Name string `json:"name,omitempty"`

	// The value of a single data item.
	Value interface{} `json:"value,omitempty"`

	// The style setting of the text label in a single bar.
	*Label `json:"label,omitempty"`

	// ItemStyle settings in this series data.
	*ItemStyle `json:"itemStyle,omitempty"`

	// Tooltip settings in this series data.
	*Tooltip `json:"tooltip,omitempty"`
}

// BoxPlotData
type BoxPlotData struct {
	// The name of data item.
	Name string `json:"name,omitempty"`

	// The value of a single data item.
	Value interface{} `json:"value,omitempty"`

	// The style setting of the text label in a single bar.
	*Label `json:"label,omitempty"`

	// ItemStyle settings in this series data.
	*ItemStyle `json:"itemStyle,omitempty"`

	// Emphasis settings in this series data.
	*Emphasis `json:"emphasis,omitempty"`

	// Tooltip settings in this series data.
	*Tooltip `json:"tooltip,omitempty"`
}

// EffectScatterData
type EffectScatterData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// FunnelData
type FunnelData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GaugeData
type GaugeData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GraphOpts is the option set for graph chart.
//type GraphChart struct {
//	//图的布局。可选：
//	// "none" 不采用任何布局，使用节点中提供的 x， y 作为节点的位置。
//	// "circular" 采用环形布局
//	// "force" 采用力引导布局
//	Layout string
//	// "force", "circular" 布局详细配置项
//	Force GraphForce
//	// 是否开启鼠标缩放和平移漫游。默认不开启。
//	Roam bool
//	// 是否在鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点
//	FocusNodeAdjacency bool
//	//
//	Categories []GraphCategory
//}

// HeatMapChart is the option set for a heatmap chart.
type HeatMapChart struct {
	//使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	//使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
}

// LineChart is the options set for a line chart.
type LineChart struct {
	// 数据堆叠，同个类目轴上系列配置相同的 stack 值可以堆叠放置
	Stack string
	// 曲线是否平滑
	Smooth bool
	// 是否使用阶梯图
	Step bool
	// 使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	// 使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
	// 是否连接空数据。
	ConnectNulls bool
}

// LineData
type LineData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
	// Symbol
	Symbol string `json:"symbol,omitempty"`
	// SymbolSize
	SymbolSize int `json:"symbolSize,omitempty"`

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// LiquidChart
type LiquidChart struct {
	// 水球图形状，可选
	// "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Shape string
	// 是否显示水球轮廓
	IsShowOutline bool
	// 是否停止动画
	IsWaveAnimation bool
}

// LiquidData
// reference https://github.com/ecomfe/echarts-liquidfill
type LiquidData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// PieChart is the option set for a pie chart.
type PieChart struct {
	// 是否展示成南丁格尔图，通过半径区分数据大小。可选择两种模式：
	// 1."radius": 扇区圆心角展现数据的百分比，半径展现数据的大小。
	// 2."area": 所有扇区圆心角相同，仅通过半径展现数据大小。
	RoseType string
	// 饼图的中心（圆心）坐标，数组的第一项是横坐标，第二项是纵坐标。
	// 支持设置成百分比，设置成百分比时第一项是相对于容器宽度，第二项是相对于容器高度
	// 使用示例
	// 设置成绝对的像素值: center: [400, 300]
	// 设置成相对的百分比: center: ['50%', '50%']
	// 默认 ["50%", "50%"]
	Center interface{}
	// 饼图的半径。可以为如下类型：
	// 1.number：直接指定外半径值。
	// 2.string：例如，'20%'，表示外半径为可视区尺寸（容器高宽中较小一项）的 20% 长度。
	// 3.Array.<number|string>：数组的第一项是内半径，第二项是外半径。
	// 每一项遵从上述 number string 的描述。
	// 默认 [0, "75%"]
	Radius interface{}
}

// PieData
type PieData struct {
	Name       string      `json:"name,omitempty"`
	Value      interface{} `json:"value,omitempty"`
	Selected   bool        `json:"selected,omitempty"`
	*Label     `json:"label,omitempty"`
	*ItemStyle `json:"itemStyle,omitempty"`
	*Tooltip   `json:"tooltip,omitempty"`
}

// ScatterChart is the option set for a scatter chart.
type ScatterChart struct {
	// 使用的 x 轴的 index，在单个图表实例中存在多个 x 轴的时候有用
	XAxisIndex int
	// 使用的 y 轴的 index，在单个图表实例中存在多个 y 轴的时候有用
	YAxisIndex int
}

// Bar3DChart is the option set for a 3D bar chart.
type Bar3DChart struct {
	Shading string
}

// WordCloudChart is the option set for a word cloud chart.
type WordCloudChart struct {
	// shape of WordCloud
	// Optional: "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow"
	Shape string
	// range of font size
	SizeRange []float32
	// range of font rotation angle
	RotationRange []float32
}

// SankeyLink represents relationship between two data nodes.
type SankeyLink struct {
	// 边的源节点名称的字符串，也支持使用数字表示源节点的索引
	Source interface{} `json:"source,omitempty"`
	// 边的目标节点名称的字符串，也支持使用数字表示源节点的索引
	Target interface{} `json:"target,omitempty"`
	// 边的数值，可以在力引导布局中用于映射到边的长度
	Value float32 `json:"value,omitempty"`
}

// SankeyNode represents a data node.
type SankeyNode struct {
	// 数据项名称
	Name string `json:"name,omitempty"`
	// 数据项值
	Value string `json:"value,omitempty"`
}

// ThemeRiverChartItem
type ThemeRiverData struct {
}

// RadarChartItem
type RadarData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// KlineData
type KlineData struct {
	// Name
	Name string `json:"name,omitempty"`
	// Value
	Value interface{} `json:"value,omitempty"`
}

// ScatterData
type ScatterData struct {
	// Name
	Name string `json:"name,omitempty"`

	// Value
	Value interface{} `json:"value,omitempty"`

	// Symbol
	Symbol string `json:"symbol,omitempty"`

	// SymbolSize
	SymbolSize int `json:"symbolSize,omitempty"`

	// SymbolRotate
	SymbolRotate int `json:"symbolRotate,omitempty"`

	// Index of x axis to combine with, which is useful for multiple x axes in one chart.
	XAxisIndex int

	// Index of y axis to combine with, which is useful for multiple y axes in one chart.
	YAxisIndex int
}

// MapData
type MapData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// HeatMapData
type HeatMapData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// WordCloudData
type WordCloudData struct {
	Name  string      `json:"name,omitempty"`
	Value interface{} `json:"value,omitempty"`
}

// GeoData
type GeoData struct {
}

// ParallelData
type ParallelData struct {
}
<|endoftext|>