<commit_before>/*
Package dot implements Graphviz's dot language in Go.

Basic Graph creation:

	g := dot.NewGraph("G")
	g.SetType(dot.DIGRAPH)
	...
	g.AddEdge(dot.NewNode("A"), dot.NewNode("B"))
	...
	fmt.Sprint(g)
*/
package dot

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

var AttributeError = errors.New("Invalid Attribute")

var GraphAttributes = []string{"Damping", "K", "URL", "aspect", "bb", "bgcolor",
	"center", "charset", "clusterrank", "colorscheme", "comment", "compound",
	"concentrate", "defaultdist", "dim", "dimen", "diredgeconstraints",
	"dpi", "epsilon", "esep", "fontcolor", "fontname", "fontnames",
	"fontpath", "fontsize", "id", "label", "labeljust", "labelloc",
	"landscape", "layers", "layersep", "layout", "levels", "levelsgap",
	"lheight", "lp", "lwidth", "margin", "maxiter", "mclimit", "mindist",
	"mode", "model", "mosek", "nodesep", "nojustify", "normalize", "nslimit",
	"nslimit1", "ordering", "orientation", "outputorder", "overlap",
	"overlap_scaling", "pack", "packmode", "pad", "page", "pagedir",
	"quadtree", "quantum", "rankdir", "ranksep", "ratio", "remincross",
	"repulsiveforce", "resolution", "root", "rotate", "searchsize", "sep",
	"showboxes", "size", "smoothing", "sortv", "splines", "start",
	"stylesheet", "target", "truecolor", "viewport", "voro_margin",
	"rank"}

var EdgeAttributes = []string{"URL", "arrowhead", "arrowsize", "arrowtail",
	"color", "colorscheme", "comment", "constraint", "decorate", "dir",
	"edgeURL", "edgehref", "edgetarget", "edgetooltip", "fontcolor",
	"fontname", "fontsize", "headURL", "headclip", "headhref", "headlabel",
	"headport", "headtarget", "headtooltip", "href", "id", "label",
	"labelURL", "labelangle", "labeldistance", "labelfloat", "labelfontcolor",
	"labelfontname", "labelfontsize", "labelhref", "labeltarget",
	"labeltooltip", "layer", "len", "lhead", "lp", "ltail", "minlen",
	"nojustify", "penwidth", "pos", "samehead", "sametail", "showboxes",
	"style", "tailURL", "tailclip", "tailhref", "taillabel", "tailport",
	"tailtarget", "tailtooltip", "target", "tooltip", "weight",
	// for subgraphs
	"rank"}

var NodeAttributes = []string{"URL", "color", "colorscheme", "comment",
	"distortion", "fillcolor", "fixedsize", "fontcolor", "fontname",
	"fontsize", "group", "height", "id", "image", "imagescale", "label",
	"labelloc", "layer", "margin", "nojustify", "orientation", "penwidth",
	"peripheries", "pin", "pos", "rects", "regular", "root", "samplepoints",
	"shape", "shapefile", "showboxes", "sides", "skew", "sortv", "style",
	"target", "tooltip", "vertices", "width", "z",
	// The following are attributes dot2tex
	"texlbl", "texmode"}

var ClusterAttributes = []string{"K", "URL", "bgcolor", "color", "colorscheme",
	"fillcolor", "fontcolor", "fontname", "fontsize", "label", "labeljust",
	"labelloc", "lheight", "lp", "lwidth", "nojustify", "pencolor",
	"penwidth", "peripheries", "sortv", "style", "target", "tooltip"}

var dotKeywords = []string{"graph", "subgraph", "digraph", "node", "edge", "strict"}

type GraphType int

const (
	DIGRAPH GraphType = iota
	GRAPH
)

// Fields common to all graph object types
type common struct {
	_type       string
	name        string
	attributes  map[string]string
	sequence    int
	parentGraph *Graph
}

type GraphObject interface {
	Get(string) string
	Set(string, string) error
	Sequence() int
	//	GetAttributes() []string
	//attributes() map[string]string
}

type GraphObjectList []GraphObject

func (gol GraphObjectList) Len() int {
	return len(gol)
}

func (gol GraphObjectList) Less(i, j int) bool {
	return gol[i].Sequence() < gol[i].Sequence()
}

func (gol GraphObjectList) Swap(i, j int) {
	gol[i], gol[j] = gol[j], gol[i]
}

type Graph struct {
	common
	strict               bool
	graphType            GraphType
	supressDisconnected  bool
	simplify             bool
	currentChildSequence int
	nodes                map[string][]*Node
	edges                map[string][]*Edge
	subgraphs            map[string][]*SubGraph
}

func NewGraph(name string) *Graph {
	g := &Graph{
		common: common{
			_type:      "graph",
			name:       name,
			attributes: make(map[string]string, 0),
		},
		nodes:                make(map[string][]*Node, 0),
		edges:                make(map[string][]*Edge, 0),
		subgraphs:            make(map[string][]*SubGraph, 0),
		currentChildSequence: 1,
	}
	g.SetParentGraph(g)
	return g
}

type SubGraph struct {
	Graph
}

func NewSubgraph(name string) *SubGraph {
	result := &SubGraph{
		Graph: *NewGraph(name),
	}
	result._type = "subgraph"
	return result
}

func indexInSlice(slice []string, toFind string) int {
	for i, v := range slice {
		if v == toFind {
			return i
		}
	}
	return -1
}

func needsQuotes(s string) bool {
	if indexInSlice(dotKeywords, s) != -1 {
		return false
	}

	return true
}

func quoteIfNecessary(s string) (result string) {
	if needsQuotes(s) {
		s = strings.Replace(s, "\"", "\\\"", -1)
		s = strings.Replace(s, "\n", "\\n", -1)
		s = strings.Replace(s, "\r", "\\r", -1)
		s = "\"" + s + "\""
	}
	return s
}

func validAttribute(attributeCollection []string, attributeName string) bool {
	return indexInSlice(attributeCollection, attributeName) != -1
}

func validGraphAttribute(attributeName string) bool {
	return validAttribute(GraphAttributes, attributeName)
}

func validNodeAttribute(attributeName string) bool {
	return validAttribute(NodeAttributes, attributeName)
}

func sortedKeys(sourceMap map[string]string) []string {
	keys := make([]string, 0, len(sourceMap))
	for k, _ := range sourceMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return keys
}

////////////////////////////////////////////////////////////////////////////////////////

func (gt GraphType) String() string {
	if gt == DIGRAPH {
		return "digraph"
	} else if gt == GRAPH {
		return "graph"
	}
	return "(invalid)"
}

func (c *common) GetParentGraph() *Graph {
	return c.parentGraph
}

func (c *common) SetParentGraph(g *Graph) {
	c.parentGraph = g
}

func (c *common) Sequence() int {
	return c.sequence
}

func (c *common) Get(attributeName string) string {
	return c.attributes[attributeName]
}

func (c *common) Set(attributeName, attributeValue string) error {
	c.attributes[attributeName] = attributeValue
	return nil
}

func setAttribute(validAttributes []string, attributes map[string]string, attributeName, attributeValue string) error {
	if validAttribute(validAttributes, attributeName) {
		attributes[attributeName] = attributeValue
		return nil
	}
	return AttributeError
}

func (g *Graph) Set(attributeName, attributeValue string) error {
	return setAttribute(GraphAttributes, g.common.attributes, attributeName, attributeValue)
}

func (n *Node) Set(attributeName, attributeValue string) error {
	return setAttribute(NodeAttributes, n.common.attributes, attributeName, attributeValue)
}

func (e *Edge) Set(attributeName, attributeValue string) error {
	return setAttribute(EdgeAttributes, e.common.attributes, attributeName, attributeValue)
}

func (c *common) setSequence(sequence int) {
	c.sequence = sequence
}

func (g *Graph) SetType(t GraphType) {
	g.graphType = t
}

func (c common) Name() string {
	return c.name
}

func (g *Graph) GetRoot() (result *Graph) {
	result = g
	for parent := g.GetParentGraph(); parent != result; parent = parent.GetParentGraph() {
		result = parent
	}
	return result
}

func (g *Graph) getNextSequenceNumber() (next int) {
	next = g.currentChildSequence
	g.currentChildSequence += 1
	return
}
func (g *Graph) AddNode(n *Node) {
	name := n.Name()
	if _, ok := g.nodes[name]; !ok {
		g.nodes[name] = make([]*Node, 0)
	}
	n.setSequence(g.getNextSequenceNumber())
	n.SetParentGraph(g.GetParentGraph())
	g.nodes[name] = append(g.nodes[name], n)
}

func (g *Graph) AddEdge(e *Edge) {
	name := e.Name()
	if _, ok := g.edges[name]; !ok {
		g.edges[name] = make([]*Edge, 0)
	}
	e.setSequence(g.getNextSequenceNumber())
	e.SetParentGraph(g.GetParentGraph())
	g.edges[name] = append(g.edges[name], e)
}

func (g *Graph) AddSubgraph(sg *SubGraph) {
	name := sg.Name()
	if _, ok := g.subgraphs[name]; !ok {
		g.subgraphs[name] = make([]*SubGraph, 0)
	}
	sg.setSequence(g.getNextSequenceNumber())
	g.subgraphs[name] = append(g.subgraphs[name], sg)
}

func (g *Graph) GetSubgraphs() (result []*SubGraph) {
	result = make([]*SubGraph, 0)
	for _, sgs := range g.subgraphs {
		for _, sg := range sgs {
			result = append(result, sg)
		}
	}
	return result
}

func (g Graph) String() string {
	parts := make([]string, 0)
	if g.strict {
		parts = append(parts, "strict ")
	}
	if g.name == "" {
		parts = append(parts, "{\n")
	} else {
		parts = append(parts, fmt.Sprintf("%s %s {\n", g.graphType, g.name))
	}

	attrs := make([]string, 0)
	for _, key := range sortedKeys(g.attributes) {
		attrs = append(attrs, key+"="+quoteIfNecessary(g.attributes[key]))
	}
	if len(attrs) > 0 {
		parts = append(parts, strings.Join(attrs, ";\n"))
		parts = append(parts, ";\n")
	}

	objectList := make(GraphObjectList, 0)

	for _, nodes := range g.nodes {
		for _, node := range nodes {
			objectList = append(objectList, node)
		}
	}
	for _, edges := range g.edges {
		for _, edge := range edges {
			objectList = append(objectList, edge)
		}
	}
	for _, subgraphs := range g.subgraphs {
		for _, subgraph := range subgraphs {
			objectList = append(objectList, subgraph)
		}
	}
	sort.Sort(objectList)

	for _, obj := range objectList {
		//@todo type-based decision making re: supressDisconnected and simplify
		//switch o := obj.(type) {
		//case *Node:
		//}
		parts = append(parts, fmt.Sprintf("%s\n", obj))
	}

	parts = append(parts, "}\n")
	return strings.Join(parts, "")
}

type Node struct {
	common
}

func NewNode(name string) *Node {
	return &Node{
		common{
			name:       name,
			attributes: make(map[string]string, 0),
		},
	}
}

func (n Node) String() string {

	name := n.name
	parts := make([]string, 0)

	attrs := make([]string, 0)
	for _, key := range sortedKeys(n.attributes) {
		attrs = append(attrs, key+"="+quoteIfNecessary(n.attributes[key]))
	}
	if len(attrs) > 0 {
		parts = append(parts, strings.Join(attrs, ", "))
	}

	//@todo don't print if node is empty
	if len(parts) > 0 {
		name += " [" + strings.Join(parts, ", ") + "]"
	}

	return name + ";"
}

