<commit_before>package json

import (
	"encoding/json"
	"fmt"
	"math/big"

	"github.com/hashicorp/hcl2/hcl"
)

func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
	tokens := scan(buf, pos{
		Filename: filename,
		Pos: hcl.Pos{
			Byte:   0,
			Line:   1,
			Column: 1,
		},
	})
	p := newPeeker(tokens)
	node, diags := parseValue(p)
	if len(diags) == 0 && p.Peek().Type != tokenEOF {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Extraneous data after value",
			Detail:   "Extra characters appear after the JSON value.",
			Subject:  p.Peek().Range.Ptr(),
		})
	}
	return node, diags
}

func parseValue(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Peek()

	wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) {
		if n != nil {
			return n, diags
		}
		return invalidVal{tok.Range}, diags
	}

	switch tok.Type {
	case tokenBraceO:
		return wrapInvalid(parseObject(p))
	case tokenBrackO:
		return wrapInvalid(parseArray(p))
	case tokenNumber:
		return wrapInvalid(parseNumber(p))
	case tokenString:
		return wrapInvalid(parseString(p))
	case tokenKeyword:
		return wrapInvalid(parseKeyword(p))
	case tokenBraceC:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	case tokenBrackC:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing array element value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	case tokenEOF:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing value",
				Detail:   "The JSON data ends prematurely.",
				Subject:  &tok.Range,
			},
		})
	default:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid start of value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	}
}

func tokenCanStartValue(tok token) bool {
	switch tok.Type {
	case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
		return true
	default:
		return false
	}
}

func parseObject(p *peeker) (node, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	open := p.Read()
	attrs := []*objectAttr{}

	// recover is used to shift the peeker to what seems to be the end of
	// our object, so that when we encounter an error we leave the peeker
	// at a reasonable point in the token stream to continue parsing.
	recover := func(tok token) {
		open := 1
		for {
			switch tok.Type {
			case tokenBraceO:
				open++
			case tokenBraceC:
				open--
				if open <= 1 {
					return
				}
			case tokenEOF:
				// Ran out of source before we were able to recover,
				// so we'll bail here and let the caller deal with it.
				return
			}
			tok = p.Read()
		}
	}

Token:
	for {
		if p.Peek().Type == tokenBraceC {
			break Token
		}

		keyNode, keyDiags := parseValue(p)
		diags = diags.Extend(keyDiags)
		if keyNode == nil {
			return nil, diags
		}

		keyStrNode, ok := keyNode.(*stringVal)
		if !ok {
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid object attribute name",
				Detail:   "A JSON object attribute name must be a string",
				Subject:  keyNode.StartRange().Ptr(),
			})
		}

		key := keyStrNode.Value

		colon := p.Read()
		if colon.Type != tokenColon {
			recover(colon)

			if colon.Type == tokenBraceC || colon.Type == tokenComma {
				// Catch common mistake of using braces instead of brackets
				// for an object.
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Missing object value",
					Detail:   "A JSON object attribute must have a value, introduced by a colon.",
					Subject:  &colon.Range,
				})
			}

			if colon.Type == tokenEquals {
				// Possible confusion with native HCL syntax.
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Missing attribute value colon",
					Detail:   "JSON uses a colon as its name/value delimiter, not an equals sign.",
					Subject:  &colon.Range,
				})
			}

			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute value colon",
				Detail:   "A colon must appear between an object attribute's name and its value.",
				Subject:  &colon.Range,
			})
		}

		valNode, valDiags := parseValue(p)
		diags = diags.Extend(valDiags)
		if valNode == nil {
			return nil, diags
		}

		attrs = append(attrs, &objectAttr{
			Name:      key,
			Value:     valNode,
			NameRange: keyStrNode.SrcRange,
		})

		switch p.Peek().Type {
		case tokenComma:
			comma := p.Read()
			if p.Peek().Type == tokenBraceC {
				// Special error message for this common mistake
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Trailing comma in object",
					Detail:   "JSON does not permit a trailing comma after the final attribute in an object.",
					Subject:  &comma.Range,
				})
			}
			continue Token
		case tokenEOF:
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Unclosed object",
				Detail:   "No closing brace was found for this JSON object.",
				Subject:  &open.Range,
			})
		case tokenBrackC:
			// Consume the bracket anyway, so that we don't return with the peeker
			// at a strange place.
			p.Read()
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Mismatched braces",
				Detail:   "A JSON object must be closed with a brace, not a bracket.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenBraceC:
			break Token
		default:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute seperator comma",
				Detail:   "A comma must appear between each attribute declaration in an object.",
				Subject:  p.Peek().Range.Ptr(),
			})
		}

	}

	close := p.Read()
	return &objectVal{
		Attrs:      attrs,
		SrcRange:   hcl.RangeBetween(open.Range, close.Range),
		OpenRange:  open.Range,
		CloseRange: close.Range,
	}, diags
}

func parseArray(p *peeker) (node, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	open := p.Read()
	vals := []node{}

	// recover is used to shift the peeker to what seems to be the end of
	// our array, so that when we encounter an error we leave the peeker
	// at a reasonable point in the token stream to continue parsing.
	recover := func(tok token) {
		open := 1
		for {
			switch tok.Type {
			case tokenBrackO:
				open++
			case tokenBrackC:
				open--
				if open <= 1 {
					return
				}
			case tokenEOF:
				// Ran out of source before we were able to recover,
				// so we'll bail here and let the caller deal with it.
				return
			}
			tok = p.Read()
		}
	}

Token:
	for {
		if p.Peek().Type == tokenBrackC {
			break Token
		}

		valNode, valDiags := parseValue(p)
		diags = diags.Extend(valDiags)
		if valNode == nil {
			return nil, diags
		}

		vals = append(vals, valNode)

		switch p.Peek().Type {
		case tokenComma:
			comma := p.Read()
			if p.Peek().Type == tokenBrackC {
				// Special error message for this common mistake
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Trailing comma in array",
					Detail:   "JSON does not permit a trailing comma after the final attribute in an array.",
					Subject:  &comma.Range,
				})
			}
			continue Token
		case tokenColon:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid array value",
				Detail:   "A colon is not used to introduce values in a JSON array.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenEOF:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Unclosed object",
				Detail:   "No closing bracket was found for this JSON array.",
				Subject:  &open.Range,
			})
		case tokenBraceC:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Mismatched brackets",
				Detail:   "A JSON array must be closed with a bracket, not a brace.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenBrackC:
			break Token
		default:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute seperator comma",
				Detail:   "A comma must appear between each value in an array.",
				Subject:  p.Peek().Range.Ptr(),
			})
		}

	}

	close := p.Read()
	return &arrayVal{
		Values:    vals,
		SrcRange:  hcl.RangeBetween(open.Range, close.Range),
		OpenRange: open.Range,
	}, diags
}

func parseNumber(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()

	// Use encoding/json to validate the number syntax.
	// TODO: Do this more directly to produce better diagnostics.
	var num json.Number
	err := json.Unmarshal(tok.Bytes, &num)
	if err != nil {
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON number",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON number."),
				Subject:  &tok.Range,
			},
		}
	}

	f, _, err := (&big.Float{}).Parse(string(num), 10)
	if err != nil {
		// Should never happen if above passed, since JSON numbers are a subset
		// of what big.Float can parse...
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON number",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON number."),
				Subject:  &tok.Range,
			},
		}
	}

	return &numberVal{
		Value:    f,
		SrcRange: tok.Range,
	}, nil
}

func parseString(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()
	var str string
	err := json.Unmarshal(tok.Bytes, &str)

	if err != nil {
		var errRange hcl.Range
		if serr, ok := err.(*json.SyntaxError); ok {
			errOfs := serr.Offset
			errPos := tok.Range.Start
			errPos.Byte += int(errOfs)

			// TODO: Use the byte offset to properly count unicode
			// characters for the column, and mark the whole of the
			// character that was wrong as part of our range.
			errPos.Column += int(errOfs)

			errEndPos := errPos
			errEndPos.Byte++
			errEndPos.Column++

			errRange = hcl.Range{
				Filename: tok.Range.Filename,
				Start:    errPos,
				End:      errEndPos,
			}
		} else {
			errRange = tok.Range
		}

		var contextRange *hcl.Range
		if errRange != tok.Range {
			contextRange = &tok.Range
		}

		// FIXME: Eventually we should parse strings directly here so
		// we can produce a more useful error message in the face fo things
		// such as invalid escapes, etc.
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON string",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON string."),
				Subject:  &errRange,
				Context:  contextRange,
			},
		}
	}

	return &stringVal{
		Value:    str,
		SrcRange: tok.Range,
	}, nil
}

func parseKeyword(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()
	s := string(tok.Bytes)

	switch s {
	case "true":
		return &booleanVal{
			Value:    true,
			SrcRange: tok.Range,
		}, nil
	case "false":
		return &booleanVal{
			Value:    false,
			SrcRange: tok.Range,
		}, nil
	case "null":
		return &nullVal{
			SrcRange: tok.Range,
		}, nil
	case "undefined", "NaN", "Infinity":
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON keyword",
				Detail:   fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
				Subject:  &tok.Range,
			},
		}
	default:
		var dym string
		if suggest := keywordSuggestion(s); suggest != "" {
			dym = fmt.Sprintf(" Did you mean %q?", suggest)
		}

		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON keyword",
				Detail:   fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
				Subject:  &tok.Range,
			},
		}
	}
}
<commit_msg>hcl/json: decode number literals at full precision<commit_after>package json

import (
	"encoding/json"
	"fmt"
	"math/big"

	"github.com/hashicorp/hcl2/hcl"
)

func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
	tokens := scan(buf, pos{
		Filename: filename,
		Pos: hcl.Pos{
			Byte:   0,
			Line:   1,
			Column: 1,
		},
	})
	p := newPeeker(tokens)
	node, diags := parseValue(p)
	if len(diags) == 0 && p.Peek().Type != tokenEOF {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Extraneous data after value",
			Detail:   "Extra characters appear after the JSON value.",
			Subject:  p.Peek().Range.Ptr(),
		})
	}
	return node, diags
}

func parseValue(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Peek()

	wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) {
		if n != nil {
			return n, diags
		}
		return invalidVal{tok.Range}, diags
	}

	switch tok.Type {
	case tokenBraceO:
		return wrapInvalid(parseObject(p))
	case tokenBrackO:
		return wrapInvalid(parseArray(p))
	case tokenNumber:
		return wrapInvalid(parseNumber(p))
	case tokenString:
		return wrapInvalid(parseString(p))
	case tokenKeyword:
		return wrapInvalid(parseKeyword(p))
	case tokenBraceC:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	case tokenBrackC:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing array element value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	case tokenEOF:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Missing value",
				Detail:   "The JSON data ends prematurely.",
				Subject:  &tok.Range,
			},
		})
	default:
		return wrapInvalid(nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid start of value",
				Detail:   "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
				Subject:  &tok.Range,
			},
		})
	}
}

func tokenCanStartValue(tok token) bool {
	switch tok.Type {
	case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
		return true
	default:
		return false
	}
}

