<commit_before>/*
Package css is a CSS3 tokenizer and parser written in Go. Both are implemented using the specifications at http://www.w3.org/TR/css-syntax-3/
Tokenizer using example:
	package main
	import (
		"fmt"
		"io"
		"os"
		"github.com/tdewolff/css"
	)
	// Tokenize CSS3 from stdin.
	func main() {
		z := css.NewTokenizer(os.Stdin)
		for {
			tt, data := z.Next()
			switch tt {
			case css.ErrorToken:
				if z.Err() != io.EOF {
					fmt.Println("Error on line", z.Line(), ":", z.Err())
				}
				return
			case css.IdentToken:
				fmt.Println("Identifier", data)
			case css.NumberToken:
				fmt.Println("Number", data)
			// ...
			}
		}
	}
Parser using example:
	package main
	import (
		"fmt"
		"os"
		"github.com/tdewolff/css"
	)
	// Parse CSS3 from stdin.
	func main() {
		stylesheet, err := css.Parse(os.Stdin)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		for _, node := range stylesheet.Nodes {
			switch m := node.(type) {
			case *css.TokenNode:
				fmt.Println("Token", string(m.Data))
			case *css.DeclarationNode:
				fmt.Println("Declaration for property", string(m.Prop.Data))
			case *css.RulesetNode:
				fmt.Println("Ruleset with", len(m.Decls), "declarations")
			case *css.AtRuleNode:
				fmt.Println("AtRule", string(m.At.Data))
			}
		}
	}
*/
package css // import "github.com/tdewolff/parse/css"

import (
	"bytes"
	"io"
	"strconv"
)

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

type TokenStream interface {
	Next() (TokenType, []byte)
	Err() error
}

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

// GrammarType determines the type of grammar.
type GrammarType uint32

// GrammarType values.
const (
	ErrorGrammar GrammarType = iota // extra token when errors occur
	AtRuleGrammar
	EndAtRuleGrammar
	RulesetGrammar
	EndRulesetGrammar
	DeclarationGrammar
	TokenGrammar
)

// String returns the string representation of a GrammarType.
func (tt GrammarType) String() string {
	switch tt {
	case ErrorGrammar:
		return "Error"
	case AtRuleGrammar:
		return "AtRule"
	case EndAtRuleGrammar:
		return "EndAtRule"
	case RulesetGrammar:
		return "Ruleset"
	case EndRulesetGrammar:
		return "EndRuleset"
	case DeclarationGrammar:
		return "Declaration"
	case TokenGrammar:
		return "Token"
	}
	return "Invalid(" + strconv.Itoa(int(tt)) + ")"
}

// ParserState denotes the state of the parser.
type ParserState uint32

// ParserState values.
const (
	StylesheetState ParserState = iota
	AtRuleState
	RulesetState
)

// String returns the string representation of a ParserState.
func (tt ParserState) String() string {
	switch tt {
	case StylesheetState:
		return "Stylesheet"
	case AtRuleState:
		return "AtRule"
	case RulesetState:
		return "Ruleset"
	}
	return "Invalid(" + strconv.Itoa(int(tt)) + ")"
}

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

// Parser is the state for the parser.
type Parser struct {
	z     TokenStream
	state []ParserState

	buf []*TokenNode
	pos int
}

// NewParser returns a new Parser for a given Tokenizer implementing TokenStream.
func NewParser(z TokenStream) *Parser {
	return &Parser{
		z,
		[]ParserState{StylesheetState},
		make([]*TokenNode, 0, 16),
		0,
	}
}

// Parse parses the entire CSS file and returns the root StylesheetNode.
func Parse(r io.Reader) (*StylesheetNode, error) {
	p := NewParser(NewTokenizer(r))
	var err error
	stylesheet := NewStylesheet()
	for {
		gt, n := p.Next()
		if gt == ErrorGrammar {
			err = p.z.Err()
			break
		}
		stylesheet.Nodes = append(stylesheet.Nodes, n)
		if err = p.parseRecursively(gt, n); err != nil {
			break
		}
	}
	if err != io.EOF {
		return stylesheet, err
	}
	return stylesheet, nil
}

func (p *Parser) parseRecursively(rootGt GrammarType, n Node) error {
	if rootGt == AtRuleGrammar {
		atRule := n.(*AtRuleNode)
		for {
			gt, m := p.Next()
			if gt == ErrorGrammar {
				return p.z.Err()
			} else if gt == EndAtRuleGrammar {
				break
			}
			atRule.Rules = append(atRule.Rules, m)
			if err := p.parseRecursively(gt, m); err != nil {
				return err
			}
		}
	} else if rootGt == RulesetGrammar {
		ruleset := n.(*RulesetNode)
		for {
			gt, m := p.Next()
			if gt == ErrorGrammar {
				return p.z.Err()
			} else if gt == EndRulesetGrammar {
				break
			}
			if decl, ok := m.(*DeclarationNode); ok {
				ruleset.Decls = append(ruleset.Decls, decl)
			}
		}
	}
	return nil
}

// Err returns the error encountered during parsing, this is often io.EOF but also other errors can be returned.
func (p Parser) Err() error {
	return p.z.Err()
}

// Next returns the next grammar unit from the CSS file.
// This is a lower-level function than Parse and is used to stream parse CSS instead of loading it fully into memory.
// Returned nodes of AtRule and Ruleset do not contain entries for Decls and Rules respectively. These are returned consecutively with calls to Next.
func (p *Parser) Next() (GrammarType, Node) {
	if p.at(ErrorToken) {
		return ErrorGrammar, nil
	}
	p.skipWhitespace()

	// return End types
	state := p.State()
	if p.at(RightBraceToken) && (state == AtRuleState || state == RulesetState) || p.at(SemicolonToken) && state == AtRuleState {
		n := p.shift()
		p.skipWhile(SemicolonToken)

		p.state = p.state[:len(p.state)-1]
		if state == AtRuleState {

			return EndAtRuleGrammar, n
		}
		return EndRulesetGrammar, n
	}

	if p.at(CDOToken) || p.at(CDCToken) {
		return TokenGrammar, p.shift()
	} else if cn := p.parseAtRule(); cn != nil {
		return AtRuleGrammar, cn
	} else if cn := p.parseRuleset(); cn != nil {
		return RulesetGrammar, cn
	} else if cn := p.parseDeclaration(); cn != nil {
		return DeclarationGrammar, cn
	}
	return TokenGrammar, p.shift()
}

func (p *Parser) State() ParserState {
	return p.state[len(p.state)-1]
}

func (p *Parser) parseAtRule() *AtRuleNode {
	if !p.at(AtKeywordToken) {
		return nil
	}
	n := NewAtRule(p.shift())
	p.skipWhitespace()
	for !p.at(SemicolonToken) && !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		n.Nodes = append(n.Nodes, p.shiftComponent())
		p.skipWhitespace()
	}
	if p.at(LeftBraceToken) {
		p.shift()
	}
	p.state = append(p.state, AtRuleState)
	return n
}

func (p *Parser) parseRuleset() *RulesetNode {
	// check if left brace appears, which is the only check if this is a valid ruleset
	i := 0
	for p.peek(i).TokenType != LeftBraceToken {
		if p.peek(i).TokenType == SemicolonToken || p.peek(i).TokenType == ErrorToken {
			return nil
		}
		i++
	}
	n := NewRuleset()
	for !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		if p.at(CommaToken) {
			p.shift()
			p.skipWhitespace()
			continue
		}
		if cn := p.parseSelector(); cn != nil {
			n.Selectors = append(n.Selectors, cn)
		}
		p.skipWhitespace()
	}
	if p.at(ErrorToken) {
		return nil
	}
	p.shift()
	p.state = append(p.state, RulesetState)
	return n
}

func (p *Parser) parseSelector() *SelectorNode {
	n := NewSelector()
	var ws *TokenNode
	for !p.at(CommaToken) && !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		if p.at(DelimToken) && (p.data()[0] == '>' || p.data()[0] == '+' || p.data()[0] == '~') {
			n.Elems = append(n.Elems, p.shift())
			p.skipWhitespace()
		} else if p.at(LeftBracketToken) {
			for !p.at(RightBracketToken) && !p.at(ErrorToken) {
				n.Elems = append(n.Elems, p.shift())
				p.skipWhitespace()
			}
			if p.at(RightBracketToken) {
				n.Elems = append(n.Elems, p.shift())
			}
		} else {
			if ws != nil {
				n.Elems = append(n.Elems, ws)
			}
			n.Elems = append(n.Elems, p.shift())
		}

		if p.at(WhitespaceToken) {
			ws = p.shift()
		} else {
			ws = nil
		}
	}
	if len(n.Elems) == 0 {
		return nil
	}
	return n
}

func (p *Parser) parseDeclaration() *DeclarationNode {
	if !p.at(IdentToken) {
		return nil
	}
	ident := p.shift()
	p.skipWhitespace()
	if !p.at(ColonToken) {
		return nil
	}
	p.shift() // colon
	p.skipWhitespace()
	n := NewDeclaration(ident)
	for !p.at(SemicolonToken) && !p.at(RightBraceToken) && !p.at(ErrorToken) {
		if p.at(DelimToken) && p.data()[0] == '!' {
			exclamation := p.shift()
			p.skipWhitespace()
			if p.at(IdentToken) && bytes.Equal(bytes.ToLower(p.data()), []byte("important")) {
				n.Important = true
				p.shift()
			} else {
				n.Vals = append(n.Vals, exclamation)
			}
		} else if cn := p.parseFunction(); cn != nil {
			n.Vals = append(n.Vals, cn)
		} else {
			n.Vals = append(n.Vals, p.shift())
		}
		p.skipWhitespace()
	}
	p.skipWhile(SemicolonToken)
	return n
}

func (p *Parser) parseFunction() *FunctionNode {
	if !p.at(FunctionToken) {
		return nil
	}
	n := NewFunction(p.shift())
	p.skipWhitespace()
	for !p.at(RightParenthesisToken) && !p.at(ErrorToken) {
		if p.at(CommaToken) {
			p.shift()
			p.skipWhitespace()
			continue
		}
		n.Args = append(n.Args, p.parseArgument())
	}
	if p.at(ErrorToken) {
		return nil
	}
	p.shift()
	return n
}

func (p *Parser) parseArgument() *ArgumentNode {
	n := NewArgument()
	for !p.at(CommaToken) && !p.at(RightParenthesisToken) && !p.at(ErrorToken) {
		n.Vals = append(n.Vals, p.shiftComponent())
		p.skipWhitespace()
	}
	return n
}

func (p *Parser) parseBlock() *BlockNode {
	if !p.at(LeftParenthesisToken) && !p.at(LeftBraceToken) && !p.at(LeftBracketToken) {
		return nil
	}
	n := NewBlock(p.shift())
	p.skipWhitespace()
	for {
		if p.at(RightBraceToken) || p.at(RightParenthesisToken) || p.at(RightBracketToken) || p.at(ErrorToken) {
			break
		}
		n.Nodes = append(n.Nodes, p.shiftComponent())
		p.skipWhitespace()
	}
	if !p.at(ErrorToken) {
		n.Close = p.shift()
	}
	return n
}

