<commit_before>package parser

import (
	"fmt"
	"strconv"

	"github.com/goby-lang/goby/ast"
	"github.com/goby-lang/goby/token"
)

var argument = map[token.Type]bool{
	token.Int:              true,
	token.String:           true,
	token.True:             true,
	token.False:            true,
	token.InstanceVariable: true,
	token.Ident:            true,
}

var precedence = map[token.Type]int{
	token.Eq:                 EQUALS,
	token.NotEq:              EQUALS,
	token.LT:                 LESSGREATER,
	token.LTE:                LESSGREATER,
	token.GT:                 LESSGREATER,
	token.GTE:                LESSGREATER,
	token.COMP:               LESSGREATER,
	token.And:                LOGIC,
	token.Or:                 LOGIC,
	token.Plus:               SUM,
	token.Minus:              SUM,
	token.Incr:               SUM,
	token.Decr:               SUM,
	token.Modulo:             SUM,
	token.Assign:             ASSIGN,
	token.Slash:              PRODUCT,
	token.Asterisk:           PRODUCT,
	token.Pow:                PRODUCT,
	token.LBracket:           INDEX,
	token.Dot:                CALL,
	token.LParen:             CALL,
	token.ResolutionOperator: CALL,
}

// Constants for denoting precedence
const (
	_ int = iota
	LOWEST
	LOGIC
	EQUALS
	LESSGREATER
	ASSIGN
	SUM
	PRODUCT
	PREFIX
	INDEX
	CALL
)

type (
	prefixParseFn func() ast.Expression
	infixParseFn  func(ast.Expression) ast.Expression
)

func (p *Parser) parseExpression(precedence int) ast.Expression {
	prefix := p.prefixParseFns[p.curToken.Type]
	if prefix == nil {
		p.noPrefixParseFnError(p.curToken.Type)
		return nil
	}

	leftExp := prefix()

	for !p.peekTokenIs(token.Semicolon) && precedence < p.peekPrecedence() && p.peekTokenAtSameLine() {

		infix := p.infixParseFns[p.peekToken.Type]

		if  argument[p.peekToken.Type] && precedence == 0 { // method call without paren case
			infix = p.parseCallExpression
		}

		if infix == nil {
			return leftExp
		}
		p.nextToken()
		leftExp = infix(leftExp)
	}

	return leftExp
}

func (p *Parser) parseSelfExpression() ast.Expression {
	return &ast.SelfExpression{Token: p.curToken}
}

func (p *Parser) parseIdentifier() ast.Expression {
	return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
}

func (p *Parser) parseConstant() ast.Expression {
	c := &ast.Constant{Token: p.curToken, Value: p.curToken.Literal}

	if p.peekTokenIs(token.ResolutionOperator) {
		p.nextToken()
		return p.parseInfixExpression(c)
	}

	return c
}

func (p *Parser) parseInstanceVariable() ast.Expression {
	return &ast.InstanceVariable{Token: p.curToken, Value: p.curToken.Literal}
}

func (p *Parser) parseIntegerLiteral() ast.Expression {
	lit := &ast.IntegerLiteral{Token: p.curToken}

	value, err := strconv.ParseInt(lit.TokenLiteral(), 0, 64)
	if err != nil {
		msg := fmt.Sprintf("could not parse %q as integer", lit.TokenLiteral())
		p.errors = append(p.errors, msg)
		return nil
	}

	lit.Value = int(value)

	return lit
}

func (p *Parser) parseStringLiteral() ast.Expression {
	lit := &ast.StringLiteral{Token: p.curToken}
	lit.Value = p.curToken.Literal

	return lit
}

func (p *Parser) parseBooleanLiteral() ast.Expression {
	lit := &ast.BooleanExpression{Token: p.curToken}

	value, err := strconv.ParseBool(lit.TokenLiteral())
	if err != nil {
		msg := fmt.Sprintf("could not parse %q as boolean", lit.TokenLiteral())
		p.errors = append(p.errors, msg)
		return nil
	}

	lit.Value = value

	return lit
}

func (p *Parser) parseNilExpression() ast.Expression {
	return &ast.NilExpression{Token: p.curToken}
}

func (p *Parser) parsePostfixExpression(receiver ast.Expression) ast.Expression {
	arguments := []ast.Expression{}
	return &ast.CallExpression{Token: p.curToken, Receiver: receiver, Method: p.curToken.Literal, Arguments: arguments}
}

func (p *Parser) parseHashExpression() ast.Expression {
	hash := &ast.HashExpression{Token: p.curToken}
	hash.Data = p.parseHashPairs()
	return hash
}

func (p *Parser) parseHashPairs() map[string]ast.Expression {
	pairs := map[string]ast.Expression{}

	if p.peekTokenIs(token.RBrace) {
		p.nextToken() // '}'
		return pairs
	}

	p.parseHashPair(pairs)

	for p.peekTokenIs(token.Comma) {
		p.nextToken()

		p.parseHashPair(pairs)
	}

	if !p.expectPeek(token.RBrace) {
		return nil
	}

	return pairs
}

func (p *Parser) parseHashPair(pairs map[string]ast.Expression) {
	var key string
	var value ast.Expression

	if !p.expectPeek(token.Ident) {
		return
	}

	key = p.parseIdentifier().(*ast.Identifier).Value

	if !p.expectPeek(token.Colon) {
		return
	}

	p.nextToken()
	value = p.parseExpression(LOWEST)
	pairs[key] = value
}

func (p *Parser) parseArrayExpression() ast.Expression {
	arr := &ast.ArrayExpression{Token: p.curToken}
	arr.Elements = p.parseArrayElements()
	return arr
}

func (p *Parser) parseArrayIndexExpression(left ast.Expression) ast.Expression {
	callExpression := &ast.CallExpression{Receiver: left, Method: "[]", Token: p.curToken}

	if p.peekTokenIs(token.RBracket) {
		return nil
	}

	p.nextToken()

	callExpression.Arguments = []ast.Expression{p.parseExpression(LOWEST)}

	if !p.expectPeek(token.RBracket) {
		return nil
	}

	// Assign value to index
	if p.peekTokenIs(token.Assign) {
		p.nextToken()
		p.nextToken()
		assignValue := p.parseExpression(LOWEST)
		callExpression.Method = "[]="
		callExpression.Arguments = append(callExpression.Arguments, assignValue)
	}

	return callExpression
}

func (p *Parser) parseArrayElements() []ast.Expression {
	elems := []ast.Expression{}

	if p.peekTokenIs(token.RBracket) {
		p.nextToken() // ']'
		return elems
	}

	p.nextToken() // start of first expression
	elems = append(elems, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		elems = append(elems, p.parseExpression(LOWEST))
	}

	if !p.expectPeek(token.RBracket) {
		return nil
	}

	return elems
}

func (p *Parser) parsePrefixExpression() ast.Expression {
	pe := &ast.PrefixExpression{
		Token:    p.curToken,
		Operator: p.curToken.Literal,
	}

	p.nextToken()

	pe.Right = p.parseExpression(PREFIX)

	return pe
}

func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
	exp := &ast.InfixExpression{
		Token:    p.curToken,
		Left:     left,
		Operator: p.curToken.Literal,
	}

	precedence := p.curPrecedence()
	p.nextToken()
	exp.Right = p.parseExpression(precedence)

	return exp
}

func (p *Parser) parseGroupedExpression() ast.Expression {
	p.nextToken()

	exp := p.parseExpression(LOWEST)

	if !p.expectPeek(token.RParen) {
		return nil
	}

	return exp
}

func (p *Parser) parseIfExpression() ast.Expression {
	ie := &ast.IfExpression{Token: p.curToken}
	p.nextToken()
	ie.Condition = p.parseExpression(LOWEST)
	ie.Consequence = p.parseBlockStatement()

	// curToken is now ELSE or RBRACE
	if p.curTokenIs(token.Else) {
		ie.Alternative = p.parseBlockStatement()
	}

	return ie
}

func (p *Parser) parseCallExpression(receiver ast.Expression) ast.Expression {
	var exp *ast.CallExpression

	if p.curTokenIs(token.LParen) || p.curTokenIs(token.Ident) || p.curTokenIs(token.Int) || p.curTokenIs(token.InstanceVariable) {

		m := receiver.(*ast.Identifier).Value
		// receiver is self
		selfTok := token.Token{Type: token.Self, Literal: "self", Line: p.curToken.Line}
		self := &ast.SelfExpression{Token: selfTok}
		receiver = self

		// current token is identifier (method name)
		exp = &ast.CallExpression{Token: p.curToken, Receiver: receiver, Method: m}

		if p.curTokenIs(token.LParen) {
			exp.Arguments = p.parseCallArguments()

		} else {
			exp.Arguments = p.parseCallArgumentsWithoutParens()
		}

	} else if p.curTokenIs(token.Dot) { // call expression has a receiver like: p.foo

		exp = &ast.CallExpression{Token: p.curToken, Receiver: receiver}

		// check if method name is identifier
		if !p.expectPeek(token.Ident) {
			return nil
		}

		exp.Method = p.curToken.Literal

		if p.peekTokenIs(token.LParen) {
			p.nextToken()
			exp.Arguments = p.parseCallArguments()
		} else { // p.foo.bar; || p.foo; || p.foo + 123
			exp.Arguments = []ast.Expression{}
		}
	}

	// Setter method call like: p.foo = x
	if p.peekTokenIs(token.Assign) {
		exp.Method = exp.Method + "="
		p.nextToken()
		p.nextToken()
		exp.Arguments = append(exp.Arguments, p.parseExpression(LOWEST))
	}

	// Parse block
	if p.peekTokenIs(token.Do) && p.acceptBlock {
		p.parseBlockParameters(exp)
	}

	return exp
}

func (p *Parser) parseBlockParameters(exp *ast.CallExpression) {
	p.nextToken()

	// Parse block arguments
	if p.peekTokenIs(token.Bar) {
		var params []*ast.Identifier

		p.nextToken()
		p.nextToken()

		param := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
		params = append(params, param)

		for p.peekTokenIs(token.Comma) {
			p.nextToken()
			p.nextToken()
			param := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
			params = append(params, param)
		}

		if !p.expectPeek(token.Bar) {
			return
		}

		exp.BlockArguments = params
	}

	exp.Block = p.parseBlockStatement()
}

func (p *Parser) parseCallArguments() []ast.Expression {
	args := []ast.Expression{}

	if p.peekTokenIs(token.RParen) {
		p.nextToken() // ')'
		return args
	}

	p.nextToken() // start of first expression
	args = append(args, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		args = append(args, p.parseExpression(LOWEST))
	}

	if !p.expectPeek(token.RParen) {
		return nil
	}

	return args
}

func (p *Parser) parseCallArgumentsWithoutParens() []ast.Expression {
	args := []ast.Expression{}

	if p.peekTokenIs(token.RParen) {
		p.nextToken() // ')'
		return args
	}

	args = append(args, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		args = append(args, p.parseExpression(LOWEST))
	}

	if p.peekTokenAtSameLine() {
		return nil
	}
	return args
}

func (p *Parser) parseYieldExpression() ast.Expression {
	ye := &ast.YieldExpression{Token: p.curToken}

	if p.peekTokenIs(token.LParen) {
		p.nextToken()
		ye.Arguments = p.parseCallArguments()
	}

	return ye
}
<commit_msg>simplify expression_parsing.go<commit_after>package parser

import (
	"fmt"
	"strconv"

	"github.com/goby-lang/goby/ast"
	"github.com/goby-lang/goby/token"
)