func parseObject(p *peeker) (node, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	open := p.Read()
	attrs := []*objectAttr{}

	// recover is used to shift the peeker to what seems to be the end of
	// our object, so that when we encounter an error we leave the peeker
	// at a reasonable point in the token stream to continue parsing.
	recover := func(tok token) {
		open := 1
		for {
			switch tok.Type {
			case tokenBraceO:
				open++
			case tokenBraceC:
				open--
				if open <= 1 {
					return
				}
			case tokenEOF:
				// Ran out of source before we were able to recover,
				// so we'll bail here and let the caller deal with it.
				return
			}
			tok = p.Read()
		}
	}

Token:
	for {
		if p.Peek().Type == tokenBraceC {
			break Token
		}

		keyNode, keyDiags := parseValue(p)
		diags = diags.Extend(keyDiags)
		if keyNode == nil {
			return nil, diags
		}

		keyStrNode, ok := keyNode.(*stringVal)
		if !ok {
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid object attribute name",
				Detail:   "A JSON object attribute name must be a string",
				Subject:  keyNode.StartRange().Ptr(),
			})
		}

		key := keyStrNode.Value

		colon := p.Read()
		if colon.Type != tokenColon {
			recover(colon)

			if colon.Type == tokenBraceC || colon.Type == tokenComma {
				// Catch common mistake of using braces instead of brackets
				// for an object.
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Missing object value",
					Detail:   "A JSON object attribute must have a value, introduced by a colon.",
					Subject:  &colon.Range,
				})
			}

			if colon.Type == tokenEquals {
				// Possible confusion with native HCL syntax.
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Missing attribute value colon",
					Detail:   "JSON uses a colon as its name/value delimiter, not an equals sign.",
					Subject:  &colon.Range,
				})
			}

			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute value colon",
				Detail:   "A colon must appear between an object attribute's name and its value.",
				Subject:  &colon.Range,
			})
		}

		valNode, valDiags := parseValue(p)
		diags = diags.Extend(valDiags)
		if valNode == nil {
			return nil, diags
		}

		attrs = append(attrs, &objectAttr{
			Name:      key,
			Value:     valNode,
			NameRange: keyStrNode.SrcRange,
		})

		switch p.Peek().Type {
		case tokenComma:
			comma := p.Read()
			if p.Peek().Type == tokenBraceC {
				// Special error message for this common mistake
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Trailing comma in object",
					Detail:   "JSON does not permit a trailing comma after the final attribute in an object.",
					Subject:  &comma.Range,
				})
			}
			continue Token
		case tokenEOF:
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Unclosed object",
				Detail:   "No closing brace was found for this JSON object.",
				Subject:  &open.Range,
			})
		case tokenBrackC:
			// Consume the bracket anyway, so that we don't return with the peeker
			// at a strange place.
			p.Read()
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Mismatched braces",
				Detail:   "A JSON object must be closed with a brace, not a bracket.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenBraceC:
			break Token
		default:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute seperator comma",
				Detail:   "A comma must appear between each attribute declaration in an object.",
				Subject:  p.Peek().Range.Ptr(),
			})
		}

	}

	close := p.Read()
	return &objectVal{
		Attrs:      attrs,
		SrcRange:   hcl.RangeBetween(open.Range, close.Range),
		OpenRange:  open.Range,
		CloseRange: close.Range,
	}, diags
}

func parseArray(p *peeker) (node, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	open := p.Read()
	vals := []node{}

	// recover is used to shift the peeker to what seems to be the end of
	// our array, so that when we encounter an error we leave the peeker
	// at a reasonable point in the token stream to continue parsing.
	recover := func(tok token) {
		open := 1
		for {
			switch tok.Type {
			case tokenBrackO:
				open++
			case tokenBrackC:
				open--
				if open <= 1 {
					return
				}
			case tokenEOF:
				// Ran out of source before we were able to recover,
				// so we'll bail here and let the caller deal with it.
				return
			}
			tok = p.Read()
		}
	}

Token:
	for {
		if p.Peek().Type == tokenBrackC {
			break Token
		}

		valNode, valDiags := parseValue(p)
		diags = diags.Extend(valDiags)
		if valNode == nil {
			return nil, diags
		}

		vals = append(vals, valNode)

		switch p.Peek().Type {
		case tokenComma:
			comma := p.Read()
			if p.Peek().Type == tokenBrackC {
				// Special error message for this common mistake
				return nil, diags.Append(&hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Trailing comma in array",
					Detail:   "JSON does not permit a trailing comma after the final attribute in an array.",
					Subject:  &comma.Range,
				})
			}
			continue Token
		case tokenColon:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid array value",
				Detail:   "A colon is not used to introduce values in a JSON array.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenEOF:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Unclosed object",
				Detail:   "No closing bracket was found for this JSON array.",
				Subject:  &open.Range,
			})
		case tokenBraceC:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Mismatched brackets",
				Detail:   "A JSON array must be closed with a bracket, not a brace.",
				Subject:  p.Peek().Range.Ptr(),
			})
		case tokenBrackC:
			break Token
		default:
			recover(p.Read())
			return nil, diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Missing attribute seperator comma",
				Detail:   "A comma must appear between each value in an array.",
				Subject:  p.Peek().Range.Ptr(),
			})
		}

	}

	close := p.Read()
	return &arrayVal{
		Values:    vals,
		SrcRange:  hcl.RangeBetween(open.Range, close.Range),
		OpenRange: open.Range,
	}, diags
}

func parseNumber(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()

	// Use encoding/json to validate the number syntax.
	// TODO: Do this more directly to produce better diagnostics.
	var num json.Number
	err := json.Unmarshal(tok.Bytes, &num)
	if err != nil {
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON number",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON number."),
				Subject:  &tok.Range,
			},
		}
	}

	f, _, err := big.ParseFloat(string(num), 10, 512, big.ToNearestEven)
	if err != nil {
		// Should never happen if above passed, since JSON numbers are a subset
		// of what big.Float can parse...
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON number",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON number."),
				Subject:  &tok.Range,
			},
		}
	}

	return &numberVal{
		Value:    f,
		SrcRange: tok.Range,
	}, nil
}

func parseString(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()
	var str string
	err := json.Unmarshal(tok.Bytes, &str)

	if err != nil {
		var errRange hcl.Range
		if serr, ok := err.(*json.SyntaxError); ok {
			errOfs := serr.Offset
			errPos := tok.Range.Start
			errPos.Byte += int(errOfs)

			// TODO: Use the byte offset to properly count unicode
			// characters for the column, and mark the whole of the
			// character that was wrong as part of our range.
			errPos.Column += int(errOfs)

			errEndPos := errPos
			errEndPos.Byte++
			errEndPos.Column++

			errRange = hcl.Range{
				Filename: tok.Range.Filename,
				Start:    errPos,
				End:      errEndPos,
			}
		} else {
			errRange = tok.Range
		}

		var contextRange *hcl.Range
		if errRange != tok.Range {
			contextRange = &tok.Range
		}

		// FIXME: Eventually we should parse strings directly here so
		// we can produce a more useful error message in the face fo things
		// such as invalid escapes, etc.
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON string",
				Detail:   fmt.Sprintf("There is a syntax error in the given JSON string."),
				Subject:  &errRange,
				Context:  contextRange,
			},
		}
	}

	return &stringVal{
		Value:    str,
		SrcRange: tok.Range,
	}, nil
}

func parseKeyword(p *peeker) (node, hcl.Diagnostics) {
	tok := p.Read()
	s := string(tok.Bytes)

	switch s {
	case "true":
		return &booleanVal{
			Value:    true,
			SrcRange: tok.Range,
		}, nil
	case "false":
		return &booleanVal{
			Value:    false,
			SrcRange: tok.Range,
		}, nil
	case "null":
		return &nullVal{
			SrcRange: tok.Range,
		}, nil
	case "undefined", "NaN", "Infinity":
		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON keyword",
				Detail:   fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
				Subject:  &tok.Range,
			},
		}
	default:
		var dym string
		if suggest := keywordSuggestion(s); suggest != "" {
			dym = fmt.Sprintf(" Did you mean %q?", suggest)
		}

		return nil, hcl.Diagnostics{
			{
				Severity: hcl.DiagError,
				Summary:  "Invalid JSON keyword",
				Detail:   fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
				Subject:  &tok.Range,
			},
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

type Message struct {
	Body string
}

func main() {
	router, err := rest.MakeRouter(
		&rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
			w.WriteJson(&Message{
				Body: "Hello World!",
			})
		}},
	)
	if err != nil {
		log.Fatal(err)
	}

	api := rest.NewApi(router)
	api.Use(rest.DefaultDevStack...)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
<commit_msg>Revert "convert helloworld to the v3 api"<commit_after>package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

type Message struct {
	Body string
}

func main() {
	handler := rest.ResourceHandler{}
	err := handler.SetRoutes(
		&rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
			w.WriteJson(&Message{
				Body: "Hello World!",
			})
		}},
	)
	if err != nil {
		log.Fatal(err)
	}
	log.Fatal(http.ListenAndServe(":8080", &handler))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

type hexkitMap map[string]json.RawMessage
type layersList []map[string]json.RawMessage
type tilesList []map[string]interface{}

var fileList = make(map[string][]string, 4096)

// Search fon all png files under the current path
func pathMap(path string, info os.FileInfo, err error) error {
	if err != nil {
		fmt.Println(err)
	}
	name := info.Name()
	lenPath := len(path)
	if lenPath > 16 && path[lenPath-4:] == ".png" {
		fileList[name] = append(fileList[name], path)
	}
	return nil
}

func failOnError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage:", os.Args[0], "CollectionPath... MapPath")
		return
	}
	// Which path separator should we use
	pathSeparator := "/"
	if os.PathSeparator != '/' {
		pathSeparator = "\\\\"
	}
	// Build the list of PNG files
	for i := 0; i <= (len(os.Args) - 2); i++ {
		err := filepath.Walk(os.Args[1], pathMap)
		failOnError(err)
	}
	// Read the file
	mapFile := os.Args[len(os.Args)-1]
	mapBlob, err := ioutil.ReadFile(mapFile)
	failOnError(err)

	// Decode it in hexMap
	var hexMap hexkitMap
	err = json.Unmarshal(mapBlob, &hexMap)
	failOnError(err)

	// Get the layers list
	layersBlob, ok := hexMap["layers"]
	if !ok {
		log.Fatal("Map format error (no layers found)")
	}
	var layers layersList
	err = json.Unmarshal(layersBlob, &layers)
	failOnError(err)

	// Search for tiles
	layersModified := false
	for i, v := range layers {
		var tiles tilesList
		tilesBlob, ok := v["tiles"]
		if !ok {
			log.Fatal("Map format error: no tiles in layer", i+1)
		}
		err = json.Unmarshal(tilesBlob, &tiles)
		if err != nil {
			log.Fatal("Map format error in layer", i+1, ":", err)
		}
		// Search for the source of tiles
		tilesModified := false
		for j, t := range tiles {
			// Ignore undefined tiles
			if t == nil {
				continue
			}
			sourceBlob, ok := t["source"]
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "no tile source found")
				continue
			}
			source, ok := sourceBlob.(string)
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "the tile source is incorrect (not a string)")
				continue
			}
			// Skip the default blank tiles
			if source[:6] == "Blank:" {
				continue
			}
			// Have we found the tile
			nameSelect := regexp.MustCompile(`[^:/\\]+$`)
			fileName := nameSelect.FindString(source)
			pathList, ok := fileList[fileName]
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "unable to find tile image file for", source)
				continue
			}
			// Search for the current path in the
			firstSplit := strings.SplitN(source, ":", 2)
			if len(firstSplit) < 2 {
				log.Println("Layer", i+1, "Tile", j+1, "incorrect source:", source)
				continue
			}
			targetCollection := firstSplit[0]
			targetPath := firstSplit[1]
			var bestMatch int
			var selected string
		pathSearch:
			for _, p := range pathList {
				splitPath := strings.Split(p, string(os.PathSeparator))
				for k, segment := range splitPath {
					if segment != targetCollection {
						continue
					}
					foundPath := pathSeparator + strings.Join(splitPath[k+1:], pathSeparator)
					if targetPath == foundPath {
						selected = targetCollection + ":" + foundPath
						break pathSearch
					}
					if len(foundPath) > bestMatch {
						bestMatch = len(foundPath)
						selected = targetCollection + ":" + foundPath
					}
				}
				// A new value was found: update the source
				if selected != "" {
					tilesModified = true
					t["source"] = selected
				}
			}
		}
		if tilesModified {
			tilesBlob, err := json.Marshal(tiles)
			failOnError(err)
			v["tiles"] = tilesBlob
			layersModified = true
		}
	}
	if layersModified {
		layersBlob, err := json.Marshal(layers)
		failOnError(err)
		hexMap["layers"] = layersBlob
	}
	b, err := json.Marshal(hexMap)
	failOnError(err)
	_, err = os.Stdout.Write(b)
	failOnError(err)
}
<commit_msg>Fix windows paths<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