func (p *Parser) shiftComponent() Node {
	if cn := p.parseBlock(); cn != nil {
		return cn
	} else if cn := p.parseFunction(); cn != nil {
		return cn
	} else {
		return p.shift()
	}
}

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

func (p *Parser) read() *TokenNode {
	tt, text := p.z.Next()
	// TODO: refactor buffer and copying
	tmp := make([]byte, len(text))
	copy(tmp, text)
	text = tmp
	// ignore comments and multiple whitespace
	if tt == CommentToken || tt == WhitespaceToken && len(p.buf) > 0 && p.buf[len(p.buf)-1].TokenType == WhitespaceToken {
		return p.read()
	}
	return NewToken(tt, text)
}

func (p *Parser) peek(i int) *TokenNode {
	if p.pos+i >= len(p.buf) {
		c := cap(p.buf)
		l := len(p.buf) - p.pos
		if p.pos+i >= c {
			// expand buffer when len is bigger than half the cap
			if 2*l > c {
				buf1 := make([]*TokenNode, l, 2*c)
				copy(buf1, p.buf[p.pos:])
				p.buf = buf1
			} else {
				copy(p.buf, p.buf[p.pos:])
				p.buf = p.buf[:l]
			}
			p.pos = 0
			if i >= cap(p.buf) {
				return NewToken(ErrorToken, []byte("looking too far ahead"))
			}
		}
		for j := len(p.buf); j <= p.pos+i; j++ {
			p.buf = append(p.buf, p.read())
		}
	}
	return p.buf[p.pos+i]
}

func (p *Parser) shift() *TokenNode {
	shifted := p.peek(0)
	p.pos++
	return shifted
}

func (p *Parser) at(tt TokenType) bool {
	return p.peek(0).TokenType == tt
}

func (p *Parser) data() []byte {
	return p.peek(0).Data
}

func (p *Parser) skipWhitespace() {
	if p.at(WhitespaceToken) {
		p.shift()
	}
}

func (p *Parser) skipWhile(tt TokenType) {
	for p.at(tt) || p.at(WhitespaceToken) {
		p.shift()
	}
}

func (p *Parser) skipUntil(tt TokenType) {
	for p.at(tt) && !p.at(ErrorToken) {
		p.shift()
	}
}
<commit_msg>Remove TokenStream interface<commit_after>/*
Package css is a CSS3 tokenizer and parser written in Go. Both are implemented using the specifications at http://www.w3.org/TR/css-syntax-3/
Tokenizer using example:
	package main
	import (
		"fmt"
		"io"
		"os"
		"github.com/tdewolff/css"
	)
	// Tokenize CSS3 from stdin.
	func main() {
		z := css.NewTokenizer(os.Stdin)
		for {
			tt, data := z.Next()
			switch tt {
			case css.ErrorToken:
				if z.Err() != io.EOF {
					fmt.Println("Error on line", z.Line(), ":", z.Err())
				}
				return
			case css.IdentToken:
				fmt.Println("Identifier", data)
			case css.NumberToken:
				fmt.Println("Number", data)
			// ...
			}
		}
	}
Parser using example:
	package main
	import (
		"fmt"
		"os"
		"github.com/tdewolff/css"
	)
	// Parse CSS3 from stdin.
	func main() {
		stylesheet, err := css.Parse(os.Stdin)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		for _, node := range stylesheet.Nodes {
			switch m := node.(type) {
			case *css.TokenNode:
				fmt.Println("Token", string(m.Data))
			case *css.DeclarationNode:
				fmt.Println("Declaration for property", string(m.Prop.Data))
			case *css.RulesetNode:
				fmt.Println("Ruleset with", len(m.Decls), "declarations")
			case *css.AtRuleNode:
				fmt.Println("AtRule", string(m.At.Data))
			}
		}
	}
*/
package css // import "github.com/tdewolff/parse/css"

import (
	"bytes"
	"io"
	"strconv"
)

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

// GrammarType determines the type of grammar.
type GrammarType uint32

// GrammarType values.
const (
	ErrorGrammar GrammarType = iota // extra token when errors occur
	AtRuleGrammar
	EndAtRuleGrammar
	RulesetGrammar
	EndRulesetGrammar
	DeclarationGrammar
	TokenGrammar
)

// String returns the string representation of a GrammarType.
func (tt GrammarType) String() string {
	switch tt {
	case ErrorGrammar:
		return "Error"
	case AtRuleGrammar:
		return "AtRule"
	case EndAtRuleGrammar:
		return "EndAtRule"
	case RulesetGrammar:
		return "Ruleset"
	case EndRulesetGrammar:
		return "EndRuleset"
	case DeclarationGrammar:
		return "Declaration"
	case TokenGrammar:
		return "Token"
	}
	return "Invalid(" + strconv.Itoa(int(tt)) + ")"
}

// ParserState denotes the state of the parser.
type ParserState uint32

// ParserState values.
const (
	StylesheetState ParserState = iota
	AtRuleState
	RulesetState
)

// String returns the string representation of a ParserState.
func (tt ParserState) String() string {
	switch tt {
	case StylesheetState:
		return "Stylesheet"
	case AtRuleState:
		return "AtRule"
	case RulesetState:
		return "Ruleset"
	}
	return "Invalid(" + strconv.Itoa(int(tt)) + ")"
}

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

// Parser is the state for the parser.
type Parser struct {
	z     *Tokenizer
	state []ParserState

	buf []*TokenNode
	pos int
}

// NewParser returns a new Parser for a Reader.
func NewParser(r io.Reader) *Parser {
	return &Parser{
		NewTokenizer(r),
		[]ParserState{StylesheetState},
		make([]*TokenNode, 0, 16),
		0,
	}
}

// Parse parses the entire CSS file and returns the root StylesheetNode.
func Parse(r io.Reader) (*StylesheetNode, error) {
	p := NewParser(r)
	var err error
	stylesheet := NewStylesheet()
	for {
		gt, n := p.Next()
		if gt == ErrorGrammar {
			err = p.z.Err()
			break
		}
		stylesheet.Nodes = append(stylesheet.Nodes, n)
		if err = p.parseRecursively(gt, n); err != nil {
			break
		}
	}
	if err != io.EOF {
		return stylesheet, err
	}
	return stylesheet, nil
}

func (p *Parser) parseRecursively(rootGt GrammarType, n Node) error {
	if rootGt == AtRuleGrammar {
		atRule := n.(*AtRuleNode)
		for {
			gt, m := p.Next()
			if gt == ErrorGrammar {
				return p.z.Err()
			} else if gt == EndAtRuleGrammar {
				break
			}
			atRule.Rules = append(atRule.Rules, m)
			if err := p.parseRecursively(gt, m); err != nil {
				return err
			}
		}
	} else if rootGt == RulesetGrammar {
		ruleset := n.(*RulesetNode)
		for {
			gt, m := p.Next()
			if gt == ErrorGrammar {
				return p.z.Err()
			} else if gt == EndRulesetGrammar {
				break
			}
			if decl, ok := m.(*DeclarationNode); ok {
				ruleset.Decls = append(ruleset.Decls, decl)
			}
		}
	}
	return nil
}

// Err returns the error encountered during parsing, this is often io.EOF but also other errors can be returned.
func (p Parser) Err() error {
	return p.z.Err()
}

// Next returns the next grammar unit from the CSS file.
// This is a lower-level function than Parse and is used to stream parse CSS instead of loading it fully into memory.
// Returned nodes of AtRule and Ruleset do not contain entries for Decls and Rules respectively. These are returned consecutively with calls to Next.
func (p *Parser) Next() (GrammarType, Node) {
	if p.at(ErrorToken) {
		return ErrorGrammar, nil
	}
	p.skipWhitespace()

	// return End types
	state := p.State()
	if p.at(RightBraceToken) && (state == AtRuleState || state == RulesetState) || p.at(SemicolonToken) && state == AtRuleState {
		n := p.shift()
		p.skipWhile(SemicolonToken)

		p.state = p.state[:len(p.state)-1]
		if state == AtRuleState {

			return EndAtRuleGrammar, n
		}
		return EndRulesetGrammar, n
	}

	if p.at(CDOToken) || p.at(CDCToken) {
		return TokenGrammar, p.shift()
	} else if cn := p.parseAtRule(); cn != nil {
		return AtRuleGrammar, cn
	} else if cn := p.parseRuleset(); cn != nil {
		return RulesetGrammar, cn
	} else if cn := p.parseDeclaration(); cn != nil {
		return DeclarationGrammar, cn
	}
	return TokenGrammar, p.shift()
}

func (p *Parser) State() ParserState {
	return p.state[len(p.state)-1]
}

func (p *Parser) parseAtRule() *AtRuleNode {
	if !p.at(AtKeywordToken) {
		return nil
	}
	n := NewAtRule(p.shift())
	p.skipWhitespace()
	for !p.at(SemicolonToken) && !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		n.Nodes = append(n.Nodes, p.shiftComponent())
		p.skipWhitespace()
	}
	if p.at(LeftBraceToken) {
		p.shift()
	}
	p.state = append(p.state, AtRuleState)
	return n
}

func (p *Parser) parseRuleset() *RulesetNode {
	// check if left brace appears, which is the only check if this is a valid ruleset
	i := 0
	for p.peek(i).TokenType != LeftBraceToken {
		if p.peek(i).TokenType == SemicolonToken || p.peek(i).TokenType == ErrorToken {
			return nil
		}
		i++
	}
	n := NewRuleset()
	for !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		if p.at(CommaToken) {
			p.shift()
			p.skipWhitespace()
			continue
		}
		if cn := p.parseSelector(); cn != nil {
			n.Selectors = append(n.Selectors, cn)
		}
		p.skipWhitespace()
	}
	if p.at(ErrorToken) {
		return nil
	}
	p.shift()
	p.state = append(p.state, RulesetState)
	return n
}

func (p *Parser) parseSelector() *SelectorNode {
	n := NewSelector()
	var ws *TokenNode
	for !p.at(CommaToken) && !p.at(LeftBraceToken) && !p.at(ErrorToken) {
		if p.at(DelimToken) && (p.data()[0] == '>' || p.data()[0] == '+' || p.data()[0] == '~') {
			n.Elems = append(n.Elems, p.shift())
			p.skipWhitespace()
		} else if p.at(LeftBracketToken) {
			for !p.at(RightBracketToken) && !p.at(ErrorToken) {
				n.Elems = append(n.Elems, p.shift())
				p.skipWhitespace()
			}
			if p.at(RightBracketToken) {
				n.Elems = append(n.Elems, p.shift())
			}
		} else {
			if ws != nil {
				n.Elems = append(n.Elems, ws)
			}
			n.Elems = append(n.Elems, p.shift())
		}

		if p.at(WhitespaceToken) {
			ws = p.shift()
		} else {
			ws = nil
		}
	}
	if len(n.Elems) == 0 {
		return nil
	}
	return n
}