type Edge struct {
	common
	points [2]*Node
}

func NewEdge(src, dst *Node) *Edge {
	return &Edge{
		common{
			_type:      "edge",
			attributes: make(map[string]string, 0),
		},
		[2]*Node{src, dst},
	}
}

func (e Edge) Source() *Node {
	return e.points[0]
}

func (e Edge) Destination() *Node {
	return e.points[1]
}

func (e Edge) String() string {
	src, dst := e.Source(), e.Destination()
	parts := make([]string, 0)

	parts = append(parts, src.Name())

	parent := e.GetParentGraph()
	if parent != nil && parent.GetRoot() != nil && parent.GetRoot().graphType == DIGRAPH {
		parts = append(parts, "->")
	} else {
		parts = append(parts, "--")
	}
	parts = append(parts, dst.Name())
	//parts = append(parts, fmt.Sprint(dst))

	attrs := make([]string, 0)
	for _, key := range sortedKeys(e.attributes) {
		attrs = append(attrs, key+"="+"\""+e.attributes[key]+"\"")
	}
	if len(attrs) > 0 {
		parts = append(parts, " [")
		parts = append(parts, strings.Join(attrs, ", "))
		parts = append(parts, "] \n")
	}

	return strings.Join(parts, " ")
}

func init() {
	sort.Strings(GraphAttributes)
	sort.Strings(NodeAttributes)
	sort.Strings(EdgeAttributes)
	sort.Strings(ClusterAttributes)
}
<commit_msg>Clean up namespace a bit<commit_after>/*
Package dot implements Graphviz's dot language in Go.

Basic Graph creation:

	g := dot.NewGraph("G")
	g.SetType(dot.DIGRAPH)
	...
	g.AddEdge(dot.NewNode("A"), dot.NewNode("B"))
	...
	fmt.Sprint(g)
*/
package dot

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

var AttributeError = errors.New("Invalid Attribute")

var graphAttributes = []string{"Damping", "K", "URL", "aspect", "bb", "bgcolor",
	"center", "charset", "clusterrank", "colorscheme", "comment", "compound",
	"concentrate", "defaultdist", "dim", "dimen", "diredgeconstraints",
	"dpi", "epsilon", "esep", "fontcolor", "fontname", "fontnames",
	"fontpath", "fontsize", "id", "label", "labeljust", "labelloc",
	"landscape", "layers", "layersep", "layout", "levels", "levelsgap",
	"lheight", "lp", "lwidth", "margin", "maxiter", "mclimit", "mindist",
	"mode", "model", "mosek", "nodesep", "nojustify", "normalize", "nslimit",
	"nslimit1", "ordering", "orientation", "outputorder", "overlap",
	"overlap_scaling", "pack", "packmode", "pad", "page", "pagedir",
	"quadtree", "quantum", "rankdir", "ranksep", "ratio", "remincross",
	"repulsiveforce", "resolution", "root", "rotate", "searchsize", "sep",
	"showboxes", "size", "smoothing", "sortv", "splines", "start",
	"stylesheet", "target", "truecolor", "viewport", "voro_margin",
	"rank"}

var edgeAttributes = []string{"URL", "arrowhead", "arrowsize", "arrowtail",
	"color", "colorscheme", "comment", "constraint", "decorate", "dir",
	"edgeURL", "edgehref", "edgetarget", "edgetooltip", "fontcolor",
	"fontname", "fontsize", "headURL", "headclip", "headhref", "headlabel",
	"headport", "headtarget", "headtooltip", "href", "id", "label",
	"labelURL", "labelangle", "labeldistance", "labelfloat", "labelfontcolor",
	"labelfontname", "labelfontsize", "labelhref", "labeltarget",
	"labeltooltip", "layer", "len", "lhead", "lp", "ltail", "minlen",
	"nojustify", "penwidth", "pos", "samehead", "sametail", "showboxes",
	"style", "tailURL", "tailclip", "tailhref", "taillabel", "tailport",
	"tailtarget", "tailtooltip", "target", "tooltip", "weight",
	// for subgraphs
	"rank"}

var nodeAttributes = []string{"URL", "color", "colorscheme", "comment",
	"distortion", "fillcolor", "fixedsize", "fontcolor", "fontname",
	"fontsize", "group", "height", "id", "image", "imagescale", "label",
	"labelloc", "layer", "margin", "nojustify", "orientation", "penwidth",
	"peripheries", "pin", "pos", "rects", "regular", "root", "samplepoints",
	"shape", "shapefile", "showboxes", "sides", "skew", "sortv", "style",
	"target", "tooltip", "vertices", "width", "z",
	// The following are attributes dot2tex
	"texlbl", "texmode"}

var ClusterAttributes = []string{"K", "URL", "bgcolor", "color", "colorscheme",
	"fillcolor", "fontcolor", "fontname", "fontsize", "label", "labeljust",
	"labelloc", "lheight", "lp", "lwidth", "nojustify", "pencolor",
	"penwidth", "peripheries", "sortv", "style", "target", "tooltip"}

var dotKeywords = []string{"graph", "subgraph", "digraph", "node", "edge", "strict"}

type GraphType int

const (
	DIGRAPH GraphType = iota
	GRAPH
)

// Fields common to all graph object types
type common struct {
	_type       string
	name        string
	attributes  map[string]string
	sequence    int
	parentGraph *Graph
}

type GraphObject interface {
	Get(string) string
	Set(string, string) error
	Sequence() int
	//	GetAttributes() []string
	//attributes() map[string]string
}

type GraphObjectList []GraphObject

func (gol GraphObjectList) Len() int {
	return len(gol)
}

func (gol GraphObjectList) Less(i, j int) bool {
	return gol[i].Sequence() < gol[i].Sequence()
}

func (gol GraphObjectList) Swap(i, j int) {
	gol[i], gol[j] = gol[j], gol[i]
}

type Graph struct {
	common
	strict               bool
	graphType            GraphType
	supressDisconnected  bool
	simplify             bool
	currentChildSequence int
	nodes                map[string][]*Node
	edges                map[string][]*Edge
	subgraphs            map[string][]*SubGraph
}

func NewGraph(name string) *Graph {
	g := &Graph{
		common: common{
			_type:      "graph",
			name:       name,
			attributes: make(map[string]string, 0),
		},
		nodes:                make(map[string][]*Node, 0),
		edges:                make(map[string][]*Edge, 0),
		subgraphs:            make(map[string][]*SubGraph, 0),
		currentChildSequence: 1,
	}
	g.SetParentGraph(g)
	return g
}

type SubGraph struct {
	Graph
}

func NewSubgraph(name string) *SubGraph {
	result := &SubGraph{
		Graph: *NewGraph(name),
	}
	result._type = "subgraph"
	return result
}

func indexInSlice(slice []string, toFind string) int {
	for i, v := range slice {
		if v == toFind {
			return i
		}
	}
	return -1
}

func needsQuotes(s string) bool {
	if indexInSlice(dotKeywords, s) != -1 {
		return false
	}

	return true
}

func quoteIfNecessary(s string) (result string) {
	if needsQuotes(s) {
		s = strings.Replace(s, "\"", "\\\"", -1)
		s = strings.Replace(s, "\n", "\\n", -1)
		s = strings.Replace(s, "\r", "\\r", -1)
		s = "\"" + s + "\""
	}
	return s
}

func validAttribute(attributeCollection []string, attributeName string) bool {
	return indexInSlice(attributeCollection, attributeName) != -1
}

func validGraphAttribute(attributeName string) bool {
	return validAttribute(graphAttributes, attributeName)
}

func validNodeAttribute(attributeName string) bool {
	return validAttribute(nodeAttributes, attributeName)
}

func sortedKeys(sourceMap map[string]string) []string {
	keys := make([]string, 0, len(sourceMap))
	for k, _ := range sourceMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return keys
}

////////////////////////////////////////////////////////////////////////////////////////

func (gt GraphType) String() string {
	if gt == DIGRAPH {
		return "digraph"
	} else if gt == GRAPH {
		return "graph"
	}
	return "(invalid)"
}

func (c *common) GetParentGraph() *Graph {
	return c.parentGraph
}

func (c *common) SetParentGraph(g *Graph) {
	c.parentGraph = g
}

func (c *common) Sequence() int {
	return c.sequence
}

func (c *common) Get(attributeName string) string {
	return c.attributes[attributeName]
}

func (c *common) Set(attributeName, attributeValue string) error {
	c.attributes[attributeName] = attributeValue
	return nil
}

func setAttribute(validAttributes []string, attributes map[string]string, attributeName, attributeValue string) error {
	if validAttribute(validAttributes, attributeName) {
		attributes[attributeName] = attributeValue
		return nil
	}
	return AttributeError
}

func (g *Graph) Set(attributeName, attributeValue string) error {
	return setAttribute(graphAttributes, g.common.attributes, attributeName, attributeValue)
}

func (n *Node) Set(attributeName, attributeValue string) error {
	return setAttribute(nodeAttributes, n.common.attributes, attributeName, attributeValue)
}

func (e *Edge) Set(attributeName, attributeValue string) error {
	return setAttribute(edgeAttributes, e.common.attributes, attributeName, attributeValue)
}

func (c *common) setSequence(sequence int) {
	c.sequence = sequence
}

func (g *Graph) SetType(t GraphType) {
	g.graphType = t
}

func (c common) Name() string {
	return c.name
}

func (g *Graph) GetRoot() (result *Graph) {
	result = g
	for parent := g.GetParentGraph(); parent != result; parent = parent.GetParentGraph() {
		result = parent
	}
	return result
}

func (g *Graph) getNextSequenceNumber() (next int) {
	next = g.currentChildSequence
	g.currentChildSequence += 1
	return
}
func (g *Graph) AddNode(n *Node) {
	name := n.Name()
	if _, ok := g.nodes[name]; !ok {
		g.nodes[name] = make([]*Node, 0)
	}
	n.setSequence(g.getNextSequenceNumber())
	n.SetParentGraph(g.GetParentGraph())
	g.nodes[name] = append(g.nodes[name], n)
}

func (g *Graph) AddEdge(e *Edge) {
	name := e.Name()
	if _, ok := g.edges[name]; !ok {
		g.edges[name] = make([]*Edge, 0)
	}
	e.setSequence(g.getNextSequenceNumber())
	e.SetParentGraph(g.GetParentGraph())
	g.edges[name] = append(g.edges[name], e)
}

func (g *Graph) AddSubgraph(sg *SubGraph) {
	name := sg.Name()
	if _, ok := g.subgraphs[name]; !ok {
		g.subgraphs[name] = make([]*SubGraph, 0)
	}
	sg.setSequence(g.getNextSequenceNumber())
	g.subgraphs[name] = append(g.subgraphs[name], sg)
}

func (g *Graph) GetSubgraphs() (result []*SubGraph) {
	result = make([]*SubGraph, 0)
	for _, sgs := range g.subgraphs {
		for _, sg := range sgs {
			result = append(result, sg)
		}
	}
	return result
}