var argument = map[token.Type]bool{
	token.Int:              true,
	token.String:           true,
	token.True:             true,
	token.False:            true,
	token.InstanceVariable: true,
	token.Ident:            true,
}

var precedence = map[token.Type]int{
	token.Eq:                 EQUALS,
	token.NotEq:              EQUALS,
	token.LT:                 LESSGREATER,
	token.LTE:                LESSGREATER,
	token.GT:                 LESSGREATER,
	token.GTE:                LESSGREATER,
	token.COMP:               LESSGREATER,
	token.And:                LOGIC,
	token.Or:                 LOGIC,
	token.Plus:               SUM,
	token.Minus:              SUM,
	token.Incr:               SUM,
	token.Decr:               SUM,
	token.Modulo:             SUM,
	token.Assign:             ASSIGN,
	token.Slash:              PRODUCT,
	token.Asterisk:           PRODUCT,
	token.Pow:                PRODUCT,
	token.LBracket:           INDEX,
	token.Dot:                CALL,
	token.LParen:             CALL,
	token.ResolutionOperator: CALL,
}

// Constants for denoting precedence
const (
	_ int = iota
	LOWEST
	LOGIC
	EQUALS
	LESSGREATER
	ASSIGN
	SUM
	PRODUCT
	PREFIX
	INDEX
	CALL
)

type (
	prefixParseFn func() ast.Expression
	infixParseFn  func(ast.Expression) ast.Expression
)

func (p *Parser) parseExpression(precedence int) ast.Expression {
	prefix := p.prefixParseFns[p.curToken.Type]
	if prefix == nil {
		p.noPrefixParseFnError(p.curToken.Type)
		return nil
	}

	leftExp := prefix()

	if argument[p.peekToken.Type] && precedence == 0 {

		infix := p.parseCallExpression
		p.nextToken()
		leftExp = infix(leftExp)

	} else {
		for !p.peekTokenIs(token.Semicolon) && precedence < p.peekPrecedence() && p.peekTokenAtSameLine() {

			infix := p.infixParseFns[p.peekToken.Type]
			if infix == nil {
				return leftExp
			}
			p.nextToken()
			leftExp = infix(leftExp)
		}
	}

	return leftExp
}

func (p *Parser) parseSelfExpression() ast.Expression {
	return &ast.SelfExpression{Token: p.curToken}
}

func (p *Parser) parseIdentifier() ast.Expression {
	return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
}

func (p *Parser) parseConstant() ast.Expression {
	c := &ast.Constant{Token: p.curToken, Value: p.curToken.Literal}

	if p.peekTokenIs(token.ResolutionOperator) {
		p.nextToken()
		return p.parseInfixExpression(c)
	}

	return c
}

func (p *Parser) parseInstanceVariable() ast.Expression {
	return &ast.InstanceVariable{Token: p.curToken, Value: p.curToken.Literal}
}

func (p *Parser) parseIntegerLiteral() ast.Expression {
	lit := &ast.IntegerLiteral{Token: p.curToken}

	value, err := strconv.ParseInt(lit.TokenLiteral(), 0, 64)
	if err != nil {
		msg := fmt.Sprintf("could not parse %q as integer", lit.TokenLiteral())
		p.errors = append(p.errors, msg)
		return nil
	}

	lit.Value = int(value)

	return lit
}

func (p *Parser) parseStringLiteral() ast.Expression {
	lit := &ast.StringLiteral{Token: p.curToken}
	lit.Value = p.curToken.Literal

	return lit
}

func (p *Parser) parseBooleanLiteral() ast.Expression {
	lit := &ast.BooleanExpression{Token: p.curToken}

	value, err := strconv.ParseBool(lit.TokenLiteral())
	if err != nil {
		msg := fmt.Sprintf("could not parse %q as boolean", lit.TokenLiteral())
		p.errors = append(p.errors, msg)
		return nil
	}

	lit.Value = value

	return lit
}

func (p *Parser) parseNilExpression() ast.Expression {
	return &ast.NilExpression{Token: p.curToken}
}

func (p *Parser) parsePostfixExpression(receiver ast.Expression) ast.Expression {
	arguments := []ast.Expression{}
	return &ast.CallExpression{Token: p.curToken, Receiver: receiver, Method: p.curToken.Literal, Arguments: arguments}
}

func (p *Parser) parseHashExpression() ast.Expression {
	hash := &ast.HashExpression{Token: p.curToken}
	hash.Data = p.parseHashPairs()
	return hash
}

func (p *Parser) parseHashPairs() map[string]ast.Expression {
	pairs := map[string]ast.Expression{}

	if p.peekTokenIs(token.RBrace) {
		p.nextToken() // '}'
		return pairs
	}

	p.parseHashPair(pairs)

	for p.peekTokenIs(token.Comma) {
		p.nextToken()

		p.parseHashPair(pairs)
	}

	if !p.expectPeek(token.RBrace) {
		return nil
	}

	return pairs
}

func (p *Parser) parseHashPair(pairs map[string]ast.Expression) {
	var key string
	var value ast.Expression

	if !p.expectPeek(token.Ident) {
		return
	}

	key = p.parseIdentifier().(*ast.Identifier).Value

	if !p.expectPeek(token.Colon) {
		return
	}

	p.nextToken()
	value = p.parseExpression(LOWEST)
	pairs[key] = value
}

func (p *Parser) parseArrayExpression() ast.Expression {
	arr := &ast.ArrayExpression{Token: p.curToken}
	arr.Elements = p.parseArrayElements()
	return arr
}

func (p *Parser) parseArrayIndexExpression(left ast.Expression) ast.Expression {
	callExpression := &ast.CallExpression{Receiver: left, Method: "[]", Token: p.curToken}

	if p.peekTokenIs(token.RBracket) {
		return nil
	}

	p.nextToken()

	callExpression.Arguments = []ast.Expression{p.parseExpression(LOWEST)}

	if !p.expectPeek(token.RBracket) {
		return nil
	}

	// Assign value to index
	if p.peekTokenIs(token.Assign) {
		p.nextToken()
		p.nextToken()
		assignValue := p.parseExpression(LOWEST)
		callExpression.Method = "[]="
		callExpression.Arguments = append(callExpression.Arguments, assignValue)
	}

	return callExpression
}

func (p *Parser) parseArrayElements() []ast.Expression {
	elems := []ast.Expression{}

	if p.peekTokenIs(token.RBracket) {
		p.nextToken() // ']'
		return elems
	}

	p.nextToken() // start of first expression
	elems = append(elems, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		elems = append(elems, p.parseExpression(LOWEST))
	}

	if !p.expectPeek(token.RBracket) {
		return nil
	}

	return elems
}

func (p *Parser) parsePrefixExpression() ast.Expression {
	pe := &ast.PrefixExpression{
		Token:    p.curToken,
		Operator: p.curToken.Literal,
	}

	p.nextToken()

	pe.Right = p.parseExpression(PREFIX)

	return pe
}

func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
	exp := &ast.InfixExpression{
		Token:    p.curToken,
		Left:     left,
		Operator: p.curToken.Literal,
	}

	precedence := p.curPrecedence()
	p.nextToken()
	exp.Right = p.parseExpression(precedence)

	return exp
}

func (p *Parser) parseGroupedExpression() ast.Expression {
	p.nextToken()

	exp := p.parseExpression(LOWEST)

	if !p.expectPeek(token.RParen) {
		return nil
	}

	return exp
}

func (p *Parser) parseIfExpression() ast.Expression {
	ie := &ast.IfExpression{Token: p.curToken}
	p.nextToken()
	ie.Condition = p.parseExpression(LOWEST)
	ie.Consequence = p.parseBlockStatement()

	// curToken is now ELSE or RBRACE
	if p.curTokenIs(token.Else) {
		ie.Alternative = p.parseBlockStatement()
	}

	return ie
}

func (p *Parser) parseCallExpression(receiver ast.Expression) ast.Expression {
	var exp *ast.CallExpression

	if p.curTokenIs(token.LParen) || argument[p.curToken.Type] {

		m := receiver.(*ast.Identifier).Value
		// receiver is self
		selfTok := token.Token{Type: token.Self, Literal: "self", Line: p.curToken.Line}
		self := &ast.SelfExpression{Token: selfTok}
		receiver = self

		// current token is identifier (method name)
		exp = &ast.CallExpression{Token: p.curToken, Receiver: receiver, Method: m}

		if p.curTokenIs(token.LParen) {
			exp.Arguments = p.parseCallArguments()

		} else {
			exp.Arguments = p.parseCallArgumentsWithoutParens()
		}

	} else if p.curTokenIs(token.Dot) { // call expression has a receiver like: p.foo

		exp = &ast.CallExpression{Token: p.curToken, Receiver: receiver}

		// check if method name is identifier
		if !p.expectPeek(token.Ident) {
			return nil
		}

		exp.Method = p.curToken.Literal

		if p.peekTokenIs(token.LParen) {
			p.nextToken()
			exp.Arguments = p.parseCallArguments()
		} else { // p.foo.bar; || p.foo; || p.foo + 123
			exp.Arguments = []ast.Expression{}
		}
	}

	// Setter method call like: p.foo = x
	if p.peekTokenIs(token.Assign) {
		exp.Method = exp.Method + "="
		p.nextToken()
		p.nextToken()
		exp.Arguments = append(exp.Arguments, p.parseExpression(LOWEST))
	}

	// Parse block
	if p.peekTokenIs(token.Do) && p.acceptBlock {
		p.parseBlockParameters(exp)
	}

	return exp
}

func (p *Parser) parseBlockParameters(exp *ast.CallExpression) {
	p.nextToken()

	// Parse block arguments
	if p.peekTokenIs(token.Bar) {
		var params []*ast.Identifier

		p.nextToken()
		p.nextToken()

		param := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
		params = append(params, param)

		for p.peekTokenIs(token.Comma) {
			p.nextToken()
			p.nextToken()
			param := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
			params = append(params, param)
		}

		if !p.expectPeek(token.Bar) {
			return
		}

		exp.BlockArguments = params
	}

	exp.Block = p.parseBlockStatement()
}

func (p *Parser) parseCallArguments() []ast.Expression {
	args := []ast.Expression{}

	if p.peekTokenIs(token.RParen) {
		p.nextToken() // ')'
		return args
	}

	p.nextToken() // start of first expression
	args = append(args, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		args = append(args, p.parseExpression(LOWEST))
	}

	if !p.expectPeek(token.RParen) {
		return nil
	}

	return args
}

func (p *Parser) parseCallArgumentsWithoutParens() []ast.Expression {
	args := []ast.Expression{}

	if p.peekTokenIs(token.RParen) {
		p.nextToken() // ')'
		return args
	}

	args = append(args, p.parseExpression(LOWEST))

	for p.peekTokenIs(token.Comma) {
		p.nextToken() // ","
		p.nextToken() // start of next expression
		args = append(args, p.parseExpression(LOWEST))
	}

	if p.peekTokenAtSameLine() {
		return nil
	}
	return args
}

func (p *Parser) parseYieldExpression() ast.Expression {
	ye := &ast.YieldExpression{Token: p.curToken}

	if p.peekTokenIs(token.LParen) {
		p.nextToken()
		ye.Arguments = p.parseCallArguments()
	}

	return ye
}
<|endoftext|>


<commit_before>package web

import (
	"net/url"
	"testing"

	"github.com/eyecuelab/kit/errorlib"

	"github.com/google/jsonapi"
	"github.com/labstack/echo"
	"github.com/stretchr/testify/assert"
)

const (
	foo = "foo"
	bar = "bar"
	baz = "baz"
)