func (p *Parser) parseDeclaration() *DeclarationNode {
	if !p.at(IdentToken) {
		return nil
	}
	ident := p.shift()
	p.skipWhitespace()
	if !p.at(ColonToken) {
		return nil
	}
	p.shift() // colon
	p.skipWhitespace()
	n := NewDeclaration(ident)
	for !p.at(SemicolonToken) && !p.at(RightBraceToken) && !p.at(ErrorToken) {
		if p.at(DelimToken) && p.data()[0] == '!' {
			exclamation := p.shift()
			p.skipWhitespace()
			if p.at(IdentToken) && bytes.Equal(bytes.ToLower(p.data()), []byte("important")) {
				n.Important = true
				p.shift()
			} else {
				n.Vals = append(n.Vals, exclamation)
			}
		} else if cn := p.parseFunction(); cn != nil {
			n.Vals = append(n.Vals, cn)
		} else {
			n.Vals = append(n.Vals, p.shift())
		}
		p.skipWhitespace()
	}
	p.skipWhile(SemicolonToken)
	return n
}

func (p *Parser) parseFunction() *FunctionNode {
	if !p.at(FunctionToken) {
		return nil
	}
	n := NewFunction(p.shift())
	p.skipWhitespace()
	for !p.at(RightParenthesisToken) && !p.at(ErrorToken) {
		if p.at(CommaToken) {
			p.shift()
			p.skipWhitespace()
			continue
		}
		n.Args = append(n.Args, p.parseArgument())
	}
	if p.at(ErrorToken) {
		return nil
	}
	p.shift()
	return n
}

func (p *Parser) parseArgument() *ArgumentNode {
	n := NewArgument()
	for !p.at(CommaToken) && !p.at(RightParenthesisToken) && !p.at(ErrorToken) {
		n.Vals = append(n.Vals, p.shiftComponent())
		p.skipWhitespace()
	}
	return n
}

func (p *Parser) parseBlock() *BlockNode {
	if !p.at(LeftParenthesisToken) && !p.at(LeftBraceToken) && !p.at(LeftBracketToken) {
		return nil
	}
	n := NewBlock(p.shift())
	p.skipWhitespace()
	for {
		if p.at(RightBraceToken) || p.at(RightParenthesisToken) || p.at(RightBracketToken) || p.at(ErrorToken) {
			break
		}
		n.Nodes = append(n.Nodes, p.shiftComponent())
		p.skipWhitespace()
	}
	if !p.at(ErrorToken) {
		n.Close = p.shift()
	}
	return n
}

func (p *Parser) shiftComponent() Node {
	if cn := p.parseBlock(); cn != nil {
		return cn
	} else if cn := p.parseFunction(); cn != nil {
		return cn
	} else {
		return p.shift()
	}
}

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

func copyBytes(src []byte) (dst []byte) {
	dst = make([]byte, len(src))
	copy(dst, src)
	return
}

func (p *Parser) read() *TokenNode {
	tt, text := p.z.Next()
	// ignore comments and multiple whitespace
	for tt == CommentToken || tt == WhitespaceToken && len(p.buf) > 0 && p.buf[len(p.buf)-1].TokenType == WhitespaceToken {
		tt, text = p.z.Next()
	}
	text = copyBytes(text) // copy necessary for whenever the tokenizer overwrites its buffer
	return NewToken(tt, text)
}

func (p *Parser) peek(i int) *TokenNode {
	if p.pos+i >= len(p.buf) {
		c := cap(p.buf)
		l := len(p.buf) - p.pos
		if p.pos+i >= c {
			// expand buffer when len is bigger than half the cap
			if 2*l > c {
				buf1 := make([]*TokenNode, l, 2*c)
				copy(buf1, p.buf[p.pos:])
				p.buf = buf1
			} else {
				copy(p.buf, p.buf[p.pos:])
				p.buf = p.buf[:l]
			}
			p.pos = 0
			if i >= cap(p.buf) {
				return NewToken(ErrorToken, []byte("looking too far ahead"))
			}
		}
		for j := len(p.buf); j <= p.pos+i; j++ {
			p.buf = append(p.buf, p.read())
		}
	}
	return p.buf[p.pos+i]
}

func (p *Parser) shift() *TokenNode {
	shifted := p.peek(0)
	p.pos++
	return shifted
}

func (p *Parser) at(tt TokenType) bool {
	return p.peek(0).TokenType == tt
}

func (p *Parser) data() []byte {
	return p.peek(0).Data
}

func (p *Parser) skipWhitespace() {
	if p.at(WhitespaceToken) {
		p.shift()
	}
}

func (p *Parser) skipWhile(tt TokenType) {
	for p.at(tt) || p.at(WhitespaceToken) {
		p.shift()
	}
}

func (p *Parser) skipUntil(tt TokenType) {
	for p.at(tt) && !p.at(ErrorToken) {
		p.shift()
	}
}
<|endoftext|>


<commit_before>// +build darwin,amd64

// Package mac is the driver to be used for applications that will run on MacOS.
package mac

/*
#include "driver.h"
#include "bridge.h"
*/
import "C"
import (
	"context"
	"crypto/md5"
	"fmt"
	"io"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/murlokswarm/app"
	"github.com/murlokswarm/app/bridge"
	"github.com/pkg/errors"
)

var (
	driver *Driver
)

// Driver is the app.Driver implementation for MacOS.
type Driver struct {
	MenubarConfig MenuBarConfig
	DockURL       string

	OnRun       func()
	OnFocus     func()
	OnBlur      func()
	OnReopen    func(hasVisibleWindows bool)
	OnFilesOpen func(filenames []string)
	OnURLOpen   func(u *url.URL)
	OnQuit      func() bool
	OnExit      func()

	factory  app.Factory
	elements app.ElementDB
	uichan   chan func()
	cancel   func()
	macos    bridge.PlatformBridge
	golang   bridge.GoBridge
	menubar  app.Menu
	dock     app.DockTile
	devID    string
}

// Name satisfies the app.Driver interface.
func (d *Driver) Name() string {
	return "MacOS"
}

// Run satisfies the app.Driver interface.
func (d *Driver) Run(f app.Factory) error {
	if driver != nil {
		return errors.Errorf("driver is already running")
	}

	d.devID = generateDevID()
	d.factory = f

	elements := app.NewElemDB()
	elements = app.NewConcurrentElemDB(elements)
	d.elements = elements

	d.uichan = make(chan func(), 4096)
	defer close(d.uichan)

	d.macos = bridge.NewPlatformBridge(handleMacOSRequest)
	d.golang = bridge.NewGoBridge(d.uichan)

	d.golang.Handle("/driver/run", d.onRun)
	d.golang.Handle("/driver/focus", d.onFocus)
	d.golang.Handle("/driver/blur", d.onBlur)
	d.golang.Handle("/driver/reopen", d.onReopen)
	d.golang.Handle("/driver/filesopen", d.onFilesOpen)
	d.golang.Handle("/driver/urlopen", d.onURLOpen)
	d.golang.Handle("/driver/quit", d.onQuit)
	d.golang.Handle("/driver/exit", d.onExit)

	d.golang.Handle("/window/move", windowHandler(onWindowMove))
	d.golang.Handle("/window/resize", windowHandler(onWindowResize))
	d.golang.Handle("/window/focus", windowHandler(onWindowFocus))
	d.golang.Handle("/window/blur", windowHandler(onWindowBlur))
	d.golang.Handle("/window/fullscreen", windowHandler(onWindowFullScreen))
	d.golang.Handle("/window/fullscreen/exit", windowHandler(onWindowExitFullScreen))
	d.golang.Handle("/window/minimize", windowHandler(onWindowMinimize))
	d.golang.Handle("/window/deminimize", windowHandler(onWindowDeminimize))
	d.golang.Handle("/window/close", windowHandler(onWindowClose))
	d.golang.Handle("/window/callback", windowHandler(onWindowCallback))
	d.golang.Handle("/window/navigate", windowHandler(onWindowNavigate))

	d.golang.Handle("/menu/close", menuHandler(onMenuClose))
	d.golang.Handle("/menu/callback", menuHandler(onMenuCallback))

	d.golang.Handle("/file/panel/select", filePanelHandler(onFilePanelClose))

	d.golang.Handle("/notification/reply", notificationHandler(onNotificationReply))

	var ctx context.Context
	ctx, d.cancel = context.WithCancel(context.Background())
	defer d.cancel()

	driver = d

	errC := make(chan error)
	go func() {
		_, err := d.macos.Request("/driver/run", nil)
		errC <- err
	}()

	for {
		select {
		case function := <-d.uichan:
			function()

		case <-ctx.Done():
			break
		}
	}
	return <-errC
}

func (d *Driver) onRun(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	err := d.newMenuBar()
	if err != nil {
		panic(err)
	}

	if d.dock, err = newDockTile(app.MenuConfig{
		DefaultURL: d.DockURL,
	}); err != nil {
		panic(err)
	}

	if d.OnRun != nil {
		d.OnRun()
	}
	return nil
}

func (d *Driver) onFocus(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnFocus == nil {
		return
	}

	d.OnFocus()
	return
}

func (d *Driver) onBlur(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnBlur == nil {
		return
	}

	d.OnBlur()
	return
}

func (d *Driver) onReopen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnReopen == nil {
		return
	}

	var hasVisibleWindows bool
	p.Unmarshal(&hasVisibleWindows)
	d.OnReopen(hasVisibleWindows)
	return
}

func (d *Driver) onFilesOpen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnFilesOpen == nil {
		return
	}

	var filenames []string
	p.Unmarshal(&filenames)
	d.OnFilesOpen(filenames)
	return
}

func (d *Driver) onURLOpen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnURLOpen == nil {
		return
	}

	var rawurl string
	p.Unmarshal(&rawurl)

	purl, err := url.Parse(rawurl)
	if err != nil {
		panic(errors.Wrap(err, "parsing url failed"))
	}

	d.OnURLOpen(purl)
	return
}

// AppName satisfies the app.Driver interface.
func (d *Driver) AppName() string {
	res, err := d.macos.Request("/driver/appname", nil)
	if err != nil {
		panic(errors.Wrap(err, "getting app name failed"))
	}

	var appName string
	res.Unmarshal(&appName)
	if len(appName) != 0 && appName != "(null)" {
		return appName
	}

	var wd string
	if wd, err = os.Getwd(); err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}
	return filepath.Base(wd)
}

// Resources satisfies the app.Driver interface.
func (d *Driver) Resources(path ...string) string {
	res, err := d.macos.Request("/driver/resources", nil)
	if err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}

	var dirname string
	res.Unmarshal(&dirname)

	wd, err := os.Getwd()
	if err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}

	if filepath.Dir(dirname) == wd {
		dirname = filepath.Join(wd, "resources")
	}

	resources := append([]string{dirname}, path...)
	return filepath.Join(resources...)
}

// Storage satisfies the app.DriverWithStorage interface.
func (d *Driver) Storage(path ...string) string {
	support, err := d.support()
	if err != nil {
		panic(errors.Wrap(err, "getting storage filepath failed"))
	}

	storage := append([]string{support}, "storage")
	storage = append(storage, path...)
	return filepath.Join(storage...)
}

func (d *Driver) support() (dirname string, err error) {
	var res bridge.Payload
	if res, err = d.macos.Request("/driver/support", nil); err != nil {
		return "", err
	}
	res.Unmarshal(&dirname)

	// Set up the support directory in case of the app is not bundled.
	if strings.HasSuffix(dirname, "{appname}") {
		var wd string
		if wd, err = os.Getwd(); err != nil {
			return "", err
		}
		appname := filepath.Base(wd) + "-" + d.devID
		dirname = strings.Replace(dirname, "{appname}", appname, 1)
	}
	return dirname, nil
}