func (g Graph) String() string {
	parts := make([]string, 0)
	if g.strict {
		parts = append(parts, "strict ")
	}
	if g.name == "" {
		parts = append(parts, "{\n")
	} else {
		parts = append(parts, fmt.Sprintf("%s %s {\n", g.graphType, g.name))
	}

	attrs := make([]string, 0)
	for _, key := range sortedKeys(g.attributes) {
		attrs = append(attrs, key+"="+quoteIfNecessary(g.attributes[key]))
	}
	if len(attrs) > 0 {
		parts = append(parts, strings.Join(attrs, ";\n"))
		parts = append(parts, ";\n")
	}

	objectList := make(GraphObjectList, 0)

	for _, nodes := range g.nodes {
		for _, node := range nodes {
			objectList = append(objectList, node)
		}
	}
	for _, edges := range g.edges {
		for _, edge := range edges {
			objectList = append(objectList, edge)
		}
	}
	for _, subgraphs := range g.subgraphs {
		for _, subgraph := range subgraphs {
			objectList = append(objectList, subgraph)
		}
	}
	sort.Sort(objectList)

	for _, obj := range objectList {
		//@todo type-based decision making re: supressDisconnected and simplify
		//switch o := obj.(type) {
		//case *Node:
		//}
		parts = append(parts, fmt.Sprintf("%s\n", obj))
	}

	parts = append(parts, "}\n")
	return strings.Join(parts, "")
}

type Node struct {
	common
}

func NewNode(name string) *Node {
	return &Node{
		common{
			name:       name,
			attributes: make(map[string]string, 0),
		},
	}
}

func (n Node) String() string {

	name := n.name
	parts := make([]string, 0)

	attrs := make([]string, 0)
	for _, key := range sortedKeys(n.attributes) {
		attrs = append(attrs, key+"="+quoteIfNecessary(n.attributes[key]))
	}
	if len(attrs) > 0 {
		parts = append(parts, strings.Join(attrs, ", "))
	}

	//@todo don't print if node is empty
	if len(parts) > 0 {
		name += " [" + strings.Join(parts, ", ") + "]"
	}

	return name + ";"
}

type Edge struct {
	common
	points [2]*Node
}

func NewEdge(src, dst *Node) *Edge {
	return &Edge{
		common{
			_type:      "edge",
			attributes: make(map[string]string, 0),
		},
		[2]*Node{src, dst},
	}
}

func (e Edge) Source() *Node {
	return e.points[0]
}

func (e Edge) Destination() *Node {
	return e.points[1]
}

func (e Edge) String() string {
	src, dst := e.Source(), e.Destination()
	parts := make([]string, 0)

	parts = append(parts, src.Name())

	parent := e.GetParentGraph()
	if parent != nil && parent.GetRoot() != nil && parent.GetRoot().graphType == DIGRAPH {
		parts = append(parts, "->")
	} else {
		parts = append(parts, "--")
	}
	parts = append(parts, dst.Name())
	//parts = append(parts, fmt.Sprint(dst))

	attrs := make([]string, 0)
	for _, key := range sortedKeys(e.attributes) {
		attrs = append(attrs, key+"="+"\""+e.attributes[key]+"\"")
	}
	if len(attrs) > 0 {
		parts = append(parts, " [")
		parts = append(parts, strings.Join(attrs, ", "))
		parts = append(parts, "] \n")
	}

	return strings.Join(parts, " ")
}

func init() {
	sort.Strings(graphAttributes)
	sort.Strings(nodeAttributes)
	sort.Strings(edgeAttributes)
	sort.Strings(ClusterAttributes)
}
<|endoftext|>


<commit_before>package mail

import (
	"github.com/WeisswurstSystems/WWM-BB/meeting"
)

func init() {
	_, err := invitationTemplate.Parse(invitationMessageTemplate)
	if err != nil {
		panic(err)
	}
}

func NewNotificiationMail(
	notificationTopic string,
	notificationShortMessage string,
	notificationMessage string,
	meeting meeting.Meeting) (mail Mail) {

	smtpData := SmtpTemplateData{
		Subject:       notificationTopic,
		BodyShortText: notificationShortMessage,
		Body:          notificationMessage,
	}

	var receiverList []string

	for _, orderItem := range meeting.Orders {
		receiverList = append(receiverList, string(orderItem.Customer))
	}

	var err error

	mail.Content, err = NewContent(smtpData)
	if err != nil {
		panic(err)
	}
	mail.Receivers = receiverList
	return mail
}
<commit_msg>link zu meeting eingebunden<commit_after>package mail

import (
	"github.com/WeisswurstSystems/WWM-BB/meeting"
)

func init() {
	_, err := invitationTemplate.Parse(invitationMessageTemplate)
	if err != nil {
		panic(err)
	}
}

func NewNotificiationMail(
	notificationTopic string,
	notificationShortMessage string,
	notificationMessage string,
	meeting meeting.Meeting) (mail Mail) {

	smtpData := SmtpTemplateData{
		BodyButtonLink: "https://weisswurstsystems.github.io/WWM-ITM/" + string(meeting.ID),
		BodyButtonText: "Gehe zu Meeting",
		Subject:       notificationTopic,
		BodyShortText: notificationShortMessage,
		Body:          notificationMessage,
	}

	var receiverList []string

	for _, orderItem := range meeting.Orders {
		receiverList = append(receiverList, string(orderItem.Customer))
	}

	var err error

	mail.Content, err = NewContent(smtpData)
	if err != nil {
		panic(err)
	}
	mail.Receivers = receiverList
	return mail
}
<|endoftext|>


<commit_before>package filemerger

import (
	"fmt"
	"fmutils"
)

func NewFileMerger(directory string) *FileMerger {
	return &FileMerger{
		Directory: directory,
		Filename: make(map[string]string),
		Count: make(map[string]int32),
	}
}

func (fm *FileMerger) Run() {
	fmt.Printf("FileMerger is handling directory: %s\n", fm.Directory)
	log.Infof("FileMerger is handling directory: %s\n", fm.Directory)

	fileList := fmutils.GetFilenameList(fm.Directory)

	fm.calMd5(fileList)
}

func (fm *FileMerger) getFilenameList() {
	fmutils.GetFilenameList(fm.Directory)
}

func (fm *FileMerger) calMd5(fileList []string) {
	for _, f := range fileList {
		md5 := fmutils.GetFileMd5(f)
		fm.Count[md5]++
		if md5 != "" {
			fm.Filename[md5] = fm.Directory
			if fm.Count[md5] > 1 {
				fm.merge(f)
			}
		}
	}
}

func (fm *FileMerger) merge(filename string) {
	fmutils.DeleteFile(filename)
	var src, dst string
	fmutils.MakeSoftLink(src, dst)
}
<commit_msg>2.19 bak<commit_after>package filemerger

import (
	"fmt"
	"fmutils"
)

func NewFileMerger(directory string) *FileMerger {
	/*
	return &FileMerger{
		Directory: directory,
		Filename: make(map[string]string),
		Count: make(map[string]int32),
	}
	*/

	fm := new(FileMerger) // creat a variables
	fm.Directory = directory
	fm.Filename = make(map[string]string)
	fm.Count = make(map[string]int32)

	return fm
}

func (fm *FileMerger) Run() { //fm:recevier *FileMerge:type Run:func
	fmt.Printf("FileMerger is handling directory: %s\n", fm.Directory)
	log.Infof("FileMerger is handling directory: %s\n", fm.Directory)

	fileList := fmutils.GetFilenameList(fm.Directory)

	fm.calMd5(fileList)
}

func (fm *FileMerger) getFilenameList() {
	fmutils.GetFilenameList(fm.Directory)
}

func (fm *FileMerger) calMd5(fileList []string) {
	for _, f := range fileList {
		md5 := fmutils.GetFileMd5(f)
		fm.Count[md5]++
		if md5 != "" {
			fm.Filename[md5] = fm.Directory
			if fm.Count[md5] > 1 {
				fm.merge(f)
			}
		}
	}
}

func (fm *FileMerger) merge(filename string) {
	fmutils.DeleteFile(filename)
	var src, dst string
	fmutils.MakeSoftLink(src, dst)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/stevencorona/elastic-haproxy/elb"
	"github.com/stevencorona/elastic-haproxy/haproxy"
	"github.com/stevencorona/elastic-haproxy/statsd"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var defaultConfigFile = "config/elastic.toml"
var flagConfigFile string

func main() {

	config := haproxy.Transform()

	flag.StringVar(&flagConfigFile, "configFile", defaultConfigFile, "Path to toml file")
	flag.Parse()

	conf := LoadConfig(flagConfigFile)

	server := new(haproxy.Server)

	// We use two channels— one to send actions to the server and one to recieve
	// notifications from it. Create them right now.
	actionChan := make(chan haproxy.Action)
	notificationChan := make(chan haproxy.Event)

	// Handle signals gracefully in another goroutine
	go gracefulSignals(server, actionChan, notificationChan)

	// Start up the HAProxy Server
	go server.Start(notificationChan, actionChan)

	// Setup the ELB HTTP Handlers
	go elb.SetupApiHandlers()

	// Fire up statsd goroutine if statsd is enabled. This might be better off in
	// a seperate binary to monitor HAProxy.
	if conf.Statsd.Enabled {
		go statsd.SendMetrics(server)
	}

	// Event loop for handling events from the HAProxy server
	// (right now, it only sends start/stop notifications)
	for {
		<-notificationChan
		log.Println("Received a notification")
		time.Sleep(2 * time.Second)

		server.Socket = conf.Haproxy.Socket
		serverInfo := server.GetInfo()
		log.Println(serverInfo)
	}

}

// Capture, capture, capture those signals. We gracefully shutdown on a
// SIGQUIT (this is what docker sends w/ docker stop) and everything else
// causes a graceful (zero-downtime) restart of HAProxy. Remember, HAProxy
// does not support reloads or graceful restarts without some... trickery.
func gracefulSignals(server *haproxy.Server, actionChan chan haproxy.Action, notificationChan chan haproxy.Event) {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT)

	for {
		s := <-signals
		log.Println("Received a signal", s)

		if s == syscall.SIGQUIT {
			log.Println("Caught SIGQUIT, Stopping HAProxy")

			// Tell server to stop and wait for a response
			actionChan <- haproxy.WantsStop

			// Race condition, this exits before we stop :( It should wait!
			<-notificationChan
			os.Exit(1)
		} else {
			actionChan <- haproxy.WantsReload
		}
	}
}
<commit_msg>fix build so it passes<commit_after>package main