type mockParamsEchoContext struct {
	queryParams map[string]string
	params      map[string]string
	echo.Context
}

var _ echo.Context = mockParamsEchoContext{}

func (m mockParamsEchoContext) QueryParams() url.Values {
	out := make(url.Values)
	for k, v := range m.queryParams {
		out[k] = []string{v}
	}
	return out
}

func (m mockParamsEchoContext) QueryParam(name string) string {
	v, _ := m.queryParams[name]
	return v
}

func (m mockParamsEchoContext) Param(name string) string {
	v, _ := m.params[name]
	return v
}

func newMock(queryParams, params map[string]string) ApiContext {
	if queryParams == nil {
		queryParams = make(map[string]string)
	}
	if params == nil {
		params = make(map[string]string)
	}
	return &apiContext{
		Context: mockParamsEchoContext{
			Context:     echo.New().AcquireContext(),
			params:      params,
			queryParams: queryParams,
		},
		payload: new(jsonapi.OnePayload),
	}
}

func Test_apiContext_RequiredQueryParams(t *testing.T) {
	want := map[string]string{
		foo: foo,
		bar: bar,
	}
	ctx := newMock(want, nil)
	got, err := ctx.RequiredQueryParams(foo, bar)
	assert.NoError(t, err)
	assert.Equal(t, want, got)

	_, err = ctx.RequiredQueryParams(foo, bar, baz)
	assert.Error(t, err)
}

func Test_apiContext_QParams(t *testing.T) {
	required := []string{foo, bar}
	queryParams := map[string]string{foo: foo, bar: bar, baz: bar}
	ctx := newMock(queryParams, nil)
	got, err := ctx.QParams(required...)
	assert.Equal(t, queryParams, got)
	assert.NoError(t, err)

	required = []string{"DNE"}
	got, err = ctx.QParams(required...)
	assert.Equal(t, map[string]string(nil), got)
	assert.Error(t, err)

}

func Test_apiContext_OptionalQueryParams(t *testing.T) {
	want := map[string]string{
		foo: foo,
		bar: bar,
		baz: "", //deliberately missing
	}
	ctx := newMock(map[string]string{foo: foo, bar: bar}, nil)
	assert.Equal(t, want, ctx.OptionalQueryParams(foo, bar, baz))
}

func Test_apiContext_BindIdParam(t *testing.T) {

	var got int
	want := 20

	ctx := newMock(nil, pair("id", "20"))
	assert.NoError(t, ctx.BindIdParam(&got))
	assert.Equal(t, want, got)

	got, want = 0, 40
	ctx = newMock(nil, pair("foobar", "40"))
	assert.NoError(t, ctx.BindIdParam(&got, "foobar"))

}

func pair(key, val string) map[string]string {
	return map[string]string{key: val}
}
func Test_QueryParamTrue(t *testing.T) {
	const foo = foo

	val, ok := newMock(pair(foo, "1"), nil).QueryParamTrue(foo)
	assert.True(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "true"), nil).QueryParamTrue(foo)
	assert.True(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "0"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "false"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "asdaqsd"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.False(t, ok)
}

func Test_RestrictedParam(t *testing.T) {
	allowed := []string{foo, bar, baz}

	val, err := newMock(nil, pair(foo, foo)).RestrictedParam(foo, allowed...)
	assert.Equal(t, val, foo)
	assert.NoError(t, err)

	val, err = newMock(nil, pair(foo, foo)).RestrictedParam(foo, "asdhjasod")
	assert.NotEqual(t, val, foo)
	assert.Error(t, err)
}

func Test_notJsonApi(t *testing.T) {
	assert.True(t, notJsonApi(errorlib.ErrorString("not a jsonapi")))
	assert.True(t, notJsonApi(errorlib.ErrorString("EOF")))
	assert.False(t, notJsonApi(errorlib.ErrorString("foobar")))
}
<commit_msg>more tests<commit_after>package web

import (
	"net/url"
	"testing"

	"github.com/eyecuelab/kit/errorlib"

	"github.com/google/jsonapi"
	"github.com/labstack/echo"
	"github.com/stretchr/testify/assert"
)

const (
	foo = "foo"
	bar = "bar"
	baz = "baz"
)

type mockParamsEchoContext struct {
	queryParams map[string]string
	params      map[string]string
	echo.Context
}

var _ echo.Context = mockParamsEchoContext{}

func (m mockParamsEchoContext) QueryParams() url.Values {
	out := make(url.Values)
	for k, v := range m.queryParams {
		out[k] = []string{v}
	}
	return out
}

func (m mockParamsEchoContext) QueryParam(name string) string {
	v, _ := m.queryParams[name]
	return v
}

func (m mockParamsEchoContext) Param(name string) string {
	v, _ := m.params[name]
	return v
}

func newMock(queryParams, params map[string]string) ApiContext {
	if queryParams == nil {
		queryParams = make(map[string]string)
	}
	if params == nil {
		params = make(map[string]string)
	}
	return &apiContext{
		Context: mockParamsEchoContext{
			Context:     echo.New().AcquireContext(),
			params:      params,
			queryParams: queryParams,
		},
		payload: new(jsonapi.OnePayload),
	}
}

func Test_apiContext_RequiredQueryParams(t *testing.T) {
	want := map[string]string{
		foo: foo,
		bar: bar,
	}
	ctx := newMock(want, nil)
	got, err := ctx.RequiredQueryParams(foo, bar)
	assert.NoError(t, err)
	assert.Equal(t, want, got)

	_, err = ctx.RequiredQueryParams(foo, bar, baz)
	assert.Error(t, err)
}

func Test_apiContext_QParams(t *testing.T) {
	required := []string{foo, bar}
	queryParams := map[string]string{foo: foo, bar: bar, baz: bar}
	ctx := newMock(queryParams, nil)
	got, err := ctx.QParams(required...)
	assert.Equal(t, queryParams, got)
	assert.NoError(t, err)

	required = []string{"DNE"}
	got, err = ctx.QParams(required...)
	assert.Equal(t, map[string]string(nil), got)
	assert.Error(t, err)

}

func Test_apiContext_OptionalQueryParams(t *testing.T) {
	want := map[string]string{
		foo: foo,
		bar: bar,
		baz: "", //deliberately missing
	}
	ctx := newMock(map[string]string{foo: foo, bar: bar}, nil)
	assert.Equal(t, want, ctx.OptionalQueryParams(foo, bar, baz))
}

func Test_apiContext_BindIdParam(t *testing.T) {

	var got int
	want := 20

	ctx := newMock(nil, pair("id", "20"))
	assert.NoError(t, ctx.BindIdParam(&got))
	assert.Equal(t, want, got)

	got, want = 0, 40
	ctx = newMock(nil, pair("foobar", "40"))
	assert.NoError(t, ctx.BindIdParam(&got, "foobar"))

}

func pair(key, val string) map[string]string {
	return map[string]string{key: val}
}
func Test_QueryParamTrue(t *testing.T) {
	const foo = foo

	val, ok := newMock(pair(foo, "1"), nil).QueryParamTrue(foo)
	assert.True(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "true"), nil).QueryParamTrue(foo)
	assert.True(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "0"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "false"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.True(t, ok)

	val, ok = newMock(pair(foo, "asdaqsd"), nil).QueryParamTrue(foo)
	assert.False(t, val)
	assert.False(t, ok)
}

func Test_RestrictedParam(t *testing.T) {
	allowed := []string{foo, bar, baz}

	val, err := newMock(nil, pair(foo, foo)).RestrictedParam(foo, allowed...)
	assert.Equal(t, val, foo)
	assert.NoError(t, err)

	val, err = newMock(nil, pair(foo, foo)).RestrictedParam(foo, "asdhjasod")
	assert.NotEqual(t, val, foo)
	assert.Error(t, err)
}

func Test_RestrictedQueryParam(t *testing.T) {
	allowed := []string{foo, bar, baz}

	val, err := newMock(pair(foo, foo), nil).RestrictedQueryParam(foo, allowed...)
	assert.Equal(t, val, foo)
	assert.NoError(t, err)

	val, err = newMock(pair(foo, foo), nil).RestrictedQueryParam(foo, "asdhjasod")
	assert.NotEqual(t, val, foo)
	assert.Error(t, err)
}

func Test_notJsonApi(t *testing.T) {
	assert.True(t, notJsonApi(errorlib.ErrorString("not a jsonapi")))
	assert.True(t, notJsonApi(errorlib.ErrorString("EOF")))
	assert.False(t, notJsonApi(errorlib.ErrorString("foobar")))
}
<|endoftext|>


<commit_before>package web_test

import (
	"fmt"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/concourse/atc"
	"github.com/concourse/testflight/gitserver"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/sclevine/agouti/matchers"
)

var _ = Describe("BuildsView", func() {
	var build atc.Build

	Context("with a job in the configuration", func() {
		var originGitServer *gitserver.Server

		BeforeEach(func() {
			originGitServer = gitserver.Start(client)
			originGitServer.CommitResource()

			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some-job",
						Plan: atc.PlanSequence{
							{
								Get: "some-input-resource",
							},
							{
								Task: "some-task",
								TaskConfig: &atc.TaskConfig{
									Platform: "linux",
									ImageResource: &atc.ImageResource{
										Type:   "docker-image",
										Source: atc.Source{"repository": "busybox"},
									},
									Inputs: []atc.TaskInputConfig{
										{Name: "some-input-resource"},
									},
									Outputs: []atc.TaskOutputConfig{
										{Name: "some-output-src"},
									},
									Run: atc.TaskRunConfig{
										Path: "cp",
										Args: []string{"-r", "some-input-resource/.", "some-output-src"},
									},
								},
							},
							{
								Put:    "some-output-resource",
								Params: atc.Params{"repository": "some-output-src"},
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some-input-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
					{
						Name: "some-output-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			build, err = team.CreateJobBuild(pipelineName, "some-job")
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			originGitServer.Stop()
		})

		It("can view resource information of a job build", func() {
			url := atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job", teamName, pipelineName))

			Expect(page.Navigate(url)).To(Succeed())
			Eventually(page.Find(".build-header.succeeded")).Should(BeFound())

			Eventually(page.All(".builds-list li")).Should(HaveCount(1))

			Expect(page.Find(".builds-list li:first-child a")).To(HaveText("#1"))
			Eventually(page.Find(".builds-list li:first-child a.succeeded"), 10*time.Second).Should(BeFound())

			Eventually(page.Find(".builds-list li:first-child .inputs .resource-name"), 10*time.Second).Should(BeFound())
			Expect(page.Find(".builds-list li:first-child .inputs .resource-name")).To(HaveText("some-input-resource"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-value")).To(MatchText("[0-9a-f]{40}"))

			Expect(page.Find(".builds-list li:first-child .outputs .resource-name")).To(HaveText("some-output-resource"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-value")).To(MatchText("[0-9a-z]{40}"))

			// button should not have the boolean attribute "disabled" set. agouti currently returns
			// an empty string in that case.
			Expect(page.Find("button.build-action")).ToNot(BeNil())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", ""))
		})

		Describe("paused pipeline", func() {
			BeforeEach(func() {
				_, err := team.PausePipeline(pipelineName)
				Expect(err).NotTo(HaveOccurred())
			})

			It("displays a blue header", func() {
				Expect(page.Navigate(atcRoute(build.URL))).To(Succeed())

				Expect(page.Navigate(atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job/builds/%s", teamName, pipelineName, build.Name)))).To(Succeed())

				// top bar should show the pipeline is paused
				Eventually(page.Find(".top-bar.test.paused"), 10*time.Second).Should(BeFound())
			})
		})
	})

	Context("when a job and a resource in the configuration have names that need be escaped", func() {
		var pipelineUrl string
		var buildsUrl string
		var resourceUrl string

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some/job",
						Plan: atc.PlanSequence{
							{
								Get: "some/resource",
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some/resource",
						Type: "time",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			pipelineUrl = atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s", teamName, pipelineName))
			Expect(page.Navigate(pipelineUrl)).To(Succeed())

			buildsUrl = fmt.Sprintf("%s/jobs/some%%2Fjob", pipelineUrl)
			resourceUrl = fmt.Sprintf("%s/resources/some%%2Fresource", pipelineUrl)
		})

		It("displays the unescaped names in the pipeline view", func() {
			Expect(page.Find(".job")).To(HaveText("some/job"))
			Expect(page.Find(".input")).To(HaveText("some/resource"))
		})

		It("can navigate to the escaped links", func() {
			Expect(page.Navigate(buildsUrl)).To(Succeed())
			Expect(page.Find("button.build-action")).To(BeFound())
			Expect(page.Navigate(resourceUrl)).To(Succeed())
			Expect(page.Find("div.resource-check-status")).To(BeFound())
		})
	})

	Context("when manual triggering of the job is disabled", func() {
		var manualTriggerDisabledBuild atc.Build

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "job-manual-trigger-disabled",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			manualTriggerDisabledBuild, err = team.CreateJobBuild(pipelineName, "job-manual-trigger-disabled")
			Expect(err).NotTo(HaveOccurred())

			_, _, pipelineVersion, _, err := team.PipelineConfig(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			config = atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name:                 "job-manual-trigger-disabled",
						DisableManualTrigger: true,
					},
				},
			}

			byteConfig, err = yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, pipelineVersion, byteConfig)
			Expect(err).NotTo(HaveOccurred())
		})

		It("should have a disabled button in the build details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))
			Eventually(page.Find("span.build-action"), 10*time.Second).Should(HaveAttribute("disabled", "true"))
		})

		It("should have a disabled button in the job details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))

			Eventually(page.Find("h1 a"), 10*time.Second).Should(BeFound())
			Expect(page.Find("h1 a").Click()).To(Succeed())
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled",
				teamName,
				pipelineName,
			))))

			Eventually(page.Find("button.build-action")).Should(BeFound())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", "true"))
		})
	})
})
<commit_msg>assert job/resource links go to the right place<commit_after>package web_test

