<commit_before>package parser

import (
	"fmt"
	"math"

	"github.com/flowdev/gflowparser/data"
	"github.com/flowdev/gparselib"
)

// PortParser parses a port including optional index.
// Semantic result: data.Port
//
// flow:
//     in (ParseData)-> [pIndex gparselib.ParseAll
//                          [gparselib.ParseLiteral, gparselib.ParseNatural]
//                      ] -> out
//     in (ParseData)-> [pOpt gparselib.ParseOptional [pIndex]] -> out
//     in (ParseData)-> [gparselib.ParseAll [ParseNameIdent, pOpt]] -> out
//
// Details:
type PortParser struct {
	pName *NameIdentParser
}

// NewPortParser creates a new parser for a port.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewPortParser() (*PortParser, error) {
	pName, err := NewNameIdentParser()
	if err != nil {
		return nil, err
	}
	return &PortParser{pName: pName}, nil
}

// ParsePort is the input port of the PortParser operation.
func (p *PortParser) ParsePort(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	pColon := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `:`)
	}
	pNumber := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		pd2, ctx2, err := gparselib.ParseNatural(pd, ctx, nil, 10)
		if err != nil {
			panic(err)
		}
		return pd2, ctx2
	}
	pIndex := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd, ctx,
			[]gparselib.SubparserOp{pColon, pNumber},
			func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
				pd2.Result.Value = pd2.SubResults[1].Value
				return pd2, ctx2
			},
		)
	}
	pOpt := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, pIndex, nil)
	}
	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{p.pName.ParseNameIdent, pOpt},
		parsePortSemantic,
	)
}
func parsePortSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	val1 := pd.SubResults[1].Value
	port := data.Port{
		Name:   (pd.SubResults[0].Value).(string),
		SrcPos: pd.Result.Pos,
	}
	if val1 != nil {
		port.HasIndex = true
		port.Index = int((val1).(uint64))
	}
	pd.Result.Value = port
	return pd, ctx
}

// ArrowParser parses a flow arrow including ports and data types.
// Semantic result: data.Arrow
//
// flow:
//     in (ParseData)-> [pOptPort gparselib.ParseOptional [ParsePort]] -> out
//     in (ParseData)-> [pLeftParen gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pRightParen gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pArrow gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pData gparselib.ParseAll
//                          [pLeftParen, ParseSpaceComment,
//                           ParseTypeList, ParseSpaceComment,
//                           pRightParen, ParseOptSpc
//                          ]
//                      ] -> out
//     in (ParseData)-> [pOptData gparselib.ParseOptional [pData]] -> out
//     in (ParseData)-> [gparselib.ParseAll
//                          [pOptPort, ParseOptSpc, pOptData,
//                           pArrow, ParseOptSpc, pOptPort
//                          ]
//                      ] -> out
//
// Details:
type ArrowParser struct {
	pPort *PortParser
	pData *TypeListParser
}

// NewArrowParser creates a new parser for a flow arrow.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewArrowParser() (*ArrowParser, error) {
	pPort, err := NewPortParser()
	if err != nil {
		return nil, err
	}
	pData, err := NewTypeListParser()
	if err != nil {
		return nil, err
	}
	return &ArrowParser{pPort: pPort, pData: pData}, nil
}

// ParseArrow is the input port of the ArrowParser operation.
func (p *ArrowParser) ParseArrow(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	pOptPort := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, p.pPort.ParsePort, nil)
	}
	pLeftParen := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `(`)
	}
	pRightParen := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `)`)
	}
	pArrow := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `->`)
	}
	pData := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd, ctx,
			[]gparselib.SubparserOp{
				pLeftParen, ParseSpaceComment,
				p.pData.ParseTypeList, ParseSpaceComment,
				pRightParen, ParseOptSpc,
			},
			func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
				pd2.Result.Value = pd2.SubResults[2].Value
				return pd2, ctx2
			},
		)
	}
	pOptData := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, pData, nil)
	}

	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{
			pOptPort, ParseOptSpc, pOptData,
			pArrow, ParseOptSpc, pOptPort,
		},
		parseArrowSemantic,
	)
}
func parseArrowSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	val0 := pd.SubResults[0].Value
	val2 := pd.SubResults[2].Value
	val5 := pd.SubResults[5].Value
	arrow := data.Arrow{SrcPos: pd.Result.Pos}
	if val0 != nil {
		port := (val0).(data.Port)
		arrow.FromPort = &port
	}
	if val2 != nil {
		arrow.Data = (val2).([]data.Type)
	}
	if val5 != nil {
		port := (val5).(data.Port)
		arrow.ToPort = &port
	}
	pd.Result.Value = arrow
	return pd, ctx
}

// FlowParser parses a complete flow.
// Semantic result: data.Flow
//
// flow:
//     in (ParseData)-> [pAnyPart gparselib.ParseAny [ParseArrow, ParseComponent]] -> out
//     in (ParseData)-> [pFullPart gparselib.ParseAll [pAnyPart, ParseOptSpc]] -> out
//     in (ParseData)-> [pPartString gparselib.ParseMulti [pFullPart]] -> out
//     in (ParseData)-> [pPartLine gparselib.ParseAll
//                          [pPartString, ParseStatementEnd]
//                      ] -> out
//     in (ParseData)-> [gparselib.ParseMulti1 [pPartLine]] -> out
//
// Details:
type FlowParser struct {
	pArrow *ArrowParser
	pComp  *ComponentParser
}

// Error messages for semantic errors.
const (
	errMsg2Arrows = "A flow line must contain alternating arrows and components " +
		"but this one has got two consecutive arrows at position %d"
	errMsg2Comps = "A flow line must contain alternating arrows and components " +
		"but this one has got two consecutive components at position %d"
	errMsgPartType = "A flow line must only contain arrows and components " +
		"but this one has got a %T at position %d"
	errMsgFirstPort = "The first arrow of this flow line is missing a source port"
	errMsgLastPort  = "The last arrow of this flow line is missing a destination port"
	errMsgFirstData = "The first arrow of this flow line is missing its data declaration"
)

// NewFlowParser creates a new parser for a flow.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewFlowParser() (*FlowParser, error) {
	pArrow, err := NewArrowParser()
	if err != nil {
		return nil, err
	}
	pComp, err := NewParseComponent()
	if err != nil {
		return nil, err
	}
	return &FlowParser{pArrow: pArrow, pComp: pComp}, nil
}

// ParseFlow is the input port of the FlowParser operation.
func (p *FlowParser) ParseFlow(pd *gparselib.ParseData, ctx interface{},
) (*gparselib.ParseData, interface{}) {
	pAnyPart := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAny(
			pd2, ctx2,
			[]gparselib.SubparserOp{p.pArrow.ParseArrow, p.pComp.ParseComponent},
			nil,
		)
	}
	pFullPart := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd2, ctx2,
			[]gparselib.SubparserOp{pAnyPart, ParseOptSpc},
			func(pd3 *gparselib.ParseData, ctx3 interface{}) (*gparselib.ParseData, interface{}) {
				pd3.Result.Value = pd3.SubResults[0].Value
				return pd3, ctx3
			},
		)
	}
	pPartString := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseMulti(pd2, ctx2, pFullPart, nil, 2, math.MaxInt32)
	}
	pPartLine := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd2, ctx2,
			[]gparselib.SubparserOp{pPartString, ParseStatementEnd},
			parsePartLineSemantic,
		)
	}
	pLines := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseMulti1(pd2, ctx2, pPartLine, parseFlowSemantic)
	}
	pEOF := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseEOF(pd2, ctx2, nil)
	}
	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{pLines, pEOF},
		func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
			pd2.Result.Value = pd2.SubResults[0].Value
			return pd2, ctx2
		},
	)
}
func parsePartLineSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	partLine := pd.SubResults[0].Value.([]interface{})
	n := len(partLine)

	var lastIsArrow, lastIsComp bool
	for i, part := range partLine {
		switch v := part.(type) {
		case data.Arrow:
			if lastIsArrow {
				pd.AddError(v.SrcPos, fmt.Sprintf(errMsg2Arrows, i+1), nil)
				return pd, ctx
			}
			lastIsArrow = true
			lastIsComp = false
		case data.Component:
			if lastIsComp {
				pd.AddError(v.SrcPos, fmt.Sprintf(errMsg2Comps, i+1), nil)
				return pd, ctx
			}
			lastIsComp = true
			lastIsArrow = false
		default:
			pd.AddError(pd.Result.Pos, fmt.Sprintf(errMsgPartType, part, i+1), nil)
			return pd, ctx
		}
	}
	var firstArrow data.Arrow
	if v, ok := partLine[0].(data.Arrow); ok {
		firstArrow = v
		if firstArrow.FromPort == nil {
			pd.AddError(pd.Result.Pos, errMsgFirstPort, nil)
		}
	} else {
		firstArrow = partLine[1].(data.Arrow)
	}
	if len(firstArrow.Data) == 0 {
		pd.AddError(pd.Result.Pos, errMsgFirstData, nil)
	}
	if lastArrow, ok := partLine[n-1].(data.Arrow); ok {
		if lastArrow.ToPort == nil {
			pd.AddError(pd.Result.Pos, errMsgLastPort, nil)
		}
	}
	if !pd.Result.HasError() {
		pd.Result.Value = partLine
	}
	return pd, ctx
}
func parseFlowSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	lines := make([][]interface{}, len(pd.SubResults))
	for i, subResult := range pd.SubResults {
		line := subResult.Value.([]interface{})
		lines[i] = line
	}
	pd.Result.Value = data.Flow{
		Parts: lines,
	}
	return pd, ctx
}
<commit_msg>Fix parser bug<commit_after>package parser

import (
	"fmt"
	"math"

	"github.com/flowdev/gflowparser/data"
	"github.com/flowdev/gparselib"
)

// PortParser parses a port including optional index.
// Semantic result: data.Port
//
// flow:
//     in (ParseData)-> [pIndex gparselib.ParseAll
//                          [gparselib.ParseLiteral, gparselib.ParseNatural]
//                      ] -> out
//     in (ParseData)-> [pOpt gparselib.ParseOptional [pIndex]] -> out
//     in (ParseData)-> [gparselib.ParseAll [ParseNameIdent, pOpt]] -> out
//
// Details:
type PortParser struct {
	pName *NameIdentParser
}

// NewPortParser creates a new parser for a port.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewPortParser() (*PortParser, error) {
	pName, err := NewNameIdentParser()
	if err != nil {
		return nil, err
	}
	return &PortParser{pName: pName}, nil
}

// ParsePort is the input port of the PortParser operation.
func (p *PortParser) ParsePort(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	pColon := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `:`)
	}
	pNumber := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		pd2, ctx2, err := gparselib.ParseNatural(pd, ctx, nil, 10)
		if err != nil {
			panic(err)
		}
		return pd2, ctx2
	}
	pIndex := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd, ctx,
			[]gparselib.SubparserOp{pColon, pNumber},
			func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
				pd2.Result.Value = pd2.SubResults[1].Value
				return pd2, ctx2
			},
		)
	}
	pOpt := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, pIndex, nil)
	}
	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{p.pName.ParseNameIdent, pOpt},
		parsePortSemantic,
	)
}
func parsePortSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	val1 := pd.SubResults[1].Value
	port := data.Port{
		Name:   (pd.SubResults[0].Value).(string),
		SrcPos: pd.Result.Pos,
	}
	if val1 != nil {
		port.HasIndex = true
		port.Index = int((val1).(uint64))
	}
	pd.Result.Value = port
	return pd, ctx
}