// NewWindow satisfies the app.DriverWithWindows interface.
func (d *Driver) NewWindow(c app.WindowConfig) (app.Window, error) {
	return newWindow(c)
}

// NewContextMenu satisfies the app.Driver interface.
func (d *Driver) NewContextMenu(c app.MenuConfig) (app.Menu, error) {
	m, err := newMenu(c, "context menu")
	if err != nil {
		return nil, err
	}

	_, err = d.macos.RequestWithAsyncResponse(
		"/driver/contextmenu/set?menu-id="+m.ID().String(),
		nil,
	)
	return m, err
}

// Render satisfies the app.Driver interface.
func (d *Driver) Render(c app.Component) error {
	e, err := d.elements.ElementByComponent(c)
	if err != nil {
		return err
	}
	return e.Render(c)
}

// ElementByComponent satisfies the app.Driver interface.
func (d *Driver) ElementByComponent(c app.Component) (app.ElementWithComponent, error) {
	return d.elements.ElementByComponent(c)
}

// NewFilePanel satisfies the app.DriverWithFilePanels interface.
func (d *Driver) NewFilePanel(c app.FilePanelConfig) error {
	return newFilePanel(c)
}

// NewShare satisfies the app.DriverWithShare interface.
func (d *Driver) NewShare(v interface{}) error {
	share := struct {
		Value string `json:"value"`
		Type  string `json:"type"`
	}{
		Value: fmt.Sprint(v),
	}

	switch v.(type) {
	case url.URL, *url.URL:
		share.Type = "url"

	default:
		share.Type = "string"
	}

	_, err := d.macos.RequestWithAsyncResponse(
		"/driver/share",
		bridge.NewPayload(share),
	)
	return err

}

// NewNotification satisfies the app.DriverWithPopupNotifications
// interface.
func (d *Driver) NewNotification(config app.NotificationConfig) error {
	_, err := newNotification(config)
	return err
}

// MenuBar satisfies the app.DriverWithMenuBar interface.
func (d *Driver) MenuBar() app.Menu {
	return d.menubar
}

func (d *Driver) newMenuBar() error {
	menubar, err := newMenu(app.MenuConfig{}, "menubar")
	if err != nil {
		return errors.Wrap(err, "creating the menu bar failed")
	}
	d.menubar = menubar

	if len(d.MenubarConfig.URL) == 0 {
		format := "mac.menubar?appurl=%s&editurl=%s&windowurl=%s&helpurl=%s"
		for _, u := range d.MenubarConfig.CustomURLs {
			format += "&custom=" + u
		}

		err = menubar.Load(
			format,
			d.MenubarConfig.AppURL,
			d.MenubarConfig.EditURL,
			d.MenubarConfig.WindowURL,
			d.MenubarConfig.HelpURL,
		)
	} else {
		err = menubar.Load(d.MenubarConfig.URL)
	}
	if err != nil {
		return err
	}

	if _, err = d.macos.Request(
		fmt.Sprintf("/driver/menubar/set?menu-id=%v", menubar.ID()),
		nil,
	); err != nil {
		return errors.Wrap(err, "set the menu bar failed")
	}
	return nil
}

// Dock satisfies the app.DriverWithDock interface.
func (d *Driver) Dock() app.DockTile {
	return d.dock
}

// CallOnUIGoroutine satisfies the app.Driver interface.
func (d *Driver) CallOnUIGoroutine(f func()) {
	d.uichan <- f
}

// Quit quits the app.
func (d *Driver) Quit() {
	d.macos.Request("/driver/quit", nil)
}

func (d *Driver) onQuit(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	quit := true

	if d.OnQuit != nil {
		quit = d.OnQuit()
	}

	res = bridge.NewPayload(quit)
	return
}

func (d *Driver) onExit(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnExit != nil {
		d.OnExit()
	}

	d.cancel()
	return nil
}

func generateDevID() string {
	h := md5.New()
	wd, _ := os.Getwd()
	io.WriteString(h, wd)
	return fmt.Sprintf("%x", h.Sum(nil))
}
<commit_msg>govet fix<commit_after>// +build darwin,amd64

// Package mac is the driver to be used for applications that will run on MacOS.
package mac

/*
#include "driver.h"
#include "bridge.h"
*/
import "C"
import (
	"context"
	"crypto/md5"
	"fmt"
	"io"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/murlokswarm/app"
	"github.com/murlokswarm/app/bridge"
	"github.com/pkg/errors"
)

var (
	driver *Driver
)

// Driver is the app.Driver implementation for MacOS.
type Driver struct {
	MenubarConfig MenuBarConfig
	DockURL       string

	OnRun       func()
	OnFocus     func()
	OnBlur      func()
	OnReopen    func(hasVisibleWindows bool)
	OnFilesOpen func(filenames []string)
	OnURLOpen   func(u *url.URL)
	OnQuit      func() bool
	OnExit      func()

	factory  app.Factory
	elements app.ElementDB
	uichan   chan func()
	cancel   func()
	macos    bridge.PlatformBridge
	golang   bridge.GoBridge
	menubar  app.Menu
	dock     app.DockTile
	devID    string
}

// Name satisfies the app.Driver interface.
func (d *Driver) Name() string {
	return "MacOS"
}

// Run satisfies the app.Driver interface.
func (d *Driver) Run(f app.Factory) error {
	if driver != nil {
		return errors.Errorf("driver is already running")
	}

	d.devID = generateDevID()
	d.factory = f

	elements := app.NewElemDB()
	elements = app.NewConcurrentElemDB(elements)
	d.elements = elements

	d.uichan = make(chan func(), 4096)
	defer close(d.uichan)

	d.macos = bridge.NewPlatformBridge(handleMacOSRequest)
	d.golang = bridge.NewGoBridge(d.uichan)

	d.golang.Handle("/driver/run", d.onRun)
	d.golang.Handle("/driver/focus", d.onFocus)
	d.golang.Handle("/driver/blur", d.onBlur)
	d.golang.Handle("/driver/reopen", d.onReopen)
	d.golang.Handle("/driver/filesopen", d.onFilesOpen)
	d.golang.Handle("/driver/urlopen", d.onURLOpen)
	d.golang.Handle("/driver/quit", d.onQuit)
	d.golang.Handle("/driver/exit", d.onExit)

	d.golang.Handle("/window/move", windowHandler(onWindowMove))
	d.golang.Handle("/window/resize", windowHandler(onWindowResize))
	d.golang.Handle("/window/focus", windowHandler(onWindowFocus))
	d.golang.Handle("/window/blur", windowHandler(onWindowBlur))
	d.golang.Handle("/window/fullscreen", windowHandler(onWindowFullScreen))
	d.golang.Handle("/window/fullscreen/exit", windowHandler(onWindowExitFullScreen))
	d.golang.Handle("/window/minimize", windowHandler(onWindowMinimize))
	d.golang.Handle("/window/deminimize", windowHandler(onWindowDeminimize))
	d.golang.Handle("/window/close", windowHandler(onWindowClose))
	d.golang.Handle("/window/callback", windowHandler(onWindowCallback))
	d.golang.Handle("/window/navigate", windowHandler(onWindowNavigate))

	d.golang.Handle("/menu/close", menuHandler(onMenuClose))
	d.golang.Handle("/menu/callback", menuHandler(onMenuCallback))

	d.golang.Handle("/file/panel/select", filePanelHandler(onFilePanelClose))

	d.golang.Handle("/notification/reply", notificationHandler(onNotificationReply))

	var ctx context.Context
	ctx, d.cancel = context.WithCancel(context.Background())
	defer d.cancel()

	driver = d

	errC := make(chan error)
	go func() {
		_, err := d.macos.Request("/driver/run", nil)
		errC <- err
	}()

	for {
		select {
		case function := <-d.uichan:
			function()

		case <-ctx.Done():
			return <-errC
		}
	}
}

func (d *Driver) onRun(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	err := d.newMenuBar()
	if err != nil {
		panic(err)
	}

	if d.dock, err = newDockTile(app.MenuConfig{
		DefaultURL: d.DockURL,
	}); err != nil {
		panic(err)
	}

	if d.OnRun != nil {
		d.OnRun()
	}
	return nil
}

func (d *Driver) onFocus(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnFocus == nil {
		return
	}

	d.OnFocus()
	return
}

func (d *Driver) onBlur(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnBlur == nil {
		return
	}

	d.OnBlur()
	return
}

func (d *Driver) onReopen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnReopen == nil {
		return
	}

	var hasVisibleWindows bool
	p.Unmarshal(&hasVisibleWindows)
	d.OnReopen(hasVisibleWindows)
	return
}

func (d *Driver) onFilesOpen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnFilesOpen == nil {
		return
	}

	var filenames []string
	p.Unmarshal(&filenames)
	d.OnFilesOpen(filenames)
	return
}

func (d *Driver) onURLOpen(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnURLOpen == nil {
		return
	}

	var rawurl string
	p.Unmarshal(&rawurl)

	purl, err := url.Parse(rawurl)
	if err != nil {
		panic(errors.Wrap(err, "parsing url failed"))
	}

	d.OnURLOpen(purl)
	return
}

// AppName satisfies the app.Driver interface.
func (d *Driver) AppName() string {
	res, err := d.macos.Request("/driver/appname", nil)
	if err != nil {
		panic(errors.Wrap(err, "getting app name failed"))
	}

	var appName string
	res.Unmarshal(&appName)
	if len(appName) != 0 && appName != "(null)" {
		return appName
	}

	var wd string
	if wd, err = os.Getwd(); err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}
	return filepath.Base(wd)
}

// Resources satisfies the app.Driver interface.
func (d *Driver) Resources(path ...string) string {
	res, err := d.macos.Request("/driver/resources", nil)
	if err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}

	var dirname string
	res.Unmarshal(&dirname)

	wd, err := os.Getwd()
	if err != nil {
		panic(errors.Wrap(err, "getting resources filepath failed"))
	}

	if filepath.Dir(dirname) == wd {
		dirname = filepath.Join(wd, "resources")
	}

	resources := append([]string{dirname}, path...)
	return filepath.Join(resources...)
}

// Storage satisfies the app.DriverWithStorage interface.
func (d *Driver) Storage(path ...string) string {
	support, err := d.support()
	if err != nil {
		panic(errors.Wrap(err, "getting storage filepath failed"))
	}

	storage := append([]string{support}, "storage")
	storage = append(storage, path...)
	return filepath.Join(storage...)
}

func (d *Driver) support() (dirname string, err error) {
	var res bridge.Payload
	if res, err = d.macos.Request("/driver/support", nil); err != nil {
		return "", err
	}
	res.Unmarshal(&dirname)

	// Set up the support directory in case of the app is not bundled.
	if strings.HasSuffix(dirname, "{appname}") {
		var wd string
		if wd, err = os.Getwd(); err != nil {
			return "", err
		}
		appname := filepath.Base(wd) + "-" + d.devID
		dirname = strings.Replace(dirname, "{appname}", appname, 1)
	}
	return dirname, nil
}