import (
	"fmt"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/concourse/atc"
	"github.com/concourse/testflight/gitserver"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/sclevine/agouti/matchers"
)

var _ = Describe("BuildsView", func() {
	var build atc.Build

	Context("with a job in the configuration", func() {
		var originGitServer *gitserver.Server

		BeforeEach(func() {
			originGitServer = gitserver.Start(client)
			originGitServer.CommitResource()

			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some-job",
						Plan: atc.PlanSequence{
							{
								Get: "some-input-resource",
							},
							{
								Task: "some-task",
								TaskConfig: &atc.TaskConfig{
									Platform: "linux",
									ImageResource: &atc.ImageResource{
										Type:   "docker-image",
										Source: atc.Source{"repository": "busybox"},
									},
									Inputs: []atc.TaskInputConfig{
										{Name: "some-input-resource"},
									},
									Outputs: []atc.TaskOutputConfig{
										{Name: "some-output-src"},
									},
									Run: atc.TaskRunConfig{
										Path: "cp",
										Args: []string{"-r", "some-input-resource/.", "some-output-src"},
									},
								},
							},
							{
								Put:    "some-output-resource",
								Params: atc.Params{"repository": "some-output-src"},
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some-input-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
					{
						Name: "some-output-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			build, err = team.CreateJobBuild(pipelineName, "some-job")
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			originGitServer.Stop()
		})

		It("can view resource information of a job build", func() {
			url := atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job", teamName, pipelineName))

			Expect(page.Navigate(url)).To(Succeed())
			Eventually(page.Find(".build-header.succeeded")).Should(BeFound())

			Eventually(page.All(".builds-list li")).Should(HaveCount(1))

			Expect(page.Find(".builds-list li:first-child a")).To(HaveText("#1"))
			Eventually(page.Find(".builds-list li:first-child a.succeeded"), 10*time.Second).Should(BeFound())

			Eventually(page.Find(".builds-list li:first-child .inputs .resource-name"), 10*time.Second).Should(BeFound())
			Expect(page.Find(".builds-list li:first-child .inputs .resource-name")).To(HaveText("some-input-resource"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-value")).To(MatchText("[0-9a-f]{40}"))

			Expect(page.Find(".builds-list li:first-child .outputs .resource-name")).To(HaveText("some-output-resource"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-value")).To(MatchText("[0-9a-z]{40}"))

			// button should not have the boolean attribute "disabled" set. agouti currently returns
			// an empty string in that case.
			Expect(page.Find("button.build-action")).ToNot(BeNil())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", ""))
		})

		Describe("paused pipeline", func() {
			BeforeEach(func() {
				_, err := team.PausePipeline(pipelineName)
				Expect(err).NotTo(HaveOccurred())
			})

			It("displays a blue header", func() {
				Expect(page.Navigate(atcRoute(build.URL))).To(Succeed())

				Expect(page.Navigate(atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job/builds/%s", teamName, pipelineName, build.Name)))).To(Succeed())

				// top bar should show the pipeline is paused
				Eventually(page.Find(".top-bar.test.paused"), 10*time.Second).Should(BeFound())
			})
		})
	})

	Context("when a job and a resource in the configuration have names that need be escaped", func() {
		var pipelineUrl string
		var buildsUrl string
		var resourceUrl string

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some/job",
						Plan: atc.PlanSequence{
							{
								Get: "some/resource",
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some/resource",
						Type: "time",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			pipelineUrl = atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s", teamName, pipelineName))
			Expect(page.Navigate(pipelineUrl)).To(Succeed())

			buildsUrl = fmt.Sprintf("%s/jobs/some%%2Fjob", pipelineUrl)
			resourceUrl = fmt.Sprintf("%s/resources/some%%2Fresource", pipelineUrl)
		})

		It("displays the unescaped names in the pipeline view", func() {
			Expect(page.Find(".job")).To(HaveText("some/job"))
			Expect(page.Find(".input")).To(HaveText("some/resource"))
		})

		It("can navigate to the escaped links", func() {
			Eventually(page.FindByLink("some/job")).Should(BeFound())
			Expect(page.FindByLink("some/job").Click()).To(Succeed())
			Eventually(page.URL).Should(Equal(buildsUrl))
			Expect(page.Navigate(atcURL)).Should(Succeed())
			Eventually(page.FindByLink("some/resource")).Should(BeFound())
			Expect(page.FindByLink("some/resource").Click()).To(Succeed())
			Eventually(page.URL).Should(Equal(resourceUrl))
		})
	})

	Context("when manual triggering of the job is disabled", func() {
		var manualTriggerDisabledBuild atc.Build

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "job-manual-trigger-disabled",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			manualTriggerDisabledBuild, err = team.CreateJobBuild(pipelineName, "job-manual-trigger-disabled")
			Expect(err).NotTo(HaveOccurred())

			_, _, pipelineVersion, _, err := team.PipelineConfig(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			config = atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name:                 "job-manual-trigger-disabled",
						DisableManualTrigger: true,
					},
				},
			}

			byteConfig, err = yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, pipelineVersion, byteConfig)
			Expect(err).NotTo(HaveOccurred())
		})

		It("should have a disabled button in the build details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))
			Eventually(page.Find("span.build-action"), 10*time.Second).Should(HaveAttribute("disabled", "true"))
		})

		It("should have a disabled button in the job details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))

			Eventually(page.Find("h1 a"), 10*time.Second).Should(BeFound())
			Expect(page.Find("h1 a").Click()).To(Succeed())
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled",
				teamName,
				pipelineName,
			))))

			Eventually(page.Find("button.build-action")).Should(BeFound())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", "true"))
		})
	})
})
<|endoftext|>


<commit_before>package blueprint

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

// A Deps value indicates the dependency file format that Ninja should expect to
// be output by a compiler.
type Deps int

const (
	DepsNone Deps = iota
	DepsGCC
	DepsMSVC
)

func (d Deps) String() string {
	switch d {
	case DepsNone:
		return "none"
	case DepsGCC:
		return "gcc"
	case DepsMSVC:
		return "msvc"
	default:
		panic(fmt.Sprintf("unknown deps value: %d", d))
	}
}

// A PoolParams object contains the set of parameters that make up a Ninja pool
// definition.
type PoolParams struct {
	Comment string // The comment that will appear above the definition.
	Depth   int    // The Ninja pool depth.
}

// A RuleParams object contains the set of parameters that make up a Ninja rule
// definition.  Each field except for Comment corresponds with a Ninja variable
// of the same name.
type RuleParams struct {
	Comment        string // The comment that will appear above the definition.
	Command        string // The command that Ninja will run for the rule.
	Depfile        string // The dependency file name.
	Deps           Deps   // The format of the dependency file.
	Description    string // The description that Ninja will print for the rule.
	Generator      bool   // Whether the rule generates the Ninja manifest file.
	Pool           Pool   // The Ninja pool to which the rule belongs.
	Restat         bool   // Whether Ninja should re-stat the rule's outputs.
	Rspfile        string // The response file.
	RspfileContent string // The response file content.
}

// A BuildParams object contains the set of parameters that make up a Ninja
// build statement.  Each field except for Args corresponds with a part of the
// Ninja build statement.  The Args field contains variable names and values
// that are set within the build statement's scope in the Ninja file.
type BuildParams struct {
	Rule      Rule              // The rule to invoke.
	Outputs   []string          // The list of output targets.
	Inputs    []string          // The list of explicit input dependencies.
	Implicits []string          // The list of implicit dependencies.
	OrderOnly []string          // The list of order-only dependencies.
	Args      map[string]string // The variable/value pairs to set.
}

// A poolDef describes a pool definition.  It does not include the name of the
// pool.
type poolDef struct {
	Comment string
	Depth   int
}

func parsePoolParams(scope scope, params *PoolParams) (*poolDef,
	error) {

	def := &poolDef{
		Comment: params.Comment,
		Depth:   params.Depth,
	}

	return def, nil
}

func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
	if p.Comment != "" {
		err := nw.Comment(p.Comment)
		if err != nil {
			return err
		}
	}

	err := nw.Pool(name)
	if err != nil {
		return err
	}

	return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
}

// A ruleDef describes a rule definition.  It does not include the name of the
// rule.
type ruleDef struct {
	Comment   string
	Pool      Pool
	Variables map[string]*ninjaString
}

func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
	error) {

	r := &ruleDef{
		Comment:   params.Comment,
		Pool:      params.Pool,
		Variables: make(map[string]*ninjaString),
	}

	if params.Command == "" {
		return nil, fmt.Errorf("encountered rule params with no command " +
			"specified")
	}

	if r.Pool != nil && !scope.IsPoolVisible(r.Pool) {
		return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool)
	}

	value, err := parseNinjaString(scope, params.Command)
	if err != nil {
		return nil, fmt.Errorf("error parsing Command param: %s", err)
	}
	r.Variables["command"] = value

	if params.Depfile != "" {
		value, err = parseNinjaString(scope, params.Depfile)
		if err != nil {
			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
		}
		r.Variables["depfile"] = value
	}

	if params.Deps != DepsNone {
		r.Variables["deps"] = simpleNinjaString(params.Deps.String())
	}

	if params.Description != "" {
		value, err = parseNinjaString(scope, params.Description)
		if err != nil {
			return nil, fmt.Errorf("error parsing Description param: %s", err)
		}
		r.Variables["description"] = value
	}

	if params.Generator {
		r.Variables["generator"] = simpleNinjaString("true")
	}

	if params.Restat {
		r.Variables["restat"] = simpleNinjaString("true")
	}

	if params.Rspfile != "" {
		value, err = parseNinjaString(scope, params.Rspfile)
		if err != nil {
			return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
		}
		r.Variables["rspfile"] = value
	}

	if params.RspfileContent != "" {
		value, err = parseNinjaString(scope, params.RspfileContent)
		if err != nil {
			return nil, fmt.Errorf("error parsing RspfileContent param: %s",
				err)
		}
		r.Variables["rspfile_content"] = value
	}

	return r, nil
}