// ArrowParser parses a flow arrow including ports and data types.
// Semantic result: data.Arrow
//
// flow:
//     in (ParseData)-> [pOptPort gparselib.ParseOptional [ParsePort]] -> out
//     in (ParseData)-> [pLeftParen gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pRightParen gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pArrow gparselib.ParseLiteral] -> out
//     in (ParseData)-> [pData gparselib.ParseAll
//                          [pLeftParen, ParseSpaceComment,
//                           ParseTypeList, ParseSpaceComment,
//                           pRightParen, ParseOptSpc
//                          ]
//                      ] -> out
//     in (ParseData)-> [pOptData gparselib.ParseOptional [pData]] -> out
//     in (ParseData)-> [gparselib.ParseAll
//                          [pOptPort, ParseOptSpc, pOptData,
//                           pArrow, ParseOptSpc, pOptPort
//                          ]
//                      ] -> out
//
// Details:
type ArrowParser struct {
	pPort *PortParser
	pData *TypeListParser
}

// NewArrowParser creates a new parser for a flow arrow.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewArrowParser() (*ArrowParser, error) {
	pPort, err := NewPortParser()
	if err != nil {
		return nil, err
	}
	pData, err := NewTypeListParser()
	if err != nil {
		return nil, err
	}
	return &ArrowParser{pPort: pPort, pData: pData}, nil
}

// ParseArrow is the input port of the ArrowParser operation.
func (p *ArrowParser) ParseArrow(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	pOptPort := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, p.pPort.ParsePort, nil)
	}
	pLeftParen := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `(`)
	}
	pRightParen := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `)`)
	}
	pArrow := func(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseLiteral(pd, ctx, nil, `->`)
	}
	pData := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd, ctx,
			[]gparselib.SubparserOp{
				pLeftParen, ParseSpaceComment,
				p.pData.ParseTypeList, ParseSpaceComment,
				pRightParen, ParseOptSpc,
			},
			func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
				pd2.Result.Value = pd2.SubResults[2].Value
				return pd2, ctx2
			},
		)
	}
	pOptData := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseOptional(pd2, ctx2, pData, nil)
	}

	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{
			pOptPort, ParseOptSpc, pOptData,
			pArrow, ParseOptSpc, pOptPort,
		},
		parseArrowSemantic,
	)
}
func parseArrowSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	val0 := pd.SubResults[0].Value
	val2 := pd.SubResults[2].Value
	val5 := pd.SubResults[5].Value
	arrow := data.Arrow{SrcPos: pd.Result.Pos}
	if val0 != nil {
		port := (val0).(data.Port)
		arrow.FromPort = &port
	}
	if val2 != nil {
		arrow.Data = (val2).([]data.Type)
	}
	if val5 != nil {
		port := (val5).(data.Port)
		arrow.ToPort = &port
	}
	pd.Result.Value = arrow
	return pd, ctx
}

// FlowParser parses a complete flow.
// Semantic result: data.Flow
//
// flow:
//     in (ParseData)-> [pAnyPart gparselib.ParseAny [ParseArrow, ParseComponent]] -> out
//     in (ParseData)-> [pFullPart gparselib.ParseAll [pAnyPart, ParseOptSpc]] -> out
//     in (ParseData)-> [pPartString gparselib.ParseMulti [pFullPart]] -> out
//     in (ParseData)-> [pPartLine gparselib.ParseAll
//                          [pPartString, ParseStatementEnd]
//                      ] -> out
//     in (ParseData)-> [gparselib.ParseMulti1 [pPartLine]] -> out
//
// Details:
type FlowParser struct {
	pArrow *ArrowParser
	pComp  *ComponentParser
}

// Error messages for semantic errors.
const (
	errMsg2Arrows = "A flow line must contain alternating arrows and components " +
		"but this one has got two consecutive arrows at position %d"
	errMsg2Comps = "A flow line must contain alternating arrows and components " +
		"but this one has got two consecutive components at position %d"
	errMsgPartType = "A flow line must only contain arrows and components " +
		"but this one has got a %T at position %d"
	errMsgFirstPort = "The first arrow of this flow line is missing a source port"
	errMsgLastPort  = "The last arrow of this flow line is missing a destination port"
	errMsgFirstData = "The first arrow of this flow line is missing its data declaration"
)

// NewFlowParser creates a new parser for a flow.
// If any regular expression used by the subparsers is invalid an error is
// returned.
func NewFlowParser() (*FlowParser, error) {
	pArrow, err := NewArrowParser()
	if err != nil {
		return nil, err
	}
	pComp, err := NewParseComponent()
	if err != nil {
		return nil, err
	}
	return &FlowParser{pArrow: pArrow, pComp: pComp}, nil
}

// ParseFlow is the input port of the FlowParser operation.
func (p *FlowParser) ParseFlow(pd *gparselib.ParseData, ctx interface{},
) (*gparselib.ParseData, interface{}) {
	pAnyPart := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAny(
			pd2, ctx2,
			[]gparselib.SubparserOp{p.pArrow.ParseArrow, p.pComp.ParseComponent},
			nil,
		)
	}
	pFullPart := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd2, ctx2,
			[]gparselib.SubparserOp{pAnyPart, ParseOptSpc},
			func(pd3 *gparselib.ParseData, ctx3 interface{}) (*gparselib.ParseData, interface{}) {
				pd3.Result.Value = pd3.SubResults[0].Value
				return pd3, ctx3
			},
		)
	}
	pPartString := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseMulti(pd2, ctx2, pFullPart, nil, 2, math.MaxInt32)
	}
	pPartLine := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseAll(pd2, ctx2,
			[]gparselib.SubparserOp{pPartString, ParseStatementEnd},
			parsePartLineSemantic,
		)
	}
	pLines := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseMulti1(pd2, ctx2, pPartLine, parseFlowSemantic)
	}
	pEOF := func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
		return gparselib.ParseEOF(pd2, ctx2, nil)
	}
	return gparselib.ParseAll(pd, ctx,
		[]gparselib.SubparserOp{pLines, pEOF},
		func(pd2 *gparselib.ParseData, ctx2 interface{}) (*gparselib.ParseData, interface{}) {
			pd2.Result.Value = pd2.SubResults[0].Value
			return pd2, ctx2
		},
	)
}
func parsePartLineSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	partLine := pd.SubResults[0].Value.([]interface{})
	n := len(partLine)

	var lastIsArrow, lastIsComp bool
	for i, part := range partLine {
		switch v := part.(type) {
		case data.Arrow:
			if lastIsArrow {
				pd.AddError(v.SrcPos, fmt.Sprintf(errMsg2Arrows, i+1), nil)
				return pd, ctx
			}
			lastIsArrow = true
			lastIsComp = false
		case data.Component:
			if lastIsComp {
				pd.AddError(v.SrcPos, fmt.Sprintf(errMsg2Comps, i+1), nil)
				return pd, ctx
			}
			lastIsComp = true
			lastIsArrow = false
		default:
			pd.AddError(pd.Result.Pos, fmt.Sprintf(errMsgPartType, part, i+1), nil)
			return pd, ctx
		}
	}
	var firstArrow data.Arrow
	if v, ok := partLine[0].(data.Arrow); ok {
		firstArrow = v
		if firstArrow.FromPort == nil {
			pd.AddError(pd.Result.Pos, errMsgFirstPort, nil)
		}
	} else {
		firstArrow = partLine[1].(data.Arrow)
	}
	if len(firstArrow.Data) == 0 {
		pd.AddError(pd.Result.Pos, errMsgFirstData, nil)
	}
	if lastArrow, ok := partLine[n-1].(data.Arrow); ok {
		if lastArrow.ToPort == nil {
			pd.AddError(pd.Result.Pos, errMsgLastPort, nil)
		}
	}
	if !pd.Result.HasError() {
		pd.Result.Value = partLine
	} else {
		pd.ResetSourcePos(-1)
	}
	return pd, ctx
}
func parseFlowSemantic(pd *gparselib.ParseData, ctx interface{}) (*gparselib.ParseData, interface{}) {
	lines := make([][]interface{}, len(pd.SubResults))
	for i, subResult := range pd.SubResults {
		line := subResult.Value.([]interface{})
		lines[i] = line
	}
	pd.Result.Value = data.Flow{
		Parts: lines,
	}
	return pd, ctx
}
<|endoftext|>


<commit_before>package parser

import (
	"go/ast"
	"go/token"
	"testing"

	"github.com/nordsieck/defect"
)

const (
	minimal = `package main
`
	comment = `// comment
package main
`
	comments = `// comment
package main

// foo
// bar
`
	simple = `package main

func main() {}
`
	hello = `package main

import "fmt"

func main() {
	fmt.Println("hello world")
}
`
)

func TestParseFile_Minimal(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", minimal)
	defect.Equal(t, err, nil)
	defect.DeepEqual(t, *file.File, ast.File{
		Package: token.Pos(1),
		Name:    &ast.Ident{NamePos: 9, Name: "main"},
		Scope:   &ast.Scope{Objects: map[string]*ast.Object{}},
	})
}

func TestParseFile_Comment(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", comment)
	defect.Equal(t, err, nil)
	comment := &ast.Comment{Slash: 1, Text: "// comment"}
	defect.DeepEqual(t, *file.File, ast.File{
		Doc:      &ast.CommentGroup{List: []*ast.Comment{comment}},
		Package:  token.Pos(12),
		Name:     &ast.Ident{NamePos: 20, Name: "main"},
		Scope:    &ast.Scope{Objects: map[string]*ast.Object{}},
		Comments: []*ast.CommentGroup{{List: []*ast.Comment{comment}}},
	})
}

func TestParseFile_Comments(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", comments)
	defect.Equal(t, err, nil)
	top := &ast.CommentGroup{List: []*ast.Comment{{Slash: 1, Text: "// comment"}}}
	next := &ast.CommentGroup{List: []*ast.Comment{
		{Slash: 26, Text: "// foo"},
		{Slash: 33, Text: "// bar"},
	}}

	defect.DeepEqual(t, *file.File, ast.File{
		Doc:      top,
		Package:  token.Pos(12),
		Name:     &ast.Ident{NamePos: 20, Name: "main"},
		Scope:    &ast.Scope{Objects: map[string]*ast.Object{}},
		Comments: []*ast.CommentGroup{top, next},
	})
}

func TestParseFile_Simple(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", simple)
	defect.Equal(t, err, nil)

	block := &ast.BlockStmt{Lbrace: 27, Rbrace: 28}
	obj := &ast.Object{Kind: ast.Fun, Name: "main", Decl: block}
	fnDecl := &ast.FuncDecl{
		Name: &ast.Ident{Name: "main", NamePos: 20, Obj: obj},
		Type: &ast.FuncType{
			Func:   15,
			Params: &ast.FieldList{Opening: 24, Closing: 25},
		},
		Body: block,
	}
	obj.Decl = fnDecl

	defect.DeepEqual(t, file.File, &ast.File{
		Package: 1,
		Name:    &ast.Ident{NamePos: 9, Name: "main"},
		Decls:   []ast.Decl{fnDecl},
		Scope:   &ast.Scope{Objects: map[string]*ast.Object{"main": obj}},
	})
}

