<commit_before>package problems

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// ProblemMediaType is the default media type for a Problem response
	ProblemMediaType = "application/problem+json"

	// ProblemMediaTypeXML is the XML variant on the Problem Media type
	ProblemMediaTypeXML = "application/problem+xml"

	// DefaultURL is the default url to use for problem types
	DefaultURL = "about:blank"
)

// Problem is the interface describing an HTTP API problem. These "problem
// details" are designed to encompass a way to carry machine- readable details
// of errors in a HTTP response to avoid the need to define new error response
// formats for HTTP APIs.
type Problem interface {
	ProblemType() (*url.URL, error)
	ProblemTitle() string
}

// ValidateProblem ensures that the provided Problem implementation meets the
// Problem description requirements. Which means that the Type is a valid uri,
// and that the Title be a non-empty string. Should the provided Problem be in
// violation of either of these requirements, an error is returned
func ValidateProblem(p Problem) error {
	_, err := p.ProblemType()
	if err != nil {
		return errors.New("Problem Type's must be valid URIs")
	}

	title := p.ProblemTitle()
	if title == "" {
		return errors.New("Problem Title's must be set")
	}
	return nil
}

// DefaultProblem is a default problem implementation. The Problem specification
// allows for arbitrary extensions to include new fields, in which case a new
// Problem implementation will likely be required
type DefaultProblem struct {
	// Type contains a URI that identifies the problem type. This URI will,
	// ideally, contain human-readable documentation for the problem when
	// dereferenced
	Type string `json:"type"`

	// Title is a short, human-readable summary of the problem type. This title
	// SHOULD NOT change from occurrence to occurrence of the problem, except for
	// purposes of localization
	Title string `json:"title"`

	// The HTTP status code for this occurrence of the problem
	Status int16 `json:"status,omitempty"`

	// A human-readable explanation specific to this occurrence of the problem.
	Detail string `json:"detail,omitempty"`

	// A URI that identifies the specific occurrence of the problem. This URI
	// may or may not yield further information if dereferenced.
	Instance string `json:"instance,omitempty"`
}

// NewProblem returns a new instance of a DefaultProblem
func NewProblem() *DefaultProblem {
	prob := &DefaultProblem{Type: DefaultURL, Title: ""}
	return prob
}

// NewStatusProblem will generate a default problem for the provided HTTP status
// code. The Problem's Status field will be set to match the status argument,
// and the Title will be set to the default Go status text for that code.
func NewStatusProblem(status int16) *DefaultProblem {
	problem := NewProblem()
	problem.Status = status
	problem.Title = http.StatusText(404)
	return problem
}

// ProblemType returns the uri for the problem type being defined and an
// optional error
func (p *DefaultProblem) ProblemType() (*url.URL, error) {
	u, err := url.Parse(p.Type)
	if err != nil {
		return nil, err
	}
	return u, nil
}

// ProblemTitle returns the unique title field for this Problem
func (p *DefaultProblem) ProblemTitle() string {
	return p.Title
}
<commit_msg>use dynamic status codes<commit_after>package problems

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// ProblemMediaType is the default media type for a Problem response
	ProblemMediaType = "application/problem+json"

	// ProblemMediaTypeXML is the XML variant on the Problem Media type
	ProblemMediaTypeXML = "application/problem+xml"

	// DefaultURL is the default url to use for problem types
	DefaultURL = "about:blank"
)

// Problem is the interface describing an HTTP API problem. These "problem
// details" are designed to encompass a way to carry machine- readable details
// of errors in a HTTP response to avoid the need to define new error response
// formats for HTTP APIs.
type Problem interface {
	ProblemType() (*url.URL, error)
	ProblemTitle() string
}

// ValidateProblem ensures that the provided Problem implementation meets the
// Problem description requirements. Which means that the Type is a valid uri,
// and that the Title be a non-empty string. Should the provided Problem be in
// violation of either of these requirements, an error is returned
func ValidateProblem(p Problem) error {
	_, err := p.ProblemType()
	if err != nil {
		return errors.New("Problem Type's must be valid URIs")
	}

	title := p.ProblemTitle()
	if title == "" {
		return errors.New("Problem Title's must be set")
	}
	return nil
}

// DefaultProblem is a default problem implementation. The Problem specification
// allows for arbitrary extensions to include new fields, in which case a new
// Problem implementation will likely be required
type DefaultProblem struct {
	// Type contains a URI that identifies the problem type. This URI will,
	// ideally, contain human-readable documentation for the problem when
	// dereferenced
	Type string `json:"type"`

	// Title is a short, human-readable summary of the problem type. This title
	// SHOULD NOT change from occurrence to occurrence of the problem, except for
	// purposes of localization
	Title string `json:"title"`

	// The HTTP status code for this occurrence of the problem
	Status int16 `json:"status,omitempty"`

	// A human-readable explanation specific to this occurrence of the problem.
	Detail string `json:"detail,omitempty"`

	// A URI that identifies the specific occurrence of the problem. This URI
	// may or may not yield further information if dereferenced.
	Instance string `json:"instance,omitempty"`
}

// NewProblem returns a new instance of a DefaultProblem
func NewProblem() *DefaultProblem {
	prob := &DefaultProblem{Type: DefaultURL, Title: ""}
	return prob
}

// NewStatusProblem will generate a default problem for the provided HTTP status
// code. The Problem's Status field will be set to match the status argument,
// and the Title will be set to the default Go status text for that code.
func NewStatusProblem(status int16) *DefaultProblem {
	problem := NewProblem()
	problem.Status = status
	problem.Title = http.StatusText(int(status))
	return problem
}

// ProblemType returns the uri for the problem type being defined and an
// optional error
func (p *DefaultProblem) ProblemType() (*url.URL, error) {
	u, err := url.Parse(p.Type)
	if err != nil {
		return nil, err
	}
	return u, nil
}

// ProblemTitle returns the unique title field for this Problem
func (p *DefaultProblem) ProblemTitle() string {
	return p.Title
}
<|endoftext|>


<commit_before>package scipipe

import (
	"errors"
	"strings"
)

// ================== Process ==================

// ExecMode specifies which execution mode should be used for a Process and
// its corresponding Tasks
type ExecMode int

const (
	// ExecModeLocal indicates that commands on the local computer
	ExecModeLocal ExecMode = iota
	// ExecModeSLURM indicates that commands should be executed on a HPC cluster
	// via a SLURM resource manager
	ExecModeSLURM ExecMode = iota
)

// Process is the central component in SciPipe after Workflow. Processes are
// long-running "services" that schedules and executes Tasks based on the IPs
// and parameters received on its in-ports and parameter ports
type Process struct {
	BaseProcess
	CommandPattern   string
	OutPortsDoStream map[string]bool
	PathFormatters   map[string]func(*Task) string
	CustomExecute    func(*Task)
	CoresPerTask     int
	ExecMode         ExecMode
	Prepend          string
	Spawn            bool
}

// NewProc returns a new Process, and initializes its ports based on the
// command pattern.
func NewProc(workflow *Workflow, name string, cmd string) *Process {
	InitLogInfo()
	p := newProcess(workflow, name, cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

// newProcess returns a new Process (without initializing its ports based on the
// command pattern. If this is what you need, use NewProc instead)
func newProcess(workflow *Workflow, name string, command string) *Process {
	p := &Process{
		BaseProcess: NewBaseProcess(
			workflow,
			name,
		),
		CommandPattern:   command,
		OutPortsDoStream: make(map[string]bool),
		PathFormatters:   make(map[string]func(*Task) string),
		Spawn:            true,
		CoresPerTask:     1,
	}
	workflow.AddProc(p)
	return p
}

// ShellExpand expands the command pattern in cmd with the concrete values
// provided in inPaths, ouPaths and params
func ShellExpand(wf *Workflow, name string, cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *Process {
	cmdExpr := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := newProcess(wf, name, cmdExpr)
	p.initPortsFromCmdPattern(cmdExpr, params)
	return p
}

// ------------------------------------------------
// Main Process API methods
// ------------------------------------------------

// In is a short-form for InPort() (of BaseProcess), which works only on Process
// processes
func (p *Process) In(portName string) *InPort {
	return p.InPort(portName)
}

// Out is a short-form for OutPort() (of BaseProcess), which works only on
// Process processes
func (p *Process) Out(portName string) *OutPort {
	return p.OutPort(portName)
}

// SetPathStatic creates an (output) path formatter returning a static string file name
func (p *Process) SetPathStatic(outPortName string, path string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return path
	}
}

// SetPathExtend creates an (output) path formatter that extends the path of
// an input IP
func (p *Process) SetPathExtend(inPortName string, outPortName string,
	extension string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return t.InPath(inPortName) + extension
	}
}

// SetPathReplace creates an (output) path formatter that uses an input's path
// but replaces parts of it.
func (p *Process) SetPathReplace(inPortName string, outPortName string, old string, new string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return strings.Replace(t.InPath(inPortName), old, new, -1)
	}
}

// SetPathCustom takes a function which produces a file path based on data
// available in *Task, such as concrete file paths and parameter values,
func (p *Process) SetPathCustom(outPortName string, pathFmtFunc func(task *Task) (path string)) {
	p.PathFormatters[outPortName] = pathFmtFunc
}

// ------- Helper methods for initialization -------