// NewWindow satisfies the app.DriverWithWindows interface.
func (d *Driver) NewWindow(c app.WindowConfig) (app.Window, error) {
	return newWindow(c)
}

// NewContextMenu satisfies the app.Driver interface.
func (d *Driver) NewContextMenu(c app.MenuConfig) (app.Menu, error) {
	m, err := newMenu(c, "context menu")
	if err != nil {
		return nil, err
	}

	_, err = d.macos.RequestWithAsyncResponse(
		"/driver/contextmenu/set?menu-id="+m.ID().String(),
		nil,
	)
	return m, err
}

// Render satisfies the app.Driver interface.
func (d *Driver) Render(c app.Component) error {
	e, err := d.elements.ElementByComponent(c)
	if err != nil {
		return err
	}
	return e.Render(c)
}

// ElementByComponent satisfies the app.Driver interface.
func (d *Driver) ElementByComponent(c app.Component) (app.ElementWithComponent, error) {
	return d.elements.ElementByComponent(c)
}

// NewFilePanel satisfies the app.DriverWithFilePanels interface.
func (d *Driver) NewFilePanel(c app.FilePanelConfig) error {
	return newFilePanel(c)
}

// NewShare satisfies the app.DriverWithShare interface.
func (d *Driver) NewShare(v interface{}) error {
	share := struct {
		Value string `json:"value"`
		Type  string `json:"type"`
	}{
		Value: fmt.Sprint(v),
	}

	switch v.(type) {
	case url.URL, *url.URL:
		share.Type = "url"

	default:
		share.Type = "string"
	}

	_, err := d.macos.RequestWithAsyncResponse(
		"/driver/share",
		bridge.NewPayload(share),
	)
	return err

}

// NewNotification satisfies the app.DriverWithPopupNotifications
// interface.
func (d *Driver) NewNotification(config app.NotificationConfig) error {
	_, err := newNotification(config)
	return err
}

// MenuBar satisfies the app.DriverWithMenuBar interface.
func (d *Driver) MenuBar() app.Menu {
	return d.menubar
}

func (d *Driver) newMenuBar() error {
	menubar, err := newMenu(app.MenuConfig{}, "menubar")
	if err != nil {
		return errors.Wrap(err, "creating the menu bar failed")
	}
	d.menubar = menubar

	if len(d.MenubarConfig.URL) == 0 {
		format := "mac.menubar?appurl=%s&editurl=%s&windowurl=%s&helpurl=%s"
		for _, u := range d.MenubarConfig.CustomURLs {
			format += "&custom=" + u
		}

		err = menubar.Load(
			format,
			d.MenubarConfig.AppURL,
			d.MenubarConfig.EditURL,
			d.MenubarConfig.WindowURL,
			d.MenubarConfig.HelpURL,
		)
	} else {
		err = menubar.Load(d.MenubarConfig.URL)
	}
	if err != nil {
		return err
	}

	if _, err = d.macos.Request(
		fmt.Sprintf("/driver/menubar/set?menu-id=%v", menubar.ID()),
		nil,
	); err != nil {
		return errors.Wrap(err, "set the menu bar failed")
	}
	return nil
}

// Dock satisfies the app.DriverWithDock interface.
func (d *Driver) Dock() app.DockTile {
	return d.dock
}

// CallOnUIGoroutine satisfies the app.Driver interface.
func (d *Driver) CallOnUIGoroutine(f func()) {
	d.uichan <- f
}

// Quit quits the app.
func (d *Driver) Quit() {
	d.macos.Request("/driver/quit", nil)
}

func (d *Driver) onQuit(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	quit := true

	if d.OnQuit != nil {
		quit = d.OnQuit()
	}

	res = bridge.NewPayload(quit)
	return
}

func (d *Driver) onExit(u *url.URL, p bridge.Payload) (res bridge.Payload) {
	if d.OnExit != nil {
		d.OnExit()
	}

	d.cancel()
	return nil
}

func generateDevID() string {
	h := md5.New()
	wd, _ := os.Getwd()
	io.WriteString(h, wd)
	return fmt.Sprintf("%x", h.Sum(nil))
}
<|endoftext|>


<commit_before>package scheduler

import (
	"fmt"
	"log"

	"github.com/hashicorp/go-memdb"
	"github.com/hashicorp/nomad/nomad/state"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// SchedulerVersion is the version of the scheduler. Changes to the
	// scheduler that are incompatible with prior schedulers will increment this
	// version. It is used to disallow dequeueing when the versions do not match
	// across the leader and the dequeueing scheduler.
	SchedulerVersion uint16 = 1
)

// BuiltinSchedulers contains the built in registered schedulers
// which are available
var BuiltinSchedulers = map[string]Factory{
	"service": NewServiceScheduler,
	"batch":   NewBatchScheduler,
	"system":  NewSystemScheduler,
}

// NewScheduler is used to instantiate and return a new scheduler
// given the scheduler name, initial state, and planner.
func NewScheduler(name string, logger *log.Logger, state State, planner Planner) (Scheduler, error) {
	// Lookup the factory function
	factory, ok := BuiltinSchedulers[name]
	if !ok {
		return nil, fmt.Errorf("unknown scheduler '%s'", name)
	}

	// Instantiate the scheduler
	sched := factory(logger, state, planner)
	return sched, nil
}

// Factory is used to instantiate a new Scheduler
type Factory func(*log.Logger, State, Planner) Scheduler

// Scheduler is the top level instance for a scheduler. A scheduler is
// meant to only encapsulate business logic, pushing the various plumbing
// into Nomad itself. They are invoked to process a single evaluation at
// a time. The evaluation may result in task allocations which are computed
// optimistically, as there are many concurrent evaluations being processed.
// The task allocations are submitted as a plan, and the current leader will
// coordinate the commmits to prevent oversubscription or improper allocations
// based on stale state.
type Scheduler interface {
	// Process is used to handle a new evaluation. The scheduler is free to
	// apply any logic necessary to make the task placements. The state and
	// planner will be provided prior to any invocations of process.
	Process(*structs.Evaluation) error
}

// State is an immutable view of the global state. This allows schedulers
// to make intelligent decisions based on allocations of other schedulers
// and to enforce complex constraints that require more information than
// is available to a local state scheduler.
type State interface {
	// Config returns the configuration of the state store
	Config() *state.StateStoreConfig

	// Nodes returns an iterator over all the nodes.
	// The type of each result is *structs.Node
	Nodes(ws memdb.WatchSet) (memdb.ResultIterator, error)

	// AllocsByJob returns the allocations by JobID
	AllocsByJob(ws memdb.WatchSet, namespace, jobID string, all bool) ([]*structs.Allocation, error)

	// AllocsByNode returns all the allocations by node
	AllocsByNode(ws memdb.WatchSet, node string) ([]*structs.Allocation, error)

	// AllocsByNodeTerminal returns all the allocations by node filtering by terminal status
	AllocsByNodeTerminal(ws memdb.WatchSet, node string, terminal bool) ([]*structs.Allocation, error)

	// GetNodeByID is used to lookup a node by ID
	NodeByID(ws memdb.WatchSet, nodeID string) (*structs.Node, error)

	// GetJobByID is used to lookup a job by ID
	JobByID(ws memdb.WatchSet, namespace, id string) (*structs.Job, error)

	// LatestDeploymentByJobID returns the latest deployment matching the given
	// job ID
	LatestDeploymentByJobID(ws memdb.WatchSet, namespace, jobID string) (*structs.Deployment, error)
}

// Planner interface is used to submit a task allocation plan.
type Planner interface {
	// SubmitPlan is used to submit a plan for consideration.
	// This will return a PlanResult or an error. It is possible
	// that this will result in a state refresh as well.
	SubmitPlan(*structs.Plan) (*structs.PlanResult, State, error)

	// UpdateEval is used to update an evaluation. This should update
	// a copy of the input evaluation since that should be immutable.
	UpdateEval(*structs.Evaluation) error

	// CreateEval is used to create an evaluation. This should set the
	// PreviousEval to that of the current evaluation.
	CreateEval(*structs.Evaluation) error

	// ReblockEval takes a blocked evaluation and re-inserts it into the blocked
	// evaluation tracker. This update occurs only in-memory on the leader. The
	// evaluation must exist in a blocked state prior to this being called such
	// that on leader changes, the evaluation will be reblocked properly.
	ReblockEval(*structs.Evaluation) error
}
<commit_msg>spelling: commits<commit_after>package scheduler

import (
	"fmt"
	"log"

	"github.com/hashicorp/go-memdb"
	"github.com/hashicorp/nomad/nomad/state"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// SchedulerVersion is the version of the scheduler. Changes to the
	// scheduler that are incompatible with prior schedulers will increment this
	// version. It is used to disallow dequeueing when the versions do not match
	// across the leader and the dequeueing scheduler.
	SchedulerVersion uint16 = 1
)

// BuiltinSchedulers contains the built in registered schedulers
// which are available
var BuiltinSchedulers = map[string]Factory{
	"service": NewServiceScheduler,
	"batch":   NewBatchScheduler,
	"system":  NewSystemScheduler,
}

// NewScheduler is used to instantiate and return a new scheduler
// given the scheduler name, initial state, and planner.
func NewScheduler(name string, logger *log.Logger, state State, planner Planner) (Scheduler, error) {
	// Lookup the factory function
	factory, ok := BuiltinSchedulers[name]
	if !ok {
		return nil, fmt.Errorf("unknown scheduler '%s'", name)
	}

	// Instantiate the scheduler
	sched := factory(logger, state, planner)
	return sched, nil
}

// Factory is used to instantiate a new Scheduler
type Factory func(*log.Logger, State, Planner) Scheduler

// Scheduler is the top level instance for a scheduler. A scheduler is
// meant to only encapsulate business logic, pushing the various plumbing
// into Nomad itself. They are invoked to process a single evaluation at
// a time. The evaluation may result in task allocations which are computed
// optimistically, as there are many concurrent evaluations being processed.
// The task allocations are submitted as a plan, and the current leader will
// coordinate the commits to prevent oversubscription or improper allocations
// based on stale state.
type Scheduler interface {
	// Process is used to handle a new evaluation. The scheduler is free to
	// apply any logic necessary to make the task placements. The state and
	// planner will be provided prior to any invocations of process.
	Process(*structs.Evaluation) error
}

// State is an immutable view of the global state. This allows schedulers
// to make intelligent decisions based on allocations of other schedulers
// and to enforce complex constraints that require more information than
// is available to a local state scheduler.
type State interface {
	// Config returns the configuration of the state store
	Config() *state.StateStoreConfig

	// Nodes returns an iterator over all the nodes.
	// The type of each result is *structs.Node
	Nodes(ws memdb.WatchSet) (memdb.ResultIterator, error)

	// AllocsByJob returns the allocations by JobID
	AllocsByJob(ws memdb.WatchSet, namespace, jobID string, all bool) ([]*structs.Allocation, error)

	// AllocsByNode returns all the allocations by node
	AllocsByNode(ws memdb.WatchSet, node string) ([]*structs.Allocation, error)

	// AllocsByNodeTerminal returns all the allocations by node filtering by terminal status
	AllocsByNodeTerminal(ws memdb.WatchSet, node string, terminal bool) ([]*structs.Allocation, error)

	// GetNodeByID is used to lookup a node by ID
	NodeByID(ws memdb.WatchSet, nodeID string) (*structs.Node, error)

	// GetJobByID is used to lookup a job by ID
	JobByID(ws memdb.WatchSet, namespace, id string) (*structs.Job, error)

	// LatestDeploymentByJobID returns the latest deployment matching the given
	// job ID
	LatestDeploymentByJobID(ws memdb.WatchSet, namespace, jobID string) (*structs.Deployment, error)
}