func TestParseFile_Hello(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", hello)
	defect.Equal(t, err, nil)

	expectedDecl := &ast.BlockStmt{
		Lbrace: 41,
		List: []ast.Stmt{
			&ast.ExprStmt{
				X: &ast.CallExpr{
					Fun: &ast.SelectorExpr{
						X:   &ast.Ident{NamePos: 44, Name: "fmt"},
						Sel: &ast.Ident{NamePos: 48, Name: "Println"},
					},
					Lparen: 55,
					Args: []ast.Expr{&ast.BasicLit{
						ValuePos: 56,
						Kind:     token.STRING,
						Value:    `"hello world"`,
					}},
					Rparen: 69,
				},
			},
		},
		Rbrace: 71,
	}

	obj := &ast.Object{
		Kind: ast.Fun,
		Name: "main",
		Decl: expectedDecl,
	}

	fnDecl := &ast.FuncDecl{
		Name: &ast.Ident{Name: "main", NamePos: 34, Obj: obj},
		Type: &ast.FuncType{
			Func:   29,
			Params: &ast.FieldList{Opening: 38, Closing: 39},
		},
		Body: expectedDecl,
	}

	obj.Decl = fnDecl

	importSpec := &ast.ImportSpec{Path: &ast.BasicLit{
		ValuePos: 22,
		Kind:     token.STRING,
		Value:    `"fmt"`,
	}}

	genDecl := &ast.GenDecl{
		TokPos: 15,
		Tok:    token.IMPORT,
		Specs:  []ast.Spec{importSpec},
	}

	defect.DeepEqual(t, file.File, &ast.File{
		Package:    1,
		Name:       &ast.Ident{NamePos: 9, Name: "main"},
		Decls:      []ast.Decl{genDecl, fnDecl},
		Scope:      &ast.Scope{Objects: map[string]*ast.Object{"main": obj}},
		Imports:    []*ast.ImportSpec{importSpec},
		Unresolved: []*ast.Ident{{NamePos: 44, Name: "fmt"}},
	})
}
<commit_msg>Remove unnecessary pre-declaration<commit_after>package parser

import (
	"go/ast"
	"go/token"
	"testing"

	"github.com/nordsieck/defect"
)

const (
	minimal = `package main
`
	comment = `// comment
package main
`
	comments = `// comment
package main

// foo
// bar
`
	simple = `package main

func main() {}
`
	hello = `package main

import "fmt"

func main() {
	fmt.Println("hello world")
}
`
)

func TestParseFile_Minimal(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", minimal)
	defect.Equal(t, err, nil)
	defect.DeepEqual(t, *file.File, ast.File{
		Package: token.Pos(1),
		Name:    &ast.Ident{NamePos: 9, Name: "main"},
		Scope:   &ast.Scope{Objects: map[string]*ast.Object{}},
	})
}

func TestParseFile_Comment(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", comment)
	defect.Equal(t, err, nil)
	comment := &ast.Comment{Slash: 1, Text: "// comment"}
	defect.DeepEqual(t, *file.File, ast.File{
		Doc:      &ast.CommentGroup{List: []*ast.Comment{comment}},
		Package:  token.Pos(12),
		Name:     &ast.Ident{NamePos: 20, Name: "main"},
		Scope:    &ast.Scope{Objects: map[string]*ast.Object{}},
		Comments: []*ast.CommentGroup{{List: []*ast.Comment{comment}}},
	})
}

func TestParseFile_Comments(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", comments)
	defect.Equal(t, err, nil)
	top := &ast.CommentGroup{List: []*ast.Comment{{Slash: 1, Text: "// comment"}}}
	next := &ast.CommentGroup{List: []*ast.Comment{
		{Slash: 26, Text: "// foo"},
		{Slash: 33, Text: "// bar"},
	}}

	defect.DeepEqual(t, *file.File, ast.File{
		Doc:      top,
		Package:  token.Pos(12),
		Name:     &ast.Ident{NamePos: 20, Name: "main"},
		Scope:    &ast.Scope{Objects: map[string]*ast.Object{}},
		Comments: []*ast.CommentGroup{top, next},
	})
}

func TestParseFile_Simple(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", simple)
	defect.Equal(t, err, nil)

	block := &ast.BlockStmt{Lbrace: 27, Rbrace: 28}
	obj := &ast.Object{Kind: ast.Fun, Name: "main"}
	fnDecl := &ast.FuncDecl{
		Name: &ast.Ident{Name: "main", NamePos: 20, Obj: obj},
		Type: &ast.FuncType{
			Func:   15,
			Params: &ast.FieldList{Opening: 24, Closing: 25},
		},
		Body: block,
	}
	obj.Decl = fnDecl

	defect.DeepEqual(t, file.File, &ast.File{
		Package: 1,
		Name:    &ast.Ident{NamePos: 9, Name: "main"},
		Decls:   []ast.Decl{fnDecl},
		Scope:   &ast.Scope{Objects: map[string]*ast.Object{"main": obj}},
	})
}

func TestParseFile_Hello(t *testing.T) {
	fs := token.NewFileSet()
	file, err := ParseFile(fs, "a.go", hello)
	defect.Equal(t, err, nil)

	expectedDecl := &ast.BlockStmt{
		Lbrace: 41,
		List: []ast.Stmt{
			&ast.ExprStmt{
				X: &ast.CallExpr{
					Fun: &ast.SelectorExpr{
						X:   &ast.Ident{NamePos: 44, Name: "fmt"},
						Sel: &ast.Ident{NamePos: 48, Name: "Println"},
					},
					Lparen: 55,
					Args: []ast.Expr{&ast.BasicLit{
						ValuePos: 56,
						Kind:     token.STRING,
						Value:    `"hello world"`,
					}},
					Rparen: 69,
				},
			},
		},
		Rbrace: 71,
	}

	obj := &ast.Object{
		Kind: ast.Fun,
		Name: "main",
		Decl: expectedDecl,
	}

	fnDecl := &ast.FuncDecl{
		Name: &ast.Ident{Name: "main", NamePos: 34, Obj: obj},
		Type: &ast.FuncType{
			Func:   29,
			Params: &ast.FieldList{Opening: 38, Closing: 39},
		},
		Body: expectedDecl,
	}

	obj.Decl = fnDecl

	importSpec := &ast.ImportSpec{Path: &ast.BasicLit{
		ValuePos: 22,
		Kind:     token.STRING,
		Value:    `"fmt"`,
	}}

	genDecl := &ast.GenDecl{
		TokPos: 15,
		Tok:    token.IMPORT,
		Specs:  []ast.Spec{importSpec},
	}

	defect.DeepEqual(t, file.File, &ast.File{
		Package:    1,
		Name:       &ast.Ident{NamePos: 9, Name: "main"},
		Decls:      []ast.Decl{genDecl, fnDecl},
		Scope:      &ast.Scope{Objects: map[string]*ast.Object{"main": obj}},
		Imports:    []*ast.ImportSpec{importSpec},
		Unresolved: []*ast.Ident{{NamePos: 44, Name: "fmt"}},
	})
}
<|endoftext|>


<commit_before>package filter

import (
	"fmt"

	"github.com/cloudfoundry/sonde-go/events"
	"github.com/evandbrown/gcp-tools-release/src/stackdriver-nozzle/firehose"
)

type filter struct {
	dest    firehose.FirehoseHandler
	enabled map[events.Envelope_EventType]bool
}

func parseEventName(name string) (events.Envelope_EventType, error) {
	if eventId, ok := events.Envelope_EventType_value[name]; ok {
		return events.Envelope_EventType(eventId), nil
	}
	return events.Envelope_Error, fmt.Errorf("unknown event name: %s", name)
}

func New(dest firehose.FirehoseHandler, eventNames []string) (firehose.FirehoseHandler, error) {
	f := filter{dest: dest, enabled: make(map[events.Envelope_EventType]bool)}

	for _, eventName := range eventNames {
		eventType, err := parseEventName(eventName)

		if err != nil {
			return nil, err
		}

		f.enabled[eventType] = true
	}

	return &f, nil
}

func (f *filter) HandleEvent(envelope *events.Envelope) error {
	if !f.enabled[envelope.GetEventType()] {
		return nil
	}
	return f.dest.HandleEvent(envelope)
}

func DisplayValidEvents() {
	fmt.Printf("Valid event choices:")
	for _, name := range events.Envelope_EventType_name {
		fmt.Printf("- ", name)
	}
}
<commit_msg>Use fmt.Prinln instead<commit_after>package filter

import (
	"fmt"

	"github.com/cloudfoundry/sonde-go/events"
	"github.com/evandbrown/gcp-tools-release/src/stackdriver-nozzle/firehose"
)

type filter struct {
	dest    firehose.FirehoseHandler
	enabled map[events.Envelope_EventType]bool
}

func parseEventName(name string) (events.Envelope_EventType, error) {
	if eventId, ok := events.Envelope_EventType_value[name]; ok {
		return events.Envelope_EventType(eventId), nil
	}
	return events.Envelope_Error, fmt.Errorf("unknown event name: %s", name)
}

func New(dest firehose.FirehoseHandler, eventNames []string) (firehose.FirehoseHandler, error) {
	f := filter{dest: dest, enabled: make(map[events.Envelope_EventType]bool)}

	for _, eventName := range eventNames {
		eventType, err := parseEventName(eventName)

		if err != nil {
			return nil, err
		}

		f.enabled[eventType] = true
	}

	return &f, nil
}

func (f *filter) HandleEvent(envelope *events.Envelope) error {
	if !f.enabled[envelope.GetEventType()] {
		return nil
	}
	return f.dest.HandleEvent(envelope)
}

func DisplayValidEvents() {
	fmt.Println("Valid event choices:")
	for _, name := range events.Envelope_EventType_name {
		fmt.Println("- ", name)
	}
}
<|endoftext|>


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

package typeconstructor

import (
	"fmt"
	"strings"

	"github.com/serulian/compiler/compilercommon"
	"github.com/serulian/compiler/compilergraph"
	"github.com/serulian/compiler/graphs/typegraph"
	"github.com/serulian/compiler/webidl"
)

// GLOBAL_CONTEXT_ANNOTATIONS are the annotations that mark an interface as being a global context
// (e.g. Window) in WebIDL.
var GLOBAL_CONTEXT_ANNOTATIONS = []string{"Global", "PrimaryGlobal"}

// CONSTRUCTOR_ANNOTATION is an annotation that describes support for a constructor on a WebIDL
// type. This translates to being able to do "new Type(...)" in ECMAScript.
const CONSTRUCTOR_ANNOTATION = "Constructor"

// NATIVE_OPERATOR_ANNOTATION is an annotation that marks an declaration as supporting the
// specified operator natively (i.e. not a custom defined operator).
const NATIVE_OPERATOR_ANNOTATION = "NativeOperator"

// SPECIALIZATION_NAMES maps WebIDL member specializations into Serulian typegraph names.
var SPECIALIZATION_NAMES = map[webidl.MemberSpecialization]string{
	webidl.GetterSpecialization: "index",
	webidl.SetterSpecialization: "setindex",
}

// SERIALIZABLE_OPS defines the WebIDL custom ops that mark a type as serializable.
var SERIALIZABLE_OPS = map[string]bool{
	"jsonifier":  true,
	"serializer": true,
}