func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
	pkgNames map[*PackageContext]string) error {

	if r.Comment != "" {
		err := nw.Comment(r.Comment)
		if err != nil {
			return err
		}
	}

	err := nw.Rule(name)
	if err != nil {
		return err
	}

	if r.Pool != nil {
		err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames))
		if err != nil {
			return err
		}
	}

	var keys []string
	for k := range r.Variables {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, name := range keys {
		err = nw.ScopedAssign(name, r.Variables[name].Value(pkgNames))
		if err != nil {
			return err
		}
	}

	return nil
}

// A buildDef describes a build target definition.
type buildDef struct {
	Rule      Rule
	Outputs   []*ninjaString
	Inputs    []*ninjaString
	Implicits []*ninjaString
	OrderOnly []*ninjaString
	Args      map[Variable]*ninjaString
	Optional  bool
}

func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
	error) {

	rule := params.Rule

	b := &buildDef{
		Rule: rule,
	}

	if !scope.IsRuleVisible(rule) {
		return nil, fmt.Errorf("Rule %s is not visible in this scope", rule)
	}

	if len(params.Outputs) == 0 {
		return nil, errors.New("Outputs param has no elements")
	}

	var err error
	b.Outputs, err = parseNinjaStrings(scope, params.Outputs)
	if err != nil {
		return nil, fmt.Errorf("error parsing Outputs param: %s", err)
	}

	b.Inputs, err = parseNinjaStrings(scope, params.Inputs)
	if err != nil {
		return nil, fmt.Errorf("error parsing Inputs param: %s", err)
	}

	b.Implicits, err = parseNinjaStrings(scope, params.Implicits)
	if err != nil {
		return nil, fmt.Errorf("error parsing Implicits param: %s", err)
	}

	b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly)
	if err != nil {
		return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
	}

	argNameScope := rule.scope()

	if len(params.Args) > 0 {
		b.Args = make(map[Variable]*ninjaString)
		for name, value := range params.Args {
			if !rule.isArg(name) {
				return nil, fmt.Errorf("unknown argument %q", name)
			}

			argVar, err := argNameScope.LookupVariable(name)
			if err != nil {
				// This shouldn't happen.
				return nil, fmt.Errorf("argument lookup error: %s", err)
			}

			ninjaValue, err := parseNinjaString(scope, value)
			if err != nil {
				return nil, fmt.Errorf("error parsing variable %q: %s", name,
					err)
			}

			b.Args[argVar] = ninjaValue
		}
	}

	return b, nil
}

func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*PackageContext]string) error {
	var (
		rule          = b.Rule.fullName(pkgNames)
		outputs       = valueList(b.Outputs, pkgNames, outputEscaper)
		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
	)
	err := nw.Build(rule, outputs, explicitDeps, implicitDeps, orderOnlyDeps)
	if err != nil {
		return err
	}

	args := make(map[string]string)

	for argVar, value := range b.Args {
		args[argVar.fullName(pkgNames)] = value.Value(pkgNames)
	}

	var keys []string
	for k := range args {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, name := range keys {
		err = nw.ScopedAssign(name, args[name])
		if err != nil {
			return err
		}
	}

	if !b.Optional {
		nw.Default(outputs...)
	}

	return nil
}

func valueList(list []*ninjaString, pkgNames map[*PackageContext]string,
	escaper *strings.Replacer) []string {

	result := make([]string, len(list))
	for i, ninjaStr := range list {
		result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
	}
	return result
}
<commit_msg>Pipe the Optional flag from BuildParams to the build definition<commit_after>package blueprint

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

// A Deps value indicates the dependency file format that Ninja should expect to
// be output by a compiler.
type Deps int

const (
	DepsNone Deps = iota
	DepsGCC
	DepsMSVC
)

func (d Deps) String() string {
	switch d {
	case DepsNone:
		return "none"
	case DepsGCC:
		return "gcc"
	case DepsMSVC:
		return "msvc"
	default:
		panic(fmt.Sprintf("unknown deps value: %d", d))
	}
}

// A PoolParams object contains the set of parameters that make up a Ninja pool
// definition.
type PoolParams struct {
	Comment string // The comment that will appear above the definition.
	Depth   int    // The Ninja pool depth.
}

// A RuleParams object contains the set of parameters that make up a Ninja rule
// definition.  Each field except for Comment corresponds with a Ninja variable
// of the same name.
type RuleParams struct {
	Comment        string // The comment that will appear above the definition.
	Command        string // The command that Ninja will run for the rule.
	Depfile        string // The dependency file name.
	Deps           Deps   // The format of the dependency file.
	Description    string // The description that Ninja will print for the rule.
	Generator      bool   // Whether the rule generates the Ninja manifest file.
	Pool           Pool   // The Ninja pool to which the rule belongs.
	Restat         bool   // Whether Ninja should re-stat the rule's outputs.
	Rspfile        string // The response file.
	RspfileContent string // The response file content.
}

// A BuildParams object contains the set of parameters that make up a Ninja
// build statement.  Each field except for Args corresponds with a part of the
// Ninja build statement.  The Args field contains variable names and values
// that are set within the build statement's scope in the Ninja file.
type BuildParams struct {
	Rule      Rule              // The rule to invoke.
	Outputs   []string          // The list of output targets.
	Inputs    []string          // The list of explicit input dependencies.
	Implicits []string          // The list of implicit dependencies.
	OrderOnly []string          // The list of order-only dependencies.
	Args      map[string]string // The variable/value pairs to set.
	Optional  bool              // Skip outputting a default statement
}

// A poolDef describes a pool definition.  It does not include the name of the
// pool.
type poolDef struct {
	Comment string
	Depth   int
}

func parsePoolParams(scope scope, params *PoolParams) (*poolDef,
	error) {

	def := &poolDef{
		Comment: params.Comment,
		Depth:   params.Depth,
	}

	return def, nil
}

func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
	if p.Comment != "" {
		err := nw.Comment(p.Comment)
		if err != nil {
			return err
		}
	}

	err := nw.Pool(name)
	if err != nil {
		return err
	}

	return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
}

// A ruleDef describes a rule definition.  It does not include the name of the
// rule.
type ruleDef struct {
	Comment   string
	Pool      Pool
	Variables map[string]*ninjaString
}

func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
	error) {

	r := &ruleDef{
		Comment:   params.Comment,
		Pool:      params.Pool,
		Variables: make(map[string]*ninjaString),
	}

	if params.Command == "" {
		return nil, fmt.Errorf("encountered rule params with no command " +
			"specified")
	}

	if r.Pool != nil && !scope.IsPoolVisible(r.Pool) {
		return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool)
	}

	value, err := parseNinjaString(scope, params.Command)
	if err != nil {
		return nil, fmt.Errorf("error parsing Command param: %s", err)
	}
	r.Variables["command"] = value

	if params.Depfile != "" {
		value, err = parseNinjaString(scope, params.Depfile)
		if err != nil {
			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
		}
		r.Variables["depfile"] = value
	}

	if params.Deps != DepsNone {
		r.Variables["deps"] = simpleNinjaString(params.Deps.String())
	}

	if params.Description != "" {
		value, err = parseNinjaString(scope, params.Description)
		if err != nil {
			return nil, fmt.Errorf("error parsing Description param: %s", err)
		}
		r.Variables["description"] = value
	}

	if params.Generator {
		r.Variables["generator"] = simpleNinjaString("true")
	}

	if params.Restat {
		r.Variables["restat"] = simpleNinjaString("true")
	}

	if params.Rspfile != "" {
		value, err = parseNinjaString(scope, params.Rspfile)
		if err != nil {
			return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
		}
		r.Variables["rspfile"] = value
	}

	if params.RspfileContent != "" {
		value, err = parseNinjaString(scope, params.RspfileContent)
		if err != nil {
			return nil, fmt.Errorf("error parsing RspfileContent param: %s",
				err)
		}
		r.Variables["rspfile_content"] = value
	}

	return r, nil
}

func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
	pkgNames map[*PackageContext]string) error {

	if r.Comment != "" {
		err := nw.Comment(r.Comment)
		if err != nil {
			return err
		}
	}

	err := nw.Rule(name)
	if err != nil {
		return err
	}

	if r.Pool != nil {
		err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames))
		if err != nil {
			return err
		}
	}

	var keys []string
	for k := range r.Variables {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, name := range keys {
		err = nw.ScopedAssign(name, r.Variables[name].Value(pkgNames))
		if err != nil {
			return err
		}
	}

	return nil
}

// A buildDef describes a build target definition.
type buildDef struct {
	Rule      Rule
	Outputs   []*ninjaString
	Inputs    []*ninjaString
	Implicits []*ninjaString
	OrderOnly []*ninjaString
	Args      map[Variable]*ninjaString
	Optional  bool
}

func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
	error) {

	rule := params.Rule

	b := &buildDef{
		Rule: rule,
	}

	if !scope.IsRuleVisible(rule) {
		return nil, fmt.Errorf("Rule %s is not visible in this scope", rule)
	}

	if len(params.Outputs) == 0 {
		return nil, errors.New("Outputs param has no elements")
	}

	var err error
	b.Outputs, err = parseNinjaStrings(scope, params.Outputs)
	if err != nil {
		return nil, fmt.Errorf("error parsing Outputs param: %s", err)
	}

	b.Inputs, err = parseNinjaStrings(scope, params.Inputs)
	if err != nil {
		return nil, fmt.Errorf("error parsing Inputs param: %s", err)
	}

	b.Implicits, err = parseNinjaStrings(scope, params.Implicits)
	if err != nil {
		return nil, fmt.Errorf("error parsing Implicits param: %s", err)
	}

	b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly)
	if err != nil {
		return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
	}

	b.Optional = params.Optional

	argNameScope := rule.scope()

	if len(params.Args) > 0 {
		b.Args = make(map[Variable]*ninjaString)
		for name, value := range params.Args {
			if !rule.isArg(name) {
				return nil, fmt.Errorf("unknown argument %q", name)
			}

			argVar, err := argNameScope.LookupVariable(name)
			if err != nil {
				// This shouldn't happen.
				return nil, fmt.Errorf("argument lookup error: %s", err)
			}

			ninjaValue, err := parseNinjaString(scope, value)
			if err != nil {
				return nil, fmt.Errorf("error parsing variable %q: %s", name,
					err)
			}

			b.Args[argVar] = ninjaValue
		}
	}

	return b, nil
}