type hexkitMap map[string]json.RawMessage
type layersList []map[string]json.RawMessage
type tilesList []map[string]interface{}

var fileList = make(map[string][]string, 4096)

// Search fon all png files under the current path
func pathMap(path string, info os.FileInfo, err error) error {
	if err != nil {
		fmt.Println(err)
	}
	name := info.Name()
	lenPath := len(path)
	if lenPath > 16 && path[lenPath-4:] == ".png" {
		fileList[name] = append(fileList[name], path)
	}
	return nil
}

func failOnError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage:", os.Args[0], "CollectionPath... MapPath")
		return
	}
	// Build the list of PNG files
	for i := 0; i <= (len(os.Args) - 2); i++ {
		err := filepath.Walk(os.Args[1], pathMap)
		failOnError(err)
	}
	// Read the file
	mapFile := os.Args[len(os.Args)-1]
	mapBlob, err := ioutil.ReadFile(mapFile)
	failOnError(err)

	// Decode it in hexMap
	var hexMap hexkitMap
	err = json.Unmarshal(mapBlob, &hexMap)
	failOnError(err)

	// Get the layers list
	layersBlob, ok := hexMap["layers"]
	if !ok {
		log.Fatal("Map format error (no layers found)")
	}
	var layers layersList
	err = json.Unmarshal(layersBlob, &layers)
	failOnError(err)

	// Search for tiles
	layersModified := false
	for i, v := range layers {
		var tiles tilesList
		tilesBlob, ok := v["tiles"]
		if !ok {
			log.Fatal("Map format error: no tiles in layer", i+1)
		}
		err = json.Unmarshal(tilesBlob, &tiles)
		if err != nil {
			log.Fatal("Map format error in layer", i+1, ":", err)
		}
		// Search for the source of tiles
		tilesModified := false
		for j, t := range tiles {
			// Ignore undefined tiles
			if t == nil {
				continue
			}
			sourceBlob, ok := t["source"]
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "no tile source found")
				continue
			}
			source, ok := sourceBlob.(string)
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "the tile source is incorrect (not a string)")
				continue
			}
			// Skip the default blank tiles
			if source[:6] == "Blank:" {
				continue
			}
			// Have we found the tile
			nameSelect := regexp.MustCompile(`[^:/\\]+$`)
			fileName := nameSelect.FindString(source)
			pathList, ok := fileList[fileName]
			if !ok {
				log.Println("Layer", i+1, "Tile", j+1, "unable to find tile image file for", source)
				continue
			}
			// Search for the current path in the
			firstSplit := strings.SplitN(source, ":", 2)
			if len(firstSplit) < 2 {
				log.Println("Layer", i+1, "Tile", j+1, "incorrect source:", source)
				continue
			}
			targetCollection := firstSplit[0]
			targetPath := firstSplit[1]
			var bestMatch int
			var selected string
		pathSearch:
			for _, p := range pathList {
				splitPath := strings.Split(p, string(os.PathSeparator))
				for k, segment := range splitPath {
					if segment != targetCollection {
						continue
					}
					foundPath := "/" + strings.Join(splitPath[k+1:], "/")
					if targetPath == foundPath {
						selected = targetCollection + ":" + foundPath
						break pathSearch
					}
					if len(foundPath) > bestMatch {
						bestMatch = len(foundPath)
						selected = targetCollection + ":" + foundPath
					}
				}
				// A new value was found: update the source
				if selected != "" {
					tilesModified = true
					t["source"] = selected
				}
			}
		}
		if tilesModified {
			tilesBlob, err := json.Marshal(tiles)
			failOnError(err)
			v["tiles"] = tilesBlob
			layersModified = true
		}
	}
	if layersModified {
		layersBlob, err := json.Marshal(layers)
		failOnError(err)
		hexMap["layers"] = layersBlob
	}
	b, err := json.Marshal(hexMap)
	failOnError(err)
	_, err = os.Stdout.Write(b)
	failOnError(err)
}
<|endoftext|>


<commit_before>package history

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

	"../conio"
	"../interpreter"
)

func atoi_(reader *strings.Reader) (int, int) {
	n := 0
	count := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		index := strings.IndexRune("0123456789", ch)
		if index >= 0 {
			n = n*10 + index
			count++
		} else {
			reader.UnreadRune()
			break
		}
	}
	return n, count
}

var Mark = "!"

func Replace(line string) (string, bool) {
	var mark rune
	for _, c := range Mark {
		mark = c
		break
	}

	var buffer bytes.Buffer
	isReplaced := false
	reader := strings.NewReader(line)
	history_count := conio.DefaultEditor.HistoryLen()

	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if ch != mark || reader.Len() <= 0 {
			buffer.WriteRune(ch)
			continue
		}
		ch, _, _ = reader.ReadRune()
		if n := strings.IndexRune("^$:*", ch); n >= 0 {
			reader.UnreadRune()
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
			}
			continue
		}
		if ch == mark { // !!
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
				continue
			} else {
				buffer.WriteRune(mark)
				continue
			}
		}
		if strings.IndexRune("0123456789", ch) >= 0 { // !n
			reader.UnreadRune()
			backno, _ := atoi_(reader)
			backno = backno % history_count
			if 0 <= backno && backno < history_count {
				insertHistory(&buffer, reader, backno)
				isReplaced = true
			}
			continue
		}
		if ch == '-' && reader.Len() > 0 { // !-n
			if number, count := atoi_(reader); count > 0 {
				backno := history_count - number - 1
				for backno < 0 {
					backno += history_count
				}
				if 0 <= backno && backno < history_count {
					insertHistory(&buffer, reader, backno)
					isReplaced = true
				} else {
					buffer.WriteRune(mark)
					buffer.WriteString("-0")
				}
				continue
			} else {
				reader.UnreadRune() // next char of '-'
			}
		}
		if ch == '?' { // !?str?
			var seekStrBuf bytes.Buffer
			lastCharIsQuestionMark := false
			for reader.Len() > 0 {
				ch, _, _ := reader.ReadRune()
				if ch == '?' {
					lastCharIsQuestionMark = true
					break
				}
				seekStrBuf.WriteRune(ch)
			}
			seekStr := seekStrBuf.String()
			found := false
			for i := history_count - 2; i >= 0; i-- {
				if strings.Contains(conio.DefaultEditor.Histories[i].Line, seekStr) {
					buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
					isReplaced = true
					found = true
					break
				}
			}
			if !found {
				buffer.WriteRune('?')
				buffer.WriteString(seekStr)
				if lastCharIsQuestionMark {
					buffer.WriteRune('?')
				}
			}
			continue
		}
		// !str
		var seekStrBuf bytes.Buffer
		seekStrBuf.WriteRune(ch)
		for reader.Len() > 0 {
			ch, _, _ := reader.ReadRune()
			if ch == ' ' {
				reader.UnreadRune()
				break
			}
			seekStrBuf.WriteRune(ch)
		}
		seekStr := seekStrBuf.String()
		found := false
		for i := history_count - 2; i >= 0; i-- {
			if strings.HasPrefix(conio.DefaultEditor.Histories[i].Line, seekStr) {
				buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
				isReplaced = true
				found = true
				break
			}
		}
		if !found {
			buffer.WriteRune(mark)
			buffer.WriteRune(ch)
		}
	}
	result := conio.NewHistoryLine(buffer.String())
	if isReplaced {
		if history_count > 0 {
			conio.DefaultEditor.Histories[history_count-1] = result
		} else {
			conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, result)
		}
	}
	return result.Line, isReplaced
}

func insertHistory(buffer *bytes.Buffer, reader *strings.Reader, historyNo int) {
	history1 := conio.DefaultEditor.Histories[historyNo]
	ch, siz, _ := reader.ReadRune()
	if siz > 0 && ch == '^' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(1))
		}
	} else if siz > 0 && ch == '$' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(-1))
		}
	} else if siz > 0 && ch == '*' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(strings.Join(history1.Word[1:], " "))
		}
	} else if siz > 0 && ch == ':' {
		n, count := atoi_(reader)
		if count <= 0 {
			buffer.WriteRune(':')
		} else if n < len(history1.Word) {
			buffer.WriteString(history1.Word[n])
		}
	} else {
		if siz > 0 {
			reader.UnreadRune()
		}
		buffer.WriteString(history1.Line)
	}
}

func CmdHistory(cmd *interpreter.Interpreter) (interpreter.ErrorLevel, error) {
	var num int
	if len(cmd.Args) >= 2 {
		num64, err := strconv.ParseInt(cmd.Args[1], 0, 32)
		if err != nil {
			switch err.(type) {
			case *strconv.NumError:
				return interpreter.NOERROR, fmt.Errorf(
					"history: %s not a number", cmd.Args[1])
			default:
				return interpreter.NOERROR, err
			}
		}
		num = int(num64)
		if num < 0 {
			num = -num
		}
	} else {
		num = 10
	}
	var start int
	if conio.DefaultEditor.HistoryLen() > num {
		start = conio.DefaultEditor.HistoryLen() - num
	} else {
		start = 0
	}
	for i, s := range conio.DefaultEditor.Histories[start:] {
		fmt.Fprintf(cmd.Stdout, "%3d : %-s\n", start+i, s.Line)
	}
	return interpreter.NOERROR, nil
}

const max_histories = 2000

func Save(path string) error {
	conio.DefaultEditor.ShrinkHistory()
	start := 0
	if conio.DefaultEditor.HistoryLen() > max_histories {
		start = conio.DefaultEditor.HistoryLen() - max_histories
	}
	fd, err := os.Create(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	for i := start; i < len(conio.DefaultEditor.Histories); i++ {
		fmt.Fprintln(fd, conio.DefaultEditor.Histories[i].Line)
	}
	return nil
}

func Load(path string) error {
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	sc := bufio.NewScanner(fd)
	for sc.Scan() {
		conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, conio.NewHistoryLine(sc.Text()))
	}
	conio.DefaultEditor.ShrinkHistory()
	return nil
}
<commit_msg>Disable History substitution between single-quotations<commit_after>package history

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

	"../conio"
	"../interpreter"
)