import (
	"flag"
	"github.com/stevencorona/elastic-haproxy/elb"
	"github.com/stevencorona/elastic-haproxy/haproxy"
	"github.com/stevencorona/elastic-haproxy/statsd"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var defaultConfigFile = "config/elastic.toml"
var flagConfigFile string

func main() {

	haproxy.Transform()

	flag.StringVar(&flagConfigFile, "configFile", defaultConfigFile, "Path to toml file")
	flag.Parse()

	conf := LoadConfig(flagConfigFile)

	server := new(haproxy.Server)

	// We use two channels— one to send actions to the server and one to recieve
	// notifications from it. Create them right now.
	actionChan := make(chan haproxy.Action)
	notificationChan := make(chan haproxy.Event)

	// Handle signals gracefully in another goroutine
	go gracefulSignals(server, actionChan, notificationChan)

	// Start up the HAProxy Server
	go server.Start(notificationChan, actionChan)

	// Setup the ELB HTTP Handlers
	go elb.SetupApiHandlers()

	// Fire up statsd goroutine if statsd is enabled. This might be better off in
	// a seperate binary to monitor HAProxy.
	if conf.Statsd.Enabled {
		go statsd.SendMetrics(server)
	}

	// Event loop for handling events from the HAProxy server
	// (right now, it only sends start/stop notifications)
	for {
		<-notificationChan
		log.Println("Received a notification")
		time.Sleep(2 * time.Second)

		server.Socket = conf.Haproxy.Socket
		serverInfo := server.GetInfo()
		log.Println(serverInfo)
	}

}

// Capture, capture, capture those signals. We gracefully shutdown on a
// SIGQUIT (this is what docker sends w/ docker stop) and everything else
// causes a graceful (zero-downtime) restart of HAProxy. Remember, HAProxy
// does not support reloads or graceful restarts without some... trickery.
func gracefulSignals(server *haproxy.Server, actionChan chan haproxy.Action, notificationChan chan haproxy.Event) {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT)

	for {
		s := <-signals
		log.Println("Received a signal", s)

		if s == syscall.SIGQUIT {
			log.Println("Caught SIGQUIT, Stopping HAProxy")

			// Tell server to stop and wait for a response
			actionChan <- haproxy.WantsStop

			// Race condition, this exits before we stop :( It should wait!
			<-notificationChan
			os.Exit(1)
		} else {
			actionChan <- haproxy.WantsReload
		}
	}
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: Apache-2.0
// Copyright 2018-2019 Authors of Cilium

package k8sTest

import (
	"context"
	"fmt"

	. "github.com/cilium/cilium/test/ginkgo-ext"
	"github.com/cilium/cilium/test/helpers"

	. "github.com/onsi/gomega"
)

var _ = Describe("K8sHealthTest", func() {
	SkipContextIf(func() bool {
		return helpers.DoesNotRunOnGKE() && helpers.DoesNotRunOnEKS()
	}, "cilium-health", func() {
		var (
			kubectl        *helpers.Kubectl
			ciliumFilename string
		)

		BeforeAll(func() {
			kubectl = helpers.CreateKubectl(helpers.K8s1VMName(), logger)

			ciliumFilename = helpers.TimestampFilename("cilium.yaml")
			DeployCiliumOptionsAndDNS(kubectl, ciliumFilename, map[string]string{
				"clusterHealthPort": "9940", // tests use of custom port
			})
		})

		AfterFailed(func() {
			kubectl.CiliumReport("cilium endpoint list")
		})

		JustAfterEach(func() {
			kubectl.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration)
		})

		AfterEach(func() {
			ExpectAllPodsTerminated(kubectl)
		})

		AfterAll(func() {
			UninstallCiliumFromManifest(kubectl, ciliumFilename)
			kubectl.CloseSSHClient()
		})

		getCilium := func(node string) (pod, ip string) {
			pod, err := kubectl.GetCiliumPodOnNode(node)
			Expect(err).Should(BeNil())

			res, err := kubectl.Get(
				helpers.CiliumNamespace,
				fmt.Sprintf("pod %s", pod)).Filter("{.status.podIP}")
			Expect(err).Should(BeNil())
			ip = res.String()

			return pod, ip
		}

		checkIP := func(pod, ip string) {
			jsonpath := "{.nodes[*].host.primary-address.ip}"
			ciliumCmd := fmt.Sprintf("cilium-health status -o jsonpath='%s'", jsonpath)

			err := kubectl.CiliumExecUntilMatch(pod, ciliumCmd, ip)
			ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Never saw cilium-health ip %s in pod %s", ip, pod)
		}

		It("Checks status between nodes", func() {
			cilium1, cilium1IP := getCilium(helpers.K8s1)
			cilium2, cilium2IP := getCilium(helpers.K8s2)

			By("checking that cilium API exposes health instances")
			checkIP(cilium1, cilium1IP)
			checkIP(cilium1, cilium2IP)
			checkIP(cilium2, cilium1IP)
			checkIP(cilium2, cilium2IP)

			By("checking that `cilium-health --probe` succeeds")
			healthCmd := "cilium-health status --probe -o json"
			status := kubectl.CiliumExecMustSucceed(context.TODO(), cilium1, healthCmd)
			Expect(status.Stdout()).ShouldNot(ContainSubstring("error"))

			apiPaths := []string{
				"endpoint.icmp",
				"endpoint.http",
				"host.primary-address.icmp",
				"host.primary-address.http",
			}
			for node := 0; node <= 1; node++ {
				healthCmd := "cilium-health status -o json"
				status := kubectl.CiliumExecMustSucceed(context.TODO(), cilium1, healthCmd, "Cannot retrieve health status")
				for _, path := range apiPaths {
					filter := fmt.Sprintf("{.nodes[%d].%s}", node, path)
					By("checking API response for %q", filter)
					data, err := status.Filter(filter)
					Expect(err).To(BeNil(), "cannot retrieve filter %q from health output", filter)
					Expect(data.String()).Should(Not((BeEmpty())))
					statusFilter := fmt.Sprintf("{.nodes[%d].%s.status}", node, path)
					By("checking API status response for %q", statusFilter)
					data, err = status.Filter(statusFilter)
					Expect(err).To(BeNil(), "cannot retrieve filter %q from health output", statusFilter)
					Expect(data.String()).Should(BeEmpty())
				}
			}
		}, 30)
	})
})
<commit_msg>ci/K8sHealthTest: Use new health-endpoint fields<commit_after>// SPDX-License-Identifier: Apache-2.0
// Copyright 2018-2019 Authors of Cilium

package k8sTest

import (
	"context"
	"fmt"

	. "github.com/cilium/cilium/test/ginkgo-ext"
	"github.com/cilium/cilium/test/helpers"

	. "github.com/onsi/gomega"
)

var _ = Describe("K8sHealthTest", func() {
	SkipContextIf(func() bool {
		return helpers.DoesNotRunOnGKE() && helpers.DoesNotRunOnEKS()
	}, "cilium-health", func() {
		var (
			kubectl        *helpers.Kubectl
			ciliumFilename string
		)

		BeforeAll(func() {
			kubectl = helpers.CreateKubectl(helpers.K8s1VMName(), logger)

			ciliumFilename = helpers.TimestampFilename("cilium.yaml")
			DeployCiliumOptionsAndDNS(kubectl, ciliumFilename, map[string]string{
				"clusterHealthPort": "9940", // tests use of custom port
			})
		})

		AfterFailed(func() {
			kubectl.CiliumReport("cilium endpoint list")
		})

		JustAfterEach(func() {
			kubectl.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration)
		})

		AfterEach(func() {
			ExpectAllPodsTerminated(kubectl)
		})

		AfterAll(func() {
			UninstallCiliumFromManifest(kubectl, ciliumFilename)
			kubectl.CloseSSHClient()
		})

		getCilium := func(node string) (pod, ip string) {
			pod, err := kubectl.GetCiliumPodOnNode(node)
			Expect(err).Should(BeNil())

			res, err := kubectl.Get(
				helpers.CiliumNamespace,
				fmt.Sprintf("pod %s", pod)).Filter("{.status.podIP}")
			Expect(err).Should(BeNil())
			ip = res.String()

			return pod, ip
		}

		checkIP := func(pod, ip string) {
			jsonpath := "{.nodes[*].host.primary-address.ip}"
			ciliumCmd := fmt.Sprintf("cilium-health status -o jsonpath='%s'", jsonpath)

			err := kubectl.CiliumExecUntilMatch(pod, ciliumCmd, ip)
			ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Never saw cilium-health ip %s in pod %s", ip, pod)
		}

		It("Checks status between nodes", func() {
			cilium1, cilium1IP := getCilium(helpers.K8s1)
			cilium2, cilium2IP := getCilium(helpers.K8s2)

			By("checking that cilium API exposes health instances")
			checkIP(cilium1, cilium1IP)
			checkIP(cilium1, cilium2IP)
			checkIP(cilium2, cilium1IP)
			checkIP(cilium2, cilium2IP)

			By("checking that `cilium-health --probe` succeeds")
			healthCmd := "cilium-health status --probe -o json"
			status := kubectl.CiliumExecMustSucceed(context.TODO(), cilium1, healthCmd)
			Expect(status.Stdout()).ShouldNot(ContainSubstring("error"))

			apiPaths := []string{
				"health-endpoint.primary-address.icmp",
				"health-endpoint.primary-address.http",
				"host.primary-address.icmp",
				"host.primary-address.http",
			}
			for node := 0; node <= 1; node++ {
				healthCmd := "cilium-health status -o json"
				status := kubectl.CiliumExecMustSucceed(context.TODO(), cilium1, healthCmd, "Cannot retrieve health status")
				for _, path := range apiPaths {
					filter := fmt.Sprintf("{.nodes[%d].%s}", node, path)
					By("checking API response for %q", filter)
					data, err := status.Filter(filter)
					Expect(err).To(BeNil(), "cannot retrieve filter %q from health output", filter)
					Expect(data.String()).Should(Not((BeEmpty())))
					statusFilter := fmt.Sprintf("{.nodes[%d].%s.status}", node, path)
					By("checking API status response for %q", statusFilter)
					data, err = status.Filter(statusFilter)
					Expect(err).To(BeNil(), "cannot retrieve filter %q from health output", statusFilter)
					Expect(data.String()).Should(BeEmpty())
				}
			}
		}, 30)
	})
})
<|endoftext|>


<commit_before>// Heavily inspired by Nick Saika (https://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html)

package main

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

type WorkRequest struct {
	Id string
}

var (
	WorkerQueue chan chan WorkRequest
	WorkQueue   = make(chan WorkRequest, 100)
)

func StartDispatcher(nworkers int) {
	// First, initialize the channel we are going to but the workers' work channels into.
	WorkerQueue = make(chan chan WorkRequest, nworkers)
	// Now, create all of our workers.
	for i := 1; i <= nworkers; i++ {
		NewWorker(i, WorkerQueue).Start()
	}
	go func() {
		for {
			select {
			case work := <-WorkQueue:
				go func() {
					// Dispatching work request
					worker := <-WorkerQueue
					worker <- work
				}()
			}
		}
	}()
}

type Worker struct {
	ID          int
	Work        chan WorkRequest
	WorkerQueue chan chan WorkRequest
	QuitChan    chan bool
}

// Create, and return the worker.
func NewWorker(id int, workerQueue chan chan WorkRequest) Worker {
	worker := Worker{
		ID:          id,
		Work:        make(chan WorkRequest),
		WorkerQueue: workerQueue,
		QuitChan:    make(chan bool)}

	return worker
}

// This function "starts" the worker by starting a goroutine, that is
// an infinite "for-select" loop.
func (w Worker) Start() {
	go func() {
		for {
			// Add ourselves into the worker queue.
			w.WorkerQueue <- w.Work

			select {
			case work := <-w.Work:
				// Receive a work request.
				fmt.Fprintf(protocolFile, "Running speedata publisher for id %s\n", work.Id)
				cmd := exec.Command(filepath.Join(bindir, "sp"+exe_suffix))
				cmd.Dir = filepath.Join(serverTemp, work.Id)
				cmd.Run()
				fmt.Fprintf(protocolFile, "Id %s finished\n", w.ID, work.Id)
			case <-w.QuitChan:
				// We have been asked to stop.
				return
			}
		}
	}()
}