// NATIVE_TYPES maps from the predefined WebIDL types to the type actually supported
// in ES. We lose some information by doing so, but it allows for compatibility
// with existing WebIDL specifications. In the future, we might find a way to
// have these types be used in a more specific manner.
var NATIVE_TYPES = map[string]string{
	"boolean":             "Boolean",
	"byte":                "Number",
	"octet":               "Number",
	"short":               "Number",
	"unsigned short":      "Number",
	"long":                "Number",
	"unsigned long":       "Number",
	"long long":           "Number",
	"float":               "Number",
	"double":              "Number",
	"unrestricted float":  "Number",
	"unrestricted double": "Number",
}

// GetConstructor returns a TypeGraph constructor for the given IRG.
func GetConstructor(irg *webidl.WebIRG) *irgTypeConstructor {
	return &irgTypeConstructor{
		irg: irg,
	}
}

// irgTypeConstructor defines a type for populating a type graph from the IRG.
type irgTypeConstructor struct {
	irg *webidl.WebIRG // The IRG being transformed.
}

func (itc *irgTypeConstructor) DefineModules(builder typegraph.GetModuleBuilder) {
	for _, module := range itc.irg.GetModules() {
		builder().
			Name(module.Name()).
			Path(string(module.InputSource())).
			SourceNode(module.Node()).
			Define()
	}
}

func (itc *irgTypeConstructor) DefineTypes(builder typegraph.GetTypeBuilder) {
	for _, module := range itc.irg.GetModules() {
		for _, declaration := range module.Declarations() {
			if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
				continue
			}

			typeBuilder := builder(module.Node())

			for _, customop := range declaration.CustomOperations() {
				if _, ok := SERIALIZABLE_OPS[customop]; ok {
					typeBuilder.WithAttribute(typegraph.SERIALIZABLE_ATTRIBUTE)
				}
			}

			typeBuilder.Name(declaration.Name()).
				SourceNode(declaration.GraphNode).
				TypeKind(typegraph.ExternalInternalType).
				Define()
		}
	}
}

func (itc *irgTypeConstructor) DefineDependencies(annotator typegraph.Annotator, graph *typegraph.TypeGraph) {
	for _, module := range itc.irg.GetModules() {
		for _, declaration := range module.Declarations() {
			if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
				continue
			}

			parentTypeString, hasParentType := declaration.ParentType()
			if !hasParentType {
				continue
			}

			parentType, err := itc.ResolveType(parentTypeString, graph)
			if err != nil {
				annotator.ReportError(declaration.GraphNode, "%v", err)
				continue
			}

			annotator.DefineParentType(declaration.GraphNode, parentType)
		}
	}
}

func (itc *irgTypeConstructor) DefineMembers(builder typegraph.GetMemberBuilder, reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
	for _, declaration := range itc.irg.Declarations() {
		// Global members get defined under their module, not their declaration.
		var parentNode = declaration.GraphNode
		if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
			parentNode = declaration.Module().GraphNode
		}

		// If the declaration has one (or more) constructors, add then as a "new".
		if declaration.HasAnnotation(CONSTRUCTOR_ANNOTATION) {
			// Declare a "new" member which returns an instance of this type.
			builder(parentNode, false).
				Name("new").
				SourceNode(declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION)[0].GraphNode).
				Define()
		}

		// Add support for any native operators.
		if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) && declaration.HasAnnotation(NATIVE_OPERATOR_ANNOTATION) {
			reporter.ReportError(declaration.GraphNode, "[NativeOperator] not supported on declarations marked with [GlobalContext]")
			return
		}

		for _, nativeOp := range declaration.GetAnnotations(NATIVE_OPERATOR_ANNOTATION) {
			opName, hasOpName := nativeOp.Value()
			if !hasOpName {
				reporter.ReportError(nativeOp.GraphNode, "Missing operator name on [NativeOperator] annotation")
				continue
			}

			// Lookup the operator under the type graph.
			opDefinition, found := graph.GetOperatorDefinition(opName)
			if !found || !opDefinition.IsStatic {
				reporter.ReportError(nativeOp.GraphNode, "Unknown native operator '%v'", opName)
				continue
			}

			// Add the operator to the type.
			builder(parentNode, true).
				Name(opName).
				SourceNode(nativeOp.GraphNode).
				Define()
		}

		// Add the declared members and specializations.
		for _, member := range declaration.Members() {
			name, hasName := member.Name()
			if hasName {
				builder(parentNode, false).
					Name(name).
					SourceNode(member.GraphNode).
					Define()
			} else {
				// This is a specialization.
				specialization, _ := member.Specialization()
				builder(parentNode, true).
					Name(SPECIALIZATION_NAMES[specialization]).
					SourceNode(member.GraphNode).
					Define()
			}
		}
	}
}

func (itc *irgTypeConstructor) DecorateMembers(decorator typegraph.GetMemberDecorator, reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
	for _, declaration := range itc.irg.Declarations() {
		if declaration.HasAnnotation(CONSTRUCTOR_ANNOTATION) {
			// For each constructor defined, create the intersection of their parameters.
			var parameters = make([]typegraph.TypeReference, 0)
			for constructorIndex, constructor := range declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION) {
				for index, parameter := range constructor.Parameters() {
					parameterType, err := itc.ResolveType(parameter.DeclaredType(), graph)
					if err != nil {
						reporter.ReportError(parameter.GraphNode, "%v", err)
						continue
					}

					var resolvedParameterType = parameterType
					if parameter.IsOptional() {
						resolvedParameterType = resolvedParameterType.AsNullable()
					}

					if index >= len(parameters) {
						// If this is not the first constructor, then this parameter is implicitly optional
						// and therefore nullable.
						if constructorIndex > 0 {
							resolvedParameterType = resolvedParameterType.AsNullable()
						}

						parameters = append(parameters, resolvedParameterType)
					} else {
						parameters[index] = parameters[index].Intersect(resolvedParameterType)
					}
				}
			}

			// Define the construction function for the type.
			typeDecl, _ := graph.GetTypeForSourceNode(declaration.GraphNode)
			var constructorFunction = graph.FunctionTypeReference(typeDecl.GetTypeReference())
			for _, parameterType := range parameters {
				constructorFunction = constructorFunction.WithParameter(parameterType)
			}

			decorator(declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION)[0].GraphNode).
				Exported(true).
				Static(true).
				ReadOnly(true).
				MemberKind(uint64(webidl.ConstructorMember)).
				MemberType(constructorFunction).
				Decorate()
		}

		for _, nativeOp := range declaration.GetAnnotations(NATIVE_OPERATOR_ANNOTATION) {
			opName, hasOpName := nativeOp.Value()
			if !hasOpName {
				continue
			}

			opDefinition, found := graph.GetOperatorDefinition(opName)
			if !found {
				continue
			}

			// Define the operator's member type based on the definition.
			typeDecl, _ := graph.GetTypeForSourceNode(declaration.GraphNode)

			var expectedReturnType = opDefinition.ExpectedReturnType(typeDecl.GetTypeReference())
			if expectedReturnType.HasReferredType(graph.BoolType()) {
				expectedReturnType, _ = itc.ResolveType("Boolean", graph)
			}

			var operatorType = graph.FunctionTypeReference(expectedReturnType)
			for _, parameter := range opDefinition.Parameters {
				operatorType = operatorType.WithParameter(parameter.ExpectedType(typeDecl.GetTypeReference()))
			}

			// Add the operator to the type.
			decorator(nativeOp.GraphNode).
				Native(true).
				Exported(true).
				SkipOperatorChecking(true).
				MemberType(operatorType).
				MemberKind(uint64(webidl.OperatorMember)).
				Decorate()
		}

		// Add the declared members.
		for _, member := range declaration.Members() {
			declaredType, err := itc.ResolveType(member.DeclaredType(), graph)
			if err != nil {
				reporter.ReportError(member.GraphNode, "%v", err)
				continue
			}

			var memberType = declaredType
			var isReadonly = member.IsReadonly()

			switch member.Kind() {
			case webidl.FunctionMember:
				isReadonly = true
				memberType = graph.FunctionTypeReference(memberType)

				// Add the parameter types.
				var markOptional = false
				for _, parameter := range member.Parameters() {
					if parameter.IsOptional() {
						markOptional = true
					}

					parameterType, err := itc.ResolveType(parameter.DeclaredType(), graph)
					if err != nil {
						reporter.ReportError(member.GraphNode, "%v", err)
						continue
					}

					// All optional parameters get marked as nullable, which means we can skip
					// passing them on function calls.
					if markOptional {
						memberType = memberType.WithParameter(parameterType.AsNullable())
					} else {
						memberType = memberType.WithParameter(parameterType)
					}
				}

			case webidl.AttributeMember:
				if len(member.Parameters()) > 0 {
					reporter.ReportError(member.GraphNode, "Attributes cannot have parameters")
				}

			default:
				panic("Unknown WebIDL member kind")
			}

			decorator := decorator(member.GraphNode)
			if _, hasName := member.Name(); !hasName {
				decorator.Native(true)
			}

			decorator.Exported(true).
				Static(member.IsStatic()).
				ReadOnly(isReadonly).
				MemberKind(uint64(member.Kind())).
				MemberType(memberType).
				Decorate()
		}
	}
}

func (itc *irgTypeConstructor) Validate(reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
}

func (itc *irgTypeConstructor) GetLocation(sourceNodeId compilergraph.GraphNodeId) (compilercommon.SourceAndLocation, bool) {
	layerNode, found := itc.irg.TryGetNode(sourceNodeId)
	if !found {
		return compilercommon.SourceAndLocation{}, false
	}

	return itc.irg.NodeLocation(layerNode), true
}

// ResolveType attempts to resolve the given type string.
func (itc *irgTypeConstructor) ResolveType(typeString string, graph *typegraph.TypeGraph) (typegraph.TypeReference, error) {
	if typeString == "any" {
		return graph.AnyTypeReference(), nil
	}

	if typeString == "void" {
		return graph.VoidTypeReference(), nil
	}

	var nullable = false
	if strings.HasSuffix(typeString, "?") {
		nullable = true
		typeString = typeString[0 : len(typeString)-1]
	}

	// Perform native type mapping.
	if found, ok := NATIVE_TYPES[typeString]; ok {
		typeString = found
	}

	declaration, hasDeclaration := itc.irg.FindDeclaration(typeString)
	if !hasDeclaration {
		return graph.AnyTypeReference(), fmt.Errorf("Could not find WebIDL type %v", typeString)
	}

	typeDecl, hasType := graph.GetTypeForSourceNode(declaration.GraphNode)
	if !hasType {
		panic("Type not found for WebIDL type declaration")
	}

	typeRef := typeDecl.GetTypeReference()
	if nullable {
		return typeRef.AsNullable(), nil
	}

	return typeRef, nil
}
<commit_msg>Disallow redefinition of WebIDL types globally<commit_after>// Copyright 2015 The Serulian Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package typeconstructor

import (
	"fmt"
	"strings"

	"github.com/streamrail/concurrent-map"

	"github.com/serulian/compiler/compilercommon"
	"github.com/serulian/compiler/compilergraph"
	"github.com/serulian/compiler/graphs/typegraph"
	"github.com/serulian/compiler/webidl"
)