func atoi_(reader *strings.Reader) (int, int) {
	n := 0
	count := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		index := strings.IndexRune("0123456789", ch)
		if index >= 0 {
			n = n*10 + index
			count++
		} else {
			reader.UnreadRune()
			break
		}
	}
	return n, count
}

var Mark = "!"

func Replace(line string) (string, bool) {
	var mark rune
	for _, c := range Mark {
		mark = c
		break
	}

	var buffer bytes.Buffer
	isReplaced := false
	reader := strings.NewReader(line)
	history_count := conio.DefaultEditor.HistoryLen()

	isQuoted := false

	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if ch == '\'' {
			isQuoted = !isQuoted
			buffer.WriteRune(ch)
			continue
		}
		if ch != mark || reader.Len() <= 0 || isQuoted {
			buffer.WriteRune(ch)
			continue
		}
		ch, _, _ = reader.ReadRune()
		if n := strings.IndexRune("^$:*", ch); n >= 0 {
			reader.UnreadRune()
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
			}
			continue
		}
		if ch == mark { // !!
			if history_count >= 2 {
				insertHistory(&buffer, reader, history_count-2)
				isReplaced = true
				continue
			} else {
				buffer.WriteRune(mark)
				continue
			}
		}
		if strings.IndexRune("0123456789", ch) >= 0 { // !n
			reader.UnreadRune()
			backno, _ := atoi_(reader)
			backno = backno % history_count
			if 0 <= backno && backno < history_count {
				insertHistory(&buffer, reader, backno)
				isReplaced = true
			}
			continue
		}
		if ch == '-' && reader.Len() > 0 { // !-n
			if number, count := atoi_(reader); count > 0 {
				backno := history_count - number - 1
				for backno < 0 {
					backno += history_count
				}
				if 0 <= backno && backno < history_count {
					insertHistory(&buffer, reader, backno)
					isReplaced = true
				} else {
					buffer.WriteRune(mark)
					buffer.WriteString("-0")
				}
				continue
			} else {
				reader.UnreadRune() // next char of '-'
			}
		}
		if ch == '?' { // !?str?
			var seekStrBuf bytes.Buffer
			lastCharIsQuestionMark := false
			for reader.Len() > 0 {
				ch, _, _ := reader.ReadRune()
				if ch == '?' {
					lastCharIsQuestionMark = true
					break
				}
				seekStrBuf.WriteRune(ch)
			}
			seekStr := seekStrBuf.String()
			found := false
			for i := history_count - 2; i >= 0; i-- {
				if strings.Contains(conio.DefaultEditor.Histories[i].Line, seekStr) {
					buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
					isReplaced = true
					found = true
					break
				}
			}
			if !found {
				buffer.WriteRune('?')
				buffer.WriteString(seekStr)
				if lastCharIsQuestionMark {
					buffer.WriteRune('?')
				}
			}
			continue
		}
		// !str
		var seekStrBuf bytes.Buffer
		seekStrBuf.WriteRune(ch)
		for reader.Len() > 0 {
			ch, _, _ := reader.ReadRune()
			if ch == ' ' {
				reader.UnreadRune()
				break
			}
			seekStrBuf.WriteRune(ch)
		}
		seekStr := seekStrBuf.String()
		found := false
		for i := history_count - 2; i >= 0; i-- {
			if strings.HasPrefix(conio.DefaultEditor.Histories[i].Line, seekStr) {
				buffer.WriteString(conio.DefaultEditor.Histories[i].Line)
				isReplaced = true
				found = true
				break
			}
		}
		if !found {
			buffer.WriteRune(mark)
			buffer.WriteRune(ch)
		}
	}
	result := conio.NewHistoryLine(buffer.String())
	if isReplaced {
		if history_count > 0 {
			conio.DefaultEditor.Histories[history_count-1] = result
		} else {
			conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, result)
		}
	}
	return result.Line, isReplaced
}

func insertHistory(buffer *bytes.Buffer, reader *strings.Reader, historyNo int) {
	history1 := conio.DefaultEditor.Histories[historyNo]
	ch, siz, _ := reader.ReadRune()
	if siz > 0 && ch == '^' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(1))
		}
	} else if siz > 0 && ch == '$' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(history1.At(-1))
		}
	} else if siz > 0 && ch == '*' {
		if len(history1.Word) >= 2 {
			buffer.WriteString(strings.Join(history1.Word[1:], " "))
		}
	} else if siz > 0 && ch == ':' {
		n, count := atoi_(reader)
		if count <= 0 {
			buffer.WriteRune(':')
		} else if n < len(history1.Word) {
			buffer.WriteString(history1.Word[n])
		}
	} else {
		if siz > 0 {
			reader.UnreadRune()
		}
		buffer.WriteString(history1.Line)
	}
}

func CmdHistory(cmd *interpreter.Interpreter) (interpreter.ErrorLevel, error) {
	var num int
	if len(cmd.Args) >= 2 {
		num64, err := strconv.ParseInt(cmd.Args[1], 0, 32)
		if err != nil {
			switch err.(type) {
			case *strconv.NumError:
				return interpreter.NOERROR, fmt.Errorf(
					"history: %s not a number", cmd.Args[1])
			default:
				return interpreter.NOERROR, err
			}
		}
		num = int(num64)
		if num < 0 {
			num = -num
		}
	} else {
		num = 10
	}
	var start int
	if conio.DefaultEditor.HistoryLen() > num {
		start = conio.DefaultEditor.HistoryLen() - num
	} else {
		start = 0
	}
	for i, s := range conio.DefaultEditor.Histories[start:] {
		fmt.Fprintf(cmd.Stdout, "%3d : %-s\n", start+i, s.Line)
	}
	return interpreter.NOERROR, nil
}

const max_histories = 2000

func Save(path string) error {
	conio.DefaultEditor.ShrinkHistory()
	start := 0
	if conio.DefaultEditor.HistoryLen() > max_histories {
		start = conio.DefaultEditor.HistoryLen() - max_histories
	}
	fd, err := os.Create(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	for i := start; i < len(conio.DefaultEditor.Histories); i++ {
		fmt.Fprintln(fd, conio.DefaultEditor.Histories[i].Line)
	}
	return nil
}

func Load(path string) error {
	fd, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fd.Close()
	sc := bufio.NewScanner(fd)
	for sc.Scan() {
		conio.DefaultEditor.Histories = append(conio.DefaultEditor.Histories, conio.NewHistoryLine(sc.Text()))
	}
	conio.DefaultEditor.ShrinkHistory()
	return nil
}
<|endoftext|>


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

package cluster

import (
	"errors"
	"fmt"
	"net"
	"reflect"
	"sync/atomic"
	"time"

	"github.com/lonng/nano/internal/codec"
	"github.com/lonng/nano/internal/env"
	"github.com/lonng/nano/internal/log"
	"github.com/lonng/nano/internal/message"
	"github.com/lonng/nano/internal/packet"
	"github.com/lonng/nano/pipeline"
	"github.com/lonng/nano/scheduler"
	"github.com/lonng/nano/session"
)

const (
	agentWriteBacklog = 16
)

var (
	// ErrBrokenPipe represents the low-level connection has broken.
	ErrBrokenPipe = errors.New("broken low-level pipe")
	// ErrBufferExceed indicates that the current session buffer is full and
	// can not receive more data.
	ErrBufferExceed = errors.New("session send buffer exceed")
)

type (
	// Agent corresponding a user, used for store raw conn information
	agent struct {
		// regular agent member
		session  *session.Session    // session
		conn     net.Conn            // low-level conn fd
		lastMid  uint64              // last message id
		state    int32               // current agent state
		chDie    chan struct{}       // wait for close
		chSend   chan pendingMessage // push message queue
		lastAt   int64               // last heartbeat unix time stamp
		decoder  *codec.Decoder      // binary decoder
		pipeline pipeline.Pipeline

		rpcHandler rpcHandler
		srv        reflect.Value // cached session reflect.Value
	}

	pendingMessage struct {
		typ     message.Type // message type
		route   string       // message route(push)
		mid     uint64       // response message id(response)
		payload interface{}  // payload
	}
)

// Create new agent instance
func newAgent(conn net.Conn, pipeline pipeline.Pipeline, rpcHandler rpcHandler) *agent {
	a := &agent{
		conn:       conn,
		state:      statusStart,
		chDie:      make(chan struct{}),
		lastAt:     time.Now().Unix(),
		chSend:     make(chan pendingMessage, agentWriteBacklog),
		decoder:    codec.NewDecoder(),
		pipeline:   pipeline,
		rpcHandler: rpcHandler,
	}

	// binding session
	s := session.New(a)
	a.session = s
	a.srv = reflect.ValueOf(s)

	return a
}

func (a *agent) send(m pendingMessage) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = ErrBrokenPipe
		}
	}()
	a.chSend <- m
	return
}

// LastMid implements the session.NetworkEntity interface
func (a *agent) LastMid() uint64 {
	return a.lastMid
}

// Push, implementation for session.NetworkEntity interface
func (a *agent) Push(route string, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	if len(a.chSend) >= agentWriteBacklog {
		return ErrBufferExceed
	}

	if env.Debug {
		switch d := v.(type) {
		case []byte:
			log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%dbytes",
				a.session.ID(), a.session.UID(), route, len(d)))
		default:
			log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%+v",
				a.session.ID(), a.session.UID(), route, v))
		}
	}

	return a.send(pendingMessage{typ: message.Push, route: route, payload: v})
}

// RPC, implementation for session.NetworkEntity interface
func (a *agent) RPC(route string, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	// TODO: buffer
	data, err := message.Serialize(v)
	if err != nil {
		return err
	}
	msg := &message.Message{
		Type:  message.Notify,
		Route: route,
		Data:  data,
	}
	a.rpcHandler(a.session, msg, true)
	return nil
}

// Response, implementation for session.NetworkEntity interface
// Response message to session
func (a *agent) Response(v interface{}) error {
	return a.ResponseMid(a.lastMid, v)
}

// ResponseMid, implementation for session.NetworkEntity interface
// Response message to session
func (a *agent) ResponseMid(mid uint64, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	if mid <= 0 {
		return ErrSessionOnNotify
	}

	if len(a.chSend) >= agentWriteBacklog {
		return ErrBufferExceed
	}

	if env.Debug {
		switch d := v.(type) {
		case []byte:
			log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%dbytes",
				a.session.ID(), a.session.UID(), mid, len(d)))
		default:
			log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%+v",
				a.session.ID(), a.session.UID(), mid, v))
		}
	}

	return a.send(pendingMessage{typ: message.Response, mid: mid, payload: v})
}

// Close, implementation for session.NetworkEntity interface
// Close closes the agent, clean inner state and close low-level connection.
// Any blocked Read or Write operations will be unblocked and return errors.
func (a *agent) Close() error {
	if a.status() == statusClosed {
		return ErrCloseClosedSession
	}
	a.setStatus(statusClosed)

	if env.Debug {
		log.Println(fmt.Sprintf("Session closed, ID=%d, UID=%d, IP=%s",
			a.session.ID(), a.session.UID(), a.conn.RemoteAddr()))
	}

	// prevent closing closed channel
	select {
	case <-a.chDie:
		// expect
	default:
		close(a.chDie)
		scheduler.PushTask(func() { session.Lifetime.Close(a.session) })
	}

	return a.conn.Close()
}