func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*PackageContext]string) error {
	var (
		rule          = b.Rule.fullName(pkgNames)
		outputs       = valueList(b.Outputs, pkgNames, outputEscaper)
		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
	)
	err := nw.Build(rule, outputs, explicitDeps, implicitDeps, orderOnlyDeps)
	if err != nil {
		return err
	}

	args := make(map[string]string)

	for argVar, value := range b.Args {
		args[argVar.fullName(pkgNames)] = value.Value(pkgNames)
	}

	var keys []string
	for k := range args {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, name := range keys {
		err = nw.ScopedAssign(name, args[name])
		if err != nil {
			return err
		}
	}

	if !b.Optional {
		nw.Default(outputs...)
	}

	return nil
}

func valueList(list []*ninjaString, pkgNames map[*PackageContext]string,
	escaper *strings.Replacer) []string {

	result := make([]string, len(list))
	for i, ninjaStr := range list {
		result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
	}
	return result
}
<|endoftext|>


<commit_before>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package url

import (
	"errors"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"golang.org/x/net/html"
	"mime"
	"net/http"
	"regexp"
	"strings"
)

type Module struct {
	regex    *regexp.Regexp
	RegexStr string `json:"regex"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "url"
}

func (m *Module) Help() string {
	return "Displays information about posted URLs."
}

func (m *Module) Defaults() {
	m.RegexStr = `(?i)\b((http|https)\://(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`
}

func (m *Module) Load(client *irc.Client) error {
	regex, err := regexp.Compile(m.RegexStr)
	if err != nil {
		return err
	}

	m.regex = regex
	client.CmdHook("privmsg", m.urlCmd)

	return nil
}

func (m *Module) urlCmd(client *irc.Client, msg irc.Message) error {
	url := m.regex.FindString(msg.Data)
	if len(url) <= 0 {
		return nil
	}

	resp, err := http.Head(url)
	if err != nil {
		return err
	}
	resp.Body.Close() // HEAD response doesn't have a body

	info := m.infoString(resp)
	if len(info) <= 0 {
		return nil
	}

	return client.Write("NOTICE %s :%s", msg.Receiver, info)
}

func (m *Module) infoString(resp *http.Response) string {
	var mtype string
	var infos []string

	ctype := resp.Header.Get("Content-Type")
	if len(ctype) > 0 {
		m, _, err := mime.ParseMediaType(ctype)
		if err == nil {
			mtype = m
			infos = append(infos, fmt.Sprintf("Type: %s", mtype))
		}
	}

	csize := resp.ContentLength
	if csize >= 0 {
		infos = append(infos, fmt.Sprintf("Size: %s", m.humanize(csize)))
	}

	if mtype == "text/html" {
		title, err := m.extractTitle(resp.Request.URL.String())
		if err == nil {
			infos = append(infos, fmt.Sprintf("Title: %s", title))
		}
	}

	info := strings.Join(infos, " | ")
	if len(info) > 0 {
		info = fmt.Sprintf("%s -- %s", strings.ToUpper(m.Name()), info)
	}

	return info
}

func (m *Module) extractTitle(url string) (title string, err error) {
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	doc, err := html.Parse(resp.Body)
	if err != nil {
		return
	}

	var parseFunc func(n *html.Node)
	parseFunc = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "title" {
			child := n.FirstChild
			if child != nil {
				title = html.UnescapeString(child.Data)
			} else {
				return
			}
		}

		for c := n.FirstChild; c != nil; c = c.NextSibling {
			parseFunc(c)
		}
	}

	parseFunc(doc)
	if len(title) <= 0 {
		err = errors.New("couldn't extract title")
		return
	}

	return
}

func (m *Module) humanize(count int64) string {
	switch {
	case count > (1 << 40):
		return fmt.Sprintf("%v TiB", count/(1<<40))
	case count > (1 << 30):
		return fmt.Sprintf("%v GiB", count/(1<<30))
	case count > (1 << 20):
		return fmt.Sprintf("%v MiB", count/(1<<20))
	case count > (1 << 10):
		return fmt.Sprintf("%v KiB", count/(1<<10))
	default:
		return fmt.Sprintf("%v B", count)
	}
}
<commit_msg>url: net/html already unescapes html entities<commit_after>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package url

import (
	"errors"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"golang.org/x/net/html"
	"mime"
	"net/http"
	"regexp"
	"strings"
)

type Module struct {
	regex    *regexp.Regexp
	RegexStr string `json:"regex"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "url"
}

func (m *Module) Help() string {
	return "Displays information about posted URLs."
}

func (m *Module) Defaults() {
	m.RegexStr = `(?i)\b((http|https)\://(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`
}

func (m *Module) Load(client *irc.Client) error {
	regex, err := regexp.Compile(m.RegexStr)
	if err != nil {
		return err
	}

	m.regex = regex
	client.CmdHook("privmsg", m.urlCmd)

	return nil
}

func (m *Module) urlCmd(client *irc.Client, msg irc.Message) error {
	url := m.regex.FindString(msg.Data)
	if len(url) <= 0 {
		return nil
	}

	resp, err := http.Head(url)
	if err != nil {
		return err
	}
	resp.Body.Close() // HEAD response doesn't have a body

	info := m.infoString(resp)
	if len(info) <= 0 {
		return nil
	}

	return client.Write("NOTICE %s :%s", msg.Receiver, info)
}

func (m *Module) infoString(resp *http.Response) string {
	var mtype string
	var infos []string

	ctype := resp.Header.Get("Content-Type")
	if len(ctype) > 0 {
		m, _, err := mime.ParseMediaType(ctype)
		if err == nil {
			mtype = m
			infos = append(infos, fmt.Sprintf("Type: %s", mtype))
		}
	}

	csize := resp.ContentLength
	if csize >= 0 {
		infos = append(infos, fmt.Sprintf("Size: %s", m.humanize(csize)))
	}

	if mtype == "text/html" {
		title, err := m.extractTitle(resp.Request.URL.String())
		if err == nil {
			infos = append(infos, fmt.Sprintf("Title: %s", title))
		}
	}

	info := strings.Join(infos, " | ")
	if len(info) > 0 {
		info = fmt.Sprintf("%s -- %s", strings.ToUpper(m.Name()), info)
	}

	return info
}

func (m *Module) extractTitle(url string) (title string, err error) {
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	doc, err := html.Parse(resp.Body)
	if err != nil {
		return
	}

	var parseFunc func(n *html.Node)
	parseFunc = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "title" {
			child := n.FirstChild
			if child != nil {
				title = child.Data
			} else {
				return
			}
		}

		for c := n.FirstChild; c != nil; c = c.NextSibling {
			parseFunc(c)
		}
	}

	parseFunc(doc)
	if len(title) <= 0 {
		err = errors.New("couldn't extract title")
		return
	}

	return
}

func (m *Module) humanize(count int64) string {
	switch {
	case count > (1 << 40):
		return fmt.Sprintf("%v TiB", count/(1<<40))
	case count > (1 << 30):
		return fmt.Sprintf("%v GiB", count/(1<<30))
	case count > (1 << 20):
		return fmt.Sprintf("%v MiB", count/(1<<20))
	case count > (1 << 10):
		return fmt.Sprintf("%v KiB", count/(1<<10))
	default:
		return fmt.Sprintf("%v B", count)
	}
}
<|endoftext|>


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

package tailer

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/exp/winfsnotify"
)

func initWatcher(abspath string, _ *autoClosingFile) (*winfsnotify.Watcher, error) {
	watcher, err := winfsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = watcher.Watch(filepath.Dir(abspath))
	if err != nil {
		watcher.Close()
		return nil, err
	}
	return watcher, nil
}

type eventLoop struct {
	watcher *winfsnotify.Watcher
}

func startEventLoop(watcher *winfsnotify.Watcher) *eventLoop {
	return &eventLoop{
		watcher: watcher,
	}
}

func (l *eventLoop) Close() error {
	// watcher.Close() may be called twice, once for the eventLoop and once for the watcher. This should be fine.
	return l.watcher.Close()
}

func (l *eventLoop) Errors() chan error {
	return l.watcher.Error
}

func (l *eventLoop) Events() chan *winfsnotify.Event {
	return l.watcher.Event
}

func processEvents(event *winfsnotify.Event, _ *winfsnotify.Watcher, fileBefore *autoClosingFile, reader *bufferedLineReader, abspath string, logger simpleLogger) (file *autoClosingFile, lines []string, err error) {
	file = fileBefore
	lines = []string{}
	var truncated bool
	logger.Debug("File system watcher received %v.\n", event.String())

	// WRITE or TRUNCATE
	if file != nil && norm(event.Name) == norm(abspath) && event.Mask&winfsnotify.FS_MODIFY == winfsnotify.FS_MODIFY {
		truncated, err = checkTruncated(file)
		if err != nil {
			return
		}
		if truncated {
			_, err = file.Seek(0, io.SeekStart)
			if err != nil {
				return
			}
		}
		var freshLines []string
		freshLines, err = reader.ReadAvailableLines(file)
		if err != nil {
			return
		}
		lines = append(lines, freshLines...)
	}

	// MOVE or DELETE
	if file != nil && norm(event.Name) == norm(abspath) && (event.Mask&winfsnotify.FS_MOVED_FROM == winfsnotify.FS_MOVED_FROM || event.Mask&winfsnotify.FS_DELETE == winfsnotify.FS_DELETE) {
		file = nil
		reader.Clear()
	}

	// CREATE
	if file == nil && norm(event.Name) == norm(abspath) && event.Mask&winfsnotify.FS_CREATE == winfsnotify.FS_CREATE {
		file, err = open(abspath)
		if err != nil {
			return
		}
		reader.Clear()
		var freshLines []string
		freshLines, err = reader.ReadAvailableLines(file)
		if err != nil {
			return
		}
		lines = append(lines, freshLines...)
	}
	return
}

// winfsnotify uses "/" instead of "\" when constructing the path in the event name.
func norm(path string) string {
	path = strings.Replace(path, "/", "\\", -1)
	path = strings.Replace(path, "\\\\", "\\", -1)
	return path
}

// On Windows, logrotate will not be able to delete or move the logfile if grok_exporter keeps the file open.
// The AutoClosingFile has an API similar to os.File, but the underlying file is closed after each operation.
type autoClosingFile struct {
	path       string
	currentPos int64
}

func open(path string) (*autoClosingFile, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return &autoClosingFile{
		path:       path,
		currentPos: 0,
	}, nil
}

func (f *autoClosingFile) Seek(offset int64, whence int) (int64, error) {
	file, err := os.Open(f.path)
	if err != nil {
		return 0, err
	}
	defer file.Close()
	result, resultErr := file.Seek(offset, whence)
	f.currentPos, err = file.Seek(0, io.SeekCurrent)
	if err != nil {
		return 0, err
	}
	return result, resultErr
}

func (f *autoClosingFile) Read(b []byte) (int, error) {
	file, err := os.Open(f.path)
	if err != nil {
		return 0, err
	}
	defer file.Close()
	_, err = file.Seek(f.currentPos, io.SeekStart)
	if err != nil {
		return 0, err
	}
	result, resultErr := file.Read(b)
	f.currentPos, err = file.Seek(0, io.SeekCurrent)
	if err != nil {
		return 0, err
	}
	return result, resultErr
}

func (f *autoClosingFile) Name() string {
	return f.path
}

func (f *autoClosingFile) Close() error {
	// nothing to do
	return nil
}

func checkTruncated(f *autoClosingFile) (bool, error) {
	file, err := os.Open(f.path)
	if err != nil {
		return false, err
	}
	defer file.Close()
	fileInfo, err := file.Stat()
	if err != nil {
		return false, fmt.Errorf("%v: Stat() failed: %v", file.Name(), err.Error())
	}
	return f.currentPos > fileInfo.Size(), nil
}
<commit_msg>backoff when os.Open() fails on Windows<commit_after>// Copyright 2016-2017 The grok_exporter Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tailer

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/exp/winfsnotify"
	"time"
)