// GLOBAL_CONTEXT_ANNOTATIONS are the annotations that mark an interface as being a global context
// (e.g. Window) in WebIDL.
var GLOBAL_CONTEXT_ANNOTATIONS = []string{"Global", "PrimaryGlobal"}

// CONSTRUCTOR_ANNOTATION is an annotation that describes support for a constructor on a WebIDL
// type. This translates to being able to do "new Type(...)" in ECMAScript.
const CONSTRUCTOR_ANNOTATION = "Constructor"

// NATIVE_OPERATOR_ANNOTATION is an annotation that marks an declaration as supporting the
// specified operator natively (i.e. not a custom defined operator).
const NATIVE_OPERATOR_ANNOTATION = "NativeOperator"

// SPECIALIZATION_NAMES maps WebIDL member specializations into Serulian typegraph names.
var SPECIALIZATION_NAMES = map[webidl.MemberSpecialization]string{
	webidl.GetterSpecialization: "index",
	webidl.SetterSpecialization: "setindex",
}

// SERIALIZABLE_OPS defines the WebIDL custom ops that mark a type as serializable.
var SERIALIZABLE_OPS = map[string]bool{
	"jsonifier":  true,
	"serializer": true,
}

// NATIVE_TYPES maps from the predefined WebIDL types to the type actually supported
// in ES. We lose some information by doing so, but it allows for compatibility
// with existing WebIDL specifications. In the future, we might find a way to
// have these types be used in a more specific manner.
var NATIVE_TYPES = map[string]string{
	"boolean":             "Boolean",
	"byte":                "Number",
	"octet":               "Number",
	"short":               "Number",
	"unsigned short":      "Number",
	"long":                "Number",
	"unsigned long":       "Number",
	"long long":           "Number",
	"float":               "Number",
	"double":              "Number",
	"unrestricted float":  "Number",
	"unrestricted double": "Number",
}

// GetConstructor returns a TypeGraph constructor for the given IRG.
func GetConstructor(irg *webidl.WebIRG) *irgTypeConstructor {
	return &irgTypeConstructor{
		irg:              irg,
		typesEncountered: cmap.New(),
	}
}

// irgTypeConstructor defines a type for populating a type graph from the IRG.
type irgTypeConstructor struct {
	irg              *webidl.WebIRG     // The IRG being transformed.
	typesEncountered cmap.ConcurrentMap // The types already encountered.
}

func (itc *irgTypeConstructor) DefineModules(builder typegraph.GetModuleBuilder) {
	for _, module := range itc.irg.GetModules() {
		builder().
			Name(module.Name()).
			Path(string(module.InputSource())).
			SourceNode(module.Node()).
			Define()
	}
}

func (itc *irgTypeConstructor) DefineTypes(builder typegraph.GetTypeBuilder) {
	for _, module := range itc.irg.GetModules() {
		for _, declaration := range module.Declarations() {
			if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
				continue
			}

			typeBuilder := builder(module.Node())

			for _, customop := range declaration.CustomOperations() {
				if _, ok := SERIALIZABLE_OPS[customop]; ok {
					typeBuilder.WithAttribute(typegraph.SERIALIZABLE_ATTRIBUTE)
				}
			}

			typeBuilder.Name(declaration.Name()).
				SourceNode(declaration.GraphNode).
				TypeKind(typegraph.ExternalInternalType).
				Define()
		}
	}
}

func (itc *irgTypeConstructor) DefineDependencies(annotator typegraph.Annotator, graph *typegraph.TypeGraph) {
	for _, module := range itc.irg.GetModules() {
		for _, declaration := range module.Declarations() {
			if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
				continue
			}

			// Ensure that we don't have duplicate types across modules. Intra-module is handled by the type
			// graph.
			existingModule, found := itc.typesEncountered.Get(declaration.Name())
			if found && existingModule != module {
				annotator.ReportError(declaration.GraphNode, "Redeclaration of WebIDL interface %v is not supported", declaration.Name())
			}

			itc.typesEncountered.Set(declaration.Name(), module)

			// Determine whether we have a parent type for inheritance.
			parentTypeString, hasParentType := declaration.ParentType()
			if !hasParentType {
				continue
			}

			parentType, err := itc.ResolveType(parentTypeString, graph)
			if err != nil {
				annotator.ReportError(declaration.GraphNode, "%v", err)
				continue
			}

			annotator.DefineParentType(declaration.GraphNode, parentType)
		}
	}
}

func (itc *irgTypeConstructor) DefineMembers(builder typegraph.GetMemberBuilder, reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
	for _, declaration := range itc.irg.Declarations() {
		// Global members get defined under their module, not their declaration.
		var parentNode = declaration.GraphNode
		if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) {
			parentNode = declaration.Module().GraphNode
		}

		// If the declaration has one (or more) constructors, add then as a "new".
		if declaration.HasAnnotation(CONSTRUCTOR_ANNOTATION) {
			// Declare a "new" member which returns an instance of this type.
			builder(parentNode, false).
				Name("new").
				SourceNode(declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION)[0].GraphNode).
				Define()
		}

		// Add support for any native operators.
		if declaration.HasOneAnnotation(GLOBAL_CONTEXT_ANNOTATIONS...) && declaration.HasAnnotation(NATIVE_OPERATOR_ANNOTATION) {
			reporter.ReportError(declaration.GraphNode, "[NativeOperator] not supported on declarations marked with [GlobalContext]")
			return
		}

		for _, nativeOp := range declaration.GetAnnotations(NATIVE_OPERATOR_ANNOTATION) {
			opName, hasOpName := nativeOp.Value()
			if !hasOpName {
				reporter.ReportError(nativeOp.GraphNode, "Missing operator name on [NativeOperator] annotation")
				continue
			}

			// Lookup the operator under the type graph.
			opDefinition, found := graph.GetOperatorDefinition(opName)
			if !found || !opDefinition.IsStatic {
				reporter.ReportError(nativeOp.GraphNode, "Unknown native operator '%v'", opName)
				continue
			}

			// Add the operator to the type.
			builder(parentNode, true).
				Name(opName).
				SourceNode(nativeOp.GraphNode).
				Define()
		}

		// Add the declared members and specializations.
		for _, member := range declaration.Members() {
			name, hasName := member.Name()
			if hasName {
				builder(parentNode, false).
					Name(name).
					SourceNode(member.GraphNode).
					Define()
			} else {
				// This is a specialization.
				specialization, _ := member.Specialization()
				builder(parentNode, true).
					Name(SPECIALIZATION_NAMES[specialization]).
					SourceNode(member.GraphNode).
					Define()
			}
		}
	}
}

func (itc *irgTypeConstructor) DecorateMembers(decorator typegraph.GetMemberDecorator, reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
	for _, declaration := range itc.irg.Declarations() {
		if declaration.HasAnnotation(CONSTRUCTOR_ANNOTATION) {
			// For each constructor defined, create the intersection of their parameters.
			var parameters = make([]typegraph.TypeReference, 0)
			for constructorIndex, constructor := range declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION) {
				for index, parameter := range constructor.Parameters() {
					parameterType, err := itc.ResolveType(parameter.DeclaredType(), graph)
					if err != nil {
						reporter.ReportError(parameter.GraphNode, "%v", err)
						continue
					}

					var resolvedParameterType = parameterType
					if parameter.IsOptional() {
						resolvedParameterType = resolvedParameterType.AsNullable()
					}

					if index >= len(parameters) {
						// If this is not the first constructor, then this parameter is implicitly optional
						// and therefore nullable.
						if constructorIndex > 0 {
							resolvedParameterType = resolvedParameterType.AsNullable()
						}

						parameters = append(parameters, resolvedParameterType)
					} else {
						parameters[index] = parameters[index].Intersect(resolvedParameterType)
					}
				}
			}

			// Define the construction function for the type.
			typeDecl, _ := graph.GetTypeForSourceNode(declaration.GraphNode)
			var constructorFunction = graph.FunctionTypeReference(typeDecl.GetTypeReference())
			for _, parameterType := range parameters {
				constructorFunction = constructorFunction.WithParameter(parameterType)
			}

			decorator(declaration.GetAnnotations(CONSTRUCTOR_ANNOTATION)[0].GraphNode).
				Exported(true).
				Static(true).
				ReadOnly(true).
				MemberKind(uint64(webidl.ConstructorMember)).
				MemberType(constructorFunction).
				Decorate()
		}

		for _, nativeOp := range declaration.GetAnnotations(NATIVE_OPERATOR_ANNOTATION) {
			opName, hasOpName := nativeOp.Value()
			if !hasOpName {
				continue
			}

			opDefinition, found := graph.GetOperatorDefinition(opName)
			if !found {
				continue
			}

			// Define the operator's member type based on the definition.
			typeDecl, _ := graph.GetTypeForSourceNode(declaration.GraphNode)

			var expectedReturnType = opDefinition.ExpectedReturnType(typeDecl.GetTypeReference())
			if expectedReturnType.HasReferredType(graph.BoolType()) {
				expectedReturnType, _ = itc.ResolveType("Boolean", graph)
			}

			var operatorType = graph.FunctionTypeReference(expectedReturnType)
			for _, parameter := range opDefinition.Parameters {
				operatorType = operatorType.WithParameter(parameter.ExpectedType(typeDecl.GetTypeReference()))
			}

			// Add the operator to the type.
			decorator(nativeOp.GraphNode).
				Native(true).
				Exported(true).
				SkipOperatorChecking(true).
				MemberType(operatorType).
				MemberKind(uint64(webidl.OperatorMember)).
				Decorate()
		}

		// Add the declared members.
		for _, member := range declaration.Members() {
			declaredType, err := itc.ResolveType(member.DeclaredType(), graph)
			if err != nil {
				reporter.ReportError(member.GraphNode, "%v", err)
				continue
			}

			var memberType = declaredType
			var isReadonly = member.IsReadonly()

			switch member.Kind() {
			case webidl.FunctionMember:
				isReadonly = true
				memberType = graph.FunctionTypeReference(memberType)

				// Add the parameter types.
				var markOptional = false
				for _, parameter := range member.Parameters() {
					if parameter.IsOptional() {
						markOptional = true
					}

					parameterType, err := itc.ResolveType(parameter.DeclaredType(), graph)
					if err != nil {
						reporter.ReportError(member.GraphNode, "%v", err)
						continue
					}

					// All optional parameters get marked as nullable, which means we can skip
					// passing them on function calls.
					if markOptional {
						memberType = memberType.WithParameter(parameterType.AsNullable())
					} else {
						memberType = memberType.WithParameter(parameterType)
					}
				}

			case webidl.AttributeMember:
				if len(member.Parameters()) > 0 {
					reporter.ReportError(member.GraphNode, "Attributes cannot have parameters")
				}

			default:
				panic("Unknown WebIDL member kind")
			}

			decorator := decorator(member.GraphNode)
			if _, hasName := member.Name(); !hasName {
				decorator.Native(true)
			}

			decorator.Exported(true).
				Static(member.IsStatic()).
				ReadOnly(isReadonly).
				MemberKind(uint64(member.Kind())).
				MemberType(memberType).
				Decorate()
		}
	}
}

func (itc *irgTypeConstructor) Validate(reporter typegraph.IssueReporter, graph *typegraph.TypeGraph) {
}