// RemoteAddr, implementation for session.NetworkEntity interface
// returns the remote network address.
func (a *agent) RemoteAddr() net.Addr {
	return a.conn.RemoteAddr()
}

// String, implementation for Stringer interface
func (a *agent) String() string {
	return fmt.Sprintf("Remote=%s, LastTime=%d", a.conn.RemoteAddr().String(), atomic.LoadInt64(&a.lastAt))
}

func (a *agent) status() int32 {
	return atomic.LoadInt32(&a.state)
}

func (a *agent) setStatus(state int32) {
	atomic.StoreInt32(&a.state, state)
}

func (a *agent) write() {
	ticker := time.NewTicker(env.Heartbeat)
	chWrite := make(chan []byte, agentWriteBacklog)
	// clean func
	defer func() {
		ticker.Stop()
		close(a.chSend)
		close(chWrite)
		a.Close()
		if env.Debug {
			log.Println(fmt.Sprintf("Session write goroutine exit, SessionID=%d, UID=%d", a.session.ID(), a.session.UID()))
		}
	}()

	for {
		select {
		case <-ticker.C:
			deadline := time.Now().Add(-2 * env.heartbeat).Unix()
			if atomic.LoadInt64(&a.lastAt) < deadline {
				log.Println(fmt.Sprintf("Session heartbeat timeout, LastTime=%d, Deadline=%d", atomic.LoadInt64(&a.lastAt), deadline))
				return
			}
			chWrite <- hbd

		case data := <-chWrite:
			// close agent while low-level conn broken
			if _, err := a.conn.Write(data); err != nil {
				log.Println(err.Error())
				return
			}

		case data := <-a.chSend:
			payload, err := message.Serialize(data.payload)
			if err != nil {
				switch data.typ {
				case message.Push:
					log.Println(fmt.Sprintf("Push: %s error: %s", data.route, err.Error()))
				case message.Response:
					log.Println(fmt.Sprintf("Response message(id: %d) error: %s", data.mid, err.Error()))
				default:
					// expect
				}
				break
			}

			// construct message and encode
			m := &message.Message{
				Type:  data.typ,
				Data:  payload,
				Route: data.route,
				ID:    data.mid,
			}
			if pipe := a.pipeline; pipe != nil {
				err := pipe.Outbound().Process(a.session, m)
				if err != nil {
					log.Println("broken pipeline", err.Error())
					break
				}
			}

			em, err := m.Encode()
			if err != nil {
				log.Println(err.Error())
				break
			}

			// packet encode
			p, err := codec.Encode(packet.Data, em)
			if err != nil {
				log.Println(err)
				break
			}
			chWrite <- p

		case <-a.chDie: // agent closed signal
			return

		case <-env.Die: // application quit
			return
		}
	}
}
<commit_msg>cluster: fix conflict while merging upstream<commit_after>// Copyright (c) nano Authors. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package cluster

import (
	"errors"
	"fmt"
	"net"
	"reflect"
	"sync/atomic"
	"time"

	"github.com/lonng/nano/internal/codec"
	"github.com/lonng/nano/internal/env"
	"github.com/lonng/nano/internal/log"
	"github.com/lonng/nano/internal/message"
	"github.com/lonng/nano/internal/packet"
	"github.com/lonng/nano/pipeline"
	"github.com/lonng/nano/scheduler"
	"github.com/lonng/nano/session"
)

const (
	agentWriteBacklog = 16
)

var (
	// ErrBrokenPipe represents the low-level connection has broken.
	ErrBrokenPipe = errors.New("broken low-level pipe")
	// ErrBufferExceed indicates that the current session buffer is full and
	// can not receive more data.
	ErrBufferExceed = errors.New("session send buffer exceed")
)

type (
	// Agent corresponding a user, used for store raw conn information
	agent struct {
		// regular agent member
		session  *session.Session    // session
		conn     net.Conn            // low-level conn fd
		lastMid  uint64              // last message id
		state    int32               // current agent state
		chDie    chan struct{}       // wait for close
		chSend   chan pendingMessage // push message queue
		lastAt   int64               // last heartbeat unix time stamp
		decoder  *codec.Decoder      // binary decoder
		pipeline pipeline.Pipeline

		rpcHandler rpcHandler
		srv        reflect.Value // cached session reflect.Value
	}

	pendingMessage struct {
		typ     message.Type // message type
		route   string       // message route(push)
		mid     uint64       // response message id(response)
		payload interface{}  // payload
	}
)

// Create new agent instance
func newAgent(conn net.Conn, pipeline pipeline.Pipeline, rpcHandler rpcHandler) *agent {
	a := &agent{
		conn:       conn,
		state:      statusStart,
		chDie:      make(chan struct{}),
		lastAt:     time.Now().Unix(),
		chSend:     make(chan pendingMessage, agentWriteBacklog),
		decoder:    codec.NewDecoder(),
		pipeline:   pipeline,
		rpcHandler: rpcHandler,
	}

	// binding session
	s := session.New(a)
	a.session = s
	a.srv = reflect.ValueOf(s)

	return a
}

func (a *agent) send(m pendingMessage) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = ErrBrokenPipe
		}
	}()
	a.chSend <- m
	return
}

// LastMid implements the session.NetworkEntity interface
func (a *agent) LastMid() uint64 {
	return a.lastMid
}

// Push, implementation for session.NetworkEntity interface
func (a *agent) Push(route string, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	if len(a.chSend) >= agentWriteBacklog {
		return ErrBufferExceed
	}

	if env.Debug {
		switch d := v.(type) {
		case []byte:
			log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%dbytes",
				a.session.ID(), a.session.UID(), route, len(d)))
		default:
			log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%+v",
				a.session.ID(), a.session.UID(), route, v))
		}
	}

	return a.send(pendingMessage{typ: message.Push, route: route, payload: v})
}

// RPC, implementation for session.NetworkEntity interface
func (a *agent) RPC(route string, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	// TODO: buffer
	data, err := message.Serialize(v)
	if err != nil {
		return err
	}
	msg := &message.Message{
		Type:  message.Notify,
		Route: route,
		Data:  data,
	}
	a.rpcHandler(a.session, msg, true)
	return nil
}

// Response, implementation for session.NetworkEntity interface
// Response message to session
func (a *agent) Response(v interface{}) error {
	return a.ResponseMid(a.lastMid, v)
}

// ResponseMid, implementation for session.NetworkEntity interface
// Response message to session
func (a *agent) ResponseMid(mid uint64, v interface{}) error {
	if a.status() == statusClosed {
		return ErrBrokenPipe
	}

	if mid <= 0 {
		return ErrSessionOnNotify
	}

	if len(a.chSend) >= agentWriteBacklog {
		return ErrBufferExceed
	}

	if env.Debug {
		switch d := v.(type) {
		case []byte:
			log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%dbytes",
				a.session.ID(), a.session.UID(), mid, len(d)))
		default:
			log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%+v",
				a.session.ID(), a.session.UID(), mid, v))
		}
	}

	return a.send(pendingMessage{typ: message.Response, mid: mid, payload: v})
}

// Close, implementation for session.NetworkEntity interface
// Close closes the agent, clean inner state and close low-level connection.
// Any blocked Read or Write operations will be unblocked and return errors.
func (a *agent) Close() error {
	if a.status() == statusClosed {
		return ErrCloseClosedSession
	}
	a.setStatus(statusClosed)

	if env.Debug {
		log.Println(fmt.Sprintf("Session closed, ID=%d, UID=%d, IP=%s",
			a.session.ID(), a.session.UID(), a.conn.RemoteAddr()))
	}

	// prevent closing closed channel
	select {
	case <-a.chDie:
		// expect
	default:
		close(a.chDie)
		scheduler.PushTask(func() { session.Lifetime.Close(a.session) })
	}

	return a.conn.Close()
}

// RemoteAddr, implementation for session.NetworkEntity interface
// returns the remote network address.
func (a *agent) RemoteAddr() net.Addr {
	return a.conn.RemoteAddr()
}

// String, implementation for Stringer interface
func (a *agent) String() string {
	return fmt.Sprintf("Remote=%s, LastTime=%d", a.conn.RemoteAddr().String(), atomic.LoadInt64(&a.lastAt))
}

func (a *agent) status() int32 {
	return atomic.LoadInt32(&a.state)
}

func (a *agent) setStatus(state int32) {
	atomic.StoreInt32(&a.state, state)
}