// Stop tells the worker to stop listening for work requests.
//
// Note that the worker will only stop *after* it has finished its work.
func (w Worker) Stop() {
	go func() {
		w.QuitChan <- true
	}()
}
<commit_msg>B: small bugfix reporting the server-worker<commit_after>// Heavily inspired by Nick Saika (https://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html)

package main

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

type WorkRequest struct {
	Id string
}

var (
	WorkerQueue chan chan WorkRequest
	WorkQueue   = make(chan WorkRequest, 100)
)

func StartDispatcher(nworkers int) {
	// First, initialize the channel we are going to but the workers' work channels into.
	WorkerQueue = make(chan chan WorkRequest, nworkers)
	// Now, create all of our workers.
	for i := 1; i <= nworkers; i++ {
		NewWorker(i, WorkerQueue).Start()
	}
	go func() {
		for {
			select {
			case work := <-WorkQueue:
				go func() {
					// Dispatching work request
					worker := <-WorkerQueue
					worker <- work
				}()
			}
		}
	}()
}

type Worker struct {
	ID          int
	Work        chan WorkRequest
	WorkerQueue chan chan WorkRequest
	QuitChan    chan bool
}

// Create, and return the worker.
func NewWorker(id int, workerQueue chan chan WorkRequest) Worker {
	worker := Worker{
		ID:          id,
		Work:        make(chan WorkRequest),
		WorkerQueue: workerQueue,
		QuitChan:    make(chan bool)}

	return worker
}

// This function "starts" the worker by starting a goroutine, that is
// an infinite "for-select" loop.
func (w Worker) Start() {
	go func() {
		for {
			// Add ourselves into the worker queue.
			w.WorkerQueue <- w.Work

			select {
			case work := <-w.Work:
				// Receive a work request.
				fmt.Fprintf(protocolFile, "Running speedata publisher for id %s\n", work.Id)
				cmd := exec.Command(filepath.Join(bindir, "sp"+exe_suffix))
				cmd.Dir = filepath.Join(serverTemp, work.Id)
				cmd.Run()
				fmt.Fprintf(protocolFile, "Id %s finished\n", work.Id)
			case <-w.QuitChan:
				// We have been asked to stop.
				return
			}
		}
	}()
}

// Stop tells the worker to stop listening for work requests.
//
// Note that the worker will only stop *after* it has finished its work.
func (w Worker) Stop() {
	go func() {
		w.QuitChan <- true
	}()
}
<|endoftext|>


<commit_before>// @author       sigu-399
// @description  An implementation of json pointer in Golang
// @created      25-02-2013

package gojsonpointer

import (

)

<commit_msg>added parsing<commit_after>// @author       sigu-399
// @description  An implementation of json pointer in Golang
// @created      25-02-2013

package gojsonpointer

import (
	"errors"
	"strings"
)

const (
	EMPTY_POINTER     = ``
	POINTER_SEPARATOR = `/`
)

const (
	INVALID_START = `JSON pointer must be empty or start with a "/"`
)

func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {

	var p JsonPointer
	err := p.parse(jsonPointerString)
	return p, err
}

type JsonPointer struct {
	stringRepresentation string
	referenceTokens      []string
}

func (p *JsonPointer) parse(jsonPointerString string) error {

	var err error
	p.stringRepresentation = jsonPointerString

	if p.stringRepresentation != EMPTY_POINTER {
		if !strings.HasPrefix(p.stringRepresentation, POINTER_SEPARATOR) {
			err = errors.New(INVALID_START)
		} else {
			referenceTokens := strings.Split(p.stringRepresentation, POINTER_SEPARATOR)
			for _, referenceToken := range referenceTokens[1:] {
				p.referenceTokens = append(p.referenceTokens, decodeReferenceToken(referenceToken))
			}
		}
	}

	return err
}

func (p *JsonPointer) String() string {
	return p.stringRepresentation
}

const (
	ENCODED_REFERENCE_TOKEN_0 = `~0`
	ENCODED_REFERENCE_TOKEN_1 = `~1`
	DECODED_REFERENCE_TOKEN_0 = `~`
	DECODED_REFERENCE_TOKEN_1 = `/`
)

func decodeReferenceToken(token string) string {
	step1 := strings.Replace(token, ENCODED_REFERENCE_TOKEN_1, DECODED_REFERENCE_TOKEN_1, -1)
	step2 := strings.Replace(step1, ENCODED_REFERENCE_TOKEN_0, DECODED_REFERENCE_TOKEN_0, -1)
	return step2
}
<|endoftext|>


<commit_before>package muniverse

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/unixpickle/essentials"
	"github.com/unixpickle/muniverse/chrome"
)

const (
	portRange        = "9000-9999"
	defaultContainer = "unixpickle/muniverse:0.7.0"
)

const (
	callTimeout           = time.Minute * 2
	chromeConnectAttempts = 10
)

// This error message occurs very infrequently when doing
// `docker run` on my machine running Ubuntu 16.04.1.
const occasionalDockerErr = "Error response from daemon: device or resource busy."

// An Env controls and observes an environment.
type Env interface {
	// Spec returns details about the environment.
	Spec() *EnvSpec

	// Reset resets the environment to a start state.
	Reset() error

	// Step sends the given events and advances the
	// episode by the given amount of time.
	//
	// If done is true, then the episode has ended.
	// After an episode ends, Reset must be called once
	// before Step may be called again.
	// However, observations may be made even after the
	// episode has ended.
	//
	// Typical event types are *chrome.MouseEvent and
	// *chrome.KeyEvent.
	Step(t time.Duration, events ...interface{}) (reward float64,
		done bool, err error)

	// Observe produces an observation for the current
	// state of the environment.
	Observe() (Obs, error)

	// Close cleans up resources used by the environment.
	//
	// After Close is called, the Env should not be used
	// anymore by any Goroutine.
	Close() error

	// Log returns internal log messages.
	// For example, it might return information about 404
	// errors.
	//
	// The returned list is a copy and may be modified by
	// the caller.
	Log() []string
}

type rawEnv struct {
	spec     EnvSpec
	gameHost string

	containerID string
	devConn     *chrome.Conn
	lastScore   float64

	// Used to garbage collect the container if we
	// exit ungracefully.
	killSocket net.Conn
}

// NewEnv creates a new environment inside the default
// Docker container.
// This may take a few minutes to run the first time,
// since it has to download a large container.
func NewEnv(spec *EnvSpec) (Env, error) {
	return NewEnvContainer(defaultContainer, spec)
}