func initWatcher(abspath string, _ *autoClosingFile) (*winfsnotify.Watcher, error) {
	watcher, err := winfsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = watcher.Watch(filepath.Dir(abspath))
	if err != nil {
		watcher.Close()
		return nil, err
	}
	return watcher, nil
}

type eventLoop struct {
	watcher *winfsnotify.Watcher
}

func startEventLoop(watcher *winfsnotify.Watcher) *eventLoop {
	return &eventLoop{
		watcher: watcher,
	}
}

func (l *eventLoop) Close() error {
	// watcher.Close() may be called twice, once for the eventLoop and once for the watcher. This should be fine.
	return l.watcher.Close()
}

func (l *eventLoop) Errors() chan error {
	return l.watcher.Error
}

func (l *eventLoop) Events() chan *winfsnotify.Event {
	return l.watcher.Event
}

func processEvents(event *winfsnotify.Event, _ *winfsnotify.Watcher, fileBefore *autoClosingFile, reader *bufferedLineReader, abspath string, logger simpleLogger) (file *autoClosingFile, lines []string, err error) {
	file = fileBefore
	lines = []string{}
	var truncated bool
	logger.Debug("File system watcher received %v.\n", event.String())

	// WRITE or TRUNCATE
	if file != nil && norm(event.Name) == norm(abspath) && event.Mask&winfsnotify.FS_MODIFY == winfsnotify.FS_MODIFY {
		truncated, err = checkTruncated(file)
		if err != nil {
			return
		}
		if truncated {
			_, err = file.Seek(0, io.SeekStart)
			if err != nil {
				return
			}
		}
		var freshLines []string
		freshLines, err = reader.ReadAvailableLines(file)
		if err != nil {
			return
		}
		lines = append(lines, freshLines...)
	}

	// MOVE or DELETE
	if file != nil && norm(event.Name) == norm(abspath) && (event.Mask&winfsnotify.FS_MOVED_FROM == winfsnotify.FS_MOVED_FROM || event.Mask&winfsnotify.FS_DELETE == winfsnotify.FS_DELETE) {
		file = nil
		reader.Clear()
	}

	// CREATE
	if file == nil && norm(event.Name) == norm(abspath) && event.Mask&winfsnotify.FS_CREATE == winfsnotify.FS_CREATE {
		file, err = open(abspath)
		if err != nil {
			return
		}
		reader.Clear()
		var freshLines []string
		freshLines, err = reader.ReadAvailableLines(file)
		if err != nil {
			return
		}
		lines = append(lines, freshLines...)
	}
	return
}

// winfsnotify uses "/" instead of "\" when constructing the path in the event name.
func norm(path string) string {
	path = strings.Replace(path, "/", "\\", -1)
	path = strings.Replace(path, "\\\\", "\\", -1)
	return path
}

// On Windows, logrotate will not be able to delete or move the logfile if grok_exporter keeps the file open.
// The AutoClosingFile has an API similar to os.File, but the underlying file is closed after each operation.
type autoClosingFile struct {
	path       string
	currentPos int64
}

func open(path string) (*autoClosingFile, error) {
	file, err := openWithBackoff(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return &autoClosingFile{
		path:       path,
		currentPos: 0,
	}, nil
}

func (f *autoClosingFile) Seek(offset int64, whence int) (int64, error) {
	file, err := openWithBackoff(f.path)
	if err != nil {
		return 0, err
	}
	defer file.Close()
	result, resultErr := file.Seek(offset, whence)
	f.currentPos, err = file.Seek(0, io.SeekCurrent)
	if err != nil {
		return 0, err
	}
	return result, resultErr
}

func (f *autoClosingFile) Read(b []byte) (int, error) {
	file, err := openWithBackoff(f.path)
	if err != nil {
		return 0, err
	}
	defer file.Close()
	_, err = file.Seek(f.currentPos, io.SeekStart)
	if err != nil {
		return 0, err
	}
	result, resultErr := file.Read(b)
	f.currentPos, err = file.Seek(0, io.SeekCurrent)
	if err != nil {
		return 0, err
	}
	return result, resultErr
}

func (f *autoClosingFile) Name() string {
	return f.path
}

func (f *autoClosingFile) Close() error {
	// nothing to do
	return nil
}

// If the file is currently opened by a logger or virus scanner, Open() will fail,
// because on Windows a file cannot be opened by two processes.
// Back off and try again, only if this error persists about 1 second give up.
func openWithBackoff(name string) (*os.File, error) {
	var (
		file *os.File
		err  error
	)
	for i := 1; i <= 3; i++ {
		file, err = os.Open(name)
		if err == nil {
			return file, nil
		}
		time.Sleep(time.Duration(i*125) * time.Millisecond)
	}
	return nil, err
}

func checkTruncated(f *autoClosingFile) (bool, error) {
	file, err := openWithBackoff(f.path)
	if err != nil {
		return false, err
	}
	defer file.Close()
	fileInfo, err := file.Stat()
	if err != nil {
		return false, fmt.Errorf("%v: Stat() failed: %v", file.Name(), err.Error())
	}
	return f.currentPos > fileInfo.Size(), nil
}
<|endoftext|>


<commit_before>package net

import (
	"encoding/binary"
	"errors"

	"gopkg.in/dedis/crypto.v0/abstract"
	"gopkg.in/dedis/onet.v1/log"
)

/*
 * Messages used by PriFi.
 * Syntax : SOURCE_DEST_CONTENT_CONTENT
 *
 * Below : Message-Switch that calls the correct function when one of this message arrives.
 */

// ALL_ALL_SHUTDOWN
// ALL_ALL_PARAMETERS
// CLI_REL_TELL_PK_AND_EPH_PK
// CLI_REL_UPSTREAM_DATA
// REL_CLI_DOWNSTREAM_DATA
// REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG
// REL_CLI_TELL_TRUSTEES_PK
// REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE
// REL_TRU_TELL_TRANSCRIPT
// TRU_REL_DC_CIPHER
// TRU_REL_SHUFFLE_SIG
// REL_TRU_TELL_RATE_CHANGE
// TRU_REL_TELL_NEW_BASE_AND_EPH_PKS
// TRU_REL_TELL_PK
// REL_TRU_TELL_RATE_CHANGE

//not used yet :
// REL_CLI_DOWNSTREAM_DATA
// CLI_REL_DOWNSTREAM_NACK

// ALL_ALL_SHUTDOWN message tells the participants to stop the protocol.
type ALL_ALL_SHUTDOWN struct {
}

// CLI_REL_TELL_PK_AND_EPH_PK message contains the public key and ephemeral key of a client
// and is sent to the relay.
type CLI_REL_TELL_PK_AND_EPH_PK struct {
	ClientID int
	Pk       abstract.Point
	EphPk    abstract.Point
}

// CLI_REL_UPSTREAM_DATA message contains the upstream data of a client for a given round
// and is sent to the relay.
type CLI_REL_UPSTREAM_DATA struct {
	ClientID int
	RoundID  int32
	Data     []byte
}

// REL_CLI_DOWNSTREAM_DATA message contains the downstream data for a client for a given round
// and is sent by the relay to the clients.
type REL_CLI_DOWNSTREAM_DATA struct {
	RoundID    int32
	Data       []byte
	FlagResync bool
}

//Converts []ByteArray -> [][]byte and returns it
func (m *REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG) GetSignatures() [][]byte {
	out := make([][]byte, 0)
	for k := range m.TrusteesSigs {
		out = append(out, m.TrusteesSigs[k].Bytes)
	}
	return out
}

// REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG message contains the ephemeral public keys and the signatures
// of the trustees and is sent by the relay to the client.
type REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG struct {
	Base         abstract.Point
	EphPks       []abstract.Point
	TrusteesSigs []ByteArray
}

// REL_CLI_TELL_TRUSTEES_PK message contains the public keys of the trustees
// and is sent by the relay to the clients.
type REL_CLI_TELL_TRUSTEES_PK struct {
	Pks []abstract.Point
}

// REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE message contains the public keys and ephemeral keys
// of the clients and is sent by the relay to the trustees.
type REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE struct {
	Pks    []abstract.Point
	EphPks []abstract.Point
	Base   abstract.Point
}

//protobuf can't handle [][]abstract.Point, so we do []PublicKeyArray
type PublicKeyArray struct {
	Keys []abstract.Point
}

//protobuf can't handle [][]byte, so we do []ByteArray
type ByteArray struct {
	Bytes []byte
}

//Converts []PublicKeyArray -> [][]abstract.Point and returns it
func (m *REL_TRU_TELL_TRANSCRIPT) GetKeys() [][]abstract.Point {
	out := make([][]abstract.Point, 0)
	for k := range m.EphPks {
		out = append(out, m.EphPks[k].Keys)
	}
	return out
}

//Converts []ByteArray -> [][]byte and returns it
func (m *REL_TRU_TELL_TRANSCRIPT) GetProofs() [][]byte {
	out := make([][]byte, 0)
	for k := range m.Proofs {
		out = append(out, m.Proofs[k].Bytes)
	}
	return out
}

// REL_TRU_TELL_TRANSCRIPT message contains all the shuffles perfomrmed in a Neff shuffle round.
// It is sent by the relay to the trustees to be verified.
type REL_TRU_TELL_TRANSCRIPT struct {
	Bases  []abstract.Point
	EphPks []PublicKeyArray
	Proofs []ByteArray
}

// TRU_REL_DC_CIPHER message contains the DC-net cipher of a trustee for a given round and is sent to the relay.
type TRU_REL_DC_CIPHER struct {
	RoundID   int32
	TrusteeID int
	Data      []byte
}

// TRU_REL_SHUFFLE_SIG contains the signatures shuffled by a trustee and is sent to the relay.
type TRU_REL_SHUFFLE_SIG struct {
	TrusteeID int
	Sig       []byte
}

// REL_TRU_TELL_RATE_CHANGE message asks the trustees to update their window capacity to adapt their
// sending rate and is sent by the relay.
type REL_TRU_TELL_RATE_CHANGE struct {
	WindowCapacity int
}

// TRU_REL_TELL_NEW_BASE_AND_EPH_PKS message contains the new ephemeral key of a trustee and
// is sent to the relay.
type TRU_REL_TELL_NEW_BASE_AND_EPH_PKS struct {
	NewBase   abstract.Point
	NewEphPks []abstract.Point
	Proof     []byte
}

// TRU_REL_TELL_PK message contains the public key of a trustee and is sent to the relay.
type TRU_REL_TELL_PK struct {
	TrusteeID int
	Pk        abstract.Point
}

/*
REL_CLI_DOWNSTREAM_DATA_UDP message is a bit special. It's a REL_CLI_DOWNSTREAM_DATA, simply named with _UDP postfix to be able to distinguish them from type,
and theoretically that should be it. But since it doesn't go through SDA (which does not support UDP yet), we have to manually convert it to bytes.
For that purpose, this message implements MarshallableMessage, defined in prifi-sda-wrapper/udp.go.
Hence, it has methods Print(), used for debug, ToBytes(), that converts it to a raw byte array, SetByte(), which simply store a byte array in the
structure (but does not decode it), and FromBytes(), which decodes the REL_CLI_DOWNSTREAM_DATA from the inner buffer set by SetBytes()
*/
type REL_CLI_DOWNSTREAM_DATA_UDP struct {
	REL_CLI_DOWNSTREAM_DATA
}

// Print prints the raw value of this message.
func (m REL_CLI_DOWNSTREAM_DATA_UDP) Print() {
	log.Printf("%+v\n", m)
}

// SetBytes sets the bytes contained in this message.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) SetContent(data REL_CLI_DOWNSTREAM_DATA) {
	m.REL_CLI_DOWNSTREAM_DATA = data
}