// ExpandParams takes a command pattern and a map of parameter names mapped to
// parameter values, and returns the command as a string where any parameter
// placeholders (on the form `{p:paramname}` are replaced with the parameter
// value from the provided parameter values map.
func ExpandParams(cmd string, params map[string]string) string {
	return expandCommandParamsAndPaths(cmd, params, nil, nil)
}

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExpr string) {
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	if params != nil {
		Debug.Println("Params:", params)
	}
	if inPaths != nil {
		Debug.Println("inPaths:", inPaths)
	}
	if outPaths != nil {
		Debug.Println("outPaths:", outPaths)
	}
	cmdExpr = cmd
	Debug.Println("Got command: ", cmd)
	for _, m := range ms {
		placeHolderStr := m[0]
		typ := m[1]
		name := m[2]
		var filePath string
		if typ == "p" {
			if params != nil {
				if val, ok := params[name]; ok {
					Debug.Println("Found param:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "i" {
			if inPaths != nil {
				if val, ok := inPaths[name]; ok {
					Debug.Println("Found inPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "o" || typ == "os" {
			if outPaths != nil {
				if val, ok := outPaths[name]; ok {
					Debug.Println("Found outPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		}
	}
	if cmd != cmdExpr {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExpr)
	}
	return
}

// Set up in- and out-ports based on the shell command pattern used to create the
// Process. Ports are set up in this way:
// `{i:PORTNAME}` specifies an in-port
// `{o:PORTNAME}` specifies an out-port
// `{os:PORTNAME}` specifies an out-port that streams via a FIFO file
// `{p:PORTNAME}` a "parameter-port", which means a port where parameters can be "streamed"
func (p *Process) initPortsFromCmdPattern(cmd string, params map[string]string) {

	// Find in/out port names and Params and set up in struct fields
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Printf("Got the following matches for placeholders in the command, when initializing ports: %v\n", ms)

	for _, m := range ms {
		if len(m) < 3 {
			msg := "Too few matches"
			CheckWithMsg(errors.New(msg), msg)
		}

		typ := m[1]
		name := m[2]
		if typ == "o" || typ == "os" {
			p.outPorts[name] = NewOutPort(name)
			p.outPorts[name].process = p
			if typ == "os" {
				p.OutPortsDoStream[name] = true
			}
		} else if typ == "i" {
			// Set up a channel on the inports, even though this is
			// often replaced by another processes output port channel.
			// It might be nice to have it init'ed with a channel
			// anyways, for use cases when we want to send IP
			// on the inport manually.
			p.inPorts[name] = NewInPort(name)
			p.inPorts[name].process = p
		} else if typ == "p" {
			if params == nil || params[name] == "" {
				p.paramInPorts[name] = NewParamInPort(name)
				p.paramInPorts[name].process = p
			}
		}
	}
}

// ============== Process Run Method ===============

// Run runs the process by instantiating and executing Tasks for all inputs
// and parameter values on its in-ports. in the case when there are no inputs
// or parameter values on the in-ports, it will run just once before it
// terminates. note that the actual execution of shell commands are done inside
// Task.Execute, not here.
func (p *Process) Run() {
	// Check that CoresPerTask is a sane number
	if p.CoresPerTask > cap(p.workflow.concurrentTasks) {
		Error.Fatalf("%s: CoresPerTask (%d) can't be greater than maxConcurrentTasks of workflow (%d)\n", p.Name(), p.CoresPerTask, cap(p.workflow.concurrentTasks))
	}

	defer p.CloseOutPorts()

	tasks := []*Task{}
	Debug.Printf("Process %s: Starting to create and schedule tasks\n", p.name)
	for t := range p.createTasks() {

		// Collect created tasks, for the second round
		// where tasks are waited for to finish, before
		// sending their outputs.
		Debug.Printf("Process %s: Instantiated task [%s] ...", p.name, t.Command)
		tasks = append(tasks, t)

		anyPreviousFifosExists := t.anyFifosExist()

		if p.ExecMode == ExecModeLocal {
			if !anyPreviousFifosExists {
				Debug.Printf("Process %s: No FIFOs existed, so creating, for task [%s] ...", p.name, t.Command)
				t.createFifos()
			}

			// Sending FIFOs for the task
			for oname, oip := range t.OutTargets {
				if oip.doStream {
					p.Out(oname).Send(oip)
				}
			}
		}

		if anyPreviousFifosExists {
			Debug.Printf("Process %s: Previous FIFOs existed, so not executing task [%s] ...\n", p.name, t.Command)
			// Since t.Execute() is not run, that normally sends the Done signal, we
			// have to send it manually here:
			go func() {
				defer close(t.Done)
				t.Done <- 1
			}()
		} else {
			Debug.Printf("Process %s: Go-Executing task in separate go-routine: [%s] ...\n", p.name, t.Command)
			// Run the task
			go t.Execute()
			Debug.Printf("Process %s: Done go-executing task in go-routine: [%s] ...\n", p.name, t.Command)
		}
	}

	Debug.Printf("Process %s: Starting to loop over %d tasks to send out targets ...\n", p.name, len(tasks))
	for _, t := range tasks {
		Debug.Printf("Process %s: Waiting for Done from task: [%s]\n", p.name, t.Command)
		<-t.Done
		Debug.Printf("Process %s: Received Done from task: [%s]\n", p.name, t.Command)
		for oname, oip := range t.OutTargets {
			if !oip.doStream {
				Debug.Printf("Process %s: Sending target on outport %s, for task [%s] ...\n", p.name, oname, t.Command)
				p.Out(oname).Send(oip)
				Debug.Printf("Process %s: Done sending target on outport %s, for task [%s] ...\n", p.name, oname, t.Command)
			}
		}
	}
}

// -------- Helper methods for the Run method ---------

func (p *Process) createTasks() (ch chan *Task) {
	ch = make(chan *Task)
	go func() {
		defer close(ch)
		for {
			inTargets, inPortsOpen := p.receiveOnInPorts()
			Debug.Printf("Process.createTasks:%s Got inTargets: %v", p.name, inTargets)
			params, paramPortsOpen := p.receiveOnParamInPorts()
			Debug.Printf("Process.createTasks:%s Got params: %s", p.name, params)
			if !inPortsOpen && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: Both inPorts and paramInPorts closed", p.name)
				break
			}
			if len(p.inPorts) == 0 && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No inports, and params closed", p.name)
				break
			}
			if len(p.paramInPorts) == 0 && !inPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No params, and inPorts closed", p.name)
				break
			}
			t := NewTask(p.workflow, p.name, p.CommandPattern, inTargets, p.PathFormatters, p.OutPortsDoStream, params, p.Prepend, p.ExecMode, p.CoresPerTask)
			if p.CustomExecute != nil {
				t.CustomExecute = p.CustomExecute
			}
			ch <- t
			if len(p.inPorts) == 0 && len(p.paramInPorts) == 0 {
				Debug.Printf("Process.createTasks:%s Breaking: No inports nor params", p.name)
				break
			}
		}
		Debug.Printf("Process.createTasks:%s Did break", p.name)
	}()
	return ch
}
<commit_msg>Don't init logs in NewProc()<commit_after>package scipipe

import (
	"errors"
	"strings"
)

// ================== Process ==================

// ExecMode specifies which execution mode should be used for a Process and
// its corresponding Tasks
type ExecMode int

const (
	// ExecModeLocal indicates that commands on the local computer
	ExecModeLocal ExecMode = iota
	// ExecModeSLURM indicates that commands should be executed on a HPC cluster
	// via a SLURM resource manager
	ExecModeSLURM ExecMode = iota
)

// Process is the central component in SciPipe after Workflow. Processes are
// long-running "services" that schedules and executes Tasks based on the IPs
// and parameters received on its in-ports and parameter ports
type Process struct {
	BaseProcess
	CommandPattern   string
	OutPortsDoStream map[string]bool
	PathFormatters   map[string]func(*Task) string
	CustomExecute    func(*Task)
	CoresPerTask     int
	ExecMode         ExecMode
	Prepend          string
	Spawn            bool
}

// NewProc returns a new Process, and initializes its ports based on the
// command pattern.
func NewProc(workflow *Workflow, name string, cmd string) *Process {
	p := newProcess(workflow, name, cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

// newProcess returns a new Process (without initializing its ports based on the
// command pattern. If this is what you need, use NewProc instead)
func newProcess(workflow *Workflow, name string, command string) *Process {
	p := &Process{
		BaseProcess: NewBaseProcess(
			workflow,
			name,
		),
		CommandPattern:   command,
		OutPortsDoStream: make(map[string]bool),
		PathFormatters:   make(map[string]func(*Task) string),
		Spawn:            true,
		CoresPerTask:     1,
	}
	workflow.AddProc(p)
	return p
}

// ShellExpand expands the command pattern in cmd with the concrete values
// provided in inPaths, ouPaths and params
func ShellExpand(wf *Workflow, name string, cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *Process {
	cmdExpr := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := newProcess(wf, name, cmdExpr)
	p.initPortsFromCmdPattern(cmdExpr, params)
	return p
}

// ------------------------------------------------
// Main Process API methods
// ------------------------------------------------

// In is a short-form for InPort() (of BaseProcess), which works only on Process
// processes
func (p *Process) In(portName string) *InPort {
	return p.InPort(portName)
}

// Out is a short-form for OutPort() (of BaseProcess), which works only on
// Process processes
func (p *Process) Out(portName string) *OutPort {
	return p.OutPort(portName)
}

// SetPathStatic creates an (output) path formatter returning a static string file name
func (p *Process) SetPathStatic(outPortName string, path string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return path
	}
}

// SetPathExtend creates an (output) path formatter that extends the path of
// an input IP
func (p *Process) SetPathExtend(inPortName string, outPortName string,
	extension string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return t.InPath(inPortName) + extension
	}
}

// SetPathReplace creates an (output) path formatter that uses an input's path
// but replaces parts of it.
func (p *Process) SetPathReplace(inPortName string, outPortName string, old string, new string) {
	p.PathFormatters[outPortName] = func(t *Task) string {
		return strings.Replace(t.InPath(inPortName), old, new, -1)
	}
}

// SetPathCustom takes a function which produces a file path based on data
// available in *Task, such as concrete file paths and parameter values,
func (p *Process) SetPathCustom(outPortName string, pathFmtFunc func(task *Task) (path string)) {
	p.PathFormatters[outPortName] = pathFmtFunc
}

// ------- Helper methods for initialization -------

// ExpandParams takes a command pattern and a map of parameter names mapped to
// parameter values, and returns the command as a string where any parameter
// placeholders (on the form `{p:paramname}` are replaced with the parameter
// value from the provided parameter values map.
func ExpandParams(cmd string, params map[string]string) string {
	return expandCommandParamsAndPaths(cmd, params, nil, nil)
}

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExpr string) {
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	if params != nil {
		Debug.Println("Params:", params)
	}
	if inPaths != nil {
		Debug.Println("inPaths:", inPaths)
	}
	if outPaths != nil {
		Debug.Println("outPaths:", outPaths)
	}
	cmdExpr = cmd
	Debug.Println("Got command: ", cmd)
	for _, m := range ms {
		placeHolderStr := m[0]
		typ := m[1]
		name := m[2]
		var filePath string
		if typ == "p" {
			if params != nil {
				if val, ok := params[name]; ok {
					Debug.Println("Found param:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "i" {
			if inPaths != nil {
				if val, ok := inPaths[name]; ok {
					Debug.Println("Found inPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "o" || typ == "os" {
			if outPaths != nil {
				if val, ok := outPaths[name]; ok {
					Debug.Println("Found outPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = strings.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		}
	}
	if cmd != cmdExpr {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExpr)
	}
	return
}

// Set up in- and out-ports based on the shell command pattern used to create the
// Process. Ports are set up in this way:
// `{i:PORTNAME}` specifies an in-port
// `{o:PORTNAME}` specifies an out-port
// `{os:PORTNAME}` specifies an out-port that streams via a FIFO file
// `{p:PORTNAME}` a "parameter-port", which means a port where parameters can be "streamed"
func (p *Process) initPortsFromCmdPattern(cmd string, params map[string]string) {

	// Find in/out port names and Params and set up in struct fields
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Printf("Got the following matches for placeholders in the command, when initializing ports: %v\n", ms)

	for _, m := range ms {
		if len(m) < 3 {
			msg := "Too few matches"
			CheckWithMsg(errors.New(msg), msg)
		}

		typ := m[1]
		name := m[2]
		if typ == "o" || typ == "os" {
			p.outPorts[name] = NewOutPort(name)
			p.outPorts[name].process = p
			if typ == "os" {
				p.OutPortsDoStream[name] = true
			}
		} else if typ == "i" {
			// Set up a channel on the inports, even though this is
			// often replaced by another processes output port channel.
			// It might be nice to have it init'ed with a channel
			// anyways, for use cases when we want to send IP
			// on the inport manually.
			p.inPorts[name] = NewInPort(name)
			p.inPorts[name].process = p
		} else if typ == "p" {
			if params == nil || params[name] == "" {
				p.paramInPorts[name] = NewParamInPort(name)
				p.paramInPorts[name].process = p
			}
		}
	}
}

// ============== Process Run Method ===============

// Run runs the process by instantiating and executing Tasks for all inputs
// and parameter values on its in-ports. in the case when there are no inputs
// or parameter values on the in-ports, it will run just once before it
// terminates. note that the actual execution of shell commands are done inside
// Task.Execute, not here.
func (p *Process) Run() {
	// Check that CoresPerTask is a sane number
	if p.CoresPerTask > cap(p.workflow.concurrentTasks) {
		Error.Fatalf("%s: CoresPerTask (%d) can't be greater than maxConcurrentTasks of workflow (%d)\n", p.Name(), p.CoresPerTask, cap(p.workflow.concurrentTasks))
	}

	defer p.CloseOutPorts()

	tasks := []*Task{}
	Debug.Printf("Process %s: Starting to create and schedule tasks\n", p.name)
	for t := range p.createTasks() {

		// Collect created tasks, for the second round
		// where tasks are waited for to finish, before
		// sending their outputs.
		Debug.Printf("Process %s: Instantiated task [%s] ...", p.name, t.Command)
		tasks = append(tasks, t)

		anyPreviousFifosExists := t.anyFifosExist()

		if p.ExecMode == ExecModeLocal {
			if !anyPreviousFifosExists {
				Debug.Printf("Process %s: No FIFOs existed, so creating, for task [%s] ...", p.name, t.Command)
				t.createFifos()
			}

			// Sending FIFOs for the task
			for oname, oip := range t.OutTargets {
				if oip.doStream {
					p.Out(oname).Send(oip)
				}
			}
		}

		if anyPreviousFifosExists {
			Debug.Printf("Process %s: Previous FIFOs existed, so not executing task [%s] ...\n", p.name, t.Command)
			// Since t.Execute() is not run, that normally sends the Done signal, we
			// have to send it manually here:
			go func() {
				defer close(t.Done)
				t.Done <- 1
			}()
		} else {
			Debug.Printf("Process %s: Go-Executing task in separate go-routine: [%s] ...\n", p.name, t.Command)
			// Run the task
			go t.Execute()
			Debug.Printf("Process %s: Done go-executing task in go-routine: [%s] ...\n", p.name, t.Command)
		}
	}

	Debug.Printf("Process %s: Starting to loop over %d tasks to send out targets ...\n", p.name, len(tasks))
	for _, t := range tasks {
		Debug.Printf("Process %s: Waiting for Done from task: [%s]\n", p.name, t.Command)
		<-t.Done
		Debug.Printf("Process %s: Received Done from task: [%s]\n", p.name, t.Command)
		for oname, oip := range t.OutTargets {
			if !oip.doStream {
				Debug.Printf("Process %s: Sending target on outport %s, for task [%s] ...\n", p.name, oname, t.Command)
				p.Out(oname).Send(oip)
				Debug.Printf("Process %s: Done sending target on outport %s, for task [%s] ...\n", p.name, oname, t.Command)
			}
		}
	}
}

// -------- Helper methods for the Run method ---------

func (p *Process) createTasks() (ch chan *Task) {
	ch = make(chan *Task)
	go func() {
		defer close(ch)
		for {
			inTargets, inPortsOpen := p.receiveOnInPorts()
			Debug.Printf("Process.createTasks:%s Got inTargets: %v", p.name, inTargets)
			params, paramPortsOpen := p.receiveOnParamInPorts()
			Debug.Printf("Process.createTasks:%s Got params: %s", p.name, params)
			if !inPortsOpen && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: Both inPorts and paramInPorts closed", p.name)
				break
			}
			if len(p.inPorts) == 0 && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No inports, and params closed", p.name)
				break
			}
			if len(p.paramInPorts) == 0 && !inPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No params, and inPorts closed", p.name)
				break
			}
			t := NewTask(p.workflow, p.name, p.CommandPattern, inTargets, p.PathFormatters, p.OutPortsDoStream, params, p.Prepend, p.ExecMode, p.CoresPerTask)
			if p.CustomExecute != nil {
				t.CustomExecute = p.CustomExecute
			}
			ch <- t
			if len(p.inPorts) == 0 && len(p.paramInPorts) == 0 {
				Debug.Printf("Process.createTasks:%s Breaking: No inports nor params", p.name)
				break
			}
		}
		Debug.Printf("Process.createTasks:%s Did break", p.name)
	}()
	return ch
}
<|endoftext|>


<commit_before>package main

import (
	"strconv"

	"github.com/shirou/gopsutil/process"
)

// procinfo represent the processes informations
type procinfo struct {
	total   string
	running string
	//TODO add zombies threads ?
}

// Get informations about the processes
func getProcinfo() procinfo {
	ret := procinfo{}

	pids, err := process.Pids() //TODO replace by something like psutil.process_iter()
	if err == nil {
		run := 0
		for i := range pids {
			proc, err := process.NewProcess(pids[i])
			if err == nil { //TODO rename `err` variables names to avoid confusion ?
				status, err := proc.Status()
				if err == nil {
					if status == "R" { // "R" for running process
						run++
					}
				}
			}
		}
		ret.total = strconv.Itoa(len(pids))
		ret.running = strconv.Itoa(run)
	}
	return ret
}
<commit_msg>Code refactoring<commit_after>package main

import (
	"strconv"

	"github.com/shirou/gopsutil/process"
)

// procinfo represent the processes informations
type procinfo struct {
	total   string
	running string
	//TODO add zombies threads ?
}

// Get informations about the processes
func getProcinfo() procinfo {
	ret := procinfo{}

	pids, err := processPids() //TODO replace by something like psutil.process_iter() if available in gopsutils
	if err == nil {
		run := 0
		for i := range pids {
			proc, err := processNewProcess(pids[i])
			if err == nil { //TODO rename `err` variables names to avoid confusion ?
				status, err := proc.Status()
				if err == nil {
					if status == "R" { // "R" for running process
						run++
					}
				}
			}
		}
		ret.total = strconv.Itoa(len(pids))
		ret.running = strconv.Itoa(run)
	}
	return ret
}

// wrap `process.Pids()` in an unexported variable for testability
var processPids = func() ([]int32, error) {
	return process.Pids()
}

// wrap `process.NewProcess()` in an unexported variable for testability
var processNewProcess = func(pid int32) (*process.Process, error) {
	return process.NewProcess(pid)
}
<|endoftext|>


<commit_before>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	// "io"
	// "os"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.StatsMap, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := stats.NewStatsMap(true, (index != nil), cfg.Uniq)

	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record, index)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record(), index)
				}
			}
		}
	}
	logger.Debug("Done")

	out <- sm
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.StatsMap, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	if v, ok := stat["general"]; ok {
		s := v.(*stats.GeneralStats)
		s.Reads.Total += br.Unmapped()
		s.Finalize()
	}

	return stat, nil
}

func waitProcess(st chan stats.StatsMap, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	stats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return stats, nil
}

func WriteOutput(output string, st stats.StatsMap) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}
<commit_msg>Add collectStats function<commit_after>// Package bamstats provides functions for computing several mapping statistics on a BAM file.
package bamstats

import (
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/config"
	"github.com/guigolab/bamstats/sam"
	"github.com/guigolab/bamstats/stats"
	"github.com/guigolab/bamstats/utils"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

// var wg sync.WaitGroup

func worker(id int, in interface{}, out chan stats.StatsMap, index *annotation.RtreeMap, cfg *config.Config, wg *sync.WaitGroup) {
	defer wg.Done()
	logger := log.WithFields(log.Fields{
		"worker": id,
	})
	logger.Debug("Starting")

	sm := stats.NewStatsMap(true, (index != nil), cfg.Uniq)

	collectStats(in, sm, index)

	logger.Debug("Done")

	out <- sm
}

func collectStats(in interface{}, sm stats.StatsMap, index *annotation.RtreeMap) {
	switch in.(type) {
	case chan *sam.Record:
		c := in.(chan *sam.Record)
		for record := range c {
			for _, s := range sm {
				s.Collect(record, index)
			}
		}
	case chan *sam.Iterator:
		iterators := in.(chan *sam.Iterator)
		for it := range iterators {
			for it.Next() {
				for _, s := range sm {
					s.Collect(it.Record(), index)
				}
			}
		}
	}
}

func process(bamFile string, index *annotation.RtreeMap, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {

	var wg sync.WaitGroup

	conf := config.NewConfig(cpu, maxBuf, reads, uniq)

	br, err := sam.NewReader(bamFile, conf)
	defer br.Close()
	if err != nil {
		return nil, err
	}
	statChan := make(chan stats.StatsMap, cpu)
	for i := 0; i < br.Workers; i++ {
		id := i + 1
		wg.Add(1)
		go worker(id, br.Channels[i], statChan, index, conf, &wg)
	}

	br.Read()

	go waitProcess(statChan, &wg)
	stat := <-statChan
	stat.Merge(statChan)
	if v, ok := stat["general"]; ok {
		s := v.(*stats.GeneralStats)
		s.Reads.Total += br.Unmapped()
		s.Finalize()
	}

	return stat, nil
}

func waitProcess(st chan stats.StatsMap, wg *sync.WaitGroup) {
	wg.Wait()
	close(st)
}

// Process process the input BAM file and collect different mapping stats.
func Process(bamFile string, anno string, cpu int, maxBuf int, reads int, uniq bool) (stats.StatsMap, error) {
	var index *annotation.RtreeMap
	if anno != "" {
		log.Infof("Creating index for %s", anno)
		start := time.Now()
		index = annotation.CreateIndex(anno, cpu)
		log.Infof("Index done in %v", time.Since(start))
	}
	start := time.Now()
	log.Infof("Collecting stats for %s", bamFile)
	stats, err := process(bamFile, index, cpu, maxBuf, reads, uniq)
	if err != nil {
		return nil, err
	}
	log.Infof("Stats done in %v", time.Since(start))
	return stats, nil
}

func WriteOutput(output string, st stats.StatsMap) {
	out := utils.NewOutput(output)
	utils.OutputJSON(out, st)
}
<|endoftext|>


<commit_before>package procfs

// Copyright Jen Andre (jandre@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"github.com/jandre/procfs/limits"
	"github.com/jandre/procfs/stat"
	"github.com/jandre/procfs/statm"
	"strconv"
	"strings"
)

//
// Structur that describes a /proc/<pid> entry
//

type Process struct {
	Pid       int            // Process ID
	Cmdline   []string       // Command line of process (argv array)
	Cwd       string         // Process current working directory
	Exe       string         // Symlink to executed command.
	Root      string         // Per-process root (e.g. chroot)
	prefix    string         // directory path, e.g. /proc/<pid>
	stat      *stat.Stat     // Status information from /proc/pid/stat - see Stat()
	statm     *statm.Statm   // Memory Status information from /proc/pid/statm - see Statm()
	limits    *limits.Limits // Per process rlimit settings from /proc/pid/limits - see Limits()
	loginuid  *int           // Maybe loginuid from /proc/pid/loginuid - see Loginuid()
	sessionid *int           // Maybe sessionid from /proc/pid/sessionid- see Sessionid()

}

//
// Read /proc information for `pid`
//
func NewProcess(pid int, lazy bool) (*Process, error) {
	prefix := path.Join("/proc", strconv.Itoa(pid))
	return NewProcessFromPath(pid, prefix, lazy)
}

//
// Read a process entry from a directory path
//
// if lazy = true, then preload the stat, limits, and other files.
//
func NewProcessFromPath(pid int, prefix string, lazy bool) (*Process, error) {
	var err error

	if _, err = os.Stat(prefix); err != nil {
		// error reading pid
		return nil, err
	}

	process := &Process{prefix: prefix, Pid: pid }
	if process.Cmdline, err = readCmdLine(prefix); err != nil {
		return nil, err
	}

	if process.Exe, err = readLink(prefix, "exe"); err != nil {
		process.Exe = ""
	}

	if process.Cwd, err = readLink(prefix, "cwd"); err != nil {
		return nil, err
	}

	if process.Root, err = readLink(prefix, "root"); err != nil {
		return nil, err
	}

	if !lazy {
		// preload all of the subdirs
		process.Stat()
		process.Limits()
		process.Loginuid()
		process.Sessionid()
	}

	return process, nil
}

const NO_VALUE = 4294967295

func clearEmpty(strings []string) []string {
	var filtered []string
	for _, v := range strings {
		if len(v) != 0 {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

//
// read integer from file
//
func readFileInteger(prefix string, file string) (*int, error) {
	filename := path.Join(prefix, file)
	str, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if val, err := strconv.Atoi(string(str)); err != nil {
		return nil, err
	} else {
		return &val, nil
	}
}

//

//
// read link
//
func readLink(prefix string, file string) (string, error) {
	filename := path.Join(prefix, file)
	str, err := os.Readlink(filename)
	if err != nil {
		return "", err
	}
	return string(str), nil
}

//
// Read a /proc/<pid>/cmdline file and break up into array
// of argv
//
func readCmdLine(prefix string) ([]string, error) {
	filename := path.Join(prefix, "cmdline")
	str, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return clearEmpty(strings.Split(string(str), "\x00")), nil
}

//
// Parser for /proc/<pid>/stat
//
func (p *Process) Stat() (*stat.Stat, error) {
	var err error
	if p.stat == nil {
		if p.stat, err = stat.New(path.Join(p.prefix, "stat")); err != nil {
			return nil, err
		}
	}

	return p.stat, nil
}

//
// Parser for /proc/<pid>/limits
//
func (p *Process) Limits() (*limits.Limits, error) {
	var err error
	if p.limits == nil {
		if p.limits, err = limits.New(path.Join(p.prefix, "limits")); err != nil {
			return nil, err
		}
	}

	return p.limits, nil
}

//
// Parses contents of /proc/pid/loginuid (if present)
//
func (p *Process) Loginuid() int {
	var err error
	if p.loginuid == nil {
		p.loginuid, err = readFileInteger(p.prefix, "loginuid")
		if err != nil {
			log.Println("Warning: unable to read loginuid so returning nil:", err)
		}

	}

	if p.loginuid != nil {
		return *p.loginuid
	}
	return NO_VALUE

}

//
// Parses contents of /proc/pid/sessionid (if present)
//
func (p *Process) Sessionid() int {
	var err error
	if p.sessionid == nil {
		p.sessionid, err = readFileInteger(p.prefix, "sessionid")
		if err != nil {
			log.Println("Warning: unable to read sessionid so returning nil:", err)
		}

	}

	if p.sessionid != nil {
		return *p.sessionid
	}
	return NO_VALUE
}
<commit_msg>add environ variables<commit_after>package procfs

// Copyright Jen Andre (jandre@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/jandre/procfs/limits"
	"github.com/jandre/procfs/stat"
	"github.com/jandre/procfs/statm"
)

//
// Process describes a /proc/<pid> entry
//
type Process struct {
	Pid       int               // Process ID
	Environ   map[string]string // Environment variables
	Cmdline   []string          // Command line of process (argv array)
	Cwd       string            // Process current working directory
	Exe       string            // Symlink to executed command.
	Root      string            // Per-process root (e.g. chroot)
	prefix    string            // directory path, e.g. /proc/<pid>
	stat      *stat.Stat        // Status information from /proc/pid/stat - see Stat()
	statm     *statm.Statm      // Memory Status information from /proc/pid/statm - see Statm()
	limits    *limits.Limits    // Per process rlimit settings from /proc/pid/limits - see Limits()
	loginuid  *int              // Maybe loginuid from /proc/pid/loginuid - see Loginuid()
	sessionid *int              // Maybe sessionid from /proc/pid/sessionid- see Sessionid()

}

//
// Read /proc information for `pid`
//
func NewProcess(pid int, lazy bool) (*Process, error) {
	prefix := path.Join("/proc", strconv.Itoa(pid))
	return NewProcessFromPath(pid, prefix, lazy)
}

//
// Read a process entry from a directory path
//
// if lazy = true, then preload the stat, limits, and other files.
//
func NewProcessFromPath(pid int, prefix string, lazy bool) (*Process, error) {
	var err error

	if _, err = os.Stat(prefix); err != nil {
		// error reading pid
		return nil, err
	}

	process := &Process{prefix: prefix, Pid: pid}
	if process.Cmdline, err = readCmdLine(prefix); err != nil {
		return nil, err
	}

	if process.Exe, err = readLink(prefix, "exe"); err != nil {
		process.Exe = ""
	}

	if process.Cwd, err = readLink(prefix, "cwd"); err != nil {
		return nil, err
	}

	if process.Root, err = readLink(prefix, "root"); err != nil {
		return nil, err
	}

	process.readEnviron()

	if !lazy {
		// preload all of the subdirs
		process.Stat()
		process.Limits()
		process.Loginuid()
		process.Sessionid()
	}

	return process, nil
}

const NO_VALUE = 4294967295

func clearEmpty(strings []string) []string {
	var filtered []string
	for _, v := range strings {
		if len(v) != 0 {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

//
// read integer from file
//
func readFileInteger(prefix string, file string) (*int, error) {
	filename := path.Join(prefix, file)
	str, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if val, err := strconv.Atoi(string(str)); err != nil {
		return nil, err
	} else {
		return &val, nil
	}
}

//

//
// read link
//
func readLink(prefix string, file string) (string, error) {
	filename := path.Join(prefix, file)
	str, err := os.Readlink(filename)
	if err != nil {
		return "", err
	}
	return string(str), nil
}

//
// Read a /proc/<pid>/cmdline file and break up into array
// of argv
//
func readCmdLine(prefix string) ([]string, error) {
	filename := path.Join(prefix, "cmdline")
	str, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return clearEmpty(strings.Split(string(str), "\x00")), nil
}

//
// Parser for /proc/<pid>/stat
//
func (p *Process) Stat() (*stat.Stat, error) {
	var err error
	if p.stat == nil {
		if p.stat, err = stat.New(path.Join(p.prefix, "stat")); err != nil {
			return nil, err
		}
	}

	return p.stat, nil
}

//
// Parser for /proc/<pid>/limits
//
func (p *Process) Limits() (*limits.Limits, error) {
	var err error
	if p.limits == nil {
		if p.limits, err = limits.New(path.Join(p.prefix, "limits")); err != nil {
			return nil, err
		}
	}

	return p.limits, nil
}

//
// Parses contents of /proc/pid/loginuid (if present)
//
func (p *Process) Loginuid() int {
	var err error
	if p.loginuid == nil {
		p.loginuid, err = readFileInteger(p.prefix, "loginuid")
		if err != nil {
			log.Println("Warning: unable to read loginuid so returning nil:", err)
		}

	}

	if p.loginuid != nil {
		return *p.loginuid
	}
	return NO_VALUE

}

//
// Parses contents of /proc/pid/sessionid (if present)
//
func (p *Process) Sessionid() int {
	var err error
	if p.sessionid == nil {
		p.sessionid, err = readFileInteger(p.prefix, "sessionid")
		if err != nil {
			log.Println("Warning: unable to read sessionid so returning nil:", err)
		}

	}

	if p.sessionid != nil {
		return *p.sessionid
	}
	return NO_VALUE
}

func (p *Process) readEnviron() {
	p.Environ = make(map[string]string, 0)
	bytes, err := ioutil.ReadFile(path.Join(p.prefix, "environ"))
	if err != nil {
		return
	}
	for _, item := range strings.Split(string(bytes), "\x00") {
		fields := strings.SplitN(item, "=", 2)
		if (len(fields) == 2) {
			p.Environ[fields[0]] = fields[1]
		}
	}
}
<|endoftext|>


<commit_before>// Package testname tries to figure out the current test name from the stack
package testname

import (
	"fmt"
	"strings"

	"github.com/facebookgo/stack"
)

var replacer = strings.NewReplacer(".", "_", "/", "_")

// Get finds the name of test and the package from the stack, if possible, or tries its best otherwise.
func Get(id string) string {
	s := stack.Callers(1)

	for _, f := range s {
		if strings.HasSuffix(f.File, "_test.go") {
			var name string
			if strings.HasPrefix(f.Name, "Test") {
				name = f.Name
			} else if strings.HasSuffix(f.Name, "SetUpSuite") {
				name = "SetUpSuite"
			} else if strings.HasSuffix(f.Name, "SetUpTest") {
				name = "SetUpTest"
			}
			if name != "" {
				file := replacer.Replace(f.File)
				return fmt.Sprintf("%s_%s_", file, name)
			}
		}
	}

	// find the first caller outside ourselves
	outside := s[0].File
	for _, f := range s {
		if f.File != s[0].File {
			outside = f.Name
			break
		}
	}

	return fmt.Sprintf("TestNameNotFound_%s_%s_", id, outside)
}
<commit_msg>replace more unsafe characters<commit_after>// Package testname tries to figure out the current test name from the stack
package testname

import (
	"fmt"
	"strings"

	"github.com/facebookgo/stack"
)

var replacer = strings.NewReplacer(".", "_", "/", "_", ":", "_")

// Get finds the name of test and the package from the stack, if possible, or tries its best otherwise.
func Get(id string) string {
	s := stack.Callers(1)

	for _, f := range s {
		if strings.HasSuffix(f.File, "_test.go") {
			var name string
			if strings.HasPrefix(f.Name, "Test") {
				name = f.Name
			} else if strings.HasSuffix(f.Name, "SetUpSuite") {
				name = "SetUpSuite"
			} else if strings.HasSuffix(f.Name, "SetUpTest") {
				name = "SetUpTest"
			}
			if name != "" {
				file := replacer.Replace(f.File)
				return fmt.Sprintf("%s_%s_", file, name)
			}
		}
	}

	// find the first caller outside ourselves
	outside := s[0].File
	for _, f := range s {
		if f.File != s[0].File {
			outside = f.Name
			break
		}
	}

	return fmt.Sprintf("TestNameNotFound_%s_%s_", id, outside)
}
<|endoftext|>


<commit_before>// Package profile provides a simple way to manage runtime/pprof
// profiling of your Go application.
package profile

import (
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sync/atomic"
)

const (
	cpuMode = iota
	memMode
	mutexMode
	blockMode
	traceMode
	threadCreateMode
	goroutineMode
)

// Profile represents an active profiling session.
type Profile struct {
	// quiet suppresses informational messages during profiling.
	quiet bool

	// noShutdownHook controls whether the profiling package should
	// hook SIGINT to write profiles cleanly.
	noShutdownHook bool

	// mode holds the type of profiling that will be made
	mode int

	// path holds the base path where various profiling files are  written.
	// If blank, the base path will be generated by ioutil.TempDir.
	path string

	// memProfileRate holds the rate for the memory profile.
	memProfileRate int

	// closer holds a cleanup function that run after each profile
	closer func()

	// stopped records if a call to profile.Stop has been made
	stopped uint32
}

// NoShutdownHook controls whether the profiling package should
// hook SIGINT to write profiles cleanly.
// Programs with more sophisticated signal handling should set
// this to true and ensure the Stop() function returned from Start()
// is called during shutdown.
func NoShutdownHook(p *Profile) { p.noShutdownHook = true }

// Quiet suppresses informational messages during profiling.
func Quiet(p *Profile) { p.quiet = true }

// CPUProfile enables cpu profiling.
// It disables any previous profiling settings.
func CPUProfile(p *Profile) { p.mode = cpuMode }

// DefaultMemProfileRate is the default memory profiling rate.
// See also http://golang.org/pkg/runtime/#pkg-variables
const DefaultMemProfileRate = 4096

// MemProfile enables memory profiling.
// It disables any previous profiling settings.
func MemProfile(p *Profile) {
	p.memProfileRate = DefaultMemProfileRate
	p.mode = memMode
}

// MemProfileRate enables memory profiling at the preferred rate.
// It disables any previous profiling settings.
func MemProfileRate(rate int) func(*Profile) {
	return func(p *Profile) {
		p.memProfileRate = rate
		p.mode = memMode
	}
}

// MutexProfile enables mutex profiling.
// It disables any previous profiling settings.
func MutexProfile(p *Profile) { p.mode = mutexMode }

// BlockProfile enables block (contention) profiling.
// It disables any previous profiling settings.
func BlockProfile(p *Profile) { p.mode = blockMode }

// Trace profile enables execution tracing.
// It disables any previous profiling settings.
func TraceProfile(p *Profile) { p.mode = traceMode }

// ThreadcreationProfile enables thread creation profiling..
// It disables any previous profiling settings.
func ThreadcreationProfile(p *Profile) { p.mode = threadCreateMode }

// GoroutineProfile enables goroutine profiling.
// It disables any previous profiling settings.
func GoroutineProfile(p *Profile) { p.mode = goroutineMode }

// ProfilePath controls the base path where various profiling
// files are written. If blank, the base path will be generated
// by ioutil.TempDir.
func ProfilePath(path string) func(*Profile) {
	return func(p *Profile) {
		p.path = path
	}
}

// Stop stops the profile and flushes any unwritten data.
func (p *Profile) Stop() {
	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
		// someone has already called close
		return
	}
	p.closer()
	atomic.StoreUint32(&started, 0)
}

// started is non zero if a profile is running.
var started uint32

// Start starts a new profiling session.
// The caller should call the Stop method on the value returned
// to cleanly stop profiling.
func Start(options ...func(*Profile)) interface {
	Stop()
} {
	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
		log.Fatal("profile: Start() already called")
	}

	var prof Profile
	for _, option := range options {
		option(&prof)
	}

	path, err := func() (string, error) {
		if p := prof.path; p != "" {
			return p, os.MkdirAll(p, 0777)
		}
		return ioutil.TempDir("", "profile")
	}()

	if err != nil {
		log.Fatalf("profile: could not create initial output directory: %v", err)
	}

	logf := func(format string, args ...interface{}) {
		if !prof.quiet {
			log.Printf(format, args...)
		}
	}

	switch prof.mode {
	case cpuMode:
		fn := filepath.Join(path, "cpu.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
		}
		logf("profile: cpu profiling enabled, %s", fn)
		pprof.StartCPUProfile(f)
		prof.closer = func() {
			pprof.StopCPUProfile()
			f.Close()
			logf("profile: cpu profiling disabled, %s", fn)
		}

	case memMode:
		fn := filepath.Join(path, "mem.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
		}
		old := runtime.MemProfileRate
		runtime.MemProfileRate = prof.memProfileRate
		logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
		prof.closer = func() {
			pprof.Lookup("heap").WriteTo(f, 0)
			f.Close()
			runtime.MemProfileRate = old
			logf("profile: memory profiling disabled, %s", fn)
		}

	case mutexMode:
		fn := filepath.Join(path, "mutex.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create mutex profile %q: %v", fn, err)
		}
		runtime.SetMutexProfileFraction(1)
		logf("profile: mutex profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("mutex"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			runtime.SetMutexProfileFraction(0)
			logf("profile: mutex profiling disabled, %s", fn)
		}

	case blockMode:
		fn := filepath.Join(path, "block.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create block profile %q: %v", fn, err)
		}
		runtime.SetBlockProfileRate(1)
		logf("profile: block profiling enabled, %s", fn)
		prof.closer = func() {
			pprof.Lookup("block").WriteTo(f, 0)
			f.Close()
			runtime.SetBlockProfileRate(0)
			logf("profile: block profiling disabled, %s", fn)
		}

	case threadCreateMode:
		fn := filepath.Join(path, "threadcreation.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create thread creation profile %q: %v", fn, err)
		}
		logf("profile: thread creation profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("threadcreate"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			logf("profile: thread creation profiling disabled, %s", fn)
		}

	case traceMode:
		fn := filepath.Join(path, "trace.out")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create trace output file %q: %v", fn, err)
		}
		if err := trace.Start(f); err != nil {
			log.Fatalf("profile: could not start trace: %v", err)
		}
		logf("profile: trace enabled, %s", fn)
		prof.closer = func() {
			trace.Stop()
			logf("profile: trace disabled, %s", fn)
		}

	case goroutineMode:
		fn := filepath.Join(path, "goroutine.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create goroutine profile %q: %v", fn, err)
		}
		logf("profile: goroutine profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("goroutine"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			logf("profile: goroutine profiling disabled, %s", fn)
		}
	}

	if !prof.noShutdownHook {
		go func() {
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			<-c

			log.Println("profile: caught interrupt, stopping profiles")
			prof.Stop()

			os.Exit(0)
		}()
	}

	return &prof
}
<commit_msg>Add MemProfileType to allow overriding type of memory profile<commit_after>// Package profile provides a simple way to manage runtime/pprof
// profiling of your Go application.
package profile

import (
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sync/atomic"
)

const (
	cpuMode = iota
	memMode
	mutexMode
	blockMode
	traceMode
	threadCreateMode
	goroutineMode
)

// Profile represents an active profiling session.
type Profile struct {
	// quiet suppresses informational messages during profiling.
	quiet bool

	// noShutdownHook controls whether the profiling package should
	// hook SIGINT to write profiles cleanly.
	noShutdownHook bool

	// mode holds the type of profiling that will be made
	mode int

	// path holds the base path where various profiling files are  written.
	// If blank, the base path will be generated by ioutil.TempDir.
	path string

	// memProfileRate holds the rate for the memory profile.
	memProfileRate int

	// memProfileType holds the profile type for memory
	// profiles. Allowed values are `heap` and `allocs`.
	memProfileType string

	// closer holds a cleanup function that run after each profile
	closer func()

	// stopped records if a call to profile.Stop has been made
	stopped uint32
}

// NoShutdownHook controls whether the profiling package should
// hook SIGINT to write profiles cleanly.
// Programs with more sophisticated signal handling should set
// this to true and ensure the Stop() function returned from Start()
// is called during shutdown.
func NoShutdownHook(p *Profile) { p.noShutdownHook = true }

// Quiet suppresses informational messages during profiling.
func Quiet(p *Profile) { p.quiet = true }

// CPUProfile enables cpu profiling.
// It disables any previous profiling settings.
func CPUProfile(p *Profile) { p.mode = cpuMode }

// DefaultMemProfileRate is the default memory profiling rate.
// See also http://golang.org/pkg/runtime/#pkg-variables
const DefaultMemProfileRate = 4096

// MemProfile enables memory profiling.
// It disables any previous profiling settings.
func MemProfile(p *Profile) {
	p.memProfileRate = DefaultMemProfileRate
	p.mode = memMode
}

// MemProfileRate enables memory profiling at the preferred rate.
// It disables any previous profiling settings.
func MemProfileRate(rate int) func(*Profile) {
	return func(p *Profile) {
		p.memProfileRate = rate
		p.mode = memMode
	}
}

// MemProfileType changes which type of memory profiling to
// enable. The only two memory types supported are alloc and heap. It
// disables any previous profiling settings.
func MemProfileType(memProfileType string) func(*Profile) {
	if memProfileType != "heap" && memProfileType != "allocs" {
		panic("Unexpected memory profile type, expected heap or alloc")
	}
	return func(p *Profile) {
		p.memProfileType = memProfileType
		p.mode = memMode
	}
}

// MutexProfile enables mutex profiling.
// It disables any previous profiling settings.
func MutexProfile(p *Profile) { p.mode = mutexMode }

// BlockProfile enables block (contention) profiling.
// It disables any previous profiling settings.
func BlockProfile(p *Profile) { p.mode = blockMode }

// Trace profile enables execution tracing.
// It disables any previous profiling settings.
func TraceProfile(p *Profile) { p.mode = traceMode }

// ThreadcreationProfile enables thread creation profiling..
// It disables any previous profiling settings.
func ThreadcreationProfile(p *Profile) { p.mode = threadCreateMode }

// GoroutineProfile enables goroutine profiling.
// It disables any previous profiling settings.
func GoroutineProfile(p *Profile) { p.mode = goroutineMode }

// ProfilePath controls the base path where various profiling
// files are written. If blank, the base path will be generated
// by ioutil.TempDir.
func ProfilePath(path string) func(*Profile) {
	return func(p *Profile) {
		p.path = path
	}
}

// Stop stops the profile and flushes any unwritten data.
func (p *Profile) Stop() {
	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
		// someone has already called close
		return
	}
	p.closer()
	atomic.StoreUint32(&started, 0)
}

// started is non zero if a profile is running.
var started uint32

// Start starts a new profiling session.
// The caller should call the Stop method on the value returned
// to cleanly stop profiling.
func Start(options ...func(*Profile)) interface {
	Stop()
} {
	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
		log.Fatal("profile: Start() already called")
	}

	var prof Profile
	for _, option := range options {
		option(&prof)
	}

	path, err := func() (string, error) {
		if p := prof.path; p != "" {
			return p, os.MkdirAll(p, 0777)
		}
		return ioutil.TempDir("", "profile")
	}()

	if err != nil {
		log.Fatalf("profile: could not create initial output directory: %v", err)
	}

	logf := func(format string, args ...interface{}) {
		if !prof.quiet {
			log.Printf(format, args...)
		}
	}

	if prof.memProfileType == "" {
		prof.memProfileType = "heap"
	}

	switch prof.mode {
	case cpuMode:
		fn := filepath.Join(path, "cpu.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
		}
		logf("profile: cpu profiling enabled, %s", fn)
		pprof.StartCPUProfile(f)
		prof.closer = func() {
			pprof.StopCPUProfile()
			f.Close()
			logf("profile: cpu profiling disabled, %s", fn)
		}

	case memMode:
		fn := filepath.Join(path, "mem.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
		}
		old := runtime.MemProfileRate
		runtime.MemProfileRate = prof.memProfileRate
		logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
		prof.closer = func() {
			pprof.Lookup(prof.memProfileType).WriteTo(f, 0)
			f.Close()
			runtime.MemProfileRate = old
			logf("profile: memory profiling disabled, %s", fn)
		}

	case mutexMode:
		fn := filepath.Join(path, "mutex.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create mutex profile %q: %v", fn, err)
		}
		runtime.SetMutexProfileFraction(1)
		logf("profile: mutex profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("mutex"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			runtime.SetMutexProfileFraction(0)
			logf("profile: mutex profiling disabled, %s", fn)
		}

	case blockMode:
		fn := filepath.Join(path, "block.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create block profile %q: %v", fn, err)
		}
		runtime.SetBlockProfileRate(1)
		logf("profile: block profiling enabled, %s", fn)
		prof.closer = func() {
			pprof.Lookup("block").WriteTo(f, 0)
			f.Close()
			runtime.SetBlockProfileRate(0)
			logf("profile: block profiling disabled, %s", fn)
		}

	case threadCreateMode:
		fn := filepath.Join(path, "threadcreation.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create thread creation profile %q: %v", fn, err)
		}
		logf("profile: thread creation profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("threadcreate"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			logf("profile: thread creation profiling disabled, %s", fn)
		}

	case traceMode:
		fn := filepath.Join(path, "trace.out")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create trace output file %q: %v", fn, err)
		}
		if err := trace.Start(f); err != nil {
			log.Fatalf("profile: could not start trace: %v", err)
		}
		logf("profile: trace enabled, %s", fn)
		prof.closer = func() {
			trace.Stop()
			logf("profile: trace disabled, %s", fn)
		}

	case goroutineMode:
		fn := filepath.Join(path, "goroutine.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create goroutine profile %q: %v", fn, err)
		}
		logf("profile: goroutine profiling enabled, %s", fn)
		prof.closer = func() {
			if mp := pprof.Lookup("goroutine"); mp != nil {
				mp.WriteTo(f, 0)
			}
			f.Close()
			logf("profile: goroutine profiling disabled, %s", fn)
		}
	}

	if !prof.noShutdownHook {
		go func() {
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			<-c

			log.Println("profile: caught interrupt, stopping profiles")
			prof.Stop()

			os.Exit(0)
		}()
	}

	return &prof
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vppcalls