func (a *agent) write() {
	ticker := time.NewTicker(env.Heartbeat)
	chWrite := make(chan []byte, agentWriteBacklog)
	// clean func
	defer func() {
		ticker.Stop()
		close(a.chSend)
		close(chWrite)
		a.Close()
		if env.Debug {
			log.Println(fmt.Sprintf("Session write goroutine exit, SessionID=%d, UID=%d", a.session.ID(), a.session.UID()))
		}
	}()

	for {
		select {
		case <-ticker.C:
			deadline := time.Now().Add(-2 * env.Heartbeat).Unix()
			if atomic.LoadInt64(&a.lastAt) < deadline {
				log.Println(fmt.Sprintf("Session heartbeat timeout, LastTime=%d, Deadline=%d", atomic.LoadInt64(&a.lastAt), deadline))
				return
			}
			chWrite <- hbd

		case data := <-chWrite:
			// close agent while low-level conn broken
			if _, err := a.conn.Write(data); err != nil {
				log.Println(err.Error())
				return
			}

		case data := <-a.chSend:
			payload, err := message.Serialize(data.payload)
			if err != nil {
				switch data.typ {
				case message.Push:
					log.Println(fmt.Sprintf("Push: %s error: %s", data.route, err.Error()))
				case message.Response:
					log.Println(fmt.Sprintf("Response message(id: %d) error: %s", data.mid, err.Error()))
				default:
					// expect
				}
				break
			}

			// construct message and encode
			m := &message.Message{
				Type:  data.typ,
				Data:  payload,
				Route: data.route,
				ID:    data.mid,
			}
			if pipe := a.pipeline; pipe != nil {
				err := pipe.Outbound().Process(a.session, m)
				if err != nil {
					log.Println("broken pipeline", err.Error())
					break
				}
			}

			em, err := m.Encode()
			if err != nil {
				log.Println(err.Error())
				break
			}

			// packet encode
			p, err := codec.Encode(packet.Data, em)
			if err != nil {
				log.Println(err)
				break
			}
			chWrite <- p

		case <-a.chDie: // agent closed signal
			return

		case <-env.Die: // application quit
			return
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>Expose newQuoteInfo publicly (#95)<commit_after><|endoftext|>


<commit_before><commit_msg>nit: Simplify test condition in trace_test.go (#2448)<commit_after><|endoftext|>


<commit_before>package main

import (
	"github.com/siggy/bbox/bbox"
)

func main() {
	// beat changes
	//   keyboard => loop
	//   keyboard => render
	msgs := []chan bbox.Beats{
		make(chan bbox.Beats),
		make(chan bbox.Beats),
	}

	// ticks
	//   loop => render
	ticks := make(chan int)

	keyboard := bbox.InitKeyboard(writeonly(msgs))
	loop := bbox.InitLoop(msgs[0], ticks)
	render := bbox.InitRender(msgs[1], ticks)

	// keyboard broadcasts quit with close(msgs)
	go keyboard.Run()
	go render.Run()

	// loop.Run() blocks until close(msgs)
	loop.Run()
}

// can't pass a slice of non-direction channels as a slice of directional
// channels, so we have to convert the whole slice to directional first.
func writeonly(channels []chan bbox.Beats) []chan<- bbox.Beats {
	ret := make([]chan<- bbox.Beats, len(channels))
	for n, ch := range channels {
		ret[n] = ch
	}
	return ret
}
<commit_msg>move comment<commit_after>package main

import (
	"github.com/siggy/bbox/bbox"
)

func main() {
	// beat changes
	//   keyboard => loop
	//   keyboard => render
	msgs := []chan bbox.Beats{
		make(chan bbox.Beats),
		make(chan bbox.Beats),
	}

	// ticks
	//   loop => render
	ticks := make(chan int)

	// keyboard broadcasts quit with close(msgs)
	keyboard := bbox.InitKeyboard(writeonly(msgs))
	loop := bbox.InitLoop(msgs[0], ticks)
	render := bbox.InitRender(msgs[1], ticks)

	go keyboard.Run()
	go render.Run()

	// loop.Run() blocks until close(msgs)
	loop.Run()
}

// can't pass a slice of non-direction channels as a slice of directional
// channels, so we have to convert the whole slice to directional first.
func writeonly(channels []chan bbox.Beats) []chan<- bbox.Beats {
	ret := make([]chan<- bbox.Beats, len(channels))
	for n, ch := range channels {
		ret[n] = ch
	}
	return ret
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"os/exec"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

var (
	// Map of Guild id's to *Play channels, used for queuing and rate-limiting guilds
	queues map[string]chan *Play = make(map[string]chan *Play)
	songs  map[string]string     = make(map[string]string)

	// Owner
	OWNER string

	// Sound encoding settings
	BITRATE        = 128
	MAX_QUEUE_SIZE = 6

	//Shard (or -1)
	SHARDS []string = make([]string, 0)

	skip bool = false
)

// Play represents an individual use of the !airhorn command
type Play struct {
	GuildID   string
	ChannelID string
	UserID    string
	Sound     *Sound
	Title     string
}

// Sound represents a sound clip
type Sound struct {
	Name string

	// Channel used for the encoder routine
	encodeChan chan []int16

	// Buffer to store encoded PCM packets
	buffer [][]byte
}

func createSound(Name string) *Sound {
	return &Sound{
		Name:       Name,
		encodeChan: make(chan []int16, 10),
		buffer:     make([][]byte, 0),
	}
}

// Encode reads data from ffmpeg and encodes it using gopus
func (s *Sound) Encode() {
	encoder, err := gopus.NewEncoder(48000, 2, gopus.Audio)
	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	encoder.SetBitrate(BITRATE * 1000)
	encoder.SetApplication(gopus.Audio)

	for {
		pcm, ok := <-s.encodeChan
		if !ok {
			// if chan closed, exit
			return
		}

		// try encoding pcm frame with Opus
		opus, err := encoder.Encode(pcm, 960, 960*2*2)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		// Append the PCM frame to our buffer
		s.buffer = append(s.buffer, opus)
	}
}

// Load attempts to load and encode a sound file from disk
func (s *Sound) Load(path string) error {
	s.encodeChan = make(chan []int16, 10)
	defer close(s.encodeChan)
	go s.Encode()

	newpath := fmt.Sprintf("%sout.m4a", path) //overwriting file doesn't work to decrease volume
	if _, err := exec.Command("ffmpeg", "-y", "-i", path, "-af", "volume=0.1", newpath).Output(); err != nil {
		fmt.Println("Error decreasing volume:", err)
	}

	ffmpeg := exec.Command("ffmpeg", "-i", newpath, "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1")
	stdout, err := ffmpeg.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return err
	}

	err = ffmpeg.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return err
	}

	for {
		// read data from ffmpeg stdout
		InBuf := make([]int16, 960*2)
		err = binary.Read(stdout, binary.LittleEndian, &InBuf)

		// If this is the end of the file, just return
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return nil
		}

		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return err
		}

		// write pcm data to the encodeChan
		s.encodeChan <- InBuf
	}

	if err = os.Remove(newpath); err != nil {
		log.Warning("Failed removal of sound file with changed volume: ", err)
	}

	return nil
}

// Plays this sound over the specified VoiceConnection
func (s *Sound) Play(vc *discordgo.VoiceConnection) {
	vc.Speaking(true)
	defer vc.Speaking(false)

	// t := time.NewTicker(time.Duration(1) * time.Second)
	// go s.control(t)

	for _, buff := range s.buffer {
		vc.OpusSend <- buff
		if skip == true {
			skip = false
			break
		}
	}
}

// func (s *Sound) control(t *time.Ticker) {
//     for {
//         <- t.C
//
//     }
// }

// Attempts to find the current users voice channel inside a given guild
func getCurrentVoiceChannel(user *discordgo.User, guild *discordgo.Guild) *discordgo.Channel {
	for _, vs := range guild.VoiceStates {
		if vs.UserID == user.ID {
			channel, _ := discord.State.Channel(vs.ChannelID)
			return channel
		}
	}
	return nil
}

// Whether a guild id is in this shard
func shardContains(guildid string) bool {
	if len(SHARDS) != 0 {
		ok := false
		for _, shard := range SHARDS {
			if len(guildid) >= 5 && string(guildid[len(guildid)-5]) == shard {
				ok = true
				break
			}
		}
		return ok
	}
	return true
}

// Enqueues a play into the ratelimit/buffer guild queue
func enqueuePlay(user *discordgo.User, guild *discordgo.Guild, sound *Sound, title string) {
	// Grab the users voice channel
	channel := getCurrentVoiceChannel(user, guild)
	if channel == nil {
		log.WithFields(log.Fields{
			"user":  user.ID,
			"guild": guild.ID,
		}).Warning("Failed to find channel to play sound in")
		return
	}

	play := &Play{
		GuildID:   guild.ID,
		ChannelID: channel.ID,
		UserID:    user.ID,
		Sound:     sound,
		Title:     title,
	}

	// Check if we already have a connection to this guild
	_, exists := queues[guild.ID]

	if exists {
		if len(queues[guild.ID]) < MAX_QUEUE_SIZE {
			queues[guild.ID] <- play
		}
	} else {
		queues[guild.ID] = make(chan *Play, MAX_QUEUE_SIZE)
		playSound(play, nil)
	}
}

func next(GuildID string) {
	skip = true
}

// Play a sound
func playSound(play *Play, vc *discordgo.VoiceConnection) (err error) {
	log.WithFields(log.Fields{
		"play": play,
	}).Info("Playing sound")

	if vc == nil {
		vc, err = discord.ChannelVoiceJoin(play.GuildID, play.ChannelID, false, false)
		// vc.Receive = false
		if err != nil {
			log.WithFields(log.Fields{
				"error": err,
			}).Error("Failed to play sound")
			delete(queues, play.GuildID)
			return err
		}
	}
	// If we need to change channels, do that now
	if vc.ChannelID != play.ChannelID {
		vc.ChangeChannel(play.ChannelID, false, false)
		time.Sleep(time.Millisecond * 125)
	}
	// Sleep for a specified amount of time before playing the sound
	time.Sleep(time.Millisecond * 32)

	// Play the sound
	play.Sound.Play(vc)

	youtubeQueues[play.GuildID].remove(play.Title)

	// If there is another song in the queue, recurse and play that
	if len(queues[play.GuildID]) > 0 {
		play := <-queues[play.GuildID]
		playSound(play, vc)
		return nil
	}

	// If the queue is empty, delete it
	time.Sleep(time.Millisecond * time.Duration(500))
	delete(queues, play.GuildID)

	vc.Disconnect()
	return nil
}
<commit_msg>Fixed deletion bug<commit_after>package main

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"os/exec"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

var (
	// Map of Guild id's to *Play channels, used for queuing and rate-limiting guilds
	queues map[string]chan *Play = make(map[string]chan *Play)
	songs  map[string]string     = make(map[string]string)

	// Owner
	OWNER string

	// Sound encoding settings
	BITRATE        = 128
	MAX_QUEUE_SIZE = 6

	//Shard (or -1)
	SHARDS []string = make([]string, 0)

	skip bool = false
)

// Play represents an individual use of the !airhorn command
type Play struct {
	GuildID   string
	ChannelID string
	UserID    string
	Sound     *Sound
	Title     string
}

// Sound represents a sound clip
type Sound struct {
	Name string

	// Channel used for the encoder routine
	encodeChan chan []int16

	// Buffer to store encoded PCM packets
	buffer [][]byte
}

func createSound(Name string) *Sound {
	return &Sound{
		Name:       Name,
		encodeChan: make(chan []int16, 10),
		buffer:     make([][]byte, 0),
	}
}

// Encode reads data from ffmpeg and encodes it using gopus
func (s *Sound) Encode() {
	encoder, err := gopus.NewEncoder(48000, 2, gopus.Audio)
	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	encoder.SetBitrate(BITRATE * 1000)
	encoder.SetApplication(gopus.Audio)

	for {
		pcm, ok := <-s.encodeChan
		if !ok {
			// if chan closed, exit
			return
		}

		// try encoding pcm frame with Opus
		opus, err := encoder.Encode(pcm, 960, 960*2*2)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		// Append the PCM frame to our buffer
		s.buffer = append(s.buffer, opus)
	}
}

// Load attempts to load and encode a sound file from disk
func (s *Sound) Load(path string) error {
	s.encodeChan = make(chan []int16, 10)
	defer close(s.encodeChan)
	go s.Encode()

	newpath := fmt.Sprintf("%sout.m4a", path) //overwriting file doesn't work to decrease volume
	if _, err := exec.Command("ffmpeg", "-y", "-i", path, "-af", "volume=0.1", newpath).Output(); err != nil {
		fmt.Println("Error decreasing volume:", err)
	}

	ffmpeg := exec.Command("ffmpeg", "-i", newpath, "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1")
	stdout, err := ffmpeg.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return err
	}

	err = ffmpeg.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return err
	}

	for {
		// read data from ffmpeg stdout
		InBuf := make([]int16, 960*2)
		err = binary.Read(stdout, binary.LittleEndian, &InBuf)

		// If this is the end of the file, just return
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			if err = os.Remove(newpath); err != nil {
				log.Warning("Failed removal of sound file with changed volume: ", err)
			}
			return nil
		}

		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return err
		}

		// write pcm data to the encodeChan
		s.encodeChan <- InBuf
	}

	return nil
}

// Plays this sound over the specified VoiceConnection
func (s *Sound) Play(vc *discordgo.VoiceConnection) {
	vc.Speaking(true)
	defer vc.Speaking(false)

	// t := time.NewTicker(time.Duration(1) * time.Second)
	// go s.control(t)

	for _, buff := range s.buffer {
		vc.OpusSend <- buff
		if skip == true {
			skip = false
			break
		}
	}
}

// func (s *Sound) control(t *time.Ticker) {
//     for {
//         <- t.C
//
//     }
// }

// Attempts to find the current users voice channel inside a given guild
func getCurrentVoiceChannel(user *discordgo.User, guild *discordgo.Guild) *discordgo.Channel {
	for _, vs := range guild.VoiceStates {
		if vs.UserID == user.ID {
			channel, _ := discord.State.Channel(vs.ChannelID)
			return channel
		}
	}
	return nil
}