// Planner interface is used to submit a task allocation plan.
type Planner interface {
	// SubmitPlan is used to submit a plan for consideration.
	// This will return a PlanResult or an error. It is possible
	// that this will result in a state refresh as well.
	SubmitPlan(*structs.Plan) (*structs.PlanResult, State, error)

	// UpdateEval is used to update an evaluation. This should update
	// a copy of the input evaluation since that should be immutable.
	UpdateEval(*structs.Evaluation) error

	// CreateEval is used to create an evaluation. This should set the
	// PreviousEval to that of the current evaluation.
	CreateEval(*structs.Evaluation) error

	// ReblockEval takes a blocked evaluation and re-inserts it into the blocked
	// evaluation tracker. This update occurs only in-memory on the leader. The
	// evaluation must exist in a blocked state prior to this being called such
	// that on leader changes, the evaluation will be reblocked properly.
	ReblockEval(*structs.Evaluation) error
}
<|endoftext|>


<commit_before>package testing

import (
	"bytes"
	"context"
	"fmt"
	"sort"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/platform"
)

var labelCmpOptions = cmp.Options{
	cmp.Comparer(func(x, y []byte) bool {
		return bytes.Equal(x, y)
	}),
	cmp.Transformer("Sort", func(in []*platform.Label) []*platform.Label {
		out := append([]*platform.Label(nil), in...) // Copy input to avoid mutating it
		sort.Slice(out, func(i, j int) bool {
			return out[i].ResourceID.String() > out[j].ResourceID.String()
		})
		return out
	}),
}

type LabelFields struct {
	Labels []*platform.Label
}

type labelServiceF func(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
)

// LabelService tests all the service functions.
func LabelService(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	tests := []struct {
		name string
		fn   labelServiceF
	}{
		{
			name: "CreateLabel",
			fn:   CreateLabel,
		},
		{
			name: "FindLabels",
			fn:   FindLabels,
		},
		{
			name: "DeleteLabel",
			fn:   DeleteLabel,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.fn(init, t)
		})
	}
}

func CreateLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label *platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic create label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "duplicate labels fail",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label Tag1 already exists"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.Background()
			err := s.CreateLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}
			defer s.DeleteLabel(ctx, *tt.args.label)

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, mappingCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func FindLabels(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		filter platform.LabelFilter
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic find labels",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "find labels filtering",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
					{
						ResourceID: MustIDBase16(bucketTwoID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			labels, err := s.FindLabels(ctx, tt.args.filter)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			if diff := cmp.Diff(labels, tt.wants.labels, mappingCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func DeleteLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic delete label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "deleting a non-existant label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label not found"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			err := s.DeleteLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}
<commit_msg>fix(testing): sort label names to prevent random test failure (#1699)<commit_after>package testing

import (
	"bytes"
	"context"
	"fmt"
	"sort"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/platform"
)

var labelCmpOptions = cmp.Options{
	cmp.Comparer(func(x, y []byte) bool {
		return bytes.Equal(x, y)
	}),
	cmp.Transformer("Sort", func(in []*platform.Label) []*platform.Label {
		out := append([]*platform.Label(nil), in...) // Copy input to avoid mutating it
		sort.Slice(out, func(i, j int) bool {
			if out[i].Name != out[j].Name {
				return out[i].Name < out[j].Name
			}
			return out[i].ResourceID.String() < out[j].ResourceID.String()
		})
		return out
	}),
}

type LabelFields struct {
	Labels []*platform.Label
}

type labelServiceF func(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
)

// LabelService tests all the service functions.
func LabelService(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	tests := []struct {
		name string
		fn   labelServiceF
	}{
		{
			name: "CreateLabel",
			fn:   CreateLabel,
		},
		{
			name: "FindLabels",
			fn:   FindLabels,
		},
		{
			name: "DeleteLabel",
			fn:   DeleteLabel,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.fn(init, t)
		})
	}
}

func CreateLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label *platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic create label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "duplicate labels fail",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label Tag1 already exists"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.Background()
			err := s.CreateLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}
			defer s.DeleteLabel(ctx, *tt.args.label)

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func FindLabels(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		filter platform.LabelFilter
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic find labels",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "find labels filtering",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
					{
						ResourceID: MustIDBase16(bucketTwoID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			labels, err := s.FindLabels(ctx, tt.args.filter)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			if diff := cmp.Diff(labels, tt.wants.labels, mappingCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func DeleteLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic delete label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "deleting a non-existant label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label not found"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			err := s.DeleteLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017-2018 Alibaba Group Holding Limited
 */
package config

import (
	"encoding/json"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
	"github.com/aliyun/aliyun-cli/cli"
	"github.com/jmespath/go-jmespath"
	"net/http"
	"strings"
	"time"
)

type AuthenticateMode string

const (
	AK         = AuthenticateMode("AK")
	StsToken   = AuthenticateMode("StsToken")
	RamRoleArn = AuthenticateMode("RamRoleArn")
	EcsRamRole = AuthenticateMode("EcsRamRole")
	RsaKeyPair = AuthenticateMode("RsaKeyPair")
)

type Profile struct {
	Name            string           `json:"name"`
	Mode            AuthenticateMode `json:"mode"`
	AccessKeyId     string           `json:"access_key_id"`
	AccessKeySecret string           `json:"access_key_secret"`
	StsToken        string           `json:"sts_token"`
	RamRoleName     string           `json:"ram_role_name"`
	RamRoleArn      string           `json:"ram_role_arn"`
	RoleSessionName string           `json:"ram_session_name"`
	PrivateKey      string           `json:"private_key"`
	KeyPairName     string           `json:"key_pair_name"`
	ExpiredSeconds  int              `json:"expired_seconds"`
	Verified        string           `json:"verified"`
	RegionId        string           `json:"region_id"`
	OutputFormat    string           `json:"output_format"`
	Language        string           `json:"language"`
	Site            string           `json:"site"`
	RetryTimeout 	int 			 `json:"retry_timeout"`
	RetryCount		int 			 `json:"retry_count"`
	parent 			*Configuration	`json:"-"`
}

func NewProfile(name string) Profile {
	return Profile{
		Name:         name,
		Mode:         AK,
		OutputFormat: "json",
		Language:     "en",
	}
}

func (cp *Profile) Validate() error {
	if cp.Mode == "" {
		return fmt.Errorf("profile %s is not configure yet, run `aliyun configure --profile %s` first", cp.Name, cp.Name)
	}
	switch cp.Mode {
	case AK:
		return cp.ValidateAK()
	case StsToken:
		err := cp.ValidateAK()
		if err != nil {
			return err
		}
		if cp.StsToken == "" {
			return fmt.Errorf("invailed sts_token")
		}
	case RamRoleArn:
		err := cp.ValidateAK()
		if err != nil {
			return err
		}
		if cp.RamRoleArn == "" {
			return fmt.Errorf("invailed ram_role_arn")
		}
		if cp.RoleSessionName == "" {
			return fmt.Errorf("invailed role_session_name")
		}
	case EcsRamRole:
		if cp.RamRoleName == "" {
			return fmt.Errorf("invailed ram_role_name")
		}
	case RsaKeyPair:
		if cp.PrivateKey == "" {
			return fmt.Errorf("invailed private_key")
		}
		if cp.KeyPairName == "" {
			return fmt.Errorf("invailed key_pair_name")
		}
	default:
		return fmt.Errorf("invailed mode: %s", cp.Mode)
	}
	if cp.RegionId == "" {
		return fmt.Errorf("region can't be empty")
	}
	return nil
}

func (cp *Profile) GetParent() *Configuration {
	return cp.parent
}

func (cp *Profile) OverwriteWithFlags(ctx *cli.Context) {
	cp.Mode = AuthenticateMode(ModeFlag.GetStringOrDefault(string(cp.Mode)))
	cp.AccessKeyId = AccessKeyIdFlag.GetStringOrDefault(cp.AccessKeyId)
	cp.AccessKeySecret = AccessKeySecretFlag.GetStringOrDefault(cp.AccessKeySecret)
	cp.StsToken = StsTokenFlag.GetStringOrDefault(cp.StsToken)
	cp.RamRoleName = RamRoleNameFlag.GetStringOrDefault(cp.RamRoleName)
	cp.RamRoleArn = RamRoleArnFlag.GetStringOrDefault(cp.RamRoleArn)
	cp.RoleSessionName = RoleSessionNameFlag.GetStringOrDefault(cp.RoleSessionName)
	cp.KeyPairName = KeyPairNameFlag.GetStringOrDefault(cp.KeyPairName)
	cp.PrivateKey = PrivateKeyFlag.GetStringOrDefault(cp.PrivateKey)
	cp.RegionId = RegionFlag.GetStringOrDefault(cp.RegionId)
	cp.Language = LanguageFlag.GetStringOrDefault(cp.Language)
	cp.RetryTimeout = RetryTimeoutFlag.GetIntegerOrDefault(cp.RetryTimeout)
	cp.RetryCount = RetryTimeoutFlag.GetIntegerOrDefault(cp.RetryCount)

	if cp.AccessKeyId != "" && cp.AccessKeySecret != "" {
		cp.Mode = AK
		if cp.StsToken != "" {
			cp.Mode = StsToken
		} else if cp.RamRoleArn != "" {
			cp.Mode = RamRoleArn
		}
	}
	if cp.PrivateKey != "" && cp.KeyPairName != "" {
		cp.Mode = RsaKeyPair
	}
}

func (cp *Profile) ValidateAK() error {
	if len(cp.AccessKeyId) == 0 {
		return fmt.Errorf("invalid access_key_id: %s", cp.AccessKeyId)
	}
	if len(cp.AccessKeySecret) == 0 {
		return fmt.Errorf("invaild access_key_secret: %s", cp.AccessKeySecret)
	}
	return nil
}

func (cp *Profile) GetClient() (*sdk.Client, error) {
	config := sdk.NewConfig()
	if cp.RetryTimeout > 0 {
		config.WithTimeout(time.Duration(cp.RetryTimeout) * time.Second)
	}
	if cp.RetryCount > 0 {
		config.WithMaxRetryTime(cp.RetryCount)
	}

	switch cp.Mode {
	case AK:
		return cp.GetClientByAK(config)
	case StsToken:
		return cp.GetClientBySts(config)
	case RamRoleArn:
		return cp.GetClientByRoleArn(config)
	case EcsRamRole:
		return cp.GetClientByEcsRamRole(config)
	case RsaKeyPair:
		return cp.GetClientByPrivateKey(config)
	default:
		return nil, fmt.Errorf("unexcepted certificate mode: %s", cp.Mode)
	}
}

func (cp *Profile) GetSessionCredential() (*signers.SessionCredential, error) {
	switch cp.Mode {
	case AK:
		return &signers.SessionCredential{
			AccessKeyId:     cp.AccessKeyId,
			AccessKeySecret: cp.AccessKeySecret,
		}, nil
	case StsToken:
		return &signers.SessionCredential{
			AccessKeyId:     cp.AccessKeyId,
			AccessKeySecret: cp.AccessKeySecret,
			StsToken:        cp.StsToken,
		}, nil
	case RamRoleArn:
		return cp.GetSessionCredentialByRoleArn()
	case EcsRamRole:
		return cp.GetSessionCredentialByEcsRamRole()
	default:
		return nil, fmt.Errorf("unsupported mode '%s' to GetSessionCredential", cp.Mode)
	}
}

func (cp *Profile) GetClientByAK(config *sdk.Config) (*sdk.Client, error) {
	if cp.AccessKeyId == "" || cp.AccessKeySecret == "" {
		return nil, fmt.Errorf("AccessKeyId/AccessKeySecret is empty! run `aliyun configure` first")
	}

	if cp.RegionId == "" {
		return nil, fmt.Errorf("default RegionId is empty! run `aliyun configure` first")
	}

	cred := credentials.NewAccessKeyCredential(cp.AccessKeyId, cp.AccessKeySecret)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetClientBySts(config *sdk.Config) (*sdk.Client, error) {
	cred := credentials.NewStsTokenCredential(cp.AccessKeyId, cp.AccessKeySecret, cp.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetSessionCredentialByRoleArn() (*signers.SessionCredential, error) {
	client, err := sts.NewClientWithAccessKey(cp.RegionId, cp.AccessKeyId, cp.AccessKeySecret)
	if err != nil {
		return nil, fmt.Errorf("new sts client failed %s", err)
	}
	if client == nil {
		return nil, fmt.Errorf("new sts client with nil")
	}

	request := sts.CreateAssumeRoleRequest()
	request.RoleArn = cp.RamRoleArn
	request.RoleSessionName = cp.RoleSessionName
	request.DurationSeconds = requests.NewInteger(600)
	request.Scheme = "https"

	response, err := client.AssumeRole(request)
	if err != nil {
		return nil, fmt.Errorf("sts:AssumeRole() failed %s", err)
	}

	return &signers.SessionCredential{
		AccessKeyId:     response.Credentials.AccessKeyId,
		AccessKeySecret: response.Credentials.AccessKeySecret,
		StsToken:        response.Credentials.AccessKeySecret,
	}, nil
}

func (cp *Profile) GetClientByRoleArn(config *sdk.Config) (*sdk.Client, error) {
	sc, err := cp.GetSessionCredentialByRoleArn()
	if err != nil {
		return nil, fmt.Errorf("get session credential failed %s", err)
	}
	cred := credentials.NewStsTokenCredential(sc.AccessKeyId, sc.AccessKeySecret, sc.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetSessionCredentialByEcsRamRole() (*signers.SessionCredential, error) {
	if cp.RamRoleName == "" {
		return nil, fmt.Errorf("RamRole is empty! run `aliyun configure` first")
	}

	requestUrl := "http://100.100.100.200/latest/meta-data/ram/security-credentials/" + cp.RamRoleName
	httpRequest, err := http.NewRequest(requests.GET, requestUrl, strings.NewReader(""))
	if err != nil {
		return nil, fmt.Errorf("new http request failed %s", err)
	}

	httpClient := &http.Client{}
	httpResponse, err := httpClient.Do(httpRequest)
	if err != nil {
		return nil, fmt.Errorf("failed get credentials from meta-data %s, please check RAM settings", err)
	}

	response := responses.NewCommonResponse()
	err = responses.Unmarshal(response, httpResponse, "")

	if response.GetHttpStatus() != http.StatusOK {
		return nil, fmt.Errorf("get meta-data status=%d please check RAM settings", response.GetHttpStatus())
	}

	var data interface{}
	err = json.Unmarshal(response.GetHttpContentBytes(), &data)
	if err != nil {
		return nil, fmt.Errorf("unmarshal meta-data fail %s", err)
	}

	code, err := jmespath.Search("Code", data)
	if err != nil {
		return nil, fmt.Errorf("read code from meta-data failed %s", err)
	}
	if code.(string) != "Success" {
		return nil, fmt.Errorf("unexcepted code = %s", code)
	}
	accessKeyId, err := jmespath.Search("AccessKeyId", data)
	if err != nil || accessKeyId == "" {
		return nil, fmt.Errorf("read AccessKeyId from meta-data failed %s", err)
	}
	accessKeySecret, err := jmespath.Search("AccessKeySecret", data)
	if err != nil || accessKeySecret == "" {
		return nil, fmt.Errorf("read AccessKeySecret from meta-data failed %s", err)
	}
	securityToken, err := jmespath.Search("SecurityToken", data)
	if err != nil || securityToken == "" {
		return nil, fmt.Errorf("read SecurityToken from meta-data failed %s", err)
	}
	return &signers.SessionCredential{
		AccessKeyId:     accessKeyId.(string),
		AccessKeySecret: accessKeySecret.(string),
		StsToken:        securityToken.(string),
	}, nil
}

func (cp *Profile) GetClientByEcsRamRole(config *sdk.Config) (*sdk.Client, error) {
	sc, err := cp.GetSessionCredentialByEcsRamRole()
	if err != nil {
		return nil, fmt.Errorf("get session credential failed %s", err)
	}

	cred := credentials.NewStsTokenCredential(sc.AccessKeyId, sc.AccessKeySecret, sc.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetClientByPrivateKey(config *sdk.Config) (*sdk.Client, error) {
	cred := credentials.NewRsaKeyPairCredential(cp.PrivateKey, cp.KeyPairName, cp.ExpiredSeconds)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}
<commit_msg>fix RAM bug for invalid session Timeout<commit_after>/*
 * Copyright (C) 2017-2018 Alibaba Group Holding Limited
 */
package config

import (
	"encoding/json"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
	"github.com/aliyun/aliyun-cli/cli"
	"github.com/jmespath/go-jmespath"
	"net/http"
	"strings"
	"time"
)

type AuthenticateMode string

const (
	AK         = AuthenticateMode("AK")
	StsToken   = AuthenticateMode("StsToken")
	RamRoleArn = AuthenticateMode("RamRoleArn")
	EcsRamRole = AuthenticateMode("EcsRamRole")
	RsaKeyPair = AuthenticateMode("RsaKeyPair")
)

type Profile struct {
	Name            string           `json:"name"`
	Mode            AuthenticateMode `json:"mode"`
	AccessKeyId     string           `json:"access_key_id"`
	AccessKeySecret string           `json:"access_key_secret"`
	StsToken        string           `json:"sts_token"`
	RamRoleName     string           `json:"ram_role_name"`
	RamRoleArn      string           `json:"ram_role_arn"`
	RoleSessionName string           `json:"ram_session_name"`
	PrivateKey      string           `json:"private_key"`
	KeyPairName     string           `json:"key_pair_name"`
	ExpiredSeconds  int              `json:"expired_seconds"`
	Verified        string           `json:"verified"`
	RegionId        string           `json:"region_id"`
	OutputFormat    string           `json:"output_format"`
	Language        string           `json:"language"`
	Site            string           `json:"site"`
	RetryTimeout 	int 			 `json:"retry_timeout"`
	RetryCount		int 			 `json:"retry_count"`
	parent 			*Configuration	`json:"-"`
}

func NewProfile(name string) Profile {
	return Profile{
		Name:         name,
		Mode:         AK,
		OutputFormat: "json",
		Language:     "en",
	}
}

func (cp *Profile) Validate() error {
	if cp.Mode == "" {
		return fmt.Errorf("profile %s is not configure yet, run `aliyun configure --profile %s` first", cp.Name, cp.Name)
	}
	switch cp.Mode {
	case AK:
		return cp.ValidateAK()
	case StsToken:
		err := cp.ValidateAK()
		if err != nil {
			return err
		}
		if cp.StsToken == "" {
			return fmt.Errorf("invailed sts_token")
		}
	case RamRoleArn:
		err := cp.ValidateAK()
		if err != nil {
			return err
		}
		if cp.RamRoleArn == "" {
			return fmt.Errorf("invailed ram_role_arn")
		}
		if cp.RoleSessionName == "" {
			return fmt.Errorf("invailed role_session_name")
		}
	case EcsRamRole:
		if cp.RamRoleName == "" {
			return fmt.Errorf("invailed ram_role_name")
		}
	case RsaKeyPair:
		if cp.PrivateKey == "" {
			return fmt.Errorf("invailed private_key")
		}
		if cp.KeyPairName == "" {
			return fmt.Errorf("invailed key_pair_name")
		}
	default:
		return fmt.Errorf("invailed mode: %s", cp.Mode)
	}
	if cp.RegionId == "" {
		return fmt.Errorf("region can't be empty")
	}
	return nil
}

func (cp *Profile) GetParent() *Configuration {
	return cp.parent
}

func (cp *Profile) OverwriteWithFlags(ctx *cli.Context) {
	cp.Mode = AuthenticateMode(ModeFlag.GetStringOrDefault(string(cp.Mode)))
	cp.AccessKeyId = AccessKeyIdFlag.GetStringOrDefault(cp.AccessKeyId)
	cp.AccessKeySecret = AccessKeySecretFlag.GetStringOrDefault(cp.AccessKeySecret)
	cp.StsToken = StsTokenFlag.GetStringOrDefault(cp.StsToken)
	cp.RamRoleName = RamRoleNameFlag.GetStringOrDefault(cp.RamRoleName)
	cp.RamRoleArn = RamRoleArnFlag.GetStringOrDefault(cp.RamRoleArn)
	cp.RoleSessionName = RoleSessionNameFlag.GetStringOrDefault(cp.RoleSessionName)
	cp.KeyPairName = KeyPairNameFlag.GetStringOrDefault(cp.KeyPairName)
	cp.PrivateKey = PrivateKeyFlag.GetStringOrDefault(cp.PrivateKey)
	cp.RegionId = RegionFlag.GetStringOrDefault(cp.RegionId)
	cp.Language = LanguageFlag.GetStringOrDefault(cp.Language)
	cp.RetryTimeout = RetryTimeoutFlag.GetIntegerOrDefault(cp.RetryTimeout)
	cp.RetryCount = RetryTimeoutFlag.GetIntegerOrDefault(cp.RetryCount)

	if cp.AccessKeyId != "" && cp.AccessKeySecret != "" {
		cp.Mode = AK
		if cp.StsToken != "" {
			cp.Mode = StsToken
		} else if cp.RamRoleArn != "" {
			cp.Mode = RamRoleArn
		}
	}
	if cp.PrivateKey != "" && cp.KeyPairName != "" {
		cp.Mode = RsaKeyPair
	}
}

func (cp *Profile) ValidateAK() error {
	if len(cp.AccessKeyId) == 0 {
		return fmt.Errorf("invalid access_key_id: %s", cp.AccessKeyId)
	}
	if len(cp.AccessKeySecret) == 0 {
		return fmt.Errorf("invaild access_key_secret: %s", cp.AccessKeySecret)
	}
	return nil
}

func (cp *Profile) GetClient() (*sdk.Client, error) {
	config := sdk.NewConfig()
	if cp.RetryTimeout > 0 {
		config.WithTimeout(time.Duration(cp.RetryTimeout) * time.Second)
	}
	if cp.RetryCount > 0 {
		config.WithMaxRetryTime(cp.RetryCount)
	}

	switch cp.Mode {
	case AK:
		return cp.GetClientByAK(config)
	case StsToken:
		return cp.GetClientBySts(config)
	case RamRoleArn:
		return cp.GetClientByRoleArn(config)
	case EcsRamRole:
		return cp.GetClientByEcsRamRole(config)
	case RsaKeyPair:
		return cp.GetClientByPrivateKey(config)
	default:
		return nil, fmt.Errorf("unexcepted certificate mode: %s", cp.Mode)
	}
}

func (cp *Profile) GetSessionCredential() (*signers.SessionCredential, error) {
	switch cp.Mode {
	case AK:
		return &signers.SessionCredential{
			AccessKeyId:     cp.AccessKeyId,
			AccessKeySecret: cp.AccessKeySecret,
		}, nil
	case StsToken:
		return &signers.SessionCredential{
			AccessKeyId:     cp.AccessKeyId,
			AccessKeySecret: cp.AccessKeySecret,
			StsToken:        cp.StsToken,
		}, nil
	case RamRoleArn:
		return cp.GetSessionCredentialByRoleArn()
	case EcsRamRole:
		return cp.GetSessionCredentialByEcsRamRole()
	default:
		return nil, fmt.Errorf("unsupported mode '%s' to GetSessionCredential", cp.Mode)
	}
}

func (cp *Profile) GetClientByAK(config *sdk.Config) (*sdk.Client, error) {
	if cp.AccessKeyId == "" || cp.AccessKeySecret == "" {
		return nil, fmt.Errorf("AccessKeyId/AccessKeySecret is empty! run `aliyun configure` first")
	}

	if cp.RegionId == "" {
		return nil, fmt.Errorf("default RegionId is empty! run `aliyun configure` first")
	}

	cred := credentials.NewAccessKeyCredential(cp.AccessKeyId, cp.AccessKeySecret)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetClientBySts(config *sdk.Config) (*sdk.Client, error) {
	cred := credentials.NewStsTokenCredential(cp.AccessKeyId, cp.AccessKeySecret, cp.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetSessionCredentialByRoleArn() (*signers.SessionCredential, error) {
	client, err := sts.NewClientWithAccessKey(cp.RegionId, cp.AccessKeyId, cp.AccessKeySecret)
	if err != nil {
		return nil, fmt.Errorf("new sts client failed %s", err)
	}
	if client == nil {
		return nil, fmt.Errorf("new sts client with nil")
	}

	request := sts.CreateAssumeRoleRequest()
	request.RoleArn = cp.RamRoleArn
	request.RoleSessionName = cp.RoleSessionName
	request.DurationSeconds = requests.NewInteger(900)
	request.Scheme = "https"

	response, err := client.AssumeRole(request)
	if err != nil {
		return nil, fmt.Errorf("sts:AssumeRole() failed %s", err)
	}

	return &signers.SessionCredential{
		AccessKeyId:     response.Credentials.AccessKeyId,
		AccessKeySecret: response.Credentials.AccessKeySecret,
		StsToken:        response.Credentials.AccessKeySecret,
	}, nil
}

func (cp *Profile) GetClientByRoleArn(config *sdk.Config) (*sdk.Client, error) {
	sc, err := cp.GetSessionCredentialByRoleArn()
	if err != nil {
		return nil, fmt.Errorf("get session credential failed %s", err)
	}
	cred := credentials.NewStsTokenCredential(sc.AccessKeyId, sc.AccessKeySecret, sc.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetSessionCredentialByEcsRamRole() (*signers.SessionCredential, error) {
	if cp.RamRoleName == "" {
		return nil, fmt.Errorf("RamRole is empty! run `aliyun configure` first")
	}

	requestUrl := "http://100.100.100.200/latest/meta-data/ram/security-credentials/" + cp.RamRoleName
	httpRequest, err := http.NewRequest(requests.GET, requestUrl, strings.NewReader(""))
	if err != nil {
		return nil, fmt.Errorf("new http request failed %s", err)
	}

	httpClient := &http.Client{}
	httpResponse, err := httpClient.Do(httpRequest)
	if err != nil {
		return nil, fmt.Errorf("failed get credentials from meta-data %s, please check RAM settings", err)
	}

	response := responses.NewCommonResponse()
	err = responses.Unmarshal(response, httpResponse, "")

	if response.GetHttpStatus() != http.StatusOK {
		return nil, fmt.Errorf("get meta-data status=%d please check RAM settings", response.GetHttpStatus())
	}

	var data interface{}
	err = json.Unmarshal(response.GetHttpContentBytes(), &data)
	if err != nil {
		return nil, fmt.Errorf("unmarshal meta-data fail %s", err)
	}

	code, err := jmespath.Search("Code", data)
	if err != nil {
		return nil, fmt.Errorf("read code from meta-data failed %s", err)
	}
	if code.(string) != "Success" {
		return nil, fmt.Errorf("unexcepted code = %s", code)
	}
	accessKeyId, err := jmespath.Search("AccessKeyId", data)
	if err != nil || accessKeyId == "" {
		return nil, fmt.Errorf("read AccessKeyId from meta-data failed %s", err)
	}
	accessKeySecret, err := jmespath.Search("AccessKeySecret", data)
	if err != nil || accessKeySecret == "" {
		return nil, fmt.Errorf("read AccessKeySecret from meta-data failed %s", err)
	}
	securityToken, err := jmespath.Search("SecurityToken", data)
	if err != nil || securityToken == "" {
		return nil, fmt.Errorf("read SecurityToken from meta-data failed %s", err)
	}
	return &signers.SessionCredential{
		AccessKeyId:     accessKeyId.(string),
		AccessKeySecret: accessKeySecret.(string),
		StsToken:        securityToken.(string),
	}, nil
}

func (cp *Profile) GetClientByEcsRamRole(config *sdk.Config) (*sdk.Client, error) {
	sc, err := cp.GetSessionCredentialByEcsRamRole()
	if err != nil {
		return nil, fmt.Errorf("get session credential failed %s", err)
	}

	cred := credentials.NewStsTokenCredential(sc.AccessKeyId, sc.AccessKeySecret, sc.StsToken)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}

func (cp *Profile) GetClientByPrivateKey(config *sdk.Config) (*sdk.Client, error) {
	cred := credentials.NewRsaKeyPairCredential(cp.PrivateKey, cp.KeyPairName, cp.ExpiredSeconds)
	client, err := sdk.NewClientWithOptions(cp.RegionId, config, cred)
	return client, err
}
<|endoftext|>


<commit_before>package zygo
func init() { GITLASTTAG = "v4.8.3"; GITLASTCOMMIT = "1433a9c89896798a1f0dac11223fc5dc5dcad874" }
<commit_msg>quiet down<commit_after>package zygo
func init() { GITLASTTAG = "v4.8.3"; GITLASTCOMMIT = "3b280bf2e62764db4ca791db019633a5cea78ea6" }
<|endoftext|>


<commit_before><commit_msg>Check error before defer<commit_after><|endoftext|>


<commit_before>package awspurge

import (
	awsclient "github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/elb"
)

var allRegions = []string{
	"ap-northeast-1",
	"ap-southeast-1",
	"ap-southeast-2",
	"eu-central-1",
	"eu-west-1",
	"sa-east-1",
	"us-east-1",
	"us-west-1",
	"us-west-2",
	// These are problematic for the first time, thus enable later. The user
	// can always define manually and set explicitly if wished.
	// "cn-north-1",
	// "us-gov-west-1",
}

type multiRegion struct {
	ec2 map[string]*ec2.EC2
	elb map[string]*elb.ELB
}

func newMultiRegion(conf *awsclient.Config, regions []string) *multiRegion {
	m := &multiRegion{
		ec2: make(map[string]*ec2.EC2, 0),
		elb: make(map[string]*elb.ELB, 0),
	}

	for _, region := range regions {
		m.ec2[region] = ec2.New(conf.Merge(&awsclient.Config{Region: awsclient.String(region)}))
		m.elb[region] = elb.New(conf.Merge(&awsclient.Config{Region: awsclient.String(region)}))
	}

	return m
}

func filterRegions(regions, excludedRegions []string) []string {
	if len(regions) == 1 && regions[0] == "all" {
		regions = allRegions
	}

	inExcluded := func(r string) bool {
		for _, region := range excludedRegions {
			if r == region {
				return true
			}
		}
		return false
	}

	finalRegions := make([]string, 0)
	for _, r := range regions {
		if !inExcluded(r) {
			finalRegions = append(finalRegions, r)
		}
	}

	return finalRegions
}
<commit_msg>awspurge: sync with new AWS api<commit_after>package awspurge

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/elb"
)

var allRegions = []string{
	"ap-northeast-1",
	"ap-southeast-1",
	"ap-southeast-2",
	"eu-central-1",
	"eu-west-1",
	"sa-east-1",
	"us-east-1",
	"us-west-1",
	"us-west-2",
	// These are problematic for the first time, thus enable later. The user
	// can always define manually and set explicitly if wished.
	// "cn-north-1",
	// "us-gov-west-1",
}

type multiRegion struct {
	ec2 map[string]*ec2.EC2
	elb map[string]*elb.ELB
}

func newMultiRegion(conf *aws.Config, regions []string) *multiRegion {
	m := &multiRegion{
		ec2: make(map[string]*ec2.EC2, 0),
		elb: make(map[string]*elb.ELB, 0),
	}

	for _, region := range regions {
		conf.MergeIn(&aws.Config{Region: aws.String(region)})

		m.ec2[region] = ec2.New(session.New(), conf)
		m.elb[region] = elb.New(session.New(), conf)
	}

	return m
}

func filterRegions(regions, excludedRegions []string) []string {
	if len(regions) == 1 && regions[0] == "all" {
		regions = allRegions
	}

	inExcluded := func(r string) bool {
		for _, region := range excludedRegions {
			if r == region {
				return true
			}
		}
		return false
	}

	finalRegions := make([]string, 0)
	for _, r := range regions {
		if !inExcluded(r) {
			finalRegions = append(finalRegions, r)
		}
	}

	return finalRegions
}
<|endoftext|>


<commit_before>// Copyright 2016 The Vermeer Light Tools 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 core

const VersionString = "0.1.0"
const VersionMajor = 0
const VersionMinor = 1
const VersionPatch = 0
const VersionRelease = "alpha"
<commit_msg>Added comments and changed version string to function.<commit_after>// Copyright 2016 The Vermeer Light Tools 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 core

import (
	"fmt"
)

// Version constants.
const (
	VersionMajor   = 0
	VersionMinor   = 1
	VersionPatch   = 0
	VersionRelease = "alpha"
)

// VersionString returns a string representing the current software version.
func VersionString() string {
	if VersionRelease == "" {
		return fmt.Sprintf("%v.%v.%v", VersionMajor, VersionMinor, VersionPatch)
	} else {
		return fmt.Sprintf("%v.%v.%v-%v", VersionMajor, VersionMinor, VersionPatch, VersionRelease)
	}
}
<|endoftext|>