import (
	"bytes"
	"fmt"
	"net"

	l3binapi "github.com/ligato/vpp-agent/plugins/vpp/binapi/ip"
	"github.com/ligato/vpp-agent/plugins/vpp/model/l3"
)

// RouteDetails is object returned as a VPP dump. It contains static route data in proto format, and VPP-specific
// metadata
type RouteDetails struct {
	Route *l3.StaticRoutes_Route
	Meta  *RouteMeta
}

// RouteMeta holds fields returned from the VPP as details which are not in the model
type RouteMeta struct {
	TableName         string
	OutgoingIfIdx     uint32
	IsIPv6            bool
	Afi               uint8
	IsLocal           bool
	IsUDPEncap        bool
	IsUnreach         bool
	IsProhibit        bool
	IsResolveHost     bool
	IsResolveAttached bool
	IsDvr             bool
	IsSourceLookup    bool
	NextHopID         uint32
	RpfID             uint32
	LabelStack        []l3binapi.FibMplsLabel
}

// DumpStaticRoutes implements route handler.
func (h *RouteHandler) DumpStaticRoutes() ([]*RouteDetails, error) {
	var routes []*RouteDetails
	// Dump IPv4 l3 FIB.
	reqCtx := h.callsChannel.SendMultiRequest(&l3binapi.IPFibDump{})
	for {
		fibDetails := &l3binapi.IPFibDetails{}
		stop, err := reqCtx.ReceiveReply(fibDetails)
		if stop {
			break
		}
		if err != nil {
			return nil, err
		}
		ipv4Route, err := h.dumpStaticRouteIPv4Details(fibDetails)
		if err != nil {
			return nil, err
		}
		routes = append(routes, ipv4Route...)
	}

	// Dump IPv6 l3 FIB.
	reqCtx = h.callsChannel.SendMultiRequest(&l3binapi.IP6FibDump{})
	for {
		fibDetails := &l3binapi.IP6FibDetails{}
		stop, err := reqCtx.ReceiveReply(fibDetails)
		if stop {
			break
		}
		if err != nil {
			return nil, err
		}
		ipv6Route, err := h.dumpStaticRouteIPv6Details(fibDetails)
		if err != nil {
			return nil, err
		}
		routes = append(routes, ipv6Route...)
	}

	return routes, nil
}