// Whether a guild id is in this shard
func shardContains(guildid string) bool {
	if len(SHARDS) != 0 {
		ok := false
		for _, shard := range SHARDS {
			if len(guildid) >= 5 && string(guildid[len(guildid)-5]) == shard {
				ok = true
				break
			}
		}
		return ok
	}
	return true
}

// Enqueues a play into the ratelimit/buffer guild queue
func enqueuePlay(user *discordgo.User, guild *discordgo.Guild, sound *Sound, title string) {
	// Grab the users voice channel
	channel := getCurrentVoiceChannel(user, guild)
	if channel == nil {
		log.WithFields(log.Fields{
			"user":  user.ID,
			"guild": guild.ID,
		}).Warning("Failed to find channel to play sound in")
		return
	}

	play := &Play{
		GuildID:   guild.ID,
		ChannelID: channel.ID,
		UserID:    user.ID,
		Sound:     sound,
		Title:     title,
	}

	// Check if we already have a connection to this guild
	_, exists := queues[guild.ID]

	if exists {
		if len(queues[guild.ID]) < MAX_QUEUE_SIZE {
			queues[guild.ID] <- play
		}
	} else {
		queues[guild.ID] = make(chan *Play, MAX_QUEUE_SIZE)
		playSound(play, nil)
	}
}

func next(GuildID string) {
	skip = true
}

// Play a sound
func playSound(play *Play, vc *discordgo.VoiceConnection) (err error) {
	log.WithFields(log.Fields{
		"play": play,
	}).Info("Playing sound")

	if vc == nil {
		vc, err = discord.ChannelVoiceJoin(play.GuildID, play.ChannelID, false, false)
		// vc.Receive = false
		if err != nil {
			log.WithFields(log.Fields{
				"error": err,
			}).Error("Failed to play sound")
			delete(queues, play.GuildID)
			return err
		}
	}
	// If we need to change channels, do that now
	if vc.ChannelID != play.ChannelID {
		vc.ChangeChannel(play.ChannelID, false, false)
		time.Sleep(time.Millisecond * 125)
	}
	// Sleep for a specified amount of time before playing the sound
	time.Sleep(time.Millisecond * 32)

	// Play the sound
	play.Sound.Play(vc)

	youtubeQueues[play.GuildID].remove(play.Title)

	// If there is another song in the queue, recurse and play that
	if len(queues[play.GuildID]) > 0 {
		play := <-queues[play.GuildID]
		playSound(play, vc)
		return nil
	}

	// If the queue is empty, delete it
	time.Sleep(time.Millisecond * time.Duration(500))
	delete(queues, play.GuildID)

	vc.Disconnect()
	return nil
}
<|endoftext|>