// NewEnvContainer creates a new environment inside a
// Docker container of the given name.
func NewEnvContainer(container string, spec *EnvSpec) (env Env, err error) {
	defer essentials.AddCtxTo("create environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	var id string

	// Retry as a workaround for an occasional error given
	// by `docker run`.
	for i := 0; i < 3; i++ {
		id, err = dockerRun(ctx, container, spec)
		if err == nil || !strings.Contains(err.Error(), occasionalDockerErr) {
			break
		}
	}

	if err != nil {
		return
	}

	ports, err := dockerBoundPorts(ctx, id)
	if err != nil {
		return
	}

	conn, err := connectDevTools(ctx, "localhost:"+ports["9222/tcp"])
	if err != nil {
		return
	}

	killSock, err := (&net.Dialer{}).DialContext(ctx, "tcp",
		"localhost:"+ports["1337/tcp"])
	if err != nil {
		conn.Close()
		return
	}

	return &rawEnv{
		spec:        *spec,
		gameHost:    "localhost",
		containerID: id,
		devConn:     conn,
		killSocket:  killSock,
	}, nil
}

// NewEnvChrome connects to an existing Chrome DevTools
// server and runs an environment in there.
//
// The gameHost argument specifies where to load games.
// For example, gameHost might be "localhost:8080" if the
// game "Foobar" should be loaded from
// "http://localhost/Foobar".
//
// The Chrome instance must have at least one page open,
// since an open page is selected and used to run the
// environment.
func NewEnvChrome(host, gameHost string, spec *EnvSpec) (env Env, err error) {
	defer essentials.AddCtxTo("create environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	conn, err := connectDevTools(ctx, host)
	if err != nil {
		return
	}

	return &rawEnv{
		spec:     *spec,
		gameHost: gameHost,
		devConn:  conn,
	}, nil
}

func (r *rawEnv) Spec() *EnvSpec {
	res := r.spec
	return &res
}

func (r *rawEnv) Reset() (err error) {
	defer essentials.AddCtxTo("reset environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	err = r.devConn.NavigateSafe(ctx, r.envURL())
	if err != nil {
		return
	}

	err = r.devConn.EvalPromise(ctx, "window.muniverse.init();", nil)
	if err != nil {
		return
	}

	err = r.devConn.EvalPromise(ctx, "window.muniverse.score();", &r.lastScore)
	err = essentials.AddCtx("get score", err)

	return
}

func (r *rawEnv) Step(t time.Duration, events ...interface{}) (reward float64,
	done bool, err error) {
	defer essentials.AddCtxTo("step environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	for _, event := range events {
		switch event := event.(type) {
		case *chrome.MouseEvent:
			err = r.devConn.DispatchMouseEvent(ctx, event)
		case *chrome.KeyEvent:
			err = r.devConn.DispatchKeyEvent(ctx, event)
		default:
			err = fmt.Errorf("unsupported event type: %T", event)
		}
		if err != nil {
			return
		}
	}

	millis := int(t / time.Millisecond)
	timeStr := strconv.Itoa(millis)
	err = r.devConn.EvalPromise(ctx, "window.muniverse.step("+timeStr+");", &done)
	if err != nil {
		return
	}

	lastScore := r.lastScore
	err = r.devConn.EvalPromise(ctx, "window.muniverse.score();", &r.lastScore)
	if err != nil {
		err = essentials.AddCtx("get score", err)
		return
	}
	reward = r.lastScore - lastScore

	return
}

func (r *rawEnv) Observe() (obs Obs, err error) {
	ctx, cancel := callCtx()
	defer cancel()

	pngData, err := r.devConn.ScreenshotPNG(ctx)
	if err != nil {
		return
	}
	return pngObs(pngData), nil
}

func (r *rawEnv) Close() (err error) {
	defer essentials.AddCtxTo("close environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	errs := []error{
		r.devConn.Close(),
	}
	if r.containerID != "" {
		_, e := dockerCommand(ctx, "kill", r.containerID)
		errs = append(errs, e)
	}

	if r.killSocket != nil {
		// TODO: look into if this can ever produce an error,
		// since the container might already have closed the
		// socket by now.
		//
		// We don't close this *before* stopping the container
		// since `docker kill` might fail if the container
		// already died and was cleaned up.
		r.killSocket.Close()
	}

	// Any calls after Close() should trigger simple errors.
	r.devConn = nil
	r.killSocket = nil

	for _, err := range errs {
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *rawEnv) Log() []string {
	return r.devConn.ConsoleLog()
}

func (r *rawEnv) envURL() string {
	return "http://" + r.gameHost + "/" + r.spec.Name
}

func callCtx() (context.Context, context.CancelFunc) {
	return context.WithTimeout(context.Background(), callTimeout)
}

func dockerRun(ctx context.Context, container string, spec *EnvSpec) (id string,
	err error) {
	args := []string{
		"run",
		"-p",
		portRange + ":9222",
		"-p",
		portRange + ":1337",
		"-d",   // Run in detached mode.
		"--rm", // Automatically delete the container.
		"-i",   // Give netcat a stdin to read from.
		container,
		fmt.Sprintf("--window-size=%d,%d", spec.Width, spec.Height),
	}

	output, err := dockerCommand(ctx, args...)
	if err != nil {
		return "", essentials.AddCtx("docker run",
			fmt.Errorf("%s (make sure docker is up-to-date)", err))
	}

	return strings.TrimSpace(string(output)), nil
}

func dockerBoundPorts(ctx context.Context,
	containerID string) (mapping map[string]string, err error) {
	defer essentials.AddCtxTo("docker inspect", &err)
	rawJSON, err := dockerCommand(ctx, "inspect", containerID)
	if err != nil {
		return nil, err
	}
	var info []struct {
		NetworkSettings struct {
			Ports map[string][]struct {
				HostPort string
			}
		}
	}
	if err := json.Unmarshal(rawJSON, &info); err != nil {
		return nil, err
	}
	if len(info) != 1 {
		return nil, errors.New("unexpected number of results")
	}
	rawMapping := info[0].NetworkSettings.Ports
	mapping = map[string]string{}
	for containerPort, hostPorts := range rawMapping {
		if len(hostPorts) != 1 {
			return nil, errors.New("unexpected number of host ports")
		}
		mapping[containerPort] = hostPorts[0].HostPort
	}
	return
}

func dockerCommand(ctx context.Context, args ...string) (output []byte, err error) {
	output, err = exec.CommandContext(ctx, "docker", args...).Output()
	if err != nil {
		if eo, ok := err.(*exec.ExitError); ok && len(eo.Stderr) > 0 {
			stderrMsg := strings.TrimSpace(string(eo.Stderr))
			err = fmt.Errorf("%s: %s", eo.String(), stderrMsg)
		}
	}
	return
}

func connectDevTools(ctx context.Context, host string) (conn *chrome.Conn,
	err error) {
	for i := 0; i < chromeConnectAttempts; i++ {
		conn, err = attemptDevTools(ctx, host)
		if err == nil {
			return
		}
		select {
		case <-time.After(time.Second):
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
	return
}

func attemptDevTools(ctx context.Context, host string) (conn *chrome.Conn,
	err error) {
	endpoints, err := chrome.Endpoints(ctx, host)
	if err != nil {
		return
	}

	for _, ep := range endpoints {
		if ep.Type == "page" {
			return chrome.NewConn(ctx, ep.WebSocketURL)
		}
	}

	return nil, errors.New("no Chrome page endpoint")
}
<commit_msg>another attempt to deal with empty websocket URL<commit_after>package muniverse

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/unixpickle/essentials"
	"github.com/unixpickle/muniverse/chrome"
)

const (
	portRange        = "9000-9999"
	defaultContainer = "unixpickle/muniverse:0.7.0"
)

const (
	callTimeout           = time.Minute * 2
	chromeConnectAttempts = 10
)

// This error message occurs very infrequently when doing
// `docker run` on my machine running Ubuntu 16.04.1.
const occasionalDockerErr = "Error response from daemon: device or resource busy."

// An Env controls and observes an environment.
type Env interface {
	// Spec returns details about the environment.
	Spec() *EnvSpec

	// Reset resets the environment to a start state.
	Reset() error

	// Step sends the given events and advances the
	// episode by the given amount of time.
	//
	// If done is true, then the episode has ended.
	// After an episode ends, Reset must be called once
	// before Step may be called again.
	// However, observations may be made even after the
	// episode has ended.
	//
	// Typical event types are *chrome.MouseEvent and
	// *chrome.KeyEvent.
	Step(t time.Duration, events ...interface{}) (reward float64,
		done bool, err error)

	// Observe produces an observation for the current
	// state of the environment.
	Observe() (Obs, error)

	// Close cleans up resources used by the environment.
	//
	// After Close is called, the Env should not be used
	// anymore by any Goroutine.
	Close() error

	// Log returns internal log messages.
	// For example, it might return information about 404
	// errors.
	//
	// The returned list is a copy and may be modified by
	// the caller.
	Log() []string
}

type rawEnv struct {
	spec     EnvSpec
	gameHost string

	containerID string
	devConn     *chrome.Conn
	lastScore   float64

	// Used to garbage collect the container if we
	// exit ungracefully.
	killSocket net.Conn
}

// NewEnv creates a new environment inside the default
// Docker container.
// This may take a few minutes to run the first time,
// since it has to download a large container.
func NewEnv(spec *EnvSpec) (Env, error) {
	return NewEnvContainer(defaultContainer, spec)
}

// NewEnvContainer creates a new environment inside a
// Docker container of the given name.
func NewEnvContainer(container string, spec *EnvSpec) (env Env, err error) {
	defer essentials.AddCtxTo("create environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	var id string

	// Retry as a workaround for an occasional error given
	// by `docker run`.
	for i := 0; i < 3; i++ {
		id, err = dockerRun(ctx, container, spec)
		if err == nil || !strings.Contains(err.Error(), occasionalDockerErr) {
			break
		}
	}

	if err != nil {
		return
	}

	ports, err := dockerBoundPorts(ctx, id)
	if err != nil {
		return
	}

	conn, err := connectDevTools(ctx, "localhost:"+ports["9222/tcp"])
	if err != nil {
		return
	}

	killSock, err := (&net.Dialer{}).DialContext(ctx, "tcp",
		"localhost:"+ports["1337/tcp"])
	if err != nil {
		conn.Close()
		return
	}

	return &rawEnv{
		spec:        *spec,
		gameHost:    "localhost",
		containerID: id,
		devConn:     conn,
		killSocket:  killSock,
	}, nil
}

// NewEnvChrome connects to an existing Chrome DevTools
// server and runs an environment in there.
//
// The gameHost argument specifies where to load games.
// For example, gameHost might be "localhost:8080" if the
// game "Foobar" should be loaded from
// "http://localhost/Foobar".
//
// The Chrome instance must have at least one page open,
// since an open page is selected and used to run the
// environment.
func NewEnvChrome(host, gameHost string, spec *EnvSpec) (env Env, err error) {
	defer essentials.AddCtxTo("create environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	conn, err := connectDevTools(ctx, host)
	if err != nil {
		return
	}

	return &rawEnv{
		spec:     *spec,
		gameHost: gameHost,
		devConn:  conn,
	}, nil
}

func (r *rawEnv) Spec() *EnvSpec {
	res := r.spec
	return &res
}

func (r *rawEnv) Reset() (err error) {
	defer essentials.AddCtxTo("reset environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	err = r.devConn.NavigateSafe(ctx, r.envURL())
	if err != nil {
		return
	}

	err = r.devConn.EvalPromise(ctx, "window.muniverse.init();", nil)
	if err != nil {
		return
	}

	err = r.devConn.EvalPromise(ctx, "window.muniverse.score();", &r.lastScore)
	err = essentials.AddCtx("get score", err)

	return
}

func (r *rawEnv) Step(t time.Duration, events ...interface{}) (reward float64,
	done bool, err error) {
	defer essentials.AddCtxTo("step environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	for _, event := range events {
		switch event := event.(type) {
		case *chrome.MouseEvent:
			err = r.devConn.DispatchMouseEvent(ctx, event)
		case *chrome.KeyEvent:
			err = r.devConn.DispatchKeyEvent(ctx, event)
		default:
			err = fmt.Errorf("unsupported event type: %T", event)
		}
		if err != nil {
			return
		}
	}

	millis := int(t / time.Millisecond)
	timeStr := strconv.Itoa(millis)
	err = r.devConn.EvalPromise(ctx, "window.muniverse.step("+timeStr+");", &done)
	if err != nil {
		return
	}

	lastScore := r.lastScore
	err = r.devConn.EvalPromise(ctx, "window.muniverse.score();", &r.lastScore)
	if err != nil {
		err = essentials.AddCtx("get score", err)
		return
	}
	reward = r.lastScore - lastScore

	return
}

func (r *rawEnv) Observe() (obs Obs, err error) {
	ctx, cancel := callCtx()
	defer cancel()

	pngData, err := r.devConn.ScreenshotPNG(ctx)
	if err != nil {
		return
	}
	return pngObs(pngData), nil
}

func (r *rawEnv) Close() (err error) {
	defer essentials.AddCtxTo("close environment", &err)

	ctx, cancel := callCtx()
	defer cancel()

	errs := []error{
		r.devConn.Close(),
	}
	if r.containerID != "" {
		_, e := dockerCommand(ctx, "kill", r.containerID)
		errs = append(errs, e)
	}

	if r.killSocket != nil {
		// TODO: look into if this can ever produce an error,
		// since the container might already have closed the
		// socket by now.
		//
		// We don't close this *before* stopping the container
		// since `docker kill` might fail if the container
		// already died and was cleaned up.
		r.killSocket.Close()
	}

	// Any calls after Close() should trigger simple errors.
	r.devConn = nil
	r.killSocket = nil

	for _, err := range errs {
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *rawEnv) Log() []string {
	return r.devConn.ConsoleLog()
}

func (r *rawEnv) envURL() string {
	return "http://" + r.gameHost + "/" + r.spec.Name
}

func callCtx() (context.Context, context.CancelFunc) {
	return context.WithTimeout(context.Background(), callTimeout)
}

func dockerRun(ctx context.Context, container string, spec *EnvSpec) (id string,
	err error) {
	args := []string{
		"run",
		"-p",
		portRange + ":9222",
		"-p",
		portRange + ":1337",
		"-d",   // Run in detached mode.
		"--rm", // Automatically delete the container.
		"-i",   // Give netcat a stdin to read from.
		container,
		fmt.Sprintf("--window-size=%d,%d", spec.Width, spec.Height),
	}

	output, err := dockerCommand(ctx, args...)
	if err != nil {
		return "", essentials.AddCtx("docker run",
			fmt.Errorf("%s (make sure docker is up-to-date)", err))
	}

	return strings.TrimSpace(string(output)), nil
}

func dockerBoundPorts(ctx context.Context,
	containerID string) (mapping map[string]string, err error) {
	defer essentials.AddCtxTo("docker inspect", &err)
	rawJSON, err := dockerCommand(ctx, "inspect", containerID)
	if err != nil {
		return nil, err
	}
	var info []struct {
		NetworkSettings struct {
			Ports map[string][]struct {
				HostPort string
			}
		}
	}
	if err := json.Unmarshal(rawJSON, &info); err != nil {
		return nil, err
	}
	if len(info) != 1 {
		return nil, errors.New("unexpected number of results")
	}
	rawMapping := info[0].NetworkSettings.Ports
	mapping = map[string]string{}
	for containerPort, hostPorts := range rawMapping {
		if len(hostPorts) != 1 {
			return nil, errors.New("unexpected number of host ports")
		}
		mapping[containerPort] = hostPorts[0].HostPort
	}
	return
}

func dockerCommand(ctx context.Context, args ...string) (output []byte, err error) {
	output, err = exec.CommandContext(ctx, "docker", args...).Output()
	if err != nil {
		if eo, ok := err.(*exec.ExitError); ok && len(eo.Stderr) > 0 {
			stderrMsg := strings.TrimSpace(string(eo.Stderr))
			err = fmt.Errorf("%s: %s", eo.String(), stderrMsg)
		}
	}
	return
}

func connectDevTools(ctx context.Context, host string) (conn *chrome.Conn,
	err error) {
	for i := 0; i < chromeConnectAttempts; i++ {
		conn, err = attemptDevTools(ctx, host)
		if err == nil {
			return
		}
		select {
		case <-time.After(time.Second):
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
	return
}

func attemptDevTools(ctx context.Context, host string) (conn *chrome.Conn,
	err error) {
	endpoints, err := chrome.Endpoints(ctx, host)
	if err != nil {
		return
	}

	for _, ep := range endpoints {
		if ep.Type == "page" && ep.WebSocketURL != "" {
			return chrome.NewConn(ctx, ep.WebSocketURL)
		}
	}

	return nil, errors.New("no Chrome page endpoint")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"strings"
	"unicode/utf8"
)

const (
	VERSION                   = "0.0.1"
	defaultPlaceholder        = "{{}}"
	keyBackspace              = 8
	keyDelete                 = 127
	keyEndOfTransmission      = 4
	keyLineFeed               = 10
	keyCarriageReturn         = 13
	keyEndOfTransmissionBlock = 23
	keyEscape                 = 27
)

var placeholder string

var usage = `fzz allows you to run a command interactively.

Usage:

	fzz command

The command MUST include the placeholder '{{}}'.

Arguments:

	-v		Print version and exit
`

func printUsage() {
	fmt.Printf(usage)
}

func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

func containsPlaceholder(s []string, ph string) bool {
	for _, v := range s {
		if strings.Contains(v, ph) {
			return true
		}
	}
	return false
}

func validPlaceholder(p string) bool {
	return len(p)%2 == 0
}

func removeLastWord(s []byte) []byte {
	fields := bytes.Fields(s)
	if len(fields) > 0 {
		r := bytes.Join(fields[:len(fields)-1], []byte{' '})
		if len(r) > 1 {
			r = append(r, ' ')
		}
		return r
	}
	return []byte{}
}

func removeLastCharacter(s []byte) []byte {
	if len(s) > 1 {
		r, rsize := utf8.DecodeLastRune(s)
		if r == utf8.RuneError {
			return s[:len(s)-1]
		} else {
			return s[:len(s)-rsize]
		}
	} else if len(s) == 1 {
		return nil
	}
	return s
}

func readCharacter(r io.Reader) <-chan []byte {
	ch := make(chan []byte)
	rs := bufio.NewScanner(r)

	go func() {
		rs.Split(bufio.ScanRunes)

		for rs.Scan() {
			b := rs.Bytes()
			ch <- b
		}
	}()

	return ch
}

func mainLoop(tty *TTY, printer *Printer, stdinbuf *bytes.Buffer) {
	var currentRunner *Runner

	input := make([]byte, 0)
	ttych := readCharacter(tty)

	tty.resetScreen()
	tty.printPrompt(input[:len(input)])

	for {
		b := <-ttych

		switch b[0] {
		case keyBackspace, keyDelete:
			input = removeLastCharacter(input)
		case keyEndOfTransmission, keyLineFeed, keyCarriageReturn:
			if currentRunner != nil {
				currentRunner.Wait()
				tty.resetScreen()
				io.Copy(os.Stdout, currentRunner.stdoutbuf)
			} else {
				tty.resetScreen()
			}
			return
		case keyEscape:
			tty.resetScreen()
			return
		case keyEndOfTransmissionBlock:
			input = removeLastWord(input)
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}

		if currentRunner != nil {
			go func(runner *Runner) {
				runner.KillWait()
			}(currentRunner)
		}

		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		printer.Reset()

		if len(input) > 0 {
			currentRunner = NewRunner(flag.Args(), placeholder, string(input), stdinbuf)
			ch, err := currentRunner.Run()
			if err != nil {
				log.Fatal(err)
			}

			go func(inputlen int) {
				for line := range ch {
					printer.Print(line)
				}
				tty.cursorAfterPrompt(inputlen)
			}(utf8.RuneCount(input))
		}
	}
}

func main() {
	flVersion := flag.Bool("v", false, "Print fzz version and quit")
	flag.Usage = printUsage
	flag.Parse()

	if *flVersion {
		fmt.Printf("fzz %s\n", VERSION)
		os.Exit(0)
	}

	if len(flag.Args()) < 2 {
		fmt.Fprintf(os.Stderr, usage)
		os.Exit(1)
	}

	if placeholder = os.Getenv("FZZ_PLACEHOLDER"); placeholder == "" {
		placeholder = defaultPlaceholder
	}

	if !validPlaceholder(placeholder) {
		fmt.Fprintln(os.Stderr, "Placeholder is not valid, needs even number of characters")
		os.Exit(1)
	}

	if !containsPlaceholder(flag.Args(), placeholder) {
		fmt.Fprintln(os.Stderr, "No placeholder in arguments")
		os.Exit(1)
	}

	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}
	defer tty.resetState()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		tty.resetState()
		os.Exit(1)
	}()
	tty.setSttyState("cbreak", "-echo")

	stdinbuf := bytes.Buffer{}
	if isPipe(os.Stdin) {
		io.Copy(&stdinbuf, os.Stdin)
	}

	printer := NewPrinter(tty, tty.cols, tty.rows-1) // prompt is one row
	mainLoop(tty, printer, &stdinbuf)
}
<commit_msg>Add Fzz struct and integrate it<commit_after>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"strings"
	"unicode/utf8"
)

const (
	VERSION                   = "0.0.1"
	defaultPlaceholder        = "{{}}"
	keyBackspace              = 8
	keyDelete                 = 127
	keyEndOfTransmission      = 4
	keyLineFeed               = 10
	keyCarriageReturn         = 13
	keyEndOfTransmissionBlock = 23
	keyEscape                 = 27
)

var placeholder string

var usage = `fzz allows you to run a command interactively.

Usage:

	fzz command

The command MUST include the placeholder '{{}}'.

Arguments:

	-v		Print version and exit
`

func printUsage() {
	fmt.Printf(usage)
}

func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

func containsPlaceholder(s []string, ph string) bool {
	for _, v := range s {
		if strings.Contains(v, ph) {
			return true
		}
	}
	return false
}

func validPlaceholder(p string) bool {
	return len(p)%2 == 0
}

func removeLastWord(s []byte) []byte {
	fields := bytes.Fields(s)
	if len(fields) > 0 {
		r := bytes.Join(fields[:len(fields)-1], []byte{' '})
		if len(r) > 1 {
			r = append(r, ' ')
		}
		return r
	}
	return []byte{}
}

func removeLastCharacter(s []byte) []byte {
	if len(s) > 1 {
		r, rsize := utf8.DecodeLastRune(s)
		if r == utf8.RuneError {
			return s[:len(s)-1]
		} else {
			return s[:len(s)-rsize]
		}
	} else if len(s) == 1 {
		return nil
	}
	return s
}

func readCharacter(r io.Reader) <-chan []byte {
	ch := make(chan []byte)
	rs := bufio.NewScanner(r)

	go func() {
		rs.Split(bufio.ScanRunes)

		for rs.Scan() {
			b := rs.Bytes()
			ch <- b
		}
	}()

	return ch
}

type Fzz struct {
	tty           *TTY
	printer       *Printer
	stdinbuf      *bytes.Buffer
	currentRunner *Runner
	input         []byte
}

func (fzz *Fzz) Loop() {
	ttych := readCharacter(fzz.tty)

	fzz.tty.resetScreen()
	fzz.tty.printPrompt(fzz.input[:len(fzz.input)])

	for {
		b := <-ttych

		switch b[0] {
		case keyBackspace, keyDelete:
			fzz.input = removeLastCharacter(fzz.input)
		case keyEndOfTransmission, keyLineFeed, keyCarriageReturn:
			if fzz.currentRunner != nil {
				fzz.currentRunner.Wait()
				fzz.tty.resetScreen()
				io.Copy(os.Stdout, fzz.currentRunner.stdoutbuf)
			} else {
				fzz.tty.resetScreen()
			}
			return
		case keyEscape:
			fzz.tty.resetScreen()
			return
		case keyEndOfTransmissionBlock:
			fzz.input = removeLastWord(fzz.input)
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			fzz.input = append(fzz.input, b...)
		}

		if fzz.currentRunner != nil {
			go func(runner *Runner) {
				runner.KillWait()
			}(fzz.currentRunner)
		}

		fzz.tty.resetScreen()
		fzz.tty.printPrompt(fzz.input[:len(fzz.input)])

		fzz.printer.Reset()

		if len(fzz.input) > 0 {
			fzz.currentRunner = NewRunner(flag.Args(), placeholder, string(fzz.input), fzz.stdinbuf)
			ch, err := fzz.currentRunner.Run()
			if err != nil {
				log.Fatal(err)
			}

			go func(inputlen int) {
				for line := range ch {
					fzz.printer.Print(line)
				}
				fzz.tty.cursorAfterPrompt(inputlen)
			}(utf8.RuneCount(fzz.input))
		}
	}
}

func main() {
	flVersion := flag.Bool("v", false, "Print fzz version and quit")
	flag.Usage = printUsage
	flag.Parse()

	if *flVersion {
		fmt.Printf("fzz %s\n", VERSION)
		os.Exit(0)
	}

	if len(flag.Args()) < 2 {
		fmt.Fprintf(os.Stderr, usage)
		os.Exit(1)
	}

	if placeholder = os.Getenv("FZZ_PLACEHOLDER"); placeholder == "" {
		placeholder = defaultPlaceholder
	}

	if !validPlaceholder(placeholder) {
		fmt.Fprintln(os.Stderr, "Placeholder is not valid, needs even number of characters")
		os.Exit(1)
	}

	if !containsPlaceholder(flag.Args(), placeholder) {
		fmt.Fprintln(os.Stderr, "No placeholder in arguments")
		os.Exit(1)
	}

	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}
	defer tty.resetState()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		tty.resetState()
		os.Exit(1)
	}()
	tty.setSttyState("cbreak", "-echo")

	stdinbuf := bytes.Buffer{}
	if isPipe(os.Stdin) {
		io.Copy(&stdinbuf, os.Stdin)
	}

	printer := NewPrinter(tty, tty.cols, tty.rows-1) // prompt is one row
	fzz := &Fzz{
		printer: printer,
		tty: tty,
		stdinbuf: &stdinbuf,
		input: make([]byte, 0),
	}
	fzz.Loop()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"strings"
)

const (
	VERSION            = "0.0.1"
	defaultPlaceholder = "{{}}"
)

var originalSttyState bytes.Buffer
var placeholder string

var usage = `fzz allows you to run a command interactively.

Usage:

	fzz command

The command MUST include the placeholder '{{}}'.

Arguments:

	-v		Print version and exit
`

func printUsage() {
	fmt.Printf(usage)
}

func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

func containsPlaceholder(s []string, ph string) bool {
	for _, v := range s {
		if strings.Contains(v, ph) {
			return true
		}
	}
	return false
}

func stripTrailingNewline(b *bytes.Buffer) {
	s := b.Bytes()
	if s[len(s)-1] == '\n' {
		b.Truncate(b.Len()-1)
	}
}

func main() {
	flVersion := flag.Bool("v", false, "Print fzz version and quit")
	flag.Usage = printUsage
	flag.Parse()

	if *flVersion {
		fmt.Printf("fzz %s\n", VERSION)
		os.Exit(2)
	}

	if len(flag.Args()) < 2 {
		fmt.Fprintf(os.Stderr, usage)
		os.Exit(2)
	}

	if placeholder = os.Getenv("FZZ_PLACEHOLDER"); placeholder == "" {
		placeholder = defaultPlaceholder
	}

	if !containsPlaceholder(flag.Args(), placeholder) {
		fmt.Fprintf(os.Stderr, "No placeholder in arguments\n")
		os.Exit(2)
	}

	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	stripTrailingNewline(&originalSttyState)
	defer tty.setSttyState(&originalSttyState)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		tty.setSttyState(&originalSttyState)
		os.Exit(1)
	}()

	tty.setSttyState(bytes.NewBufferString("cbreak"))
	tty.setSttyState(bytes.NewBufferString("-echo"))

	printer := NewPrinter(tty, tty.cols, tty.rows-3)
	runner := &Runner{
		printer:     printer,
		template:    flag.Args(),
		placeholder: placeholder,
	}

	if isPipe(os.Stdin) {
		runner.stdinbuf = new(bytes.Buffer)
		io.Copy(runner.stdinbuf, os.Stdin)
	}

	input := make([]byte, 0)
	b := make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 8, 127:
			// Backspace, delete
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<commit_msg>Change exit codes<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"strings"
)

const (
	VERSION            = "0.0.1"
	defaultPlaceholder = "{{}}"
)

var originalSttyState bytes.Buffer
var placeholder string

var usage = `fzz allows you to run a command interactively.

Usage:

	fzz command

The command MUST include the placeholder '{{}}'.

Arguments:

	-v		Print version and exit
`

func printUsage() {
	fmt.Printf(usage)
}

func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

func containsPlaceholder(s []string, ph string) bool {
	for _, v := range s {
		if strings.Contains(v, ph) {
			return true
		}
	}
	return false
}

func stripTrailingNewline(b *bytes.Buffer) {
	s := b.Bytes()
	if s[len(s)-1] == '\n' {
		b.Truncate(b.Len()-1)
	}
}

func main() {
	flVersion := flag.Bool("v", false, "Print fzz version and quit")
	flag.Usage = printUsage
	flag.Parse()

	if *flVersion {
		fmt.Printf("fzz %s\n", VERSION)
		os.Exit(0)
	}

	if len(flag.Args()) < 2 {
		fmt.Fprintf(os.Stderr, usage)
		os.Exit(1)
	}

	if placeholder = os.Getenv("FZZ_PLACEHOLDER"); placeholder == "" {
		placeholder = defaultPlaceholder
	}

	if !containsPlaceholder(flag.Args(), placeholder) {
		fmt.Fprintln(os.Stderr, "No placeholder in arguments")
		os.Exit(1)
	}

	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	stripTrailingNewline(&originalSttyState)
	defer tty.setSttyState(&originalSttyState)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		tty.setSttyState(&originalSttyState)
		os.Exit(1)
	}()

	tty.setSttyState(bytes.NewBufferString("cbreak"))
	tty.setSttyState(bytes.NewBufferString("-echo"))

	printer := NewPrinter(tty, tty.cols, tty.rows-3)
	runner := &Runner{
		printer:     printer,
		template:    flag.Args(),
		placeholder: placeholder,
	}

	if isPipe(os.Stdin) {
		runner.stdinbuf = new(bytes.Buffer)
		io.Copy(runner.stdinbuf, os.Stdin)
	}

	input := make([]byte, 0)
	b := make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 8, 127:
			// Backspace, delete
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<|endoftext|>


<commit_before>package zwave

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

type FrameHeader uint8
type FrameType uint8

const (
	FrameHeaderData uint8 = 0x01
	FrameHeaderAck  uint8 = 0x06
	FrameHeaderNak  uint8 = 0x15
	FrameHeaderCan  uint8 = 0x18
)

const (
	FrameTypeReq uint8 = 0x00
	FrameTypeRes uint8 = 0x01
)

type ZFrame struct {

	// Header is one of FrameHeader*
	Header uint8

	// Length = byte length of all fields, excluding Header and Checksum
	Length uint8

	// Type is one of FrameType*
	Type uint8

	// Payload is the command id and command payload
	Payload []byte

	// Checksum = 0xff XOR Type XOR Length XOR payload[0] XOR [...payload[n]]
	Checksum uint8
}

func NewRequestFrame(payload []byte) *ZFrame {
	frame := ZFrame{
		Header:  FrameHeaderData,
		Type:    FrameTypeReq,
		Length:  uint8(len(payload) + 2), // payload length plus Type and Length
		Payload: payload,
	}

	frame.SetChecksum()

	return &frame
}

func NewNakFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderNak,
	}
}

func NewAckFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderAck,
	}
}

func NewCanFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderCan,
	}
}