func (h *RouteHandler) dumpStaticRouteIPv4Details(fibDetails *l3binapi.IPFibDetails) ([]*RouteDetails, error) {
	return h.dumpStaticRouteIPDetails(fibDetails.TableID, fibDetails.TableName, fibDetails.Address, fibDetails.AddressLength, fibDetails.Path, false)
}

func (h *RouteHandler) dumpStaticRouteIPv6Details(fibDetails *l3binapi.IP6FibDetails) ([]*RouteDetails, error) {
	return h.dumpStaticRouteIPDetails(fibDetails.TableID, fibDetails.TableName, fibDetails.Address, fibDetails.AddressLength, fibDetails.Path, true)
}

// dumpStaticRouteIPDetails processes static route details and returns a route objects. Number of routes returned
// depends on size of path list.
func (h *RouteHandler) dumpStaticRouteIPDetails(tableID uint32, tableName []byte, address []byte, prefixLen uint8, paths []l3binapi.FibPath, ipv6 bool) ([]*RouteDetails, error) {
	// Common fields for every route path (destination IP, VRF)
	var dstIP string
	if ipv6 {
		dstIP = fmt.Sprintf("%s/%d", net.IP(address).To16().String(), uint32(prefixLen))
	} else {
		dstIP = fmt.Sprintf("%s/%d", net.IP(address[:4]).To4().String(), uint32(prefixLen))
	}

	var routeDetails []*RouteDetails

	// Paths
	if len(paths) > 0 {
		for _, path := range paths {
			// Next hop IP address
			var nextHopIP string
			if ipv6 {
				nextHopIP = fmt.Sprintf("%s", net.IP(path.NextHop).To16().String())
			} else {
				nextHopIP = fmt.Sprintf("%s", net.IP(path.NextHop[:4]).To4().String())
			}

			// Route type (if via VRF is used)
			var routeType l3.StaticRoutes_Route_RouteType
			var viaVrfID uint32
			if path.SwIfIndex == NextHopOutgoingIfUnset && path.TableID != tableID {
				// outgoing interface not specified and path table id not equal to route table id = inter-VRF route
				routeType = l3.StaticRoutes_Route_INTER_VRF
				viaVrfID = path.TableID
			} else if uintToBool(path.IsDrop) {
				routeType = l3.StaticRoutes_Route_DROP
			} else {
				routeType = l3.StaticRoutes_Route_INTRA_VRF // default
			}

			// Outgoing interface
			var ifName string
			var ifIdx uint32
			if path.SwIfIndex != ^uint32(0) {
				var exists bool
				ifIdx = path.SwIfIndex
				if ifName, _, exists = h.ifIndexes.LookupName(path.SwIfIndex); !exists {
					h.log.Warnf("Static route dump: interface name for index %d not found", path.SwIfIndex)
				}
			}

			// Route configuration
			route := &l3.StaticRoutes_Route{
				Type:              routeType,
				VrfId:             tableID,
				DstIpAddr:         dstIP,
				NextHopAddr:       nextHopIP,
				OutgoingInterface: ifName,
				Weight:            uint32(path.Weight),
				Preference:        uint32(path.Preference),
				ViaVrfId:          viaVrfID,
			}

			// Route metadata
			meta := &RouteMeta{
				TableName:         string(bytes.SplitN(tableName, []byte{0x00}, 2)[0]),
				OutgoingIfIdx:     ifIdx,
				NextHopID:         path.NextHopID,
				IsIPv6:            ipv6,
				RpfID:             path.RpfID,
				Afi:               path.Afi,
				IsLocal:           uintToBool(path.IsLocal),
				IsUDPEncap:        uintToBool(path.IsUDPEncap),
				IsDvr:             uintToBool(path.IsDvr),
				IsProhibit:        uintToBool(path.IsProhibit),
				IsResolveAttached: uintToBool(path.IsResolveAttached),
				IsResolveHost:     uintToBool(path.IsResolveHost),
				IsSourceLookup:    uintToBool(path.IsSourceLookup),
				IsUnreach:         uintToBool(path.IsUnreach),
				LabelStack:        path.LabelStack,
			}

			routeDetails = append(routeDetails, &RouteDetails{
				Route: route,
				Meta:  meta,
			})
		}
	} else {
		// Return route without path fields, but this is not a valid configuration
		h.log.Warnf("Route with destination IP %s (VRF %d) has no path specified", dstIP, tableID)
		route := &l3.StaticRoutes_Route{
			Type:      l3.StaticRoutes_Route_INTRA_VRF, // default
			VrfId:     tableID,
			DstIpAddr: dstIP,
		}
		meta := &RouteMeta{
			TableName: string(bytes.SplitN(tableName, []byte{0x00}, 2)[0]),
		}
		routeDetails = append(routeDetails, &RouteDetails{
			Route: route,
			Meta:  meta,
		})
	}

	return routeDetails, nil
}
<commit_msg>fix vpp route type dump<commit_after>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vppcalls