<commit_before>/*
 * Minio Client (C) 2015 Minio, Inc.
 *
 * 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 cmd

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/mc/pkg/probe"
)

// diff specific flags.
var (
	diffFlags = []cli.Flag{}
)

// Compute differences between two files or folders.
var diffCmd = cli.Command{
	Name:   "diff",
	Usage:  "List objects with size difference or missing between two folders or buckets.",
	Action: mainDiff,
	Before: setGlobalsFromContext,
	Flags:  append(diffFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] FIRST SECOND

FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}
DESCRIPTION:
  Diff only lists missing objects or objects with size differences. It *DOES NOT* compare contents.
  i.e. objects of same name and size, but differing in content are not noticed.

  Differences between source and destination are represented by mark notations with following meaning:
    > - indicates file should be copied.
    < - indicates file should be deleted.
    ! - indicates file differs in size or type.

EXAMPLES:
  1. Compare a local folder with a folder on Amazon S3 cloud storage.
     $ {{.HelpName}} ~/Photos s3/MyBucket/Photos

  2. Compare two different folders on a local filesystem.
     $ {{.HelpName}} ~/Photos /Media/Backup/Photos

`,
}

// diffMessage json container for diff messages
type diffMessage struct {
	Status        string       `json:"status"`
	FirstURL      string       `json:"first"`
	SecondURL     string       `json:"second"`
	Diff          differType   `json:"diff"`
	Error         *probe.Error `json:"error,omitempty"`
	firstContent  *clientContent
	secondContent *clientContent
}

// String colorized diff message
func (d diffMessage) String() string {
	msg := ""
	switch d.Diff {
	case differInFirst:
		msg = console.Colorize("DiffOnlyInFirst", "< "+d.FirstURL)
	case differInSecond:
		msg = console.Colorize("DiffOnlyInSecond", "> "+d.SecondURL)
	case differInType:
		msg = console.Colorize("DiffType", "! "+d.SecondURL)
	case differInSize:
		msg = console.Colorize("DiffSize", "! "+d.SecondURL)
	case differInTime:
		msg = console.Colorize("DiffTime", "! "+d.SecondURL)
	default:
		fatalIf(errDummy().Trace(d.FirstURL, d.SecondURL),
			"Unhandled difference between `"+d.FirstURL+"` and `"+d.SecondURL+"`.")
	}
	return msg

}

// JSON jsonified diff message
func (d diffMessage) JSON() string {
	d.Status = "success"
	diffJSONBytes, e := json.Marshal(d)
	fatalIf(probe.NewError(e),
		"Unable to marshal diff message `"+d.FirstURL+"`, `"+d.SecondURL+"` and `"+string(d.Diff)+"`.")
	return string(diffJSONBytes)
}

func checkDiffSyntax(ctx *cli.Context) {
	if len(ctx.Args()) != 2 {
		cli.ShowCommandHelpAndExit(ctx, "diff", 1) // last argument is exit code
	}
	for _, arg := range ctx.Args() {
		if strings.TrimSpace(arg) == "" {
			fatalIf(errInvalidArgument().Trace(ctx.Args()...), "Unable to validate empty argument.")
		}
	}
	URLs := ctx.Args()
	firstURL := URLs[0]
	secondURL := URLs[1]

	// Diff only works between two directories, verify them below.

	// Verify if firstURL is accessible.
	_, firstContent, err := url2Stat(firstURL)
	if err != nil {
		fatalIf(err.Trace(firstURL), fmt.Sprintf("Unable to stat '%s'.", firstURL))
	}

	// Verify if its a directory.
	if !firstContent.Type.IsDir() {
		fatalIf(errInvalidArgument().Trace(firstURL), fmt.Sprintf("`%s` is not a folder.", firstURL))
	}

	// Verify if secondURL is accessible.
	_, secondContent, err := url2Stat(secondURL)
	if err != nil {
		fatalIf(err.Trace(secondURL), fmt.Sprintf("Unable to stat '%s'.", secondURL))
	}

	// Verify if its a directory.
	if !secondContent.Type.IsDir() {
		fatalIf(errInvalidArgument().Trace(secondURL), fmt.Sprintf("`%s` is not a folder.", secondURL))
	}
}

// doDiffMain runs the diff.
func doDiffMain(firstURL, secondURL string) error {
	// Source and targets are always directories
	sourceSeparator := string(newClientURL(firstURL).Separator)
	if !strings.HasSuffix(firstURL, sourceSeparator) {
		firstURL = firstURL + sourceSeparator
	}
	targetSeparator := string(newClientURL(secondURL).Separator)
	if !strings.HasSuffix(secondURL, targetSeparator) {
		secondURL = secondURL + targetSeparator
	}

	// Expand aliased urls.
	firstAlias, firstURL, _ := mustExpandAlias(firstURL)
	secondAlias, secondURL, _ := mustExpandAlias(secondURL)

	firstClient, err := newClientFromAlias(firstAlias, firstURL)
	if err != nil {
		fatalIf(err.Trace(firstAlias, firstURL, secondAlias, secondURL),
			fmt.Sprintf("Failed to diff '%s' and '%s'", firstURL, secondURL))
	}

	secondClient, err := newClientFromAlias(secondAlias, secondURL)
	if err != nil {
		fatalIf(err.Trace(firstAlias, firstURL, secondAlias, secondURL),
			fmt.Sprintf("Failed to diff '%s' and '%s'", firstURL, secondURL))
	}

	// Diff first and second urls.
	for diffMsg := range objectDifference(firstClient, secondClient, firstURL, secondURL) {
		if diffMsg.Error != nil {
			errorIf(diffMsg.Error, "Unable to calculate objects difference.")
			// Ignore error and proceed to next object.
			continue
		}
		printMsg(diffMsg)
	}

	return nil
}

// mainDiff main for 'diff'.
func mainDiff(ctx *cli.Context) error {

	// check 'diff' cli arguments.
	checkDiffSyntax(ctx)

	// Additional command specific theme customization.
	console.SetColor("DiffMessage", color.New(color.FgGreen, color.Bold))
	console.SetColor("DiffOnlyInFirst", color.New(color.FgRed))
	console.SetColor("DiffOnlyInSecond", color.New(color.FgGreen))
	console.SetColor("DiffType", color.New(color.FgMagenta))
	console.SetColor("DiffSize", color.New(color.FgYellow, color.Bold))
	console.SetColor("DiffTime", color.New(color.FgYellow, color.Bold))

	URLs := ctx.Args()
	firstURL := URLs.Get(0)
	secondURL := URLs.Get(1)

	return doDiffMain(firstURL, secondURL)
}
<commit_msg>Revises 'mc diff' command help menu (#2383)<commit_after>/*
 * Minio Client (C) 2015 Minio, Inc.
 *
 * 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 cmd

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/mc/pkg/probe"
)

// diff specific flags.
var (
	diffFlags = []cli.Flag{}
)

// Compute differences between two files or folders.
var diffCmd = cli.Command{
	Name:   "diff",
	Usage:  "List differences in object name, size, and date between folders.",
	Action: mainDiff,
	Before: setGlobalsFromContext,
	Flags:  append(diffFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] FIRST SECOND

FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}
DESCRIPTION:
  Diff only calculates differences in object name, size and time.
  It *DOES NOT* compare objects' contents.

LEGEND:
    > - object is only in source.
    < - object is only in destination.
    ! - newer object is in source.

EXAMPLES:
  1. Compare a local folder with a folder on Amazon S3 cloud storage.
     $ {{.HelpName}} ~/Photos s3/mybucket/Photos

  2. Compare two folders on a local filesystem.
     $ {{.HelpName}} ~/Photos /Media/Backup/Photos
`,
}

// diffMessage json container for diff messages
type diffMessage struct {
	Status        string       `json:"status"`
	FirstURL      string       `json:"first"`
	SecondURL     string       `json:"second"`
	Diff          differType   `json:"diff"`
	Error         *probe.Error `json:"error,omitempty"`
	firstContent  *clientContent
	secondContent *clientContent
}

// String colorized diff message
func (d diffMessage) String() string {
	msg := ""
	switch d.Diff {
	case differInFirst:
		msg = console.Colorize("DiffOnlyInFirst", "< "+d.FirstURL)
	case differInSecond:
		msg = console.Colorize("DiffOnlyInSecond", "> "+d.SecondURL)
	case differInType:
		msg = console.Colorize("DiffType", "! "+d.SecondURL)
	case differInSize:
		msg = console.Colorize("DiffSize", "! "+d.SecondURL)
	case differInTime:
		msg = console.Colorize("DiffTime", "! "+d.SecondURL)
	default:
		fatalIf(errDummy().Trace(d.FirstURL, d.SecondURL),
			"Unhandled difference between `"+d.FirstURL+"` and `"+d.SecondURL+"`.")
	}
	return msg

}

// JSON jsonified diff message
func (d diffMessage) JSON() string {
	d.Status = "success"
	diffJSONBytes, e := json.Marshal(d)
	fatalIf(probe.NewError(e),
		"Unable to marshal diff message `"+d.FirstURL+"`, `"+d.SecondURL+"` and `"+string(d.Diff)+"`.")
	return string(diffJSONBytes)
}

func checkDiffSyntax(ctx *cli.Context) {
	if len(ctx.Args()) != 2 {
		cli.ShowCommandHelpAndExit(ctx, "diff", 1) // last argument is exit code
	}
	for _, arg := range ctx.Args() {
		if strings.TrimSpace(arg) == "" {
			fatalIf(errInvalidArgument().Trace(ctx.Args()...), "Unable to validate empty argument.")
		}
	}
	URLs := ctx.Args()
	firstURL := URLs[0]
	secondURL := URLs[1]

	// Diff only works between two directories, verify them below.

	// Verify if firstURL is accessible.
	_, firstContent, err := url2Stat(firstURL)
	if err != nil {
		fatalIf(err.Trace(firstURL), fmt.Sprintf("Unable to stat '%s'.", firstURL))
	}

	// Verify if its a directory.
	if !firstContent.Type.IsDir() {
		fatalIf(errInvalidArgument().Trace(firstURL), fmt.Sprintf("`%s` is not a folder.", firstURL))
	}

	// Verify if secondURL is accessible.
	_, secondContent, err := url2Stat(secondURL)
	if err != nil {
		fatalIf(err.Trace(secondURL), fmt.Sprintf("Unable to stat '%s'.", secondURL))
	}

	// Verify if its a directory.
	if !secondContent.Type.IsDir() {
		fatalIf(errInvalidArgument().Trace(secondURL), fmt.Sprintf("`%s` is not a folder.", secondURL))
	}
}

// doDiffMain runs the diff.
func doDiffMain(firstURL, secondURL string) error {
	// Source and targets are always directories
	sourceSeparator := string(newClientURL(firstURL).Separator)
	if !strings.HasSuffix(firstURL, sourceSeparator) {
		firstURL = firstURL + sourceSeparator
	}
	targetSeparator := string(newClientURL(secondURL).Separator)
	if !strings.HasSuffix(secondURL, targetSeparator) {
		secondURL = secondURL + targetSeparator
	}

	// Expand aliased urls.
	firstAlias, firstURL, _ := mustExpandAlias(firstURL)
	secondAlias, secondURL, _ := mustExpandAlias(secondURL)

	firstClient, err := newClientFromAlias(firstAlias, firstURL)
	if err != nil {
		fatalIf(err.Trace(firstAlias, firstURL, secondAlias, secondURL),
			fmt.Sprintf("Failed to diff '%s' and '%s'", firstURL, secondURL))
	}

	secondClient, err := newClientFromAlias(secondAlias, secondURL)
	if err != nil {
		fatalIf(err.Trace(firstAlias, firstURL, secondAlias, secondURL),
			fmt.Sprintf("Failed to diff '%s' and '%s'", firstURL, secondURL))
	}

	// Diff first and second urls.
	for diffMsg := range objectDifference(firstClient, secondClient, firstURL, secondURL) {
		if diffMsg.Error != nil {
			errorIf(diffMsg.Error, "Unable to calculate objects difference.")
			// Ignore error and proceed to next object.
			continue
		}
		printMsg(diffMsg)
	}

	return nil
}

// mainDiff main for 'diff'.
func mainDiff(ctx *cli.Context) error {

	// check 'diff' cli arguments.
	checkDiffSyntax(ctx)

	// Additional command specific theme customization.
	console.SetColor("DiffMessage", color.New(color.FgGreen, color.Bold))
	console.SetColor("DiffOnlyInFirst", color.New(color.FgRed))
	console.SetColor("DiffOnlyInSecond", color.New(color.FgGreen))
	console.SetColor("DiffType", color.New(color.FgMagenta))
	console.SetColor("DiffSize", color.New(color.FgYellow, color.Bold))
	console.SetColor("DiffTime", color.New(color.FgYellow, color.Bold))

	URLs := ctx.Args()
	firstURL := URLs.Get(0)
	secondURL := URLs.Get(1)

	return doDiffMain(firstURL, secondURL)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/aristanetworks/goarista/gnmi"

	"github.com/aristanetworks/glog"
	pb "github.com/openconfig/gnmi/proto/gnmi"
)

// TODO: Make this more clear
var help = `Usage of gnmi:
gnmi -addr ADDRESS:PORT [options...]
  capabilities
  get PATH+
  subscribe PATH+
  ((update|replace PATH JSON)|(delete PATH))+
`

func exitWithError(s string) {
	flag.Usage()
	fmt.Fprintln(os.Stderr, s)
	os.Exit(1)
}

func main() {
	cfg := &gnmi.Config{}
	flag.StringVar(&cfg.Addr, "addr", "", "Address of gNMI gRPC server")
	flag.StringVar(&cfg.CAFile, "cafile", "", "Path to server TLS certificate file")
	flag.StringVar(&cfg.CertFile, "certfile", "", "Path to client TLS certificate file")
	flag.StringVar(&cfg.KeyFile, "keyfile", "", "Path to client TLS private key file")
	flag.StringVar(&cfg.Password, "password", "", "Password to authenticate with")
	flag.StringVar(&cfg.Username, "username", "", "Username to authenticate with")
	flag.BoolVar(&cfg.TLS, "tls", false, "Enable TLS")

	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, help)
		flag.PrintDefaults()
	}
	flag.Parse()
	if cfg.Addr == "" {
		exitWithError("error: address not specified")
	}

	args := flag.Args()

	ctx := gnmi.NewContext(context.Background(), cfg)
	client := gnmi.Dial(cfg)

	var setOps []*gnmi.Operation
	for i := 0; i < len(args); i++ {
		switch args[i] {
		case "capabilities":
			if len(setOps) != 0 {
				exitWithError("error: 'capabilities' not allowed after 'merge|replace|delete'")
			}
			err := gnmi.Capabilities(ctx, client)
			if err != nil {
				glog.Fatal(err)
			}
			return
		case "get":
			if len(setOps) != 0 {
				exitWithError("error: 'get' not allowed after 'merge|replace|delete'")
			}
			err := gnmi.Get(ctx, client, gnmi.SplitPaths(args[i+1:]))
			if err != nil {
				glog.Fatal(err)
			}
			return
		case "subscribe":
			if len(setOps) != 0 {
				exitWithError("error: 'subscribe' not allowed after 'merge|replace|delete'")
			}
			respChan := make(chan *pb.SubscribeResponse)
			errChan := make(chan error)
			defer close(respChan)
			defer close(errChan)
			go gnmi.Subscribe(ctx, client, gnmi.SplitPaths(args[i+1:]), respChan, errChan)
			for {
				select {
				case resp := <-respChan:
					if err := gnmi.LogSubscribeResponse(resp); err != nil {
						exitWithError(err.Error())
					}
				case err := <-errChan:
					exitWithError(err.Error())
				}
			}
		case "update", "replace", "delete":
			if len(args) == i+1 {
				exitWithError("error: missing path")
			}
			op := &gnmi.Operation{
				Type: args[i],
			}
			i++
			op.Path = gnmi.SplitPath(args[i])
			if op.Type != "delete" {
				if len(args) == i+1 {
					exitWithError("error: missing JSON")
				}
				i++
				op.Val = args[i]
			}
			setOps = append(setOps, op)
		default:
			exitWithError(fmt.Sprintf("error: unknown operation %q", args[i]))
		}
	}
	if len(setOps) == 0 {
		flag.Usage()
		os.Exit(1)
	}
	err := gnmi.Set(ctx, client, setOps)
	if err != nil {
		glog.Fatal(err)
	}

}
<commit_msg>cmd/gnmi: do not print usage for internal errors<commit_after>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/aristanetworks/goarista/gnmi"

	"github.com/aristanetworks/glog"
	pb "github.com/openconfig/gnmi/proto/gnmi"
)

// TODO: Make this more clear
var help = `Usage of gnmi:
gnmi -addr ADDRESS:PORT [options...]
  capabilities
  get PATH+
  subscribe PATH+
  ((update|replace PATH JSON)|(delete PATH))+
`

func usageAndExit(s string) {
	flag.Usage()
	if s != "" {
		fmt.Fprintln(os.Stderr, s)
	}
	os.Exit(1)
}

func main() {
	cfg := &gnmi.Config{}
	flag.StringVar(&cfg.Addr, "addr", "", "Address of gNMI gRPC server")
	flag.StringVar(&cfg.CAFile, "cafile", "", "Path to server TLS certificate file")
	flag.StringVar(&cfg.CertFile, "certfile", "", "Path to client TLS certificate file")
	flag.StringVar(&cfg.KeyFile, "keyfile", "", "Path to client TLS private key file")
	flag.StringVar(&cfg.Password, "password", "", "Password to authenticate with")
	flag.StringVar(&cfg.Username, "username", "", "Username to authenticate with")
	flag.BoolVar(&cfg.TLS, "tls", false, "Enable TLS")

	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, help)
		flag.PrintDefaults()
	}
	flag.Parse()
	if cfg.Addr == "" {
		usageAndExit("error: address not specified")
	}

	args := flag.Args()

	ctx := gnmi.NewContext(context.Background(), cfg)
	client := gnmi.Dial(cfg)

	var setOps []*gnmi.Operation
	for i := 0; i < len(args); i++ {
		switch args[i] {
		case "capabilities":
			if len(setOps) != 0 {
				usageAndExit("error: 'capabilities' not allowed after 'merge|replace|delete'")
			}
			err := gnmi.Capabilities(ctx, client)
			if err != nil {
				glog.Fatal(err)
			}
			return
		case "get":
			if len(setOps) != 0 {
				usageAndExit("error: 'get' not allowed after 'merge|replace|delete'")
			}
			err := gnmi.Get(ctx, client, gnmi.SplitPaths(args[i+1:]))
			if err != nil {
				glog.Fatal(err)
			}
			return
		case "subscribe":
			if len(setOps) != 0 {
				usageAndExit("error: 'subscribe' not allowed after 'merge|replace|delete'")
			}
			respChan := make(chan *pb.SubscribeResponse)
			errChan := make(chan error)
			defer close(respChan)
			defer close(errChan)
			go gnmi.Subscribe(ctx, client, gnmi.SplitPaths(args[i+1:]), respChan, errChan)
			for {
				select {
				case resp := <-respChan:
					if err := gnmi.LogSubscribeResponse(resp); err != nil {
						glog.Fatal(err)
					}
				case err := <-errChan:
					glog.Fatal(err)
				}
			}
		case "update", "replace", "delete":
			if len(args) == i+1 {
				usageAndExit("error: missing path")
			}
			op := &gnmi.Operation{
				Type: args[i],
			}
			i++
			op.Path = gnmi.SplitPath(args[i])
			if op.Type != "delete" {
				if len(args) == i+1 {
					usageAndExit("error: missing JSON")
				}
				i++
				op.Val = args[i]
			}
			setOps = append(setOps, op)
		default:
			usageAndExit(fmt.Sprintf("error: unknown operation %q", args[i]))
		}
	}
	if len(setOps) == 0 {
		usageAndExit("")
	}
	err := gnmi.Set(ctx, client, setOps)
	if err != nil {
		glog.Fatal(err)
	}

}
<|endoftext|>