func (itc *irgTypeConstructor) GetLocation(sourceNodeId compilergraph.GraphNodeId) (compilercommon.SourceAndLocation, bool) {
	layerNode, found := itc.irg.TryGetNode(sourceNodeId)
	if !found {
		return compilercommon.SourceAndLocation{}, false
	}

	return itc.irg.NodeLocation(layerNode), true
}

// ResolveType attempts to resolve the given type string.
func (itc *irgTypeConstructor) ResolveType(typeString string, graph *typegraph.TypeGraph) (typegraph.TypeReference, error) {
	if typeString == "any" {
		return graph.AnyTypeReference(), nil
	}

	if typeString == "void" {
		return graph.VoidTypeReference(), nil
	}

	var nullable = false
	if strings.HasSuffix(typeString, "?") {
		nullable = true
		typeString = typeString[0 : len(typeString)-1]
	}

	// Perform native type mapping.
	if found, ok := NATIVE_TYPES[typeString]; ok {
		typeString = found
	}

	declaration, hasDeclaration := itc.irg.FindDeclaration(typeString)
	if !hasDeclaration {
		return graph.AnyTypeReference(), fmt.Errorf("Could not find WebIDL type %v", typeString)
	}

	typeDecl, hasType := graph.GetTypeForSourceNode(declaration.GraphNode)
	if !hasType {
		panic("Type not found for WebIDL type declaration")
	}

	typeRef := typeDecl.GetTypeReference()
	if nullable {
		return typeRef.AsNullable(), nil
	}

	return typeRef, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 addons

import "k8s.io/minikube/pkg/minikube/config"

type setFn func(*config.ClusterConfig, string, string) error

// Addon represents an addon
type Addon struct {
	name        string
	set         func(*config.ClusterConfig, string, string) error
	validations []setFn
	callbacks   []setFn
}

// Addons is a list of all addons
var Addons = []*Addon{
	{
		name:      "dashboard",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},

	{
		name:      "default-storageclass",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableStorageClasses},
	},
	{
		name:      "efk",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "freshpod",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:        "gvisor",
		set:         SetBool,
		validations: []setFn{IsRuntimeContainerd},
		callbacks:   []setFn{enableOrDisableAddon},
	},
	{
		name:      "helm-tiller",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ingress",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ingress-dns",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "istio-provisioner",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "istio",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "kubevirt",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "logviewer",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "metrics-server",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "nvidia-driver-installer",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "nvidia-gpu-device-plugin",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "olm",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry-creds",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry-aliases",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
		//TODO - add other settings
		//TODO check if registry addon is enabled
	},
	{
		name:      "storage-provisioner",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "storage-provisioner-gluster",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableStorageClasses},
	},
	{
		name:      "metallb",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ambassador",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "heapster",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
}
<commit_msg>remove unneccesary config<commit_after>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 addons

import "k8s.io/minikube/pkg/minikube/config"

type setFn func(*config.ClusterConfig, string, string) error

// Addon represents an addon
type Addon struct {
	name        string
	set         func(*config.ClusterConfig, string, string) error
	validations []setFn
	callbacks   []setFn
}

// Addons is a list of all addons
var Addons = []*Addon{
	{
		name:      "dashboard",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},

	{
		name:      "default-storageclass",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableStorageClasses},
	},
	{
		name:      "efk",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "freshpod",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:        "gvisor",
		set:         SetBool,
		validations: []setFn{IsRuntimeContainerd},
		callbacks:   []setFn{enableOrDisableAddon},
	},
	{
		name:      "helm-tiller",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ingress",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ingress-dns",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "istio-provisioner",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "istio",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "kubevirt",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "logviewer",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "metrics-server",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "nvidia-driver-installer",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "nvidia-gpu-device-plugin",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "olm",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry-creds",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "registry-aliases",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
		//TODO - add other settings
		//TODO check if registry addon is enabled
	},
	{
		name:      "storage-provisioner",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "storage-provisioner-gluster",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableStorageClasses},
	},
	{
		name:      "metallb",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
	{
		name:      "ambassador",
		set:       SetBool,
		callbacks: []setFn{enableOrDisableAddon},
	},
}
<|endoftext|>


<commit_before>package radosAPI

import (
	"errors"
	"fmt"
	"time"

	"github.com/QuentinPerez/go-encodeUrl"
)

func init() {
	encurl.AddEncodeFunc(ifTimeIsNotNilCeph)
}

func ifTimeIsNotNilCeph(obj interface{}) (string, bool, error) {
	if val, ok := obj.(*time.Time); ok {
		if val != nil {
			return fmt.Sprintf("%v-%d-%v %v:%v:%v",
				val.Year(), val.Month(), val.Day(),
				val.Hour(), val.Minute(), val.Second()), true, nil
		}
		return "", false, nil
	}
	return "", false, errors.New("this field should be a *time.Time")
}
<commit_msg>fix formatting for optional date for UsageConfig (needs two digits)<commit_after>package radosAPI

import (
	"errors"
	"fmt"
	"time"

	"github.com/QuentinPerez/go-encodeUrl"
)

func init() {
	encurl.AddEncodeFunc(ifTimeIsNotNilCeph)
}

func ifTimeIsNotNilCeph(obj interface{}) (string, bool, error) {
	if val, ok := obj.(*time.Time); ok {
		if val != nil {
			return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d",
				val.Year(), val.Month(), val.Day(),
				val.Hour(), val.Minute(), val.Second()), true, nil
		}
		return "", false, nil
	}
	return "", false, errors.New("this field should be a *time.Time")
}
<|endoftext|>


<commit_before>package client

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
)

type Row []interface{}

type Result struct {
	Columns []string `json:"columns"`
	Rows    []Row    `json:"rows"`
}

func (res *Result) Format() []map[string]interface{} {
	var items []map[string]interface{}

	for _, row := range res.Rows {
		item := make(map[string]interface{})

		for i, c := range res.Columns {
			item[c] = row[i]
		}

		items = append(items, item)
	}

	return items
}

func (res *Result) CSV() []byte {
	buff := &bytes.Buffer{}
	writer := csv.NewWriter(buff)

	writer.Write(res.Columns)

	for _, row := range res.Rows {
		record := make([]string, len(res.Columns))

		for i, item := range row {
			if item != nil {
				record[i] = fmt.Sprintf("%v", item)
			} else {
				record[i] = ""
			}
		}

		err := writer.Write(record)

		if err != nil {
			fmt.Println(err)
			break
		}
	}

	writer.Flush()
	return buff.Bytes()
}

func (res *Result) JSON() []byte {
	records := []map[string]interface{}{}

	for _, row := range res.Rows {
		record := map[string]interface{}{}
		for i, col := range res.Columns {
			record[col] = row[i]
		}
		records = append(records, record)
	}

	data, _ := json.Marshal(records)
	return data
}
<commit_msg>Remove duplicate code<commit_after>package client

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
)

type Row []interface{}

type Result struct {
	Columns []string `json:"columns"`
	Rows    []Row    `json:"rows"`
}

func (res *Result) Format() []map[string]interface{} {
	var items []map[string]interface{}

	for _, row := range res.Rows {
		item := make(map[string]interface{})

		for i, c := range res.Columns {
			item[c] = row[i]
		}

		items = append(items, item)
	}

	return items
}

func (res *Result) CSV() []byte {
	buff := &bytes.Buffer{}
	writer := csv.NewWriter(buff)

	writer.Write(res.Columns)

	for _, row := range res.Rows {
		record := make([]string, len(res.Columns))

		for i, item := range row {
			if item != nil {
				record[i] = fmt.Sprintf("%v", item)
			} else {
				record[i] = ""
			}
		}

		err := writer.Write(record)

		if err != nil {
			fmt.Println(err)
			break
		}
	}

	writer.Flush()
	return buff.Bytes()
}

func (res *Result) JSON() []byte {
	data, _ := json.Marshal(res.Format())
	return data
}
<|endoftext|>


<commit_before>package crank

import (
	"log"
	"os"
)

// Manager manages multiple process groups
type Manager struct {
	configPath      string
	config          *ProcessConfig
	socket          *os.File
	processCount    int
	processEvent    chan ProcessEvent
	startAction     chan *ProcessConfig
	shutdownAction  chan bool
	childs          processSet
	shuttingDown    bool
	startingTracker *TimeoutTracker
	stoppingTracker *TimeoutTracker
}

func NewManager(configPath string, socket *os.File) *Manager {
	config, err := loadProcessConfig(configPath)
	if err != nil {
		log.Println("Could not load config file: ", err)
	}

	manager := &Manager{
		configPath:      configPath,
		config:          config,
		socket:          socket,
		processEvent:    make(chan ProcessEvent),
		startAction:     make(chan *ProcessConfig),
		childs:          make(processSet),
		startingTracker: NewTimeoutTracker(),
		stoppingTracker: NewTimeoutTracker(),
	}
	return manager
}

func (_ *Manager) log(format string, v ...interface{}) {
	log.Printf("[manager] "+format, v...)
}

// Run starts the event loop for the manager process
func (self *Manager) Run() {
	if self.config != nil && self.config.Command != "" {
		self.startNewProcess(self.config)
	}

	go self.startingTracker.Run()
	go self.stoppingTracker.Run()

	for {
		select {
		// actions
		case config := <-self.startAction:
			if self.childs.starting() != nil {
				self.log("Ignore, new process is already being started")
				continue
			}
			self.startNewProcess(config)
		case <-self.shutdownAction:
			if self.shuttingDown {
				self.log("Already shutting down")
				continue
			}
			self.shuttingDown = true
			self.childs.each(func(p *Process) {
				self.stopProcess(p)
			})
		// timeouts
		case process := <-self.startingTracker.timeoutNotification:
			self.log("Process did not start in time. pid=%s", process.Pid())
			process.Kill()
		case process := <-self.stoppingTracker.timeoutNotification:
			self.log("Process did not stop in time. pid=%s", process.Pid())
			process.Kill()
		// process state transitions
		case e := <-self.processEvent:
			switch event := e.(type) {
			case *ProcessReadyEvent:
				process := event.process
				self.startingTracker.Remove(process)
				if process != self.childs.starting() {
					fail("Some other process is ready")
					continue
				}
				self.log("Process %d is ready", process.Pid())
				current := self.childs.ready()
				if current != nil {
					self.log("Shutting down the current process %d", current.Pid())
					self.stopProcess(current)
				}
				err := process.config.save(self.configPath)
				if err != nil {
					self.log("Failed saving the config: %s", err)
				}
				self.childs.updateState(process, PROCESS_READY)
			case *ProcessExitEvent:
				process := event.process

				self.startingTracker.Remove(process)
				self.stoppingTracker.Remove(process)
				self.childs.rem(process)

				self.log("Process exited. pid=%d code=%d err=%s", process.Pid(), event.code, event.err)

				if self.childs.len() == 0 {
					goto exit
				}
			default:
				fail("Unknown event: ", e)
			}
		}
	}

exit:

	// Cleanup
	self.childs.each(func(p *Process) {
		p.Kill()
	})
}

// Restart queues and starts excecuting a restart job to replace the old process group with a new one.
func (self *Manager) Reload() {
	self.Start(self.config)
}

func (self *Manager) Start(c *ProcessConfig) {
	self.startAction <- c
}

func (self *Manager) Shutdown() {
	self.shutdownAction <- true
}