import (
	"bytes"
	"fmt"
	"net"

	l3binapi "github.com/ligato/vpp-agent/plugins/vpp/binapi/ip"
	"github.com/ligato/vpp-agent/plugins/vpp/model/l3"
)

// RouteDetails is object returned as a VPP dump. It contains static route data in proto format, and VPP-specific
// metadata
type RouteDetails struct {
	Route *l3.StaticRoutes_Route
	Meta  *RouteMeta
}

// RouteMeta holds fields returned from the VPP as details which are not in the model
type RouteMeta struct {
	TableName         string
	OutgoingIfIdx     uint32
	IsIPv6            bool
	Afi               uint8
	IsLocal           bool
	IsUDPEncap        bool
	IsUnreach         bool
	IsProhibit        bool
	IsResolveHost     bool
	IsResolveAttached bool
	IsDvr             bool
	IsSourceLookup    bool
	NextHopID         uint32
	RpfID             uint32
	LabelStack        []l3binapi.FibMplsLabel
}

// DumpStaticRoutes implements route handler.
func (h *RouteHandler) DumpStaticRoutes() ([]*RouteDetails, error) {
	var routes []*RouteDetails
	// Dump IPv4 l3 FIB.
	reqCtx := h.callsChannel.SendMultiRequest(&l3binapi.IPFibDump{})
	for {
		fibDetails := &l3binapi.IPFibDetails{}
		stop, err := reqCtx.ReceiveReply(fibDetails)
		if stop {
			break
		}
		if err != nil {
			return nil, err
		}
		ipv4Route, err := h.dumpStaticRouteIPv4Details(fibDetails)
		if err != nil {
			return nil, err
		}
		routes = append(routes, ipv4Route...)
	}

	// Dump IPv6 l3 FIB.
	reqCtx = h.callsChannel.SendMultiRequest(&l3binapi.IP6FibDump{})
	for {
		fibDetails := &l3binapi.IP6FibDetails{}
		stop, err := reqCtx.ReceiveReply(fibDetails)
		if stop {
			break
		}
		if err != nil {
			return nil, err
		}
		ipv6Route, err := h.dumpStaticRouteIPv6Details(fibDetails)
		if err != nil {
			return nil, err
		}
		routes = append(routes, ipv6Route...)
	}

	return routes, nil
}