func (z *ZFrame) IsRequest() bool {
	return z.Type == FrameTypeReq
}

func (z *ZFrame) IsResponse() bool {
	return z.Type == FrameTypeRes
}

func (z *ZFrame) IsAck() bool {
	return z.Header == FrameHeaderAck
}

func (z *ZFrame) IsNak() bool {
	return z.Header == FrameHeaderNak
}

func (z *ZFrame) IsCan() bool {
	return z.Header == FrameHeaderCan
}

func (z *ZFrame) IsData() bool {
	return z.Header == FrameHeaderData
}

// CalcChecksum calculates the checksum for this frame, given the current data.
// The Z-Wave checksum is calculated by taking 0xFF XOR Length XOR Type XOR Payload[0:n]
func (z *ZFrame) CalcChecksum() uint8 {
	var csum uint8 = 0xff
	csum ^= z.Length
	csum ^= z.Type

	for i := 0; i < len(z.Payload); i++ {
		csum ^= z.Payload[i]
	}

	return csum
}

// SetChecksum calculates the frame checksum and saves it into the frame
func (z *ZFrame) SetChecksum() {
	z.Checksum = z.CalcChecksum()
}

// VerifyChecksum calculates a checksum for the frame and compares it to the
// frame's checksum, returning an error if they do not agree
func (z *ZFrame) VerifyChecksum() error {
	if z.Header != FrameHeaderData {
		return nil
	}

	if z.Checksum != z.CalcChecksum() {
		return errors.New("Invalid checksum")
	}

	return nil
}