func (self *Manager) startNewProcess(config *ProcessConfig) {
	self.log("Starting a new process: %s", config)
	self.processCount += 1
	process, err := startProcess(self.processCount, config, self.socket, self.processEvent)
	if err != nil {
		self.log("Failed to start the process", err)
		return
	}
	self.childs.add(process)
	self.startingTracker.Add(process, process.config.StartTimeout)
}

func (self *Manager) stopProcess(process *Process) {
	if self.childs[process] == PROCESS_STOPPING {
		return
	}
	process.Shutdown()
	self.stoppingTracker.Add(process, process.config.StopTimeout)
	self.childs.updateState(process, PROCESS_STOPPING)
}
<commit_msg>Avoid starting new processes during shutdown<commit_after>package crank

import (
	"log"
	"os"
)

// Manager manages multiple process groups
type Manager struct {
	configPath      string
	config          *ProcessConfig
	socket          *os.File
	processCount    int
	processEvent    chan ProcessEvent
	startAction     chan *ProcessConfig
	shutdownAction  chan bool
	childs          processSet
	shuttingDown    bool
	startingTracker *TimeoutTracker
	stoppingTracker *TimeoutTracker
}

func NewManager(configPath string, socket *os.File) *Manager {
	config, err := loadProcessConfig(configPath)
	if err != nil {
		log.Println("Could not load config file: ", err)
	}

	manager := &Manager{
		configPath:      configPath,
		config:          config,
		socket:          socket,
		processEvent:    make(chan ProcessEvent),
		startAction:     make(chan *ProcessConfig),
		childs:          make(processSet),
		startingTracker: NewTimeoutTracker(),
		stoppingTracker: NewTimeoutTracker(),
	}
	return manager
}

func (_ *Manager) log(format string, v ...interface{}) {
	log.Printf("[manager] "+format, v...)
}

// Run starts the event loop for the manager process
func (self *Manager) Run() {
	if self.config != nil && self.config.Command != "" {
		self.startNewProcess(self.config)
	}

	go self.startingTracker.Run()
	go self.stoppingTracker.Run()

	for {
		select {
		// actions
		case config := <-self.startAction:
			if self.shuttingDown {
				self.log("Ignore start, manager is shutting down")
				continue
			}
			if self.childs.starting() != nil {
				self.log("Ignore start, new process is already being started")
				continue
			}
			self.startNewProcess(config)
		case <-self.shutdownAction:
			if self.shuttingDown {
				self.log("Already shutting down")
				continue
			}
			self.shuttingDown = true
			self.childs.each(func(p *Process) {
				self.stopProcess(p)
			})
		// timeouts
		case process := <-self.startingTracker.timeoutNotification:
			self.log("Process did not start in time. pid=%s", process.Pid())
			process.Kill()
		case process := <-self.stoppingTracker.timeoutNotification:
			self.log("Process did not stop in time. pid=%s", process.Pid())
			process.Kill()
		// process state transitions
		case e := <-self.processEvent:
			switch event := e.(type) {
			case *ProcessReadyEvent:
				process := event.process
				self.startingTracker.Remove(process)
				if process != self.childs.starting() {
					fail("Some other process is ready")
					continue
				}
				self.log("Process %d is ready", process.Pid())
				current := self.childs.ready()
				if current != nil {
					self.log("Shutting down the current process %d", current.Pid())
					self.stopProcess(current)
				}
				err := process.config.save(self.configPath)
				if err != nil {
					self.log("Failed saving the config: %s", err)
				}
				self.childs.updateState(process, PROCESS_READY)
			case *ProcessExitEvent:
				process := event.process

				self.startingTracker.Remove(process)
				self.stoppingTracker.Remove(process)
				self.childs.rem(process)

				self.log("Process exited. pid=%d code=%d err=%s", process.Pid(), event.code, event.err)

				if self.childs.len() == 0 {
					goto exit
				}
			default:
				fail("Unknown event: ", e)
			}
		}
	}

exit:

	// Cleanup
	self.childs.each(func(p *Process) {
		p.Kill()
	})
}

// Restart queues and starts excecuting a restart job to replace the old process group with a new one.
func (self *Manager) Reload() {
	self.Start(self.config)
}

func (self *Manager) Start(c *ProcessConfig) {
	self.startAction <- c
}

func (self *Manager) Shutdown() {
	self.shutdownAction <- true
}

func (self *Manager) startNewProcess(config *ProcessConfig) {
	self.log("Starting a new process: %s", config)
	self.processCount += 1
	process, err := startProcess(self.processCount, config, self.socket, self.processEvent)
	if err != nil {
		self.log("Failed to start the process", err)
		return
	}
	self.childs.add(process)
	self.startingTracker.Add(process, process.config.StartTimeout)
}

func (self *Manager) stopProcess(process *Process) {
	if self.childs[process] == PROCESS_STOPPING {
		return
	}
	process.Shutdown()
	self.stoppingTracker.Add(process, process.config.StopTimeout)
	self.childs.updateState(process, PROCESS_STOPPING)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/wait"
	"knative.dev/pkg/logging"
)

// Preparer is a way for the caller to modify the HTTP request before it goes out.
type Preparer func(r *http.Request) *http.Request

// Verifier is a way for the caller to validate the HTTP response after it comes back.
type Verifier func(r *http.Response, b []byte) (bool, error)

// WithHeader sets a header in the probe request.
func WithHeader(name, value string) Preparer {
	return func(r *http.Request) *http.Request {
		r.Header.Set(name, value)
		return r
	}
}

// WithHost sets the host in the probe request.
func WithHost(host string) Preparer {
	return func(r *http.Request) *http.Request {
		r.Host = host
		return r
	}
}

// WithPath sets the path in the probe request.
func WithPath(path string) Preparer {
	return func(r *http.Request) *http.Request {
		r.URL.Path = path
		return r
	}
}

// ExpectsBody validates that the body of the probe response matches the provided string.
func ExpectsBody(body string) Verifier {
	return func(r *http.Response, b []byte) (bool, error) {
		if string(b) == body {
			return true, nil
		}
		return false, fmt.Errorf("unexpected body: want %q, got %q", body, string(b))
	}
}

// ExpectsHeader validates that the given header of the probe response matches the provided string.
func ExpectsHeader(name, value string) Verifier {
	return func(r *http.Response, _ []byte) (bool, error) {
		if r.Header.Get(name) == value {
			return true, nil
		}
		return false, fmt.Errorf("unexpected header %q: want %q, got %q", name, value, r.Header.Get(name))
	}
}

// ExpectsStatusCodes validates that the given status code of the probe response matches the provided int.
func ExpectsStatusCodes(statusCodes []int) Verifier {
	return func(r *http.Response, _ []byte) (bool, error) {
		for _, v := range statusCodes {
			if r.StatusCode == v {
				return true, nil
			}
		}
		return false, fmt.Errorf("unexpected status code: want %v, got %v", statusCodes, r.StatusCode)
	}
}

// Do sends a single probe to given target, e.g. `http://revision.default.svc.cluster.local:81`.
// Do returns whether the probe was successful or not, or there was an error probing.
func Do(ctx context.Context, transport http.RoundTripper, target string, ops ...interface{}) (bool, error) {
	req, err := http.NewRequest(http.MethodGet, target, nil)
	if err != nil {
		return false, fmt.Errorf("%s is not a valid URL: %w", target, err)
	}
	for _, op := range ops {
		if po, ok := op.(Preparer); ok {
			req = po(req)
		}
	}

	req = req.WithContext(ctx)
	resp, err := transport.RoundTrip(req)
	if err != nil {
		return false, fmt.Errorf("error roundtripping %s: %w", target, err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, fmt.Errorf("error reading body: %w", err)
	}

	for _, op := range ops {
		if vo, ok := op.(Verifier); ok {
			if ok, err := vo(resp, body); err != nil || !ok {
				return false, err
			}
		}
	}
	return true, nil
}

// Done is a callback that is executed when the async probe has finished.
// `arg` is given by the caller at the offering time, while `success` and `err`
// are the return values of the `Do` call.
// It is assumed that the opaque arg is consistent for a given target and
// we will coalesce concurrent Offer invocations on target.
type Done func(arg interface{}, success bool, err error)

// Manager manages async probes and makes sure we run concurrently only a single
// probe for the same key.
type Manager struct {
	cb Done
	// NB: it is paramount to use a transport that will close the connection
	// after every request here. Otherwise the cached connections will prohibit
	// scaling to zero, due to unsuccessful probes to the Activator.
	transport http.RoundTripper

	// mu guards keys.
	mu   sync.Mutex
	keys sets.String
}

// New creates a new Manager, that will invoke the given callback when
// async probing is finished.
func New(cb Done, transport http.RoundTripper) *Manager {
	return &Manager{
		keys:      sets.NewString(),
		cb:        cb,
		transport: transport,
	}
}

// Offer executes asynchronous probe using `target` as the key.
// If a probe with the same key already exists, Offer will return false and the
// call is discarded. If the request is accepted, Offer returns true.
// Otherwise Offer starts a goroutine that periodically executes
// `Do`, until timeout is reached, the probe succeeds, or fails with an error.
// In the end the callback is invoked with the provided `arg` and probing results.
func (m *Manager) Offer(ctx context.Context, target string, arg interface{}, period, timeout time.Duration, ops ...interface{}) bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.keys.Has(target) {
		return false
	}
	m.keys.Insert(target)
	m.doAsync(ctx, target, arg, period, timeout, ops...)
	return true
}

// doAsync starts a go routine that probes the target with given period.
func (m *Manager) doAsync(ctx context.Context, target string, arg interface{}, period, timeout time.Duration, ops ...interface{}) {
	logger := logging.FromContext(ctx)
	go func() {
		defer func() {
			m.mu.Lock()
			defer m.mu.Unlock()
			m.keys.Delete(target)
		}()
		var (
			result bool
			inErr  error
		)
		err := wait.PollImmediate(period, timeout, func() (bool, error) {
			result, inErr = Do(ctx, m.transport, target, ops...)
			// Do not return error, which is from verifierError, as retry is expected until timeout.
			return result, nil
		})
		if inErr != nil {
			logger.Errorw("Unable to read sockstat", zap.Error(inErr))
		}
		m.cb(arg, result, err)
	}()
}
<commit_msg>Create prober request with context right away (#344)<commit_after>/*
Copyright 2020 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/wait"
	"knative.dev/pkg/logging"
)

// Preparer is a way for the caller to modify the HTTP request before it goes out.
type Preparer func(r *http.Request) *http.Request

// Verifier is a way for the caller to validate the HTTP response after it comes back.
type Verifier func(r *http.Response, b []byte) (bool, error)

// WithHeader sets a header in the probe request.
func WithHeader(name, value string) Preparer {
	return func(r *http.Request) *http.Request {
		r.Header.Set(name, value)
		return r
	}
}

// WithHost sets the host in the probe request.
func WithHost(host string) Preparer {
	return func(r *http.Request) *http.Request {
		r.Host = host
		return r
	}
}

// WithPath sets the path in the probe request.
func WithPath(path string) Preparer {
	return func(r *http.Request) *http.Request {
		r.URL.Path = path
		return r
	}
}

// ExpectsBody validates that the body of the probe response matches the provided string.
func ExpectsBody(body string) Verifier {
	return func(r *http.Response, b []byte) (bool, error) {
		if string(b) == body {
			return true, nil
		}
		return false, fmt.Errorf("unexpected body: want %q, got %q", body, string(b))
	}
}