func (h *RouteHandler) dumpStaticRouteIPv4Details(fibDetails *l3binapi.IPFibDetails) ([]*RouteDetails, error) {
	return h.dumpStaticRouteIPDetails(fibDetails.TableID, fibDetails.TableName, fibDetails.Address, fibDetails.AddressLength, fibDetails.Path, false)
}

func (h *RouteHandler) dumpStaticRouteIPv6Details(fibDetails *l3binapi.IP6FibDetails) ([]*RouteDetails, error) {
	return h.dumpStaticRouteIPDetails(fibDetails.TableID, fibDetails.TableName, fibDetails.Address, fibDetails.AddressLength, fibDetails.Path, true)
}

// dumpStaticRouteIPDetails processes static route details and returns a route objects. Number of routes returned
// depends on size of path list.
func (h *RouteHandler) dumpStaticRouteIPDetails(tableID uint32, tableName []byte, address []byte, prefixLen uint8, paths []l3binapi.FibPath, ipv6 bool) ([]*RouteDetails, error) {
	// Common fields for every route path (destination IP, VRF)
	var dstIP string
	if ipv6 {
		dstIP = fmt.Sprintf("%s/%d", net.IP(address).To16().String(), uint32(prefixLen))
	} else {
		dstIP = fmt.Sprintf("%s/%d", net.IP(address[:4]).To4().String(), uint32(prefixLen))
	}

	var routeDetails []*RouteDetails

	// Paths
	if len(paths) > 0 {
		for _, path := range paths {
			// Next hop IP address
			var nextHopIP string
			if ipv6 {
				nextHopIP = fmt.Sprintf("%s", net.IP(path.NextHop).To16().String())
			} else {
				nextHopIP = fmt.Sprintf("%s", net.IP(path.NextHop[:4]).To4().String())
			}

			// Route type (if via VRF is used)
			var routeType l3.StaticRoutes_Route_RouteType
			var viaVrfID uint32
			if uintToBool(path.IsDrop) {
				routeType = l3.StaticRoutes_Route_DROP
			} else if path.SwIfIndex == NextHopOutgoingIfUnset && path.TableID != tableID {
				// outgoing interface not specified and path table is not equal to route table id = inter-VRF route
				routeType = l3.StaticRoutes_Route_INTER_VRF
				viaVrfID = path.TableID
			} else {
				routeType = l3.StaticRoutes_Route_INTRA_VRF // default
			}

			// Outgoing interface
			var ifName string
			var ifIdx uint32
			if path.SwIfIndex == NextHopOutgoingIfUnset {
				ifIdx = NextHopOutgoingIfUnset
			} else {
				var exists bool
				ifIdx = path.SwIfIndex
				if ifName, _, exists = h.ifIndexes.LookupName(path.SwIfIndex); !exists {
					h.log.Warnf("Static route dump: interface name for index %d not found", path.SwIfIndex)
				}
			}

			// Route configuration
			route := &l3.StaticRoutes_Route{
				Type:              routeType,
				VrfId:             tableID,
				DstIpAddr:         dstIP,
				NextHopAddr:       nextHopIP,
				OutgoingInterface: ifName,
				Weight:            uint32(path.Weight),
				Preference:        uint32(path.Preference),
				ViaVrfId:          viaVrfID,
			}

			// Route metadata
			meta := &RouteMeta{
				TableName:         string(bytes.SplitN(tableName, []byte{0x00}, 2)[0]),
				OutgoingIfIdx:     ifIdx,
				NextHopID:         path.NextHopID,
				IsIPv6:            ipv6,
				RpfID:             path.RpfID,
				Afi:               path.Afi,
				IsLocal:           uintToBool(path.IsLocal),
				IsUDPEncap:        uintToBool(path.IsUDPEncap),
				IsDvr:             uintToBool(path.IsDvr),
				IsProhibit:        uintToBool(path.IsProhibit),
				IsResolveAttached: uintToBool(path.IsResolveAttached),
				IsResolveHost:     uintToBool(path.IsResolveHost),
				IsSourceLookup:    uintToBool(path.IsSourceLookup),
				IsUnreach:         uintToBool(path.IsUnreach),
				LabelStack:        path.LabelStack,
			}

			routeDetails = append(routeDetails, &RouteDetails{
				Route: route,
				Meta:  meta,
			})
		}
	} else {
		// Return route without path fields, but this is not a valid configuration
		h.log.Warnf("Route with destination IP %s (VRF %d) has no path specified", dstIP, tableID)
		route := &l3.StaticRoutes_Route{
			Type:      l3.StaticRoutes_Route_INTRA_VRF, // default
			VrfId:     tableID,
			DstIpAddr: dstIP,
		}
		meta := &RouteMeta{
			TableName: string(bytes.SplitN(tableName, []byte{0x00}, 2)[0]),
		}
		routeDetails = append(routeDetails, &RouteDetails{
			Route: route,
			Meta:  meta,
		})
	}

	return routeDetails, nil
}
<|endoftext|>