// ToBytes encodes a message into a slice of bytes.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) ToBytes() ([]byte, error) {

	//convert the message to bytes
	buf := make([]byte, 4+len(m.REL_CLI_DOWNSTREAM_DATA.Data)+4)
	resyncInt := 0
	if m.REL_CLI_DOWNSTREAM_DATA.FlagResync {
		resyncInt = 1
	}

	binary.BigEndian.PutUint32(buf[0:4], uint32(m.REL_CLI_DOWNSTREAM_DATA.RoundID))
	binary.BigEndian.PutUint32(buf[len(buf)-4:], uint32(resyncInt)) //todo : to be coded on one byte
	copy(buf[4:len(buf)-4], m.REL_CLI_DOWNSTREAM_DATA.Data)

	return buf, nil

}

// FromBytes decodes the message contained in the message's byteEncoded field.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) FromBytes(buffer []byte) (interface{}, error) {

	//the smallest message is 4 bytes, indicating a length of 0
	if len(buffer) < 8 { //4 (roundID) + 4 (flagResync)
		e := "Messages.go : FromBytes() : cannot decode, smaller than 8 bytes"
		return REL_CLI_DOWNSTREAM_DATA_UDP{}, errors.New(e)
	}

	roundID := int32(binary.BigEndian.Uint32(buffer[0:4]))
	flagResyncInt := int(binary.BigEndian.Uint32(buffer[len(buffer)-4:]))
	data := buffer[4 : len(buffer)-4]

	flagResync := false
	if flagResyncInt == 1 {
		flagResync = true
	}

	innerMessage := REL_CLI_DOWNSTREAM_DATA{roundID, data, flagResync} //This wrapping feels weird
	resultMessage := REL_CLI_DOWNSTREAM_DATA_UDP{innerMessage}

	return resultMessage, nil
}
<commit_msg>Adds flag in downstream message<commit_after>package net

import (
	"encoding/binary"
	"errors"

	"gopkg.in/dedis/crypto.v0/abstract"
	"gopkg.in/dedis/onet.v1/log"
)

/*
 * Messages used by PriFi.
 * Syntax : SOURCE_DEST_CONTENT_CONTENT
 *
 * Below : Message-Switch that calls the correct function when one of this message arrives.
 */

// ALL_ALL_SHUTDOWN
// ALL_ALL_PARAMETERS
// CLI_REL_TELL_PK_AND_EPH_PK
// CLI_REL_UPSTREAM_DATA
// REL_CLI_DOWNSTREAM_DATA
// REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG
// REL_CLI_TELL_TRUSTEES_PK
// REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE
// REL_TRU_TELL_TRANSCRIPT
// TRU_REL_DC_CIPHER
// TRU_REL_SHUFFLE_SIG
// REL_TRU_TELL_RATE_CHANGE
// TRU_REL_TELL_NEW_BASE_AND_EPH_PKS
// TRU_REL_TELL_PK
// REL_TRU_TELL_RATE_CHANGE

//not used yet :
// REL_CLI_DOWNSTREAM_DATA
// CLI_REL_DOWNSTREAM_NACK

// ALL_ALL_SHUTDOWN message tells the participants to stop the protocol.
type ALL_ALL_SHUTDOWN struct {
}

// CLI_REL_TELL_PK_AND_EPH_PK message contains the public key and ephemeral key of a client
// and is sent to the relay.
type CLI_REL_TELL_PK_AND_EPH_PK struct {
	ClientID int
	Pk       abstract.Point
	EphPk    abstract.Point
}

// CLI_REL_UPSTREAM_DATA message contains the upstream data of a client for a given round
// and is sent to the relay.
type CLI_REL_UPSTREAM_DATA struct {
	ClientID int
	RoundID  int32
	Data     []byte
}

// REL_CLI_DOWNSTREAM_DATA message contains the downstream data for a client for a given round
// and is sent by the relay to the clients.
type REL_CLI_DOWNSTREAM_DATA struct {
	RoundID               int32
	Data                  []byte
	FlagResync            bool
	FlagOpenClosedRequest bool
}

//Converts []ByteArray -> [][]byte and returns it
func (m *REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG) GetSignatures() [][]byte {
	out := make([][]byte, 0)
	for k := range m.TrusteesSigs {
		out = append(out, m.TrusteesSigs[k].Bytes)
	}
	return out
}

// REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG message contains the ephemeral public keys and the signatures
// of the trustees and is sent by the relay to the client.
type REL_CLI_TELL_EPH_PKS_AND_TRUSTEES_SIG struct {
	Base         abstract.Point
	EphPks       []abstract.Point
	TrusteesSigs []ByteArray
}

// REL_CLI_TELL_TRUSTEES_PK message contains the public keys of the trustees
// and is sent by the relay to the clients.
type REL_CLI_TELL_TRUSTEES_PK struct {
	Pks []abstract.Point
}

// REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE message contains the public keys and ephemeral keys
// of the clients and is sent by the relay to the trustees.
type REL_TRU_TELL_CLIENTS_PKS_AND_EPH_PKS_AND_BASE struct {
	Pks    []abstract.Point
	EphPks []abstract.Point
	Base   abstract.Point
}

//protobuf can't handle [][]abstract.Point, so we do []PublicKeyArray
type PublicKeyArray struct {
	Keys []abstract.Point
}

//protobuf can't handle [][]byte, so we do []ByteArray
type ByteArray struct {
	Bytes []byte
}

//Converts []PublicKeyArray -> [][]abstract.Point and returns it
func (m *REL_TRU_TELL_TRANSCRIPT) GetKeys() [][]abstract.Point {
	out := make([][]abstract.Point, 0)
	for k := range m.EphPks {
		out = append(out, m.EphPks[k].Keys)
	}
	return out
}

//Converts []ByteArray -> [][]byte and returns it
func (m *REL_TRU_TELL_TRANSCRIPT) GetProofs() [][]byte {
	out := make([][]byte, 0)
	for k := range m.Proofs {
		out = append(out, m.Proofs[k].Bytes)
	}
	return out
}

// REL_TRU_TELL_TRANSCRIPT message contains all the shuffles perfomrmed in a Neff shuffle round.
// It is sent by the relay to the trustees to be verified.
type REL_TRU_TELL_TRANSCRIPT struct {
	Bases  []abstract.Point
	EphPks []PublicKeyArray
	Proofs []ByteArray
}

// TRU_REL_DC_CIPHER message contains the DC-net cipher of a trustee for a given round and is sent to the relay.
type TRU_REL_DC_CIPHER struct {
	RoundID   int32
	TrusteeID int
	Data      []byte
}

// TRU_REL_SHUFFLE_SIG contains the signatures shuffled by a trustee and is sent to the relay.
type TRU_REL_SHUFFLE_SIG struct {
	TrusteeID int
	Sig       []byte
}

// REL_TRU_TELL_RATE_CHANGE message asks the trustees to update their window capacity to adapt their
// sending rate and is sent by the relay.
type REL_TRU_TELL_RATE_CHANGE struct {
	WindowCapacity int
}

// TRU_REL_TELL_NEW_BASE_AND_EPH_PKS message contains the new ephemeral key of a trustee and
// is sent to the relay.
type TRU_REL_TELL_NEW_BASE_AND_EPH_PKS struct {
	NewBase   abstract.Point
	NewEphPks []abstract.Point
	Proof     []byte
}

// TRU_REL_TELL_PK message contains the public key of a trustee and is sent to the relay.
type TRU_REL_TELL_PK struct {
	TrusteeID int
	Pk        abstract.Point
}

/*
REL_CLI_DOWNSTREAM_DATA_UDP message is a bit special. It's a REL_CLI_DOWNSTREAM_DATA, simply named with _UDP postfix to be able to distinguish them from type,
and theoretically that should be it. But since it doesn't go through SDA (which does not support UDP yet), we have to manually convert it to bytes.
For that purpose, this message implements MarshallableMessage, defined in prifi-sda-wrapper/udp.go.
Hence, it has methods Print(), used for debug, ToBytes(), that converts it to a raw byte array, SetByte(), which simply store a byte array in the
structure (but does not decode it), and FromBytes(), which decodes the REL_CLI_DOWNSTREAM_DATA from the inner buffer set by SetBytes()
*/
type REL_CLI_DOWNSTREAM_DATA_UDP struct {
	REL_CLI_DOWNSTREAM_DATA
}

// Print prints the raw value of this message.
func (m REL_CLI_DOWNSTREAM_DATA_UDP) Print() {
	log.Printf("%+v\n", m)
}

// SetBytes sets the bytes contained in this message.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) SetContent(data REL_CLI_DOWNSTREAM_DATA) {
	m.REL_CLI_DOWNSTREAM_DATA = data
}

// ToBytes encodes a message into a slice of bytes.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) ToBytes() ([]byte, error) {

	//convert the message to bytes
	buf := make([]byte, 4+len(m.REL_CLI_DOWNSTREAM_DATA.Data)+4+4)
	resyncInt := 0
	if m.REL_CLI_DOWNSTREAM_DATA.FlagResync {
		resyncInt = 1
	}
	openclosedInt := 0
	if m.REL_CLI_DOWNSTREAM_DATA.FlagOpenClosedRequest {
		openclosedInt = 1
	}

	// [0:4 roundID] [4:end-8 data] [end-8:end-4 resyncFlag] [end-4:end openClosedFlag]
	binary.BigEndian.PutUint32(buf[0:4], uint32(m.REL_CLI_DOWNSTREAM_DATA.RoundID))
	binary.BigEndian.PutUint32(buf[len(buf)-8:len(buf)-4], uint32(resyncInt)) //todo : to be coded on one byte
	binary.BigEndian.PutUint32(buf[len(buf)-4:], uint32(openclosedInt))       //todo : to be coded on one byte
	copy(buf[4:len(buf)-8], m.REL_CLI_DOWNSTREAM_DATA.Data)

	return buf, nil

}

// FromBytes decodes the message contained in the message's byteEncoded field.
func (m *REL_CLI_DOWNSTREAM_DATA_UDP) FromBytes(buffer []byte) (interface{}, error) {

	//the smallest message is 4 bytes, indicating a length of 0
	if len(buffer) < 8 { //4 (roundID) + 4 (flagResync)
		e := "Messages.go : FromBytes() : cannot decode, smaller than 8 bytes"
		return REL_CLI_DOWNSTREAM_DATA_UDP{}, errors.New(e)
	}

	// [0:4 roundID] [4:end-8 data] [end-8:end-4 resyncFlag] [end-4:end openClosedFlag]
	roundID := int32(binary.BigEndian.Uint32(buffer[0:4]))
	flagResyncInt := int(binary.BigEndian.Uint32(buffer[len(buffer)-8 : len(buffer)-4]))
	flagOpenClosedInt := int(binary.BigEndian.Uint32(buffer[len(buffer)-4:]))
	data := buffer[4 : len(buffer)-8]

	flagResync := false
	if flagResyncInt == 1 {
		flagResync = true
	}
	flagOpenClosed := false
	if flagOpenClosedInt == 1 {
		flagOpenClosed = true
	}

	innerMessage := REL_CLI_DOWNSTREAM_DATA{roundID, data, flagResync, flagOpenClosed}
	resultMessage := REL_CLI_DOWNSTREAM_DATA_UDP{innerMessage}

	return resultMessage, nil
}
<|endoftext|>