// Marshal this frame into a byte slice
func (z *ZFrame) Marshal() []byte {
	buf := new(bytes.Buffer)

	switch z.Header {
	case FrameHeaderData:
		// Data frames have the whole kit and caboodle
		binary.Write(buf, binary.BigEndian, uint8(z.Header))
		binary.Write(buf, binary.BigEndian, uint8(z.Length))
		binary.Write(buf, binary.BigEndian, uint8(z.Type))
		buf.Write(z.Payload)
		binary.Write(buf, binary.BigEndian, uint8(z.Checksum))
	default:
		// Non-data frames are just a single byte
		binary.Write(buf, binary.BigEndian, uint8(z.Header))
	}

	return buf.Bytes()
}

// UnmarshalFrame turns a byte slice into a ZFrame
func UnmarshalFrame(frame []byte) *ZFrame {
	if frame[0] != FrameHeaderData {
		return &ZFrame{
			Header: frame[0],
		}
	}

	return &ZFrame{
		Header:   frame[0],
		Length:   frame[1],
		Type:     frame[2],
		Payload:  frame[3 : len(frame)-2],
		Checksum: frame[len(frame)-1],
	}
}
<commit_msg>fix frame payload unmarshaling<commit_after>package zwave

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

type FrameHeader uint8
type FrameType uint8

const (
	FrameHeaderData uint8 = 0x01
	FrameHeaderAck  uint8 = 0x06
	FrameHeaderNak  uint8 = 0x15
	FrameHeaderCan  uint8 = 0x18
)

const (
	FrameTypeReq uint8 = 0x00
	FrameTypeRes uint8 = 0x01
)

type ZFrame struct {

	// Header is one of FrameHeader*
	Header uint8

	// Length = byte length of all fields, excluding Header and Checksum
	Length uint8

	// Type is one of FrameType*
	Type uint8

	// Payload is the command id and command payload
	Payload []byte

	// Checksum = 0xff XOR Type XOR Length XOR payload[0] XOR [...payload[n]]
	Checksum uint8
}

func NewRequestFrame(payload []byte) *ZFrame {
	frame := ZFrame{
		Header:  FrameHeaderData,
		Type:    FrameTypeReq,
		Length:  uint8(len(payload) + 2), // payload length plus Type and Length
		Payload: payload,
	}

	frame.SetChecksum()

	return &frame
}

func NewNakFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderNak,
	}
}

func NewAckFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderAck,
	}
}

func NewCanFrame() *ZFrame {
	return &ZFrame{
		Header: FrameHeaderCan,
	}
}

func (z *ZFrame) IsRequest() bool {
	return z.Type == FrameTypeReq
}

func (z *ZFrame) IsResponse() bool {
	return z.Type == FrameTypeRes
}

func (z *ZFrame) IsAck() bool {
	return z.Header == FrameHeaderAck
}

func (z *ZFrame) IsNak() bool {
	return z.Header == FrameHeaderNak
}

func (z *ZFrame) IsCan() bool {
	return z.Header == FrameHeaderCan
}

func (z *ZFrame) IsData() bool {
	return z.Header == FrameHeaderData
}

// CalcChecksum calculates the checksum for this frame, given the current data.
// The Z-Wave checksum is calculated by taking 0xFF XOR Length XOR Type XOR Payload[0:n]
func (z *ZFrame) CalcChecksum() uint8 {
	var csum uint8 = 0xff
	csum ^= z.Length
	csum ^= z.Type

	for i := 0; i < len(z.Payload); i++ {
		csum ^= z.Payload[i]
	}

	return csum
}

// SetChecksum calculates the frame checksum and saves it into the frame
func (z *ZFrame) SetChecksum() {
	z.Checksum = z.CalcChecksum()
}

// VerifyChecksum calculates a checksum for the frame and compares it to the
// frame's checksum, returning an error if they do not agree
func (z *ZFrame) VerifyChecksum() error {
	if z.Header != FrameHeaderData {
		return nil
	}

	if z.Checksum != z.CalcChecksum() {
		return errors.New("Invalid checksum")
	}

	return nil
}

// Marshal this frame into a byte slice
func (z *ZFrame) Marshal() []byte {
	buf := new(bytes.Buffer)

	switch z.Header {
	case FrameHeaderData:
		// Data frames have the whole kit and caboodle
		binary.Write(buf, binary.BigEndian, uint8(z.Header))
		binary.Write(buf, binary.BigEndian, uint8(z.Length))
		binary.Write(buf, binary.BigEndian, uint8(z.Type))
		buf.Write(z.Payload)
		binary.Write(buf, binary.BigEndian, uint8(z.Checksum))
	default:
		// Non-data frames are just a single byte
		binary.Write(buf, binary.BigEndian, uint8(z.Header))
	}

	return buf.Bytes()
}

// UnmarshalFrame turns a byte slice into a ZFrame
func UnmarshalFrame(frame []byte) *ZFrame {
	if frame[0] != FrameHeaderData {
		return &ZFrame{
			Header: frame[0],
		}
	}

	return &ZFrame{
		Header:   frame[0],
		Length:   frame[1],
		Type:     frame[2],
		Payload:  frame[3 : len(frame)-1],
		Checksum: frame[len(frame)-1],
	}
}
<|endoftext|>