<commit_before>package clui

import (
	xs "github.com/huandu/xstrings"
	term "github.com/nsf/termbox-go"
	"regexp"
	"strings"
)

// Ellipsize truncates text to maxWidth by replacing a
// substring in the middle with ellipsis and keeping
// the beginning and ending of the string untouched.
// If maxWidth is less than 5 then no ellipsis is
// added, the text is just truncated from the right.
func Ellipsize(str string, maxWidth int) string {
	ln := xs.Len(str)
	if ln <= maxWidth {
		return str
	}

	if maxWidth < 5 {
		return xs.Slice(str, 0, maxWidth)
	}

	left := int((maxWidth - 3) / 2)
	right := maxWidth - left - 3
	return xs.Slice(str, 0, left) + "..." + xs.Slice(str, ln-right, -1)
}

// CutText makes a text no longer than maxWidth
func CutText(str string, maxWidth int) string {
	ln := xs.Len(str)
	if ln <= maxWidth {
		return str
	}

	return xs.Slice(str, 0, maxWidth)
}

// AlignText calculates the initial position of the text
// output depending on str length and available width.
// The str is truncated in case of its lenght greater than
// width. Function returns shift that should be added to
// original label position before output instead of padding
// the string with spaces. The reason is to make possible
// to draw a label aligned but with transparent beginning
// and ending. If you do not need transparency you can
// add spaces manually using the returned shift value
func AlignText(str string, width int, align Align) (shift int, out string) {
	length := xs.Len(str)

	if length >= width {
		return 0, CutText(str, width)
	}

	if align == AlignRight {
		return width - length, str
	} else if align == AlignCenter {
		return (width - length) / 2, str
	}

	return 0, str
}

// AlignColorizedText does the same as AlignText does but
// it preserves the color of the letters by adding correct
// color tags to the line beginning.
// Note: function is ineffective and a bit slow - do not use
// it everywhere
func AlignColorizedText(str string, width int, align Align) (int, string) {
	rawText := UnColorizeText(str)
	length := xs.Len(rawText)

	if length <= width {
		shift, _ := AlignText(rawText, width, align)
		return shift, str
	}

	skip := 0
	if align == AlignRight {
		skip = length - width
	} else if align == AlignCenter {
		skip = (length - width) / 2
	}

	fgChanged, bgChanged := false, false
	curr := 0
	parser := NewColorParser(str, term.ColorBlack, term.ColorBlack)
	out := ""
	for curr < skip+width {
		elem := parser.NextElement()

		if elem.Type == ElemEndOfText {
			break
		}

		if elem.Type == ElemPrintable {
			curr++
			if curr == skip+1 {
				if fgChanged {
					out += "<t:" + ColorToString(elem.Fg) + ">"
				}
				if bgChanged {
					out += "<b:" + ColorToString(elem.Bg) + ">"
				}
				out += string(elem.Ch)
			} else if curr > skip+1 {
				out += string(elem.Ch)
			}
		} else if elem.Type == ElemTextColor {
			fgChanged = true
			if curr > skip+1 {
				out += "<t:" + ColorToString(elem.Fg) + ">"
			}
		} else if elem.Type == ElemBackColor {
			bgChanged = true
			if curr > skip+1 {
				out += "<b:" + ColorToString(elem.Bg) + ">"
			}
		}
	}

	return 0, out
}

// SliceColorized returns a slice of text with correct color
// tags. start and end are real printable rune indices
func SliceColorized(str string, start, end int) string {
	if str == "" {
		return str
	}
	if start < 0 {
		start = 0
	}

	fgChanged, bgChanged := false, false
	curr := 0
	parser := NewColorParser(str, term.ColorBlack, term.ColorBlack)
	var out string
	for {
		if end != -1 && curr >= end {
			break
		}
		elem := parser.NextElement()
		if elem.Type == ElemEndOfText {
			break
		}

		switch elem.Type {
		case ElemTextColor:
			fgChanged = true
			if out != "" {
				out += "<t:" + ColorToString(elem.Fg) + ">"
			}
		case ElemBackColor:
			bgChanged = true
			if out != "" {
				out += "<b:" + ColorToString(elem.Bg) + ">"
			}
		case ElemPrintable:
			if curr == start {
				if fgChanged {
					out += "<t:" + ColorToString(elem.Fg) + ">"
				}
				if bgChanged {
					out += "<b:" + ColorToString(elem.Bg) + ">"
				}
			}
			if curr >= start {
				out += string(elem.Ch)
			}
			curr++
		}
	}

	return out
}

// UnColorizeText removes all color-related tags from the
// string. Tags to remove: <(f|t|b|c):.*>
func UnColorizeText(str string) string {
	rx := regexp.MustCompile("<(f|c|t|b):[^>]*>")

	return rx.ReplaceAllString(str, "")
}

// StringToColor returns attribute by its string description.
// Description is the list of attributes separated with
// spaces, plus or pipe symbols. You can use 8 base colors:
// black, white, red, green, blue, magenta, yellow, cyan
// and a few modifiers:
// bold or bright, underline or underlined, reverse
// Note: some terminals do not support all modifiers, e.g,
// Windows one understands only bold/bright - it makes the
// color brighter with the modidierA
// Examples: "red bold", "green+underline+bold"
func StringToColor(str string) term.Attribute {
	var parts []string
	if strings.ContainsRune(str, '+') {
		parts = strings.Split(str, "+")
	} else if strings.ContainsRune(str, '|') {
		parts = strings.Split(str, "|")
	} else if strings.ContainsRune(str, ' ') {
		parts = strings.Split(str, " ")
	} else {
		parts = append(parts, str)
	}

	var cmap = map[string]term.Attribute{
		"default":    term.ColorDefault,
		"black":      term.ColorBlack,
		"red":        term.ColorRed,
		"green":      term.ColorGreen,
		"yellow":     term.ColorYellow,
		"blue":       term.ColorBlue,
		"magenta":    term.ColorMagenta,
		"cyan":       term.ColorCyan,
		"white":      term.ColorWhite,
		"bold":       term.AttrBold,
		"bright":     term.AttrBold, // windows make color brighter when it is bold
		"underline":  term.AttrUnderline,
		"underlined": term.AttrUnderline,
		"reverse":    term.AttrReverse,
	}

	var clr term.Attribute
	for _, item := range parts {
		item = strings.Trim(item, " ")
		item = strings.ToLower(item)

		c, ok := cmap[item]
		if ok {
			clr |= c
		}
	}

	return clr
}

// ColorToString returns string representation of the attribute
func ColorToString(attr term.Attribute) string {
	var out string
	colors := []string{
		"", "black", "red", "green", "yellow",
		"blue", "magenta", "cyan", "white"}

	rawClr := attr & 15
	if rawClr < 8 {
		out += colors[rawClr] + " "
	}

	if attr&term.AttrBold != 0 {
		out += "bold "
	}
	if attr&term.AttrUnderline != 0 {
		out += "underline "
	}
	if attr&term.AttrReverse != 0 {
		out += "reverse "
	}

	return strings.TrimSpace(out)
}
<commit_msg>textutil: add color map facilities<commit_after>package clui

import (
	xs "github.com/huandu/xstrings"
	term "github.com/nsf/termbox-go"
	"regexp"
	"strings"
)

var (
	colorMap = map[string]term.Attribute{
		"default":    term.ColorDefault,
		"black":      term.ColorBlack,
		"red":        term.ColorRed,
		"green":      term.ColorGreen,
		"yellow":     term.ColorYellow,
		"blue":       term.ColorBlue,
		"magenta":    term.ColorMagenta,
		"cyan":       term.ColorCyan,
		"white":      term.ColorWhite,
		"bold":       term.AttrBold,
		"bright":     term.AttrBold, // windows make color brighter when it is bold
		"underline":  term.AttrUnderline,
		"underlined": term.AttrUnderline,
		"reverse":    term.AttrReverse,
	}
)

// Ellipsize truncates text to maxWidth by replacing a
// substring in the middle with ellipsis and keeping
// the beginning and ending of the string untouched.
// If maxWidth is less than 5 then no ellipsis is
// added, the text is just truncated from the right.
func Ellipsize(str string, maxWidth int) string {
	ln := xs.Len(str)
	if ln <= maxWidth {
		return str
	}

	if maxWidth < 5 {
		return xs.Slice(str, 0, maxWidth)
	}

	left := int((maxWidth - 3) / 2)
	right := maxWidth - left - 3
	return xs.Slice(str, 0, left) + "..." + xs.Slice(str, ln-right, -1)
}

// CutText makes a text no longer than maxWidth
func CutText(str string, maxWidth int) string {
	ln := xs.Len(str)
	if ln <= maxWidth {
		return str
	}

	return xs.Slice(str, 0, maxWidth)
}