// ExpectsHeader validates that the given header of the probe response matches the provided string.
func ExpectsHeader(name, value string) Verifier {
	return func(r *http.Response, _ []byte) (bool, error) {
		if r.Header.Get(name) == value {
			return true, nil
		}
		return false, fmt.Errorf("unexpected header %q: want %q, got %q", name, value, r.Header.Get(name))
	}
}

// ExpectsStatusCodes validates that the given status code of the probe response matches the provided int.
func ExpectsStatusCodes(statusCodes []int) Verifier {
	return func(r *http.Response, _ []byte) (bool, error) {
		for _, v := range statusCodes {
			if r.StatusCode == v {
				return true, nil
			}
		}
		return false, fmt.Errorf("unexpected status code: want %v, got %v", statusCodes, r.StatusCode)
	}
}

// Do sends a single probe to given target, e.g. `http://revision.default.svc.cluster.local:81`.
// Do returns whether the probe was successful or not, or there was an error probing.
func Do(ctx context.Context, transport http.RoundTripper, target string, ops ...interface{}) (bool, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
	if err != nil {
		return false, fmt.Errorf("%s is not a valid URL: %w", target, err)
	}
	for _, op := range ops {
		if po, ok := op.(Preparer); ok {
			req = po(req)
		}
	}

	resp, err := transport.RoundTrip(req)
	if err != nil {
		return false, fmt.Errorf("error roundtripping %s: %w", target, err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, fmt.Errorf("error reading body: %w", err)
	}

	for _, op := range ops {
		if vo, ok := op.(Verifier); ok {
			if ok, err := vo(resp, body); err != nil || !ok {
				return false, err
			}
		}
	}
	return true, nil
}

// Done is a callback that is executed when the async probe has finished.
// `arg` is given by the caller at the offering time, while `success` and `err`
// are the return values of the `Do` call.
// It is assumed that the opaque arg is consistent for a given target and
// we will coalesce concurrent Offer invocations on target.
type Done func(arg interface{}, success bool, err error)

// Manager manages async probes and makes sure we run concurrently only a single
// probe for the same key.
type Manager struct {
	cb Done
	// NB: it is paramount to use a transport that will close the connection
	// after every request here. Otherwise the cached connections will prohibit
	// scaling to zero, due to unsuccessful probes to the Activator.
	transport http.RoundTripper

	// mu guards keys.
	mu   sync.Mutex
	keys sets.String
}

// New creates a new Manager, that will invoke the given callback when
// async probing is finished.
func New(cb Done, transport http.RoundTripper) *Manager {
	return &Manager{
		keys:      sets.NewString(),
		cb:        cb,
		transport: transport,
	}
}

// Offer executes asynchronous probe using `target` as the key.
// If a probe with the same key already exists, Offer will return false and the
// call is discarded. If the request is accepted, Offer returns true.
// Otherwise Offer starts a goroutine that periodically executes
// `Do`, until timeout is reached, the probe succeeds, or fails with an error.
// In the end the callback is invoked with the provided `arg` and probing results.
func (m *Manager) Offer(ctx context.Context, target string, arg interface{}, period, timeout time.Duration, ops ...interface{}) bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.keys.Has(target) {
		return false
	}
	m.keys.Insert(target)
	m.doAsync(ctx, target, arg, period, timeout, ops...)
	return true
}

// doAsync starts a go routine that probes the target with given period.
func (m *Manager) doAsync(ctx context.Context, target string, arg interface{}, period, timeout time.Duration, ops ...interface{}) {
	logger := logging.FromContext(ctx)
	go func() {
		defer func() {
			m.mu.Lock()
			defer m.mu.Unlock()
			m.keys.Delete(target)
		}()
		var (
			result bool
			inErr  error
		)
		err := wait.PollImmediate(period, timeout, func() (bool, error) {
			result, inErr = Do(ctx, m.transport, target, ops...)
			// Do not return error, which is from verifierError, as retry is expected until timeout.
			return result, nil
		})
		if inErr != nil {
			logger.Errorw("Unable to read sockstat", zap.Error(inErr))
		}
		m.cb(arg, result, err)
	}()
}
<|endoftext|>


<commit_before>package prompt

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

	"github.com/tmrts/boilr/pkg/util/tlog"
)

const (
	// PromptFormatMessage is a format message for value prompts.
	PromptFormatMessage = "Please choose a value for %q"

	// PromptChoiceFormatMessage is a format message for choice prompts.
	PromptChoiceFormatMessage = "Please choose an option for %q\n%v    Select from %v..%v"
)

func scanLine() (string, error) {
	input := bufio.NewReader(os.Stdin)
	line, err := input.ReadString('\n')
	if err != nil {
		return line, err
	}

	return strings.TrimSuffix(line, "\n"), nil
}

// TODO add GetLine method using a channel
// TODO use interfaces to eliminate code duplication
func newString(name string, defval interface{}) func() interface{} {
	var cache interface{}
	return func() interface{} {
		if cache == nil {
			cache = func() interface{} {
				tlog.Prompt(fmt.Sprintf(PromptFormatMessage, name), defval)

				line, err := scanLine()
				if err != nil {
					tlog.Warn(err.Error())
					return line
				}

				if line == "" {
					return defval
				}

				return line
			}()
		}

		return cache
	}
}

var (
	booleanValues = map[string]bool{
		"y":    true,
		"yes":  true,
		"yup":  true,
		"true": true,

		"n":     false,
		"no":    false,
		"nope":  false,
		"false": false,
	}
)

func newBool(name string, defval bool) func() interface{} {
	var cache interface{}
	return func() interface{} {
		if cache == nil {
			cache = func() interface{} {
				tlog.Prompt(fmt.Sprintf(PromptFormatMessage, name), defval)

				choice, err := scanLine()
				if err != nil {
					tlog.Warn(err.Error())
					return choice
				}

				if choice == "" {
					return defval
				}

				val, ok := booleanValues[strings.ToLower(choice)]
				if !ok {
					tlog.Warn(fmt.Sprintf("Unrecognized choice %q, using the default", choice))

					return defval
				}

				return val
			}()
		}

		return cache
	}
}

// Choice contains the values for a choice
type Choice struct {
	// Default choice
	Default int

	// List of choices
	Choices []string
}

func formattedChoices(cs []string) (s string) {
	for i, c := range cs {
		s += fmt.Sprintf("    %v -  %q\n", i+1, c)
	}

	return
}

func newSlice(name string, choices []string) func() interface{} {
	var cache interface{}
	return func() interface{} {
		if cache == nil {
			defindex := 0
			defval := choices[defindex]
			cache = func() interface{} {
				s := formattedChoices(choices)
				tlog.Prompt(fmt.Sprintf(PromptChoiceFormatMessage, name, s, 1, len(choices)), defindex+1)

				choice, err := scanLine()
				if err != nil {
					tlog.Warn(err.Error())
					return choice
				}

				if choice == "" {
					return defval
				}

				index, err := strconv.Atoi(choice)
				if err != nil {
					return err
				}

				if index > len(choices)+1 || index < 1 {
					tlog.Warn(fmt.Sprintf("Unrecognized choice %v, using the default", index))

					return defval
				}

				return choices[index-1]
			}()
		}

		return cache
	}
}

// New returns a prompt closure when executed asks for
// user input and has a default value that returns result.
func New(name string, defval interface{}) func() interface{} {
	// TODO use reflect package
	// TODO add a prompt as such "How many Items will you enter", "Enter each" use in "{{range Items}}"
	switch defval := defval.(type) {
	case bool:
		return newBool(name, defval)
	case []interface{}:
		if len(defval) == 0 {
			tlog.Warn(fmt.Sprintf("empty list of choices for %q", name))
			return nil
		}

		var s []string
		for _, v := range defval {
			s = append(s, v.(string))
		}

		return newSlice(name, s)
	}

	return newString(name, defval)
}
<commit_msg>Extract an interface for prompt functions<commit_after>package prompt

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

	"github.com/tmrts/boilr/pkg/util/tlog"
)

type templateFunc func() interface{}

type Interface interface {
	// PromptMessage returns a proper prompt message for the given field with the given default value.
	PromptMessage(string) string
	EvaluateChoice(string) (interface{}, error)
}

type Chain struct {
	Prompts []Interface
}

type strPrompt string

func (p strPrompt) PromptMessage(name string) string {
	return fmt.Sprintf("Please choose a value for %q", name)
}

func (p strPrompt) EvaluateChoice(c string) (interface{}, error) {
	if c != "" {
		return c, nil
	}

	return string(p), nil
}

type boolPrompt bool

func (p boolPrompt) PromptMessage(name string) string {
	return fmt.Sprintf("Please choose a value for %q", name)
}

var (
	booleanValues = map[string]bool{
		"y":    true,
		"yes":  true,
		"yup":  true,
		"true": true,

		"n":     false,
		"no":    false,
		"nope":  false,
		"false": false,
	}
)

func (p boolPrompt) EvaluateChoice(c string) (interface{}, error) {
	if val, ok := booleanValues[c]; ok {
		return val, nil
	}

	return bool(p), nil
}

// TODO: add proper format messages for multiple choices
type multipleChoicePrompt []string

func (p multipleChoicePrompt) PromptMessage(name string) string {
	return fmt.Sprintf("Please choose an index for %q", name)
}

func (p multipleChoicePrompt) EvaluateChoice(c string) (interface{}, error) {
	if c != "" {
		index, err := strconv.Atoi(c)
		if err != nil {
			tlog.Warn(fmt.Sprintf("Unrecognized choice %v, using the default choice", index))

			return p[0], nil
		}

		fmt.Println(len(p))
		if index > len(p) || index < 1 {
			tlog.Warn(fmt.Sprintf("Unrecognized choice %v, using the default choice", index))

			return p[0], nil
		}

		return p[index-1], nil
	}

	return p[0], nil
}

// TODO add deep pretty printer
// TODO handle TOML
func Func(defval interface{}) Interface {
	switch defval := defval.(type) {
	case bool:
		return boolPrompt(defval)
	case []interface{}:
		if len(defval) == 0 {
			tlog.Warn(fmt.Sprintf("empty list of choices"))
			return nil
		}

		var s []string
		for _, v := range defval {
			s = append(s, fmt.Sprint(v))
		}

		return multipleChoicePrompt(s)
	}

	return strPrompt(fmt.Sprint(defval))
}

func scanLine() (string, error) {
	input := bufio.NewReader(os.Stdin)
	line, err := input.ReadString('\n')
	if err != nil {
		return line, err
	}

	return strings.TrimSuffix(line, "\n"), nil
}

// New returns a prompt closure when executed asks for
// user input once and caches it for further invocations
// and has a default value that returns result.
func New(fieldName string, defval interface{}) func() interface{} {
	prompt := Func(defval)

	var cachedValue interface{}
	return func() interface{} {
		if cachedValue == nil {
			msg := prompt.PromptMessage(fieldName)

			tlog.Prompt(msg, defval)

			choice, err := scanLine()
			if err != nil {
				tlog.Warn(err.Error())
			}

			cachedValue, err = prompt.EvaluateChoice(choice)
			if err != nil {
				tlog.Warn(err.Error())
			}
		}

		return cachedValue
	}
}
<|endoftext|>