// AlignText calculates the initial position of the text
// output depending on str length and available width.
// The str is truncated in case of its lenght greater than
// width. Function returns shift that should be added to
// original label position before output instead of padding
// the string with spaces. The reason is to make possible
// to draw a label aligned but with transparent beginning
// and ending. If you do not need transparency you can
// add spaces manually using the returned shift value
func AlignText(str string, width int, align Align) (shift int, out string) {
	length := xs.Len(str)

	if length >= width {
		return 0, CutText(str, width)
	}

	if align == AlignRight {
		return width - length, str
	} else if align == AlignCenter {
		return (width - length) / 2, str
	}

	return 0, str
}

// AlignColorizedText does the same as AlignText does but
// it preserves the color of the letters by adding correct
// color tags to the line beginning.
// Note: function is ineffective and a bit slow - do not use
// it everywhere
func AlignColorizedText(str string, width int, align Align) (int, string) {
	rawText := UnColorizeText(str)
	length := xs.Len(rawText)

	if length <= width {
		shift, _ := AlignText(rawText, width, align)
		return shift, str
	}

	skip := 0
	if align == AlignRight {
		skip = length - width
	} else if align == AlignCenter {
		skip = (length - width) / 2
	}

	fgChanged, bgChanged := false, false
	curr := 0
	parser := NewColorParser(str, term.ColorBlack, term.ColorBlack)
	out := ""
	for curr < skip+width {
		elem := parser.NextElement()

		if elem.Type == ElemEndOfText {
			break
		}

		if elem.Type == ElemPrintable {
			curr++
			if curr == skip+1 {
				if fgChanged {
					out += "<t:" + ColorToString(elem.Fg) + ">"
				}
				if bgChanged {
					out += "<b:" + ColorToString(elem.Bg) + ">"
				}
				out += string(elem.Ch)
			} else if curr > skip+1 {
				out += string(elem.Ch)
			}
		} else if elem.Type == ElemTextColor {
			fgChanged = true
			if curr > skip+1 {
				out += "<t:" + ColorToString(elem.Fg) + ">"
			}
		} else if elem.Type == ElemBackColor {
			bgChanged = true
			if curr > skip+1 {
				out += "<b:" + ColorToString(elem.Bg) + ">"
			}
		}
	}

	return 0, out
}

// SliceColorized returns a slice of text with correct color
// tags. start and end are real printable rune indices
func SliceColorized(str string, start, end int) string {
	if str == "" {
		return str
	}
	if start < 0 {
		start = 0
	}

	fgChanged, bgChanged := false, false
	curr := 0
	parser := NewColorParser(str, term.ColorBlack, term.ColorBlack)
	var out string
	for {
		if end != -1 && curr >= end {
			break
		}
		elem := parser.NextElement()
		if elem.Type == ElemEndOfText {
			break
		}

		switch elem.Type {
		case ElemTextColor:
			fgChanged = true
			if out != "" {
				out += "<t:" + ColorToString(elem.Fg) + ">"
			}
		case ElemBackColor:
			bgChanged = true
			if out != "" {
				out += "<b:" + ColorToString(elem.Bg) + ">"
			}
		case ElemPrintable:
			if curr == start {
				if fgChanged {
					out += "<t:" + ColorToString(elem.Fg) + ">"
				}
				if bgChanged {
					out += "<b:" + ColorToString(elem.Bg) + ">"
				}
			}
			if curr >= start {
				out += string(elem.Ch)
			}
			curr++
		}
	}

	return out
}

// UnColorizeText removes all color-related tags from the
// string. Tags to remove: <(f|t|b|c):.*>
func UnColorizeText(str string) string {
	rx := regexp.MustCompile("<(f|c|t|b):[^>]*>")

	return rx.ReplaceAllString(str, "")
}

// StringToColor returns attribute by its string description.
// Description is the list of attributes separated with
// spaces, plus or pipe symbols. You can use 8 base colors:
// black, white, red, green, blue, magenta, yellow, cyan
// and a few modifiers:
// bold or bright, underline or underlined, reverse
// Note: some terminals do not support all modifiers, e.g,
// Windows one understands only bold/bright - it makes the
// color brighter with the modidierA
// Examples: "red bold", "green+underline+bold"
func StringToColor(str string) term.Attribute {
	var parts []string
	if strings.ContainsRune(str, '+') {
		parts = strings.Split(str, "+")
	} else if strings.ContainsRune(str, '|') {
		parts = strings.Split(str, "|")
	} else if strings.ContainsRune(str, ' ') {
		parts = strings.Split(str, " ")
	} else {
		parts = append(parts, str)
	}

	var clr term.Attribute
	for _, item := range parts {
		item = strings.Trim(item, " ")
		item = strings.ToLower(item)

		c, ok := colorMap[item]
		if ok {
			clr |= c
		}
	}

	return clr
}

// GetColorMap returns the color map (id is the color name, value its code)
func GetColorMap() map[string]term.Attribute {
	return colorMap
}

// SetColorMap sets a custom color map (id is the color name, value its code)
func SetColorMap(cmap map[string]term.Attribute) {
	colorMap = cmap
}

// ColorToString returns string representation of the attribute
func ColorToString(attr term.Attribute) string {
	var out string

	rawClr := attr & 15
	if rawClr < 8 {
		for k, v := range colorMap {
			if v == rawClr {
				out += k + " "
				break
			}
		}
	}

	if attr&term.AttrBold != 0 {
		out += "bold "
	}
	if attr&term.AttrUnderline != 0 {
		out += "underline "
	}
	if attr&term.AttrReverse != 0 {
		out += "reverse "
	}

	return strings.TrimSpace(out)
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"bitbucket.org/denkhaus/mirsvc/util"
	"github.com/BurntSushi/toml"
	"github.com/juju/errors"
)

type Command struct {
	Cmd  string   `toml:"cmd"`
	Args []string `toml:"args"`
}

type TemplateSet struct {
	Name            string  `toml:"name"`
	TemplatePath    string  `toml:"template"`
	DestinationPath string  `toml:"dest"`
	Check           Command `toml:"check"`
	Run             Command `toml:"run"`
}

type Config struct {
	Repeat int           `toml:"repeat"`
	Host   string        `toml:"host"`
	Sets   []TemplateSet `toml:"set"`
}

//////////////////////////////////////////////////////////////////////////////
func (cnf *Config) Print() {
	printInfo("check every %d seconds", cnf.Repeat)
	printInfo("metadata host: %s", cnf.Host)
	printInfo(" %d template sets found", len(cnf.Sets))

	util.Inspect(cnf)
}

//////////////////////////////////////////////////////////////////////////////
func (cnf *Config) Check() error {
	if cnf.Host == "" || cnf.Repeat == 0 {
		return errors.New("invalid runtime options")
	}

	if len(cnf.Sets) == 0 {
		return errors.New("no template sets provided")
	}

	return nil
}

//////////////////////////////////////////////////////////////////////////////
func readConfig(configPath string) (*Config, error) {
	conf := new(Config)
	if _, err := os.Stat(configPath); err != nil {
		return nil, errors.New("no configuration file not found")
	}

	if _, err := toml.DecodeFile(configPath, conf); err != nil {
		return nil, errors.Annotate(err, "create config")
	}

	return conf, nil
}
<commit_msg>autopush@1446211686<commit_after>package main

import (
	"os"

	"github.com/BurntSushi/toml"
	"github.com/juju/errors"
)

type Command struct {
	Cmd  string   `toml:"cmd"`
	Args []string `toml:"args"`
}

type TemplateSet struct {
	Name            string  `toml:"name"`
	TemplatePath    string  `toml:"template"`
	DestinationPath string  `toml:"dest"`
	Check           Command `toml:"check"`
	Run             Command `toml:"run"`
}

type Config struct {
	Repeat int           `toml:"repeat"`
	Host   string        `toml:"host"`
	Sets   []TemplateSet `toml:"set"`
}

//////////////////////////////////////////////////////////////////////////////
func (cnf *Config) Print() {
	printInfo("check every %d seconds", cnf.Repeat)
	printInfo("metadata host: %s", cnf.Host)
	printInfo(" %d template sets found", len(cnf.Sets))
}

//////////////////////////////////////////////////////////////////////////////
func (cnf *Config) Check() error {
	if cnf.Host == "" || cnf.Repeat == 0 {
		return errors.New("invalid runtime options")
	}

	if len(cnf.Sets) == 0 {
		return errors.New("no template sets provided")
	}

	return nil
}

//////////////////////////////////////////////////////////////////////////////
func readConfig(configPath string) (*Config, error) {
	conf := new(Config)
	if _, err := os.Stat(configPath); err != nil {
		return nil, errors.New("no configuration file not found")
	}

	if _, err := toml.DecodeFile(configPath, conf); err != nil {
		return nil, errors.Annotate(err, "create config")
	}

	return conf, nil
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"os"
	"regexp"
	"strings"
//   "fmt"
)

var ptn *regexp.Regexp
var parse_err error
var conf = make(map[string]string)

func init() {
	ptn, parse_err = regexp.Compile(`[#]*[A-Za-z0-9\_\-\.\t ]+[=].*`)
	if parse_err != nil {
		panic(parse_err)
	}
}

func Parse(filename string) (err error) {
	fi, err := os.Stat(filename)
	if err != nil {
		return
	}

	f, err := os.Open(filename)
	if err != nil {
		return
	}

	buff := make([]byte, fi.Size())
	f.Read(buff)
	f.Close()

	str := ptn.FindAllString(string(buff), -1)

	for i := 0; i < len(str); i++ {
		switch {
		case strings.Index(str[i], "#") == 0:
			// 说明是注释行，什么也不做
		case strings.Index(str[i], "=") > 0:
			kvs := strings.Split(str[i], "=")
			key, val := strings.TrimSpace(kvs[0]), strings.Trim(kvs[1], `"`)
			conf[key] = val
		default:
			err = errors.New("parse config files failed! please check this line:" + str[i])
			return
		}
	}
	return
}

//调试用，返回conf对象的指针，一般情况下最好不要使用
func GetAll() *map[string]string {
	return &conf
}

func Get(key string) (value string) {
	value = conf[key]
	return
}
<commit_msg>增加临时设置键值和获取多个键值的方法<commit_after>package config

import (
	"errors"
	"os"
	"regexp"
	"strings"
//   "fmt"
)

var ptn *regexp.Regexp
var parse_err error
var conf = make(map[string]string)

func init() {
	ptn, parse_err = regexp.Compile(`[#]*[A-Za-z0-9\_\-\.\t ]+[=].*`)
	if parse_err != nil {
		panic(parse_err)
	}
}

func Parse(filename string) (err error) {
	fi, err := os.Stat(filename)
	if err != nil {
		return
	}

	f, err := os.Open(filename)
	if err != nil {
		return
	}

	buff := make([]byte, fi.Size())
	f.Read(buff)
	f.Close()

	str := ptn.FindAllString(string(buff), -1)

	for i := 0; i < len(str); i++ {
		switch {
		case strings.Index(str[i], "#") == 0:
			// 说明是注释行，什么也不做
		case strings.Index(str[i], "=") > 0:
			kvs := strings.Split(str[i], "=")
			key, val := strings.TrimSpace(kvs[0]), strings.Trim(kvs[1], `"`)
			conf[key] = val
		default:
			err = errors.New("parse config files failed! please check this line:" + str[i])
			return
		}
	}
	return
}

//调试用，返回conf对象的指针，一般情况下最好不要使用
func GetAll() *map[string]string {
	return &conf
}

// 获取单个键值
func Get(key string) (value string) {
	value = conf[key]
	return
}

// 临时设置键值。本方法不会覆盖配置文件，因此重启后无法保存。
func Set(key string, value string) {
	conf[key] = value
	return
}

// 以可变参数的形式获取多个键值，并按顺序返回值的切片。
func GetMulti(keys ...string)(values []string){
	for _, key := range keys {
		values = append(values, conf[key])
	}
  return
}
<|endoftext|>