<commit_before>package schema

import (
	"fmt"
	"strings"
	"unicode/utf8"
)

// TokenType enum
type TokenType uint8

// Lex items
const (
	TokenError             TokenType = iota // 0 Lexer Error
	TokenEOF                                // 1 End of File
	TokenComment                            // 2 Comment
	TokenMessage                            // 3 Message keyword
	TokenVersion                            // 4 Version keyword
	TokenValueType                          // 5 Value type ID (string, uint8)
	TokenRequired                           // 6 Required Keyword
	TokenOptional                           // 7 Optional Keyword
	TokenVersionNumber                      // 8 Version ID
	TokenOpenCurlyBracket                   // 9 Left {
	TokenCloseCurlyBracket                  // 10 Right }
	TokenOpenArrayBracket                   // 11 Open Array [
	TokenCloseArrayBracket                  // 12 Close Array ]
	TokenEquals                             // 13 Equals sign
	TokenIdentifier                         // 14 Message or Field Name
	TokenReference                          // 15 Message type reference
)

// Constant Punctuation and Keywords
const (
	openScope  = "{"
	closeScope = "}"
	openArray  = "["
	closeArray = "]"
	equals     = "="
	comment    = "//"
	dollarRef  = "$"
	message    = "type"
	version    = "version"
	required   = "required"
	optional   = "optional"
)

const eof = -1

// Types
var TypeNames = []string{"string",
	"uint8", "int8",
	"uint16", "int16",
	"uint32", "int32",
	"uint64", "int64",
	"float32", "float64", "timestamp",
	"tuple", "any",
}

// Token struct
type Token struct {
	Type  TokenType // Type, such as itemNumber
	Value string    // Value, such as "23.2"
}

// Used to print tokens
func (t Token) String() string {
	switch t.Type {
	case TokenEOF:
		return "EOF"
	case TokenError:
		return t.Value
	}
	if len(t.Value) > 10 {
		return fmt.Sprintf("%.25q...", t.Value)
	}
	return fmt.Sprintf("%q", t.Value)
}

// Handler is simpley a function which takes a single Token argument
type Handler func(Token)

// stateFn represents the state of the scanner
// as a function and returns the next state.
type stateFn func(*Lexer) stateFn

// NewLexer creates a new scanner from the input
func NewLexer(name, input string, h Handler) *Lexer {
	return &Lexer{
		Name:    name,
		input:   input + "\n",
		state:   lexText,
		handler: h,
		// tokens: make(chan Token, 2),
	}
}

// Lexer holds the state of the scanner
type Lexer struct {
	Name    string  // Used to error reports
	input   string  // the string being scanned
	Start   int     // start position of this item
	Pos     int     // current position in the input
	Width   int     // width of last rune read
	state   stateFn // next state function
	handler Handler // token handler
	// tokens chan Token // channel of scanned tokens
}

// Run lexes the input by executing state functions
// until the state is nil
func (l *Lexer) run() {
	for state := lexText; state != nil; {
		state = state(l)
	}
	// close(l.tokens) // no more tokens will be delivered
}

// emit passes an item pack to the client
func (l *Lexer) emit(t TokenType) {
	tok := Token{t, l.input[l.Start:l.Pos]}
	// fmt.Println("token: ", tok)
	l.handler(tok)
	// l.tokens <- Token{t, l.input[l.Start:l.Pos]}
	l.Start = l.Pos
}

func (l *Lexer) remaining() string {
	return l.input[l.Pos:]
}

func (l *Lexer) skipWhitespace() {
	l.acceptRun(" \t\r\n")
	l.ignore()
}

func (l *Lexer) next() (r rune) {
	if l.Pos >= len(l.input) {
		l.Width = 0
		return eof
	}
	r, l.Width = utf8.DecodeRuneInString(l.remaining())
	l.Pos += l.Width
	return
}

// ignore steps over the pending input before this point
func (l *Lexer) ignore() {
	l.Start = l.Pos
}

// backup steps back one rune
func (l *Lexer) backup() {
	l.Pos -= l.Width
}

// peek returns but does not consume the next rune in the input
func (l *Lexer) peek() (r rune) {
	r = l.next()
	l.backup()
	return
}

// func (l *Lexer) NextToken() Token {
// 	for {
// 		select {
// 		case item := <-l.tokens:
// 			return item
// 		default:
// 			l.state = l.state(l)
// 		}
// 	}
// 	panic("not reached")
// }

// accept consumes the next rune
// if it's in the valid set
func (l *Lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}

// consumes a run of runes from the valid set
func (l *Lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

// errorf returns an error token and terminates the scan
// by passing back a nil pointer that will be the next
// state thus terminating the lexer
func (l *Lexer) errorf(format string, args ...interface{}) stateFn {
	l.handler(Token{TokenError, fmt.Sprintf(format, args...)})
	// l.tokens <- Token{TokenError, fmt.Sprintf(format, args...)}
	return nil
}

// Main lexer loop
func lexText(l *Lexer) stateFn {
OUTER:
	for {
		l.skipWhitespace()

		if strings.HasPrefix(l.remaining(), comment) { // Start comment
			return lexComment // state function which lexes a comment
		} else if strings.HasPrefix(l.remaining(), message) { // Start message
			return lexMessage // state function which lexes a message
		} else if strings.HasPrefix(l.remaining(), version) { // Start version
			return lexVersion // state function which lexes a version
		} else if strings.HasPrefix(l.remaining(), required) { // Start required field
			return lexField // state function which lexes a field
		} else if strings.HasPrefix(l.remaining(), optional) { // Start optional field
			return lexField // state function which lexes a field
		} else if strings.HasPrefix(l.remaining(), closeScope) { // Close scope
			l.Pos += len(closeScope)
			l.emit(TokenCloseCurlyBracket)
		} else {
			switch r := l.next(); {

			case r == eof: // reached EOF?
				l.emit(TokenEOF)
				break OUTER
			default:
				l.errorf("unknown token")
			}
		}
	}

	// Stops the run loop
	return nil
}

// Lexes a comment line
func lexComment(l *Lexer) stateFn {
	l.skipWhitespace()

	// if strings.HasPrefix(l.remaining(), comment) {
	// skip comment //
	l.Pos += len(comment)

	// find next new line and add location to pos which
	// advances the scanner
	if index := strings.Index(l.remaining(), "\n"); index > 0 {
		l.Pos += index
	} else {
		l.Pos += len(l.remaining())
		// l.emit(TokenComment)
		// break
	}

	// emit the comment string
	l.emit(TokenComment)

	l.skipWhitespace()
	// }

	// continue on scanner
	return lexText
}

func lexMessage(l *Lexer) stateFn {
	// skip message keyword
	l.Pos += len(message)

	// emit keyword
	l.emit(TokenMessage)
	l.skipWhitespace()

	return lexIdentifier
}

func lexIdentifier(l *Lexer) stateFn {
	l.skipWhitespace()

	for {

		if strings.HasPrefix(l.remaining(), openScope) {

			l.emit(TokenIdentifier)
			return lexMessageBody
		}

		if l.next() == eof {
			return l.errorf("expected {")
		}

		// switch r := l.next(); {
		// case unicode.IsSpace(r):
		//  l.ignore()
		// case unicode.IsLetter(r) || unicode.IsDigit(r):
		//  insideId = true
		// case r == '.' || r == '-' || r == '_':
		// }
	}
}

// func lexIdentifier(l *Lexer) stateFn {

//  // find open bracket
//  if index := strings.IndexAny(l.input[l.Pos:], "{"); index > 0 {

//      // update pos without open bracket
//      l.Pos += (index - 1)

//      // emit identifier
//      l.emit(TokenIdentifier)

//      // lex message contents
//      return lexMessageBody
//  }
//  return l.errorf("missing message body")
// }

func lexMessageBody(l *Lexer) stateFn {
	l.Pos += len(openScope)
	l.emit(TokenOpenCurlyBracket)
	return lexText
}

func lexVersion(l *Lexer) stateFn {
	l.Pos += len(version)
	l.emit(TokenVersion)
	l.skipWhitespace()

	l.acceptRun("0123456789")
	l.emit(TokenVersionNumber)
	l.skipWhitespace()

	if strings.HasPrefix(l.remaining(), openScope) {
		l.Pos += len(openScope)
		l.emit(TokenOpenCurlyBracket)
	} else {
		return l.errorf("missing version body")
	}
	return lexText
}

func lexField(l *Lexer) stateFn {
	l.skipWhitespace()

	for strings.HasPrefix(l.remaining(), comment) {
		lexComment(l)
	}

	if strings.HasPrefix(l.remaining(), required) {
		l.Pos += len(required)
		l.emit(TokenRequired)
	} else if strings.HasPrefix(l.remaining(), optional) {
		l.Pos += len(optional)
		l.emit(TokenOptional)
	} else if strings.HasPrefix(l.remaining(), closeScope) {
		l.Pos += len(closeScope)
		l.emit(TokenCloseCurlyBracket)
		return lexText
	} else {
		return l.errorf("expected 'required' or 'optional'")
	}

	l.skipWhitespace()

	// lexIdentifier(l)
	for {
		if strings.HasPrefix(l.remaining(), equals) {
			l.emit(TokenIdentifier)
			break
		} else if l.next() == eof {
			return l.errorf("expected =")
		}
	}

	l.skipWhitespace()
	if l.accept("=") {
		l.emit(TokenEquals)
	} else {
		return l.errorf("expected '=' sign")
	}
	l.skipWhitespace()

	lexType(l)
	return lexField
}

func lexType(l *Lexer) stateFn {
	// l.skipWhitespace()
	if strings.HasPrefix(l.remaining(), openArray) {
		l.Pos += len(openArray)
		l.emit(TokenOpenArrayBracket)

		l.skipWhitespace()
		lexType(l)
		l.skipWhitespace()

		if strings.HasPrefix(l.remaining(), closeArray) {
			l.Pos += len(closeArray)
			l.emit(TokenCloseArrayBracket)
			return lexField
		}
		return l.errorf("expected ]")

	} else if strings.HasPrefix(l.remaining(), dollarRef) {
		l.emit(TokenReference)
		for {
			if strings.HasPrefix(l.remaining(), "\n") {
				l.emit(TokenIdentifier)
				break
			} else if l.next() == eof {
				return l.errorf("expected }")
			}
		}
		return lexField
	} else {
		for _, t := range TypeNames {
			if strings.HasPrefix(l.remaining(), t) {
				l.Pos += len(t)
				l.emit(TokenValueType)
				return lexField
			}
		}
	}
	return l.errorf("expected type name, reference or array type")
}
<commit_msg>Cleanup Lexer<commit_after>package schema

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// TokenType enum
type TokenType uint8

// Lex items
const (
	TokenError             TokenType = iota // 0 Lexer Error
	TokenEOF                                // 1 End of File
	TokenComment                            // 2 Comment
	TokenTypeDef                            // 3 Type keyword
	TokenVersion                            // 4 Version keyword
	TokenValueType                          // 5 Value type ID (string, uint8)
	TokenRequired                           // 6 Required Keyword
	TokenOptional                           // 7 Optional Keyword
	TokenVersionNumber                      // 8 Version ID
	TokenOpenCurlyBracket                   // 9 Left {
	TokenCloseCurlyBracket                  // 10 Right }
	TokenOpenArrayBracket                   // 11 Open Array [
	TokenCloseArrayBracket                  // 12 Close Array ]
	TokenEquals                             // 13 Equals sign
	TokenIdentifier                         // 14 Message or Field Name
	TokenReference                          // 15 Message type reference
	TokenComma                              // 16 Comma
	TokenPeriod                             // 17 Period
	TokenNamespace                          // 18 Namespace keyword
	TokenImport                             // 19 Import keyword
	TokenFrom                               // 20 From keyword
	TokenAs                                 // 21 As keyword
	TokenPackage                            // 22 Package keyword
	TokenPackageName                        // 23 Package name
)

// Constant Punctuation and Keywords
const (
	openScope  = "{"
	closeScope = "}"
	openArray  = "["
	closeArray = "]"
	equals     = "="
	comment    = "//"
	dollarRef  = "$"
	typeDef    = "type"
	version    = "version"
	required   = "required"
	optional   = "optional"
	period     = "."
	comma      = ","
	from       = "from"
	imp        = "import"
	as         = "as"
	pkg        = "package"
)

// eof represents the end of file/input
const eof = -1

// Reserved Types
var TypeNames = []string{"string", "byte",
	"uint8", "int8",
	"uint16", "int16",
	"uint32", "int32",
	"uint64", "int64",
	"float32", "float64", "timestamp",
	"tuple", "any",
}

// Token struct
type Token struct {
	Type  TokenType // Type, such as itemNumber
	Value string    // Value, such as "23.2"
}

// Used to print tokens
func (t Token) String() string {
	switch t.Type {
	case TokenEOF:
		return "EOF"
	case TokenError:
		return t.Value
	}
	if len(t.Value) > 10 {
		return fmt.Sprintf("%.25q...", t.Value)
	}
	return fmt.Sprintf("%q", t.Value)
}

// Handler is simpley a function which takes a single Token argument
type Handler func(Token)

// stateFn represents the state of the scanner
// as a function and returns the next state.
type stateFn func(*Lexer) stateFn

// NewLexer creates a new scanner from the input
func NewLexer(name, input string, h Handler) *Lexer {
	return &Lexer{
		Name:    name,
		input:   input + "\n",
		state:   lexText,
		handler: h,
		// tokens: make(chan Token, 2),
	}
}

// Lexer holds the state of the scanner
type Lexer struct {
	Name    string  // Used to error reports
	input   string  // the string being scanned
	Start   int     // start position of this item
	Pos     int     // current position in the input
	Width   int     // width of last rune read
	state   stateFn // next state function
	handler Handler // token handler
	// tokens chan Token // channel of scanned tokens
}

// Run lexes the input by executing state functions
// until the state is nil
func (l *Lexer) run() {
	for state := lexText; state != nil; {
		state = state(l)
	}
	// close(l.tokens) // no more tokens will be delivered
}

// emit passes an item pack to the client
func (l *Lexer) emit(t TokenType) {

	// if the position is the same as the start, do not emit a token
	if l.Pos == l.Start {
		return
	}

	tok := Token{t, l.input[l.Start:l.Pos]}
	// fmt.Println("token: ", tok)
	l.handler(tok)
	// l.tokens <- Token{t, l.input[l.Start:l.Pos]}
	l.Start = l.Pos
}

// remaining returns the input from the current position until the end
func (l *Lexer) remaining() string {
	return l.input[l.Pos:]
}

// advance increases the current position by the given amount
func (l *Lexer) advance(incr int) {
	l.Pos += incr
	return
}

// skipWhitespace ignores all whitespace characters
func (l *Lexer) skipWhitespace() {
	l.acceptRun(" \t\r\n")
	l.ignore()
}

// next advances the lexer position and returns the next rune. If the input
// does not have any more runes, an `eof` is returned.
func (l *Lexer) next() (r rune) {
	if l.Pos >= len(l.input) {
		l.Width = 0
		return eof
	}
	r, l.Width = utf8.DecodeRuneInString(l.remaining())
	l.advance(l.Width)
	return
}

// ignore steps over the pending input before this point
func (l *Lexer) ignore() {
	l.Start = l.Pos
}

// backup steps back one rune
func (l *Lexer) backup() {
	l.Pos -= l.Width
}

// peek returns but does not consume the next rune in the input
func (l *Lexer) peek() (r rune) {
	r = l.next()
	l.backup()
	return
}

// accept consumes the next rune if it's in the valid set
func (l *Lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}

// consumes a run of runes from the valid set
func (l *Lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

// errorf returns an error token and terminates the scan
// by passing back a nil pointer that will be the next
// state thus terminating the lexer
func (l *Lexer) errorf(format string, args ...interface{}) stateFn {
	l.handler(Token{TokenError, fmt.Sprintf(format, args...)})
	// l.tokens <- Token{TokenError, fmt.Sprintf(format, args...)}
	return nil
}

// Main lexer loop
func lexText(l *Lexer) stateFn {
OUTER:
	for {
		l.skipWhitespace()

		if strings.HasPrefix(l.remaining(), comment) { // Start comment
			// state function which lexes a comment
			return lexComment
		} else if strings.HasPrefix(l.remaining(), pkg) { // Start package decl
			// state function which lexes a package decl
			return lexPackage
		} else if strings.HasPrefix(l.remaining(), from) { // Start from decl
			// state function which lexes a from decl
			return lexFrom
		} else if strings.HasPrefix(l.remaining(), imp) { // Start import decl
			// state function which lexes a imp decl
			return lexImport
		} else if strings.HasPrefix(l.remaining(), typeDef) { // Start type def
			// state function which lexes a type
			return lexMessage
		} else if strings.HasPrefix(l.remaining(), version) { // Start version
			// state function which lexes a version
			return lexVersion
		} else if strings.HasPrefix(l.remaining(), required) { // Start required field
			// state function which lexes a field
			return lexField
		} else if strings.HasPrefix(l.remaining(), optional) { // Start optional field
			// state function which lexes a field
			return lexField
		} else if strings.HasPrefix(l.remaining(), closeScope) { // Close scope
			l.Pos += len(closeScope)
			l.emit(TokenCloseCurlyBracket)
		} else {
			switch r := l.next(); {

			case r == eof: // reached EOF?
				l.emit(TokenEOF)
				break OUTER
			default:
				l.errorf("unknown token: ", string(r))
			}
		}
	}

	// Stops the run loop
	return nil
}

// Lexes a comment line
func lexComment(l *Lexer) stateFn {
	l.skipWhitespace()

	// if strings.HasPrefix(l.remaining(), comment) {
	// skip comment //
	l.Pos += len(comment)

	// find next new line and add location to pos which
	// advances the scanner
	if index := strings.Index(l.remaining(), "\n"); index > 0 {
		l.Pos += index
	} else {
		l.Pos += len(l.remaining())
		// l.emit(TokenComment)
		// break
	}

	// emit the comment string
	l.emit(TokenComment)

	l.skipWhitespace()
	// }

	// continue on scanner
	return lexText
}

func lexMessage(l *Lexer) stateFn {
	// skip message keyword
	l.Pos += len(typeDef)

	// emit keyword
	l.emit(TokenTypeDef)
	l.skipWhitespace()

	return lexIdentifier
}

func lexPackage(l *Lexer) stateFn {
	// skip package keyword
	l.Pos += len(pkg)

	// emit package token
	l.emit(TokenPackage)

	// skip whitespace
	l.skipWhitespace()

	// lex package name
	return lexPackageName
}

func lexPackageName(l *Lexer) stateFn {

	// lex package name
	var lastPeriod bool
OUTER:
	for {

		switch r := l.next(); {
		case unicode.IsLetter(r):
			lastPeriod = false
		case r == '.' || r == '_':
			lastPeriod = true
		case unicode.Is(unicode.White_Space, r):
			l.backup()
			break OUTER
		default:
			l.backup()
			lastPeriod = false
			return l.errorf("expected newline after package name")
		}
	}

	if lastPeriod {
		return l.errorf("package names cannot end with a period or underscore")
	}

	// emit package name
	l.emit(TokenPackageName)

	return lexText
}

func lexFrom(l *Lexer) stateFn {

	// skip package keyword
	l.Pos += len(from)

	// emit from token
	l.emit(TokenFrom)

	// skip whitespace
	l.skipWhitespace()

	// lex package name
	return lexPackageName
}

func lexImport(l *Lexer) stateFn {

	// skip package keyword
	l.Pos += len(imp)

	// emit from token
	l.emit(TokenImport)

	// skip whitespace
	l.skipWhitespace()

	// lex type name
	var lastComma bool
OUTER:
	for {

		switch r := l.next(); {
		case unicode.IsLetter(r):
			lastComma = false
		case r == ',':
			lastComma = true

			// emit type name
			l.emit(TokenIdentifier)
		case r == '\n':
			l.backup()
			break OUTER
		case unicode.Is(unicode.White_Space, r):
			l.skipWhitespace()
		default:
			l.backup()
			lastComma = false
			return l.errorf("expected newline after package name")
		}
	}

	if lastComma {
		return l.errorf("package names cannot end with a comma")
	}

	// emit last type name
	l.emit(TokenIdentifier)

	// lex package name
	return lexText
}

func lexIdentifier(l *Lexer) stateFn {
	l.skipWhitespace()

	for {

		if strings.HasPrefix(l.remaining(), openScope) {

			l.emit(TokenIdentifier)
			return lexMessageBody
		}

		if l.next() == eof {
			return l.errorf("expected {")
		}

		// switch r := l.next(); {
		// case unicode.IsSpace(r):
		//  l.ignore()
		// case unicode.IsLetter(r) || unicode.IsDigit(r):
		//  insideId = true
		// case r == '.' || r == '-' || r == '_':
		// }
	}
}

// func lexIdentifier(l *Lexer) stateFn {

//  // find open bracket
//  if index := strings.IndexAny(l.input[l.Pos:], "{"); index > 0 {

//      // update pos without open bracket
//      l.Pos += (index - 1)

//      // emit identifier
//      l.emit(TokenIdentifier)

//      // lex message contents
//      return lexMessageBody
//  }
//  return l.errorf("missing message body")
// }

func lexMessageBody(l *Lexer) stateFn {
	l.Pos += len(openScope)
	l.emit(TokenOpenCurlyBracket)
	return lexText
}

func lexVersion(l *Lexer) stateFn {
	l.Pos += len(version)
	l.emit(TokenVersion)
	l.skipWhitespace()

	l.acceptRun("0123456789")
	l.emit(TokenVersionNumber)
	l.skipWhitespace()

	if strings.HasPrefix(l.remaining(), openScope) {
		l.Pos += len(openScope)
		l.emit(TokenOpenCurlyBracket)
	} else {
		return l.errorf("missing version body")
	}
	return lexText
}

func lexField(l *Lexer) stateFn {
	l.skipWhitespace()

	for strings.HasPrefix(l.remaining(), comment) {
		lexComment(l)
	}

	if strings.HasPrefix(l.remaining(), required) {
		l.Pos += len(required)
		l.emit(TokenRequired)
	} else if strings.HasPrefix(l.remaining(), optional) {
		l.Pos += len(optional)
		l.emit(TokenOptional)
	} else if strings.HasPrefix(l.remaining(), closeScope) {
		l.Pos += len(closeScope)
		l.emit(TokenCloseCurlyBracket)
		return lexText
	} else {
		return l.errorf("expected 'required' or 'optional'")
	}

	l.skipWhitespace()

	// lexIdentifier(l)
	for {
		if strings.HasPrefix(l.remaining(), equals) {
			l.emit(TokenIdentifier)
			break
		} else if l.next() == eof {
			return l.errorf("expected =")
		}
	}

	l.skipWhitespace()
	if l.accept("=") {
		l.emit(TokenEquals)
	} else {
		return l.errorf("expected '=' sign")
	}
	l.skipWhitespace()

	lexType(l)
	return lexField
}

func lexType(l *Lexer) stateFn {
	// l.skipWhitespace()
	if strings.HasPrefix(l.remaining(), openArray) {
		l.Pos += len(openArray)
		l.emit(TokenOpenArrayBracket)

		l.skipWhitespace()
		lexType(l)
		l.skipWhitespace()

		if strings.HasPrefix(l.remaining(), closeArray) {
			l.Pos += len(closeArray)
			l.emit(TokenCloseArrayBracket)
			return lexField
		}
		return l.errorf("expected ]")

	} else if strings.HasPrefix(l.remaining(), dollarRef) {
		l.emit(TokenReference)
		for {
			if strings.HasPrefix(l.remaining(), "\n") {
				l.emit(TokenIdentifier)
				break
			} else if l.next() == eof {
				return l.errorf("expected }")
			}
		}
		return lexField
	} else {
		for _, t := range TypeNames {
			if strings.HasPrefix(l.remaining(), t) {
				l.Pos += len(t)
				l.emit(TokenValueType)
				return lexField
			}
		}
	}
	return l.errorf("expected type name, reference or array type")
}
<|endoftext|>


<commit_before>package yiigo

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/youtube/vitess/go/pools"
	"golang.org/x/net/context"
)

type Redis struct {
	CacheName string
}

var (
	redisPool *pools.ResourcePool
	redisMux  sync.Mutex
)

type ResourceConn struct {
	redis.Conn
}

// 关闭连接资源
func (r ResourceConn) Close() {
	r.Conn.Close()
}

/**
 * 初始化Redis连接池
 */
func initRedis() {
	redisMux.Lock()
	defer redisMux.Unlock()

	if redisPool == nil {
		poolMinActive := GetEnvInt("redis", "poolMinActive", 10)
		poolMaxActive := GetEnvInt("redis", "poolMaxActive", 20)
		poolIdleTimeout := GetEnvInt("redis", "poolIdleTimeout", 60000)

		redisPool = pools.NewResourcePool(func() (pools.Resource, error) {
			conn, err := dialRedis()
			return ResourceConn{conn}, err
		}, poolMinActive, poolMaxActive, time.Duration(poolIdleTimeout)*time.Millisecond)
	}
}

/**
 * 连接Redis
 * @return redis.Conn, error
 */
func dialRedis() (redis.Conn, error) {
	host := GetEnvString("redis", "host", "localhost")
	port := GetEnvInt("redis", "port", 6379)
	connectTimeout := GetEnvInt("redis", "connectTimeout", 10000)
	readTimeout := GetEnvInt("redis", "readTimeout", 10000)
	writeTimeout := GetEnvInt("redis", "writeTimeout", 10000)

	dsn := fmt.Sprintf("%s:%d", host, port)
	conn, err := redis.DialTimeout("tcp", dsn, time.Duration(connectTimeout)*time.Millisecond, time.Duration(readTimeout)*time.Millisecond, time.Duration(writeTimeout)*time.Millisecond)

	if err != nil {
		return nil, err
	}

	return conn, nil
}

/**
 * 获取Redis资源
 * @return pools.Resource, error
 */
func getRedisConn() (pools.Resource, error) {
	if redisPool == nil {
		return nil, errors.New("redis pool is empty")
	}

	if redisPool.IsClosed() {
		initRedis()
	}

	ctx := context.TODO()
	rc, err := redisPool.Get(ctx)

	if err != nil {
		return nil, err
	}

	return rc, err
}

func (r *Redis) getKey(key string) string {
	prefix := GetEnvString("redis", "prefix", "yii")

	if strings.TrimSpace(key) == "" {
		return fmt.Sprintf("%s:%s", prefix, r.CacheName)
	}

	return fmt.Sprintf("%s:%s:%s", prefix, r.CacheName, key)
}

// string cmd

/**
 * SET
 * @param key string
 * @param data interface{}
 * @return error
 */
func (r *Redis) Set(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheData, err := json.Marshal(data)

	if err != nil {
		return err
	}

	cacheKey := r.getKey(key)

	_, err = conn.Do("SET", cacheKey, cacheData)

	if err != nil {
		return err
	}

	return nil
}

/**
 * MSET
 * @param data map[string]interface{}
 * @return error
 */
func (r *Redis) MSet(data map[string]interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	args := []interface{}{}

	for k, v := range data {
		cacheData, err := json.Marshal(v)

		if err != nil {
			return err
		}

		args = append(args, r.getKey(k), cacheData)
	}

	_, err = conn.Do("MSET", args...)

	if err != nil {
		return err
	}

	return nil
}

/**
 * GET
 * @param key string
 * @param data interface{} (指针)
 * @return error
 */
func (r *Redis) Get(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	cacheData, err := conn.Do("GET", cacheKey)

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	err = json.Unmarshal(cacheData.([]byte), data)

	if err != nil {
		return err
	}

	return nil
}

/**
 * MGET
 * @param keys []string
 * @param data interface{} (切片指针)
 * @return error
 */
func (r *Redis) MGet(keys []string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	args := []interface{}{}

	for _, key := range keys {
		args = append(args, r.getKey(key))
	}

	cacheData, err := redis.ByteSlices(conn.Do("MGET", args...))

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	if len(cacheData) > 0 {
		refVal := reflect.Indirect(reflect.ValueOf(data))

		if refVal.Kind() == reflect.Slice {
			refValType := refVal.Type().Elem()
			refVal.Set(reflect.MakeSlice(refVal.Type(), 0, 0))

			for _, v := range cacheData {
				if v != nil {
					elem := reflect.New(refValType).Elem()
					err := json.Unmarshal(v, elem.Addr().Interface())

					if err != nil {
						return err
					}

					refVal.Set(reflect.Append(refVal, elem))
				}
			}
		}
	}

	return nil
}

// hash cmd

/**
 * HSET
 * @param key string
 * @param field interface{}
 * @data interface{}
 * @return error
 */
func (r *Redis) HSet(key string, field interface{}, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheData, err := json.Marshal(data)

	if err != nil {
		return err
	}

	cacheKey := r.getKey(key)

	_, err = conn.Do("HSET", cacheKey, field, cacheData)

	if err != nil {
		return err
	}

	return nil
}

/**
 * HMSET
 * @param key string
 * @param data map[interface{}]interface{}
 * @return error
 */
func (r *Redis) HMSet(key string, data map[interface{}]interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	args := []interface{}{}
	args = append(args, r.getKey(key))

	for field, v := range data {
		cacheData, err := json.Marshal(v)

		if err != nil {
			return err
		}

		args = append(args, field, cacheData)
	}

	_, err = conn.Do("HMSet", args...)

	if err != nil {
		return err
	}

	return nil
}

/**
 * HGET
 * @param key string
 * @param field interface{}
 * @param data interface{} (指针)
 * @return error
 */
func (r *Redis) HGet(key string, field interface{}, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	cacheData, err := conn.Do("HGET", cacheKey, field)

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	err = json.Unmarshal(cacheData.([]byte), data)

	if err != nil {
		return err
	}

	return nil
}

/**
 * HMGET
 * @param key string
 * @param fields []interface{}
 * @param data interface{} (切片指针)
 * @return error
 */
func (r *Redis) HMGet(key string, fields []interface{}, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	args := []interface{}{}
	args = append(args, r.getKey(key))

	for _, field := range fields {
		args = append(args, field)
	}

	cacheData, err := redis.ByteSlices(conn.Do("HMGET", args...))

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	if len(cacheData) > 0 {
		refVal := reflect.Indirect(reflect.ValueOf(data))

		if refVal.Kind() == reflect.Slice {
			refValType := refVal.Type().Elem()
			refVal.Set(reflect.MakeSlice(refVal.Type(), 0, 0))

			for _, v := range cacheData {
				if v != nil {
					elem := reflect.New(refValType).Elem()
					err := json.Unmarshal(v, elem.Addr().Interface())

					if err != nil {
						return err
					}

					refVal.Set(reflect.Append(refVal, elem))
				}
			}
		}
	}

	return nil
}

/**
 * HDEL
 * @param key
 * @param field interface{}
 * @return error
 */
func (r *Redis) HDel(key string, field interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	_, err = conn.Do("HDEL", cacheKey, field)

	if err != nil {
		return err
	}

	return nil
}

/**
 * HLEN
 * @param key string
 * @return int64, error
 */
func (r *Redis) HLen(key string) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("HLEN", cacheKey)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

/**
 * HINCRBY
 * @param key string
 * @param field interface{}
 * @param inc int
 * @return int64, error
 */
func (r *Redis) HIncrBy(key string, field interface{}, inc int) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("HINCRBY", cacheKey, field, inc)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

// list cmd

/**
 * LPUSH
 * @param key string
 * @param data interface{}
 * @return error
 */
func (r *Redis) LPush(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheData, err := json.Marshal(data)

	if err != nil {
		return err
	}

	cacheKey := r.getKey(key)

	_, err = conn.Do("LPUSH", cacheKey, cacheData)

	if err != nil {
		return err
	}

	return nil
}

/**
 * LPOP
 * @param key string
 * @param data interface{} (指针)
 * @return error
 */
func (r *Redis) LPop(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	cacheData, err := conn.Do("LPOP", cacheKey)

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	err = json.Unmarshal(cacheData.([]byte), data)

	if err != nil {
		return err
	}

	return nil
}

/**
 * RPUSH
 * @param key string
 * @param data interface{}
 * @return error
 */
func (r *Redis) RPush(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheData, err := json.Marshal(data)

	if err != nil {
		return err
	}

	cacheKey := r.getKey(key)

	_, err = conn.Do("RPUSH", cacheKey, cacheData)

	if err != nil {
		return err
	}

	return nil
}

/**
 * RPOP
 * @param key string
 * @param data interface{} (指针)
 * @return error
 */
func (r *Redis) RPop(key string, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	cacheData, err := conn.Do("RPOP", cacheKey)

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	err = json.Unmarshal(cacheData.([]byte), data)

	if err != nil {
		return err
	}

	return nil
}

/**
 * LLEN
 * @param key string
 * return int64, error
 */
func (r *Redis) LLen(key string) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("LLEN", cacheKey)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

/**
 * LRANGE
 * @param key string
 * @param start int
 * @param end int
 * @param data interface{} (切片指针)
 * @return error
 */
func (r *Redis) LRange(key string, start int, end int, data interface{}) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn)

	cacheKey := r.getKey(key)

	cacheData, err := redis.ByteSlices(conn.Do("LRANGE", cacheKey, start, end))

	if err != nil {
		return err
	}

	if cacheData == nil {
		return errors.New("not found")
	}

	if len(cacheData) > 0 {
		refVal := reflect.Indirect(reflect.ValueOf(data))

		if refVal.Kind() == reflect.Slice {
			refValType := refVal.Type().Elem()
			refVal.Set(reflect.MakeSlice(refVal.Type(), 0, 0))

			for _, v := range cacheData {
				if v != nil {
					elem := reflect.New(refValType).Elem()
					err := json.Unmarshal(v, elem.Addr().Interface())

					if err != nil {
						return err
					}

					refVal.Set(reflect.Append(refVal, elem))
				}
			}
		}
	}

	return nil
}

// key cmd

/**
 * HMSET
 * @param key string
 * @return error
 */
func (r *Redis) Del(key string) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	_, err = conn.Do("DEL", cacheKey)

	if err != nil {
		return err
	}

	return nil
}

/**
 * EXPIRE
 * @param key string
 * @param time int
 * @return error
 */
func (r *Redis) Expire(key string, time int) error {
	rc, err := getRedisConn()

	if err != nil {
		return err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	_, err = conn.Do("EXPIRE", cacheKey, time)

	if err != nil {
		return err
	}

	return nil
}

/**
 * INCR
 * @param key string
 * @return int64, error
 */
func (r *Redis) Incr(key string) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("INCR", cacheKey)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

/**
 * INCRBY
 * @param key string
 * @param inc int
 * @return int64, error
 */
func (r *Redis) IncrBy(key string, inc int) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("INCRBY", cacheKey, inc)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

/**
 * DECR
 * @param key string
 * @return int64, error
 */
func (r *Redis) Decr(key string) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("DECR", cacheKey)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}

/**
 * DECRBY
 * @param key string
 * @param inc int
 * @return int64, error
 */
func (r *Redis) DecrBy(key string, inc int) (int64, error) {
	rc, err := getRedisConn()

	if err != nil {
		return 0, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	cacheKey := r.getKey(key)

	result, err := conn.Do("DECRBY", cacheKey, inc)

	if err != nil {
		return 0, err
	}

	count, ok := result.(int64)

	if !ok {
		return 0, fmt.Errorf("invalid type assertion, result %v is %v", result, reflect.TypeOf(result))
	}

	return count, nil
}
<commit_msg>update<commit_after>package yiigo

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/youtube/vitess/go/pools"
	"golang.org/x/net/context"
)

type Redis struct{}

var (
	redisPool *pools.ResourcePool
	redisMux  sync.Mutex
)

type ResourceConn struct {
	redis.Conn
}

// 关闭连接资源
func (r ResourceConn) Close() {
	r.Conn.Close()
}

/**
 * 初始化 Redis 连接池
 */
func initRedis() {
	redisMux.Lock()
	defer redisMux.Unlock()

	if redisPool == nil {
		poolMinActive := GetEnvInt("redis", "poolMinActive", 10)
		poolMaxActive := GetEnvInt("redis", "poolMaxActive", 20)
		poolIdleTimeout := GetEnvInt("redis", "poolIdleTimeout", 60000)

		redisPool = pools.NewResourcePool(func() (pools.Resource, error) {
			conn, err := dialRedis()
			return ResourceConn{conn}, err
		}, poolMinActive, poolMaxActive, time.Duration(poolIdleTimeout)*time.Millisecond)
	}
}

/**
 * 连接 Redis
 * @return redis.Conn, error
 */
func dialRedis() (redis.Conn, error) {
	host := GetEnvString("redis", "host", "localhost")
	port := GetEnvInt("redis", "port", 6379)
	connectTimeout := GetEnvInt("redis", "connectTimeout", 10000)
	readTimeout := GetEnvInt("redis", "readTimeout", 10000)
	writeTimeout := GetEnvInt("redis", "writeTimeout", 10000)

	dsn := fmt.Sprintf("%s:%d", host, port)
	conn, err := redis.DialTimeout("tcp", dsn, time.Duration(connectTimeout)*time.Millisecond, time.Duration(readTimeout)*time.Millisecond, time.Duration(writeTimeout)*time.Millisecond)

	if err != nil {
		return nil, err
	}

	return conn, nil
}

/**
 * 获取 Redis 连接资源
 * @return pools.Resource, error
 */
func (r *Redis) getConn() (pools.Resource, error) {
	if redisPool == nil {
		return nil, errors.New("redis pool is empty")
	}

	if redisPool.IsClosed() {
		initRedis()
	}

	ctx := context.TODO()
	rc, err := redisPool.Get(ctx)

	if err != nil {
		return nil, err
	}

	return rc, err
}

/**
 * Do 执行一条redis命令
 * @param cmd string
 * @param args ...interface{}
 * @return interface{}, error
 */
func (r *Redis) Do(cmd string, args ...interface{}) (interface{}, error) {
	rc, err := r.getConn()

	if err != nil {
		return nil, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	reply, err := conn.Do(cmd, args...)

	return reply, err
}

/**
 * Pipeline redis管道 执行一组redis命令
 * @param cmds map[string][]interface{}
 * @return interface{}, error
 */
func (r *Redis) Pipeline(cmds map[string][]interface{}) (interface{}, error) {
	rc, err := r.getConn()

	if err != nil {
		return nil, err
	}

	defer redisPool.Put(rc)

	conn := rc.(ResourceConn).Conn

	for k, v := range cmds {
		conn.Send(k, v...)
	}

	reply, err := conn.Do("EXEC")

	return reply, err
}
<|endoftext|>


<commit_before>package picasso

import (
	"image"
	"image/color"
	"math"
)

type orientation bool

const (
	horizontal orientation = orientation(false)
	vertical   orientation = orientation(true)
)

func getImageOrientation(image image.Image) orientation {
	rect := image.Bounds()
	if rect.Dx() >= rect.Dy() {
		return horizontal
	} else {
		return vertical
	}
}

func DrawGridLayout(images []image.Image, width int) image.Image {
	l := gridLayout{}
	orientation, node := l.compose(images)
	height := l.getHeight(orientation, width)
	return node.Draw(width, height)
}

func DrawGridLayoutWithBorder(images []image.Image, width int, borderColor color.Color, borderWidth int) image.Image {
	l := gridLayout{}
	orientation, node := l.compose(images)
	height := l.getHeight(orientation, width)
	return node.DrawWithBorder(width, height, borderColor, borderWidth)
}

type gridLayout struct{}

func (l gridLayout) getHeight(orientation orientation, width int) int {
	if orientation == horizontal {
		return int(float32(width) / math.Sqrt2)
	} else {
		return int(float32(width) * math.Sqrt2)
	}
}

func (l gridLayout) compose(images []image.Image) (orientation, Node) {
	orientation, imagesToBeComposed := l.composableSubset(images)
	if orientation == horizontal {
		return orientation, l.splitVertically(imagesToBeComposed)
	} else {
		return orientation, l.splitHorizontally(imagesToBeComposed)
	}
}

func (l gridLayout) splitVertically(images []image.Image) Node {
	if len(images) == 1 {
		return Picture{images[0]}
	}
	// add + 1 to effectively round the division up as we want initially there to be more images on the left side
	// so that when we start moving images from one side to the other, the numbers would stay more or less in balance
	midPoint := (len(images) + 1) / 2
	proposedLeftImages := images[:midPoint]
	proposedRightImages := images[midPoint:]
	proposedLeftHorizontalCount, proposedLeftVerticalCount := l.countComposedOrientation(0, 0, proposedLeftImages)
	proposedRightHorizontalCount, proposedRightVerticalCount := l.countComposedOrientation(0, 0, proposedRightImages)
	// now we need to redistribute the images so that both sides would compose to vertical orientation
	// NB: this method expects this to be possible
	if proposedLeftHorizontalCount == 1 && proposedLeftVerticalCount == 0 && proposedRightHorizontalCount == 1 && proposedRightVerticalCount == 1 {
		leftImages, rightImages := move1VerticalCountOver(proposedLeftImages, proposedRightImages)
		return VerticalSplit{
			Ratio: 1,
			Left:  l.splitHorizontally(leftImages),
			Right: l.splitHorizontally(rightImages),
		}
	} else if proposedLeftHorizontalCount == 1 && proposedLeftVerticalCount == 1 && proposedRightHorizontalCount == 1 && proposedRightVerticalCount == 0 {
		leftImages, rightImages := move1HorizontalCountOver(proposedLeftImages, proposedRightImages)
		return VerticalSplit{
			Ratio: 1,
			Left:  l.splitHorizontally(leftImages),
			Right: l.splitHorizontally(rightImages),
		}
	}
	return VerticalSplit{
		Ratio: 1,
		Left:  l.splitHorizontally(proposedLeftImages),
		Right: l.splitHorizontally(proposedRightImages),
	}
}

func (l gridLayout) splitHorizontally(images []image.Image) Node {
	if len(images) == 1 {
		return Picture{images[0]}
	}
	// add + 1 for same reasons as above
	midPoint := (len(images) + 1) / 2
	proposedTopImages := images[:midPoint]
	proposedBottomImages := images[midPoint:]
	proposedTopHorizontalCount, proposedTopVerticalCount := l.countComposedOrientation(0, 0, proposedTopImages)
	proposedBottomHorizontalCount, proposedBottomVerticalCount := l.countComposedOrientation(0, 0, proposedBottomImages)
	// now we need to redistribute the images so that both sides would compose to horizontal orientation
	// NB: this method expects this to be possible
	if proposedTopHorizontalCount == 0 && proposedTopVerticalCount == 1 && proposedBottomHorizontalCount == 1 && proposedBottomVerticalCount == 1 {
		topImages, bottomImages := move1HorizontalCountOver(proposedTopImages, proposedBottomImages)
		return HorizontalSplit{
			Ratio:  1,
			Top:    l.splitVertically(topImages),
			Bottom: l.splitVertically(bottomImages),
		}
	} else if proposedTopHorizontalCount == 1 && proposedTopVerticalCount == 1 && proposedBottomHorizontalCount == 0 && proposedBottomVerticalCount == 1 {
		topImages, bottomImages := move1VerticalCountOver(proposedTopImages, proposedBottomImages)
		return HorizontalSplit{
			Ratio:  1,
			Top:    l.splitVertically(topImages),
			Bottom: l.splitVertically(bottomImages),
		}
	}
	return HorizontalSplit{
		Ratio:  1,
		Top:    l.splitVertically(proposedTopImages),
		Bottom: l.splitVertically(proposedBottomImages),
	}
}

func move1VerticalCountOver(aImages, bImages []image.Image) ([]image.Image, []image.Image) {
	aHasHorizontal, lastAHorizontalIndex := indexOfLastHorizontalImage(aImages)
	bHasVertical, lastBVerticalIndex := indexOfLastVerticalImage(bImages)
	if aHasHorizontal && bHasVertical {
		return swapImage(aImages, lastAHorizontalIndex, bImages, lastBVerticalIndex)
	}
	aHasVertical, lastAVerticalIndex := indexOfLastVerticalImage(aImages)
	if aHasVertical {
		return move1ImageOver(aImages, lastAVerticalIndex, bImages)
	}
	nextToLastAHorizontalIndex := indexOfNextToLastHorizontalImage(aImages)
	return move2ImagesOver(aImages, nextToLastAHorizontalIndex, lastAHorizontalIndex, bImages)
}

func move1HorizontalCountOver(aImages, bImages []image.Image) ([]image.Image, []image.Image) {
	aHasVertical, lastAVerticalIndex := indexOfLastVerticalImage(aImages)
	bHasHorizontal, lastBHorizontalIndex := indexOfLastHorizontalImage(bImages)
	if aHasVertical && bHasHorizontal {
		return swapImage(aImages, lastAVerticalIndex, bImages, lastBHorizontalIndex)
	}
	aHasHorizontal, lastAHorizontalIndex := indexOfLastHorizontalImage(aImages)
	if aHasHorizontal {
		return move1ImageOver(aImages, lastAHorizontalIndex, bImages)
	}
	nextToLastAVerticalIndex := indexOfNextToLastVerticalImage(aImages)
	return move2ImagesOver(aImages, nextToLastAVerticalIndex, lastAVerticalIndex, bImages)
}

func move1ImageOver(aImages []image.Image, aIndex int, bImages []image.Image) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages)-1)
	copy(newAImages[:aIndex], aImages[:aIndex])
	copy(newAImages[aIndex:], aImages[aIndex+1:])
	newBImages := append(bImages, aImages[aIndex])
	return newAImages, newBImages
}

// move2ImagesOver expects aIndex1 < aIndex2
func move2ImagesOver(aImages []image.Image, aIndex1, aIndex2 int, bImages []image.Image) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages)-2)
	copy(newAImages[:aIndex1], aImages[:aIndex1])
	copy(newAImages[aIndex1:aIndex2-1], aImages[aIndex1+1:aIndex2])
	copy(newAImages[aIndex2-1:], aImages[aIndex2+1:])
	newBImages := append(bImages, aImages[aIndex1], aImages[aIndex2])
	return newAImages, newBImages
}

func swapImage(aImages []image.Image, aIndex int, bImages []image.Image, bIndex int) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages))
	newBImages := make([]image.Image, len(bImages))
	for i, image := range aImages {
		if i == aIndex {
			newAImages[i] = bImages[bIndex]
			continue
		}
		newAImages[i] = image
	}
	for i, image := range bImages {
		if i == bIndex {
			newBImages[i] = aImages[aIndex]
			continue
		}
		newBImages[i] = image
	}
	return newAImages, newBImages
}

func indexOfLastVerticalImage(images []image.Image) (hasVerticalImage bool, index int) {
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == vertical {
			return true, i
		}
	}
	return false, -1
}

func indexOfNextToLastVerticalImage(images []image.Image) int {
	foundLast := false
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == vertical {
			if !foundLast {
				foundLast = true
				continue
			}
			return i
		}
	}
	return -1
}

func indexOfLastHorizontalImage(images []image.Image) (hasHorizontalImage bool, index int) {
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == horizontal {
			return true, i
		}
	}
	return false, -1
}

func indexOfNextToLastHorizontalImage(images []image.Image) int {
	foundLast := false
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == horizontal {
			if !foundLast {
				foundLast = true
				continue
			}
			return i
		}
	}
	return -1
}

// composableSubset returns either all or all but the last image provided depending on if all images can be used to create the layout
// without any gaps. It also returns what orientation the resulting images would take when composed.
func (l gridLayout) composableSubset(images []image.Image) (orientation, []image.Image) {
	horizontalCount, verticalCount := l.countComposedOrientation(0, 0, images)
	if horizontalCount == 1 && verticalCount == 1 {
		lastImageOrientation := getImageOrientation(images[len(images)-1])
		if lastImageOrientation == horizontal {
			return vertical, images[:len(images)-1]
		} else {
			return horizontal, images[:len(images)-1]
		}
	}

	if horizontalCount == 1 {
		return horizontal, images
	} else {
		return vertical, images
	}
}

// countComposedOrientation recursively traverses the list of provided images and looks at what would the orienation of the composed image
// be if all of those images were put into a grid layout. It works by counting 2 horizontal (landscape) images as a single vertical
// (portrait) image and vice versa.
func (l gridLayout) countComposedOrientation(horizontalCount, verticalCount int, images []image.Image) (int, int) {
	if len(images) == 0 {
		return horizontalCount, verticalCount
	}
	newHorizontalCount, newVerticalCount := l.addImageToComposedOrientationCount(horizontalCount, verticalCount, images[0])
	return l.countComposedOrientation(newHorizontalCount, newVerticalCount, images[1:])
}

func (l gridLayout) addImageToComposedOrientationCount(horizontalCount, verticalCount int, image image.Image) (int, int) {
	orientation := getImageOrientation(image)
	if orientation == vertical {
		return l.addVertical(horizontalCount, verticalCount)
	} else {
		return l.addHorizontal(horizontalCount, verticalCount)
	}
}
func (l gridLayout) addHorizontal(horizontalCount, verticalCount int) (int, int) {
	if horizontalCount == 1 {
		return l.merge2Horizontals(horizontalCount+1, verticalCount)
	}
	return horizontalCount + 1, verticalCount
}

func (l gridLayout) merge2Horizontals(horizontalCount, verticalCount int) (int, int) {
	return l.addVertical(horizontalCount-2, verticalCount)
}

func (l gridLayout) addVertical(horizontalCount, verticalCount int) (int, int) {
	if verticalCount == 1 {
		return l.merge2Verticals(horizontalCount, verticalCount+1)
	}
	return horizontalCount, verticalCount + 1
}

func (l gridLayout) merge2Verticals(horizontalCount, verticalCount int) (int, int) {
	return l.addHorizontal(horizontalCount, verticalCount-2)
}
<commit_msg>Add checks for empty image arrays for public interface<commit_after>package picasso

import (
	"image"
	"image/color"
	"math"
)

type orientation bool

const (
	horizontal orientation = orientation(false)
	vertical   orientation = orientation(true)
)

func getImageOrientation(image image.Image) orientation {
	rect := image.Bounds()
	if rect.Dx() >= rect.Dy() {
		return horizontal
	} else {
		return vertical
	}
}

func DrawGridLayout(images []image.Image, width int) image.Image {
	if len(images) == 0 {
		return nil
	}
	l := gridLayout{}
	orientation, node := l.compose(images)
	height := l.getHeight(orientation, width)
	return node.Draw(width, height)
}

func DrawGridLayoutWithBorder(images []image.Image, width int, borderColor color.Color, borderWidth int) image.Image {
	if len(images) == 0 {
		return nil
	}
	l := gridLayout{}
	orientation, node := l.compose(images)
	height := l.getHeight(orientation, width)
	return node.DrawWithBorder(width, height, borderColor, borderWidth)
}

type gridLayout struct{}

func (l gridLayout) getHeight(orientation orientation, width int) int {
	if orientation == horizontal {
		return int(float32(width) / math.Sqrt2)
	} else {
		return int(float32(width) * math.Sqrt2)
	}
}

func (l gridLayout) compose(images []image.Image) (orientation, Node) {
	orientation, imagesToBeComposed := l.composableSubset(images)
	if orientation == horizontal {
		return orientation, l.splitVertically(imagesToBeComposed)
	} else {
		return orientation, l.splitHorizontally(imagesToBeComposed)
	}
}

func (l gridLayout) splitVertically(images []image.Image) Node {
	if len(images) == 1 {
		return Picture{images[0]}
	}
	// add + 1 to effectively round the division up as we want initially there to be more images on the left side
	// so that when we start moving images from one side to the other, the numbers would stay more or less in balance
	midPoint := (len(images) + 1) / 2
	proposedLeftImages := images[:midPoint]
	proposedRightImages := images[midPoint:]
	proposedLeftHorizontalCount, proposedLeftVerticalCount := l.countComposedOrientation(0, 0, proposedLeftImages)
	proposedRightHorizontalCount, proposedRightVerticalCount := l.countComposedOrientation(0, 0, proposedRightImages)
	// now we need to redistribute the images so that both sides would compose to vertical orientation
	// NB: this method expects this to be possible
	if proposedLeftHorizontalCount == 1 && proposedLeftVerticalCount == 0 && proposedRightHorizontalCount == 1 && proposedRightVerticalCount == 1 {
		leftImages, rightImages := move1VerticalCountOver(proposedLeftImages, proposedRightImages)
		return VerticalSplit{
			Ratio: 1,
			Left:  l.splitHorizontally(leftImages),
			Right: l.splitHorizontally(rightImages),
		}
	} else if proposedLeftHorizontalCount == 1 && proposedLeftVerticalCount == 1 && proposedRightHorizontalCount == 1 && proposedRightVerticalCount == 0 {
		leftImages, rightImages := move1HorizontalCountOver(proposedLeftImages, proposedRightImages)
		return VerticalSplit{
			Ratio: 1,
			Left:  l.splitHorizontally(leftImages),
			Right: l.splitHorizontally(rightImages),
		}
	}
	return VerticalSplit{
		Ratio: 1,
		Left:  l.splitHorizontally(proposedLeftImages),
		Right: l.splitHorizontally(proposedRightImages),
	}
}

func (l gridLayout) splitHorizontally(images []image.Image) Node {
	if len(images) == 1 {
		return Picture{images[0]}
	}
	// add + 1 for same reasons as above
	midPoint := (len(images) + 1) / 2
	proposedTopImages := images[:midPoint]
	proposedBottomImages := images[midPoint:]
	proposedTopHorizontalCount, proposedTopVerticalCount := l.countComposedOrientation(0, 0, proposedTopImages)
	proposedBottomHorizontalCount, proposedBottomVerticalCount := l.countComposedOrientation(0, 0, proposedBottomImages)
	// now we need to redistribute the images so that both sides would compose to horizontal orientation
	// NB: this method expects this to be possible
	if proposedTopHorizontalCount == 0 && proposedTopVerticalCount == 1 && proposedBottomHorizontalCount == 1 && proposedBottomVerticalCount == 1 {
		topImages, bottomImages := move1HorizontalCountOver(proposedTopImages, proposedBottomImages)
		return HorizontalSplit{
			Ratio:  1,
			Top:    l.splitVertically(topImages),
			Bottom: l.splitVertically(bottomImages),
		}
	} else if proposedTopHorizontalCount == 1 && proposedTopVerticalCount == 1 && proposedBottomHorizontalCount == 0 && proposedBottomVerticalCount == 1 {
		topImages, bottomImages := move1VerticalCountOver(proposedTopImages, proposedBottomImages)
		return HorizontalSplit{
			Ratio:  1,
			Top:    l.splitVertically(topImages),
			Bottom: l.splitVertically(bottomImages),
		}
	}
	return HorizontalSplit{
		Ratio:  1,
		Top:    l.splitVertically(proposedTopImages),
		Bottom: l.splitVertically(proposedBottomImages),
	}
}

func move1VerticalCountOver(aImages, bImages []image.Image) ([]image.Image, []image.Image) {
	aHasHorizontal, lastAHorizontalIndex := indexOfLastHorizontalImage(aImages)
	bHasVertical, lastBVerticalIndex := indexOfLastVerticalImage(bImages)
	if aHasHorizontal && bHasVertical {
		return swapImage(aImages, lastAHorizontalIndex, bImages, lastBVerticalIndex)
	}
	aHasVertical, lastAVerticalIndex := indexOfLastVerticalImage(aImages)
	if aHasVertical {
		return move1ImageOver(aImages, lastAVerticalIndex, bImages)
	}
	nextToLastAHorizontalIndex := indexOfNextToLastHorizontalImage(aImages)
	return move2ImagesOver(aImages, nextToLastAHorizontalIndex, lastAHorizontalIndex, bImages)
}

func move1HorizontalCountOver(aImages, bImages []image.Image) ([]image.Image, []image.Image) {
	aHasVertical, lastAVerticalIndex := indexOfLastVerticalImage(aImages)
	bHasHorizontal, lastBHorizontalIndex := indexOfLastHorizontalImage(bImages)
	if aHasVertical && bHasHorizontal {
		return swapImage(aImages, lastAVerticalIndex, bImages, lastBHorizontalIndex)
	}
	aHasHorizontal, lastAHorizontalIndex := indexOfLastHorizontalImage(aImages)
	if aHasHorizontal {
		return move1ImageOver(aImages, lastAHorizontalIndex, bImages)
	}
	nextToLastAVerticalIndex := indexOfNextToLastVerticalImage(aImages)
	return move2ImagesOver(aImages, nextToLastAVerticalIndex, lastAVerticalIndex, bImages)
}

func move1ImageOver(aImages []image.Image, aIndex int, bImages []image.Image) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages)-1)
	copy(newAImages[:aIndex], aImages[:aIndex])
	copy(newAImages[aIndex:], aImages[aIndex+1:])
	newBImages := append(bImages, aImages[aIndex])
	return newAImages, newBImages
}

// move2ImagesOver expects aIndex1 < aIndex2
func move2ImagesOver(aImages []image.Image, aIndex1, aIndex2 int, bImages []image.Image) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages)-2)
	copy(newAImages[:aIndex1], aImages[:aIndex1])
	copy(newAImages[aIndex1:aIndex2-1], aImages[aIndex1+1:aIndex2])
	copy(newAImages[aIndex2-1:], aImages[aIndex2+1:])
	newBImages := append(bImages, aImages[aIndex1], aImages[aIndex2])
	return newAImages, newBImages
}

func swapImage(aImages []image.Image, aIndex int, bImages []image.Image, bIndex int) ([]image.Image, []image.Image) {
	newAImages := make([]image.Image, len(aImages))
	newBImages := make([]image.Image, len(bImages))
	for i, image := range aImages {
		if i == aIndex {
			newAImages[i] = bImages[bIndex]
			continue
		}
		newAImages[i] = image
	}
	for i, image := range bImages {
		if i == bIndex {
			newBImages[i] = aImages[aIndex]
			continue
		}
		newBImages[i] = image
	}
	return newAImages, newBImages
}

func indexOfLastVerticalImage(images []image.Image) (hasVerticalImage bool, index int) {
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == vertical {
			return true, i
		}
	}
	return false, -1
}

func indexOfNextToLastVerticalImage(images []image.Image) int {
	foundLast := false
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == vertical {
			if !foundLast {
				foundLast = true
				continue
			}
			return i
		}
	}
	return -1
}

func indexOfLastHorizontalImage(images []image.Image) (hasHorizontalImage bool, index int) {
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == horizontal {
			return true, i
		}
	}
	return false, -1
}

func indexOfNextToLastHorizontalImage(images []image.Image) int {
	foundLast := false
	for i := len(images) - 1; i >= 0; i-- {
		orientation := getImageOrientation(images[i])
		if orientation == horizontal {
			if !foundLast {
				foundLast = true
				continue
			}
			return i
		}
	}
	return -1
}

// composableSubset returns either all or all but the last image provided depending on if all images can be used to create the layout
// without any gaps. It also returns what orientation the resulting images would take when composed.
func (l gridLayout) composableSubset(images []image.Image) (orientation, []image.Image) {
	horizontalCount, verticalCount := l.countComposedOrientation(0, 0, images)
	if horizontalCount == 1 && verticalCount == 1 {
		lastImageOrientation := getImageOrientation(images[len(images)-1])
		if lastImageOrientation == horizontal {
			return vertical, images[:len(images)-1]
		} else {
			return horizontal, images[:len(images)-1]
		}
	}

	if horizontalCount == 1 {
		return horizontal, images
	} else {
		return vertical, images
	}
}

// countComposedOrientation recursively traverses the list of provided images and looks at what would the orienation of the composed image
// be if all of those images were put into a grid layout. It works by counting 2 horizontal (landscape) images as a single vertical
// (portrait) image and vice versa.
func (l gridLayout) countComposedOrientation(horizontalCount, verticalCount int, images []image.Image) (int, int) {
	if len(images) == 0 {
		return horizontalCount, verticalCount
	}
	newHorizontalCount, newVerticalCount := l.addImageToComposedOrientationCount(horizontalCount, verticalCount, images[0])
	return l.countComposedOrientation(newHorizontalCount, newVerticalCount, images[1:])
}

func (l gridLayout) addImageToComposedOrientationCount(horizontalCount, verticalCount int, image image.Image) (int, int) {
	orientation := getImageOrientation(image)
	if orientation == vertical {
		return l.addVertical(horizontalCount, verticalCount)
	} else {
		return l.addHorizontal(horizontalCount, verticalCount)
	}
}
func (l gridLayout) addHorizontal(horizontalCount, verticalCount int) (int, int) {
	if horizontalCount == 1 {
		return l.merge2Horizontals(horizontalCount+1, verticalCount)
	}
	return horizontalCount + 1, verticalCount
}

func (l gridLayout) merge2Horizontals(horizontalCount, verticalCount int) (int, int) {
	return l.addVertical(horizontalCount-2, verticalCount)
}

func (l gridLayout) addVertical(horizontalCount, verticalCount int) (int, int) {
	if verticalCount == 1 {
		return l.merge2Verticals(horizontalCount, verticalCount+1)
	}
	return horizontalCount, verticalCount + 1
}

func (l gridLayout) merge2Verticals(horizontalCount, verticalCount int) (int, int) {
	return l.addHorizontal(horizontalCount, verticalCount-2)
}
<|endoftext|>


<commit_before>package sgr

import (
	"fmt"
	"strconv"
	"strings"
)

func MustParse(format string) string {
	str, err := parse(true, false, format)
	if err != nil {
		panic(err)
	}
	return str
}

func MustParseln(format string) string {
	str, err := parse(true, true, format)
	if err != nil {
		panic(err)
	}
	return str
}

func MustParseWithoutReset(format string) string {
	str, err := parse(false, false, format)
	if err != nil {
		panic(err)
	}
	return str
}

func Parse(format string) (string, error) {
	return parse(true, false, format)
}

func Parseln(format string) (string, error) {
	return parse(true, true, format)
}

func ParseWithoutReset(format string) (string, error) {
	return parse(false, false, format)
}

func parse(reset bool, newline bool, format string) (string, error) {
	// Builder used to build the colored string.
	sb := new(sgrBuilder)

	// position in the parsing process
	pos := 0

	// index of the currenctly processing square bracket start
	idxStart := 0
	idxEnd := 0

	for {
		// Find next square bracket, break loop when none was found.
		relBlockOpen := strings.IndexRune(format[pos:], '[')
		if relBlockOpen == -1 {
			sb.appendString(format[pos:])
			break
		}
		idxStart = pos + relBlockOpen

		// Test for escaped square bracket
		if format[idxStart+1] == '[' {
			sb.appendString(format[pos : idxStart+2])
			pos = idxStart + 2
			continue
		}

		// Add skipped string (if any)
		if idxStart > pos { //idxStart > pos+1 ???
			sb.appendString(format[pos:idxStart])
		}

		// Find square bracket end
		relBlockClose := strings.IndexRune(format[idxStart:], ']')
		if relBlockClose == -1 {
			return "", fmt.Errorf("Opened square bracket never closed at pos %d.", idxStart)
		}
		idxEnd = idxStart + relBlockClose

		// found a block
		block := format[idxStart+1 : idxEnd]
		fields := strings.Fields(block)
		for _, field := range fields {

			switch field {
			// Options
			case "reset":
				sb.appendSgr(Reset)
			case "fg-reset":
				sb.appendSgr(ResetForegroundColor)
			case "bg-reset":
				sb.appendSgr(ResetBackgroundColor)
			case "bold":
				sb.appendSgr(Bold)
			case "boldOff":
				sb.appendSgr(BoldOff)
			case "underline":
				sb.appendSgr(Underline)
			case "underlineOff":
				sb.appendSgr(UnderlineOff)
			case "blink":
				sb.appendSgr(Blink)
			case "blinkOff":
				sb.appendSgr(BlinkOff)
			case "imageNegative":
				sb.appendSgr(ImageNegative)
			case "imagePositive":
				sb.appendSgr(ImagePositive)
			case "framed":
				sb.appendSgr(Framed)
			case "encircled":
				sb.appendSgr(Encircled)
			case "framedEncircledOff":
				sb.appendSgr(FramedEncircledOff)
			case "overlined":
				sb.appendSgr(Overlined)
			case "overlinedOff":
				sb.appendSgr(OverlinedOff)

			// Foreground Colors
			case "fg-black":
				sb.appendSgr(FgBlack)
			case "fg-red":
				sb.appendSgr(FgRed)
			case "fg-green":
				sb.appendSgr(FgGreen)
			case "fg-yellow":
				sb.appendSgr(FgYellow)
			case "fg-blue":
				sb.appendSgr(FgBlue)
			case "fg-magenta":
				sb.appendSgr(FgMagenta)
			case "fg-cyan":
				sb.appendSgr(FgCyan)
			case "fg-grey":
				sb.appendSgr(FgGrey)
			case "fg-white":
				sb.appendSgr(FgWhite)

			// Background Colors
			case "bg-black":
				sb.appendSgr(BgBlack)
			case "bg-red":
				sb.appendSgr(BgRed)
			case "bg-green":
				sb.appendSgr(BgGreen)
			case "bg-yellow":
				sb.appendSgr(BgYellow)
			case "bg-blue":
				sb.appendSgr(BgBlue)
			case "bg-magenta":
				sb.appendSgr(BgMagenta)
			case "bg-cyan":
				sb.appendSgr(BgCyan)
			case "bg-grey":
				sb.appendSgr(BgGrey)
			case "bg-white":
				sb.appendSgr(BgWhite)

			// Not Found
			default:
				isFgColor := strings.HasPrefix(field, "fg-")
				isBgColor := strings.HasPrefix(field, "bg-")
				if isFgColor || isBgColor {
					// Get the color number from the fg- or bg- code
					clr, err := strconv.Atoi(field[3:])
					if err != nil {
						goto invalidBlockCode
					}
					if clr < 0 || clr > 255 {
						return "", fmt.Errorf("Invalid color code %s. Expecting 0-255 or a defined color.", field[3:])
					}

					// Add actual fg or bg color to sgrBuilder
					if isFgColor {
						sb.appendSgr(FgColor(clr))
					} else {
						sb.appendSgr(BgColor(clr))
					}
					continue // next field
				} else {
					// not valid
					goto invalidBlockCode
				}
			}

		invalidBlockCode:
			return "", fmt.Errorf("Invalid block code '%s' in block at position %d.", field, idxStart)

		}

		// Change starting position for next iteration.
		pos = idxEnd + 1
	}

	if reset {
		sb.appendSgr(Reset)
	}

	if newline {
		sb.appendString("\n")
	}

	return sb.string(), nil
}
<commit_msg>Bugfix for defined cases, they shouldn't reach the invalidBlockCode label.<commit_after>package sgr

import (
	"fmt"
	"strconv"
	"strings"
)

func MustParse(format string) string {
	str, err := parse(true, false, format)
	if err != nil {
		panic(err)
	}
	return str
}

func MustParseln(format string) string {
	str, err := parse(true, true, format)
	if err != nil {
		panic(err)
	}
	return str
}

func MustParseWithoutReset(format string) string {
	str, err := parse(false, false, format)
	if err != nil {
		panic(err)
	}
	return str
}

func Parse(format string) (string, error) {
	return parse(true, false, format)
}

func Parseln(format string) (string, error) {
	return parse(true, true, format)
}

func ParseWithoutReset(format string) (string, error) {
	return parse(false, false, format)
}

func parse(reset bool, newline bool, format string) (string, error) {
	// Builder used to build the colored string.
	sb := new(sgrBuilder)

	// position in the parsing process
	pos := 0

	// index of the currenctly processing square bracket start
	idxStart := 0
	idxEnd := 0

	for {
		// Find next square bracket, break loop when none was found.
		relBlockOpen := strings.IndexRune(format[pos:], '[')
		if relBlockOpen == -1 {
			sb.appendString(format[pos:])
			break
		}
		idxStart = pos + relBlockOpen

		// Test for escaped square bracket
		if format[idxStart+1] == '[' {
			sb.appendString(format[pos : idxStart+2])
			pos = idxStart + 2
			continue
		}

		// Add skipped string (if any)
		if idxStart > pos { //idxStart > pos+1 ???
			sb.appendString(format[pos:idxStart])
		}

		// Find square bracket end
		relBlockClose := strings.IndexRune(format[idxStart:], ']')
		if relBlockClose == -1 {
			return "", fmt.Errorf("Opened square bracket never closed at pos %d.", idxStart)
		}
		idxEnd = idxStart + relBlockClose

		// found a block
		block := format[idxStart+1 : idxEnd]
		fields := strings.Fields(block)
		for _, field := range fields {

			switch field {
			// Options
			case "reset":
				sb.appendSgr(Reset)
			case "fg-reset":
				sb.appendSgr(ResetForegroundColor)
			case "bg-reset":
				sb.appendSgr(ResetBackgroundColor)
			case "bold":
				sb.appendSgr(Bold)
			case "boldOff":
				sb.appendSgr(BoldOff)
			case "underline":
				sb.appendSgr(Underline)
			case "underlineOff":
				sb.appendSgr(UnderlineOff)
			case "blink":
				sb.appendSgr(Blink)
			case "blinkOff":
				sb.appendSgr(BlinkOff)
			case "imageNegative":
				sb.appendSgr(ImageNegative)
			case "imagePositive":
				sb.appendSgr(ImagePositive)
			case "framed":
				sb.appendSgr(Framed)
			case "encircled":
				sb.appendSgr(Encircled)
			case "framedEncircledOff":
				sb.appendSgr(FramedEncircledOff)
			case "overlined":
				sb.appendSgr(Overlined)
			case "overlinedOff":
				sb.appendSgr(OverlinedOff)

			// Foreground Colors
			case "fg-black":
				sb.appendSgr(FgBlack)
			case "fg-red":
				sb.appendSgr(FgRed)
			case "fg-green":
				sb.appendSgr(FgGreen)
			case "fg-yellow":
				sb.appendSgr(FgYellow)
			case "fg-blue":
				sb.appendSgr(FgBlue)
			case "fg-magenta":
				sb.appendSgr(FgMagenta)
			case "fg-cyan":
				sb.appendSgr(FgCyan)
			case "fg-grey":
				sb.appendSgr(FgGrey)
			case "fg-white":
				sb.appendSgr(FgWhite)

			// Background Colors
			case "bg-black":
				sb.appendSgr(BgBlack)
			case "bg-red":
				sb.appendSgr(BgRed)
			case "bg-green":
				sb.appendSgr(BgGreen)
			case "bg-yellow":
				sb.appendSgr(BgYellow)
			case "bg-blue":
				sb.appendSgr(BgBlue)
			case "bg-magenta":
				sb.appendSgr(BgMagenta)
			case "bg-cyan":
				sb.appendSgr(BgCyan)
			case "bg-grey":
				sb.appendSgr(BgGrey)
			case "bg-white":
				sb.appendSgr(BgWhite)

			// Not Found
			default:
				isFgColor := strings.HasPrefix(field, "fg-")
				isBgColor := strings.HasPrefix(field, "bg-")
				if isFgColor || isBgColor {
					// Get the color number from the fg- or bg- code
					clr, err := strconv.Atoi(field[3:])
					if err != nil {
						goto invalidBlockCode
					}
					if clr < 0 || clr > 255 {
						return "", fmt.Errorf("Invalid color code %s. Expecting 0-255 or a defined color.", field[3:])
					}

					// Add actual fg or bg color to sgrBuilder
					if isFgColor {
						sb.appendSgr(FgColor(clr))
					} else {
						sb.appendSgr(BgColor(clr))
					}
					continue // next field
				} else {
					// not valid
					goto invalidBlockCode
				}
			}

			continue
		invalidBlockCode:
			return "", fmt.Errorf("Invalid block code '%s' in block at position %d.", field, idxStart)

		}

		// Change starting position for next iteration.
		pos = idxEnd + 1
	}

	if reset {
		sb.appendSgr(Reset)
	}

	if newline {
		sb.appendString("\n")
	}

	return sb.string(), nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/bogem/id3v2/bbpool"
	"github.com/bogem/id3v2/lrpool"
	"github.com/bogem/id3v2/util"
)

const frameHeaderSize = 10

var errBlankFrame = errors.New("id or size of frame are blank")

type frameHeader struct {
	ID        string
	FrameSize int64
}

func parseTag(file *os.File) (*Tag, error) {
	if file == nil {
		err := errors.New("Invalid file: file is nil")
		return nil, err
	}

	header, err := parseHeader(file)
	if err == errNoTag {
		return newTag(file, 0, 4), nil
	}
	if err != nil {
		err = errors.New("trying to parse tag header: " + err.Error())
		return nil, err
	}
	if header.Version < 3 {
		err = errors.New(fmt.Sprint("unsupported version of ID3 tag: ", header.Version))
		return nil, err
	}

	t := newTag(file, tagHeaderSize+header.FramesSize, header.Version)
	err = t.parseAllFrames()

	return t, err
}

func newTag(file *os.File, originalSize int64, version byte) *Tag {
	return &Tag{
		frames:    make(map[string]Framer),
		sequences: make(map[string]*sequence),

		file:         file,
		originalSize: originalSize,
		version:      version,
	}
}

func (t *Tag) parseAllFrames() error {
	// Initial position of read - beginning of first frame
	if _, err := t.file.Seek(tagHeaderSize, os.SEEK_SET); err != nil {
		return err
	}

	framesSize := t.originalSize - tagHeaderSize
	fileReader := io.LimitReader(t.file, framesSize)

	for {
		id, frame, err := parseFrame(fileReader)
		if err == io.EOF || err == errBlankFrame || err == util.ErrInvalidSizeFormat {
			break
		}
		if err != nil {
			return err
		}

		t.AddFrame(id, frame)
	}

	return nil
}

func parseFrame(rd io.Reader) (id string, frame Framer, err error) {
	header, err := parseFrameHeader(rd)
	if err != nil {
		return "", nil, err
	}
	id = header.ID

	frameRd := lrpool.Get()
	defer lrpool.Put(frameRd)
	frameRd.R = rd
	frameRd.N = header.FrameSize

	frame, err = parseFrameBody(id, frameRd)
	return id, frame, err
}

func parseFrameHeader(rd io.Reader) (frameHeader, error) {
	var header frameHeader

	// Limit the rd by frameHeaderSize, so fhBuf can read only 'frameHeaderSize'
	// bytes.
	headerRd := lrpool.Get()
	defer lrpool.Put(headerRd)
	headerRd.R = rd
	headerRd.N = frameHeaderSize

	fhBuf := bbpool.Get()
	defer bbpool.Put(fhBuf)

	_, err := fhBuf.ReadFrom(headerRd)
	if err != nil {
		return header, err
	}
	fhBytes := fhBuf.Bytes()

	id := string(fhBytes[:4])
	frameSize, err := util.ParseSize(fhBytes[4:8])
	if err != nil {
		return header, err
	}

	if id == "" || frameSize == 0 {
		return header, errBlankFrame
	}

	header.ID = id
	header.FrameSize = frameSize
	return header, nil

}

func parseFrameBody(id string, rd io.Reader) (Framer, error) {
	if id[0] == 'T' {
		return parseTextFrame(rd)
	}

	if parseFunc, exists := parsers[id]; exists {
		return parseFunc(rd)
	}

	return parseUnknownFrame(rd)
}
<commit_msg>Get LimitReader from pool in parseAllFrames<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/bogem/id3v2/bbpool"
	"github.com/bogem/id3v2/lrpool"
	"github.com/bogem/id3v2/util"
)

const frameHeaderSize = 10

var errBlankFrame = errors.New("id or size of frame are blank")

type frameHeader struct {
	ID        string
	FrameSize int64
}

func parseTag(file *os.File) (*Tag, error) {
	if file == nil {
		err := errors.New("Invalid file: file is nil")
		return nil, err
	}

	header, err := parseHeader(file)
	if err == errNoTag {
		return newTag(file, 0, 4), nil
	}
	if err != nil {
		err = errors.New("trying to parse tag header: " + err.Error())
		return nil, err
	}
	if header.Version < 3 {
		err = errors.New(fmt.Sprint("unsupported version of ID3 tag: ", header.Version))
		return nil, err
	}

	t := newTag(file, tagHeaderSize+header.FramesSize, header.Version)
	err = t.parseAllFrames()

	return t, err
}

func newTag(file *os.File, originalSize int64, version byte) *Tag {
	return &Tag{
		frames:    make(map[string]Framer),
		sequences: make(map[string]*sequence),

		file:         file,
		originalSize: originalSize,
		version:      version,
	}
}

func (t *Tag) parseAllFrames() error {
	// Initial position of read - beginning of first frame
	if _, err := t.file.Seek(tagHeaderSize, os.SEEK_SET); err != nil {
		return err
	}

	framesSize := t.originalSize - tagHeaderSize
	fileReader := lrpool.Get()
	defer lrpool.Put(fileReader)
	fileReader.R = t.file
	fileReader.N = framesSize

	for {
		id, frame, err := parseFrame(fileReader)
		if err == io.EOF || err == errBlankFrame || err == util.ErrInvalidSizeFormat {
			break
		}
		if err != nil {
			return err
		}

		t.AddFrame(id, frame)
	}

	return nil
}

func parseFrame(rd io.Reader) (id string, frame Framer, err error) {
	header, err := parseFrameHeader(rd)
	if err != nil {
		return "", nil, err
	}
	id = header.ID

	frameRd := lrpool.Get()
	defer lrpool.Put(frameRd)
	frameRd.R = rd
	frameRd.N = header.FrameSize

	frame, err = parseFrameBody(id, frameRd)
	return id, frame, err
}

func parseFrameHeader(rd io.Reader) (frameHeader, error) {
	var header frameHeader

	// Limit the rd by frameHeaderSize, so fhBuf can read only 'frameHeaderSize'
	// bytes.
	headerRd := lrpool.Get()
	defer lrpool.Put(headerRd)
	headerRd.R = rd
	headerRd.N = frameHeaderSize

	fhBuf := bbpool.Get()
	defer bbpool.Put(fhBuf)

	_, err := fhBuf.ReadFrom(headerRd)
	if err != nil {
		return header, err
	}
	fhBytes := fhBuf.Bytes()

	id := string(fhBytes[:4])
	frameSize, err := util.ParseSize(fhBytes[4:8])
	if err != nil {
		return header, err
	}

	if id == "" || frameSize == 0 {
		return header, errBlankFrame
	}

	header.ID = id
	header.FrameSize = frameSize
	return header, nil

}

func parseFrameBody(id string, rd io.Reader) (Framer, error) {
	if id[0] == 'T' {
		return parseTextFrame(rd)
	}

	if parseFunc, exists := parsers[id]; exists {
		return parseFunc(rd)
	}

	return parseUnknownFrame(rd)
}
<|endoftext|>


<commit_before>package goku

// all the thing about route
// may be change route to interface ?

import (
    "fmt"
    "regexp"
    //"path"
    "github.com/QLeelulu/goku/utils"
)

var (
    regPathParse *regexp.Regexp = regexp.MustCompile("/?\\{[\\w\\-_]+\\}") // matched like this: /{controller}
)

// Route config
//      var rt = &Route {
//          Name: "default",
//          Pattern: "/{controller}/{action}/{id}",
//          Default: map[string]string { "controller": "home", "action": "index", "id": "0", },
//          Constraint: map[string]string { "id": "\\d+" }
//      }
// and then, must init the router
//      rt.Init()
// and then, you can use it
//      rt.Match("/home/index")
//
type Route struct {
    Name       string            // the router name
    Pattern    string            // url pattern config, eg. /{controller}/{action}/{id}
    Default    map[string]string // default value for Pattern
    Constraint map[string]string // constraint for Pattern, value is regexp str
    IsStatic   bool              // whether the route is for static file

    rePath *regexp.Regexp
    inited bool
}

func (router *Route) Init() {
    if router.inited {
        return
    }
    if router.Name == "" {
        panic("Route: Name must be set")
    }
    if router.Pattern == "" {
        panic("Route: Pattern must be set")
    }
    if router.Default == nil {
        router.Default = make(map[string]string)
    }
    if router.Constraint == nil {
        router.Constraint = make(map[string]string)
    }

    //  /{controller}/{action}/{id}
    //      => /(?P<controller>[^\?#/]+)/(?P<action>[^\?#/]+)/(?P<id>[^\?#/]+)?
    r := regPathParse.ReplaceAllStringFunc(router.Pattern, func(s string) string {
        slash := ""
        if s[0] == '/' {
            slash = "/"
            s = s[1:]
        }
        name, reg, need := s[1:len(s)-1], "", ""
        if v, ok := router.Constraint[name]; ok {
            reg = v
        } else {
            if name == "controller" || name == "action" {
                reg = "[^\\.\\?#/]+"
            } else {
                reg = "[^\\?#/]+"
            }
        }
        // if default value exist, it's options
        if _, ok := router.Default[name]; ok {
            need = "?"
            if slash != "" {
                //  / => /?
                slash = slash + "?"
            }
        }
        //(?P<name>re)
        return fmt.Sprintf("%s(?P<%s>%s)%s", slash, name, reg, need)
    })
    router.rePath = regexp.MustCompile("^" + r + "$")
    router.inited = true
}

func (router *Route) Match(url string) (rd *RouteData, matched bool) {
    if !router.inited {
        router.Init()
    }
    if router.IsStatic {
        rd, matched = router.matchStatic(url)
        if matched {
            return
        }
    }
    md, ok := utils.NamedRegexpGroup(url, router.rePath)
    if !ok {
        return
    }
    if md["controller"] == "" && router.Default["controller"] == "" {
        return
    }
    if md["action"] == "" && router.Default["action"] == "" {
        return
    }

    rd = new(RouteData)
    rd.Url = url
    rd.Route = router

    for k, v := range router.Default {
        if md[k] == "" {
            md[k] = v
        }
    }

    rd.Controller = md["controller"]
    rd.Action = md["action"]
    delete(md, "controller")
    delete(md, "action")
    rd.Params = md

    matched = true
    return
}

// static file route match
// if has group ,return group 1, else return the url
// e.g.
//		pattern: /static/.*  , url: /static/logo.gif , static path: /static/logo.gif
//		pattern: /static/(.*)  , url: /static/logo.gif , static path: logo.gif
func (route *Route) matchStatic(url string) (rd *RouteData, matched bool) {
    rst := route.rePath.FindStringSubmatch(url)
    lrst := len(rst)
    if lrst < 1 {
        return
    }
    rd = new(RouteData)
    rd.Url = url
    rd.Route = route
    if lrst > 1 {
        rd.FilePath = rst[1]
    } else {
        rd.FilePath = rst[0]
    }
    matched = true
    return
}

type RouteData struct {
    Url        string
    Route      *Route // is this field need ?
    Controller string
    Action     string
    Params     map[string]string
    FilePath   string // if is a static file route, this will be set
}

func (rd *RouteData) Get(name string) (val string, ok bool) {
    val, ok = rd.Params[name]
    return
}

type RouteTable struct {
    Routes []*Route
}

func (rt *RouteTable) Match(url string) (rd *RouteData, matched bool) {
    if url == "" {
        return
    }
    for _, router := range rt.Routes {
        if _rd, ok := router.Match(url); ok {
            rd = _rd
            matched = true
            break
        }
    }
    return
}

func (rt *RouteTable) AddRoute(route *Route) {
    route.Init()
    rt.Routes = append(rt.Routes, route)
}

// add a new route
// params:
//	+ name: route name
//  + url:  url pattern
//  + default: map[string]string, default value for url pattern
//  + constraint: map[string]string, constraint for url pattern
func (rt *RouteTable) Map(name string, url string, args ...interface{}) {
    var defaultData map[string]string
    var constraint map[string]string
    if len(args) > 0 {
        defaultData = args[0].(map[string]string)
    }
    if len(args) > 1 {
        constraint = args[1].(map[string]string)
    }

    route := &Route{
        Name:       name,
        Pattern:    url,
        Default:    defaultData,
        Constraint: constraint,
    }
    route.Init()
    rt.Routes = append(rt.Routes, route)
}

// static file route match
// if has group ,return group 1, else return the url
// e.g.
//		pattern: /static/.*  , url: /static/logo.gif , static path: /static/logo.gif
//		pattern: /static/(.*)  , url: /static/logo.gif , static path: logo.gif
func (rt *RouteTable) Static(name string, pattern string) {
    route := &Route{
        Name:     name,
        IsStatic: true,
        Pattern:  pattern,
    }
    route.Init()
    rt.Routes = append(rt.Routes, route)
}
<commit_msg>route<commit_after>package goku

// all the thing about route
// may be change route to interface ?

import (
    "fmt"
    "regexp"
    //"path"
    "github.com/QLeelulu/goku/utils"
)

var (
    regPathParse *regexp.Regexp = regexp.MustCompile("/?\\{[\\w\\-_]+\\}") // matched like this: /{controller}
)

// Route config
//      var rt = &Route {
//          Name: "default",
//          Pattern: "/{controller}/{action}/{id}",
//          Default: map[string]string { "controller": "home", "action": "index", "id": "0", },
//          Constraint: map[string]string { "id": "\\d+" }
//      }
// and then, must init the router
//      rt.Init()
// and then, you can use it
//      rt.Match("/home/index")
//
type Route struct {
    Name       string            // the router name
    Pattern    string            // url pattern config, eg. /{controller}/{action}/{id}
    Default    map[string]string // default value for Pattern
    Constraint map[string]string // constraint for Pattern, value is regexp str
    IsStatic   bool              // whether the route is for static file

    rePath *regexp.Regexp
    inited bool
}

func (router *Route) Init() {
    if router.inited {
        return
    }
    if router.Name == "" {
        panic("Route: Name must be set")
    }
    if router.Pattern == "" {
        panic("Route: Pattern must be set")
    }
    if router.Default == nil {
        router.Default = make(map[string]string)
    }
    if router.Constraint == nil {
        router.Constraint = make(map[string]string)
    }

    //  /{controller}/{action}/{id}
    //      => /(?P<controller>[^\?#/]+)/(?P<action>[^\?#/]+)/(?P<id>[^\?#/]+)?
    r := regPathParse.ReplaceAllStringFunc(router.Pattern, func(s string) string {
        slash := ""
        if s[0] == '/' {
            slash = "/"
            s = s[1:]
        }
        name, reg, need := s[1:len(s)-1], "", ""
        if v, ok := router.Constraint[name]; ok {
            reg = v
        } else {
            if name == "controller" || name == "action" {
                reg = "[^\\.\\?#/]+"
            } else {
                reg = "[^\\?#/]+"
            }
        }
        // if default value exist, it's options
        if _, ok := router.Default[name]; ok {
            need = "?"
            if slash != "" {
                //  / => /?
                slash = slash + "?"
            }
        }
        //(?P<name>re)
        return fmt.Sprintf("%s(?P<%s>%s)%s", slash, name, reg, need)
    })
    if r != "" && r[len(r)-1] == '/' {
        r = r + "?"
    }
    router.rePath = regexp.MustCompile("^" + r + "$")
    router.inited = true
}

func (router *Route) Match(url string) (rd *RouteData, matched bool) {
    if !router.inited {
        router.Init()
    }
    if router.IsStatic {
        rd, matched = router.matchStatic(url)
        if matched {
            return
        }
    }
    md, ok := utils.NamedRegexpGroup(url, router.rePath)
    if !ok {
        return
    }
    if md["controller"] == "" && router.Default["controller"] == "" {
        return
    }
    if md["action"] == "" && router.Default["action"] == "" {
        return
    }

    rd = new(RouteData)
    rd.Url = url
    rd.Route = router

    for k, v := range router.Default {
        if md[k] == "" {
            md[k] = v
        }
    }

    rd.Controller = md["controller"]
    rd.Action = md["action"]
    delete(md, "controller")
    delete(md, "action")
    rd.Params = md

    matched = true
    return
}

// static file route match
// if has group ,return group 1, else return the url
// e.g.
//		pattern: /static/.*  , url: /static/logo.gif , static path: /static/logo.gif
//		pattern: /static/(.*)  , url: /static/logo.gif , static path: logo.gif
func (route *Route) matchStatic(url string) (rd *RouteData, matched bool) {
    rst := route.rePath.FindStringSubmatch(url)
    lrst := len(rst)
    if lrst < 1 {
        return
    }
    rd = new(RouteData)
    rd.Url = url
    rd.Route = route
    if lrst > 1 {
        rd.FilePath = rst[1]
    } else {
        rd.FilePath = rst[0]
    }
    matched = true
    return
}

type RouteData struct {
    Url        string
    Route      *Route // is this field need ?
    Controller string
    Action     string
    Params     map[string]string
    FilePath   string // if is a static file route, this will be set
}

func (rd *RouteData) Get(name string) (val string, ok bool) {
    val, ok = rd.Params[name]
    return
}

type RouteTable struct {
    Routes []*Route
}

func (rt *RouteTable) Match(url string) (rd *RouteData, matched bool) {
    if url == "" {
        return
    }
    for _, router := range rt.Routes {
        if _rd, ok := router.Match(url); ok {
            rd = _rd
            matched = true
            break
        }
    }
    return
}

func (rt *RouteTable) AddRoute(route *Route) {
    route.Init()
    rt.Routes = append(rt.Routes, route)
}

// add a new route
// params:
//	+ name: route name
//  + url:  url pattern
//  + default: map[string]string, default value for url pattern
//  + constraint: map[string]string, constraint for url pattern
func (rt *RouteTable) Map(name string, url string, args ...interface{}) {
    var defaultData map[string]string
    var constraint map[string]string
    if len(args) > 0 {
        defaultData = args[0].(map[string]string)
    }
    if len(args) > 1 {
        constraint = args[1].(map[string]string)
    }

    route := &Route{
        Name:       name,
        Pattern:    url,
        Default:    defaultData,
        Constraint: constraint,
    }
    route.Init()
    rt.Routes = append(rt.Routes, route)
}

// static file route match
// if has group ,return group 1, else return the url
// e.g.
//		pattern: /static/.*  , url: /static/logo.gif , static path: /static/logo.gif
//		pattern: /static/(.*)  , url: /static/logo.gif , static path: logo.gif
func (rt *RouteTable) Static(name string, pattern string) {
    route := &Route{
        Name:     name,
        IsStatic: true,
        Pattern:  pattern,
    }
    route.Init()
    rt.Routes = append(rt.Routes, route)
}
<|endoftext|>


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

// mount_gcsfuse is a small helper for using gcsfuse with mount(8).
//
// mount_gcsfuse can be invoked using a command-line of the form expected for
// mount helpers. It calls the gcsfuse binary, which must be in $PATH, and
// waits for it to complete. The device is passed as --bucket, and other known
// options are converted to appropriate flags.
//
// mount_gcsfuse does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

import (
	"errors"
	"fmt"
	"log"
	"os"
)

// A 'name=value' mount option. If '=value' is not present, only the name will
// be filled in.
type Option struct {
	Name  string
	Value string
}

// Parse a single comma-separated list of mount options.
func parseOpts(s string) (opts []Option, err error) {
	err = errors.New("TODO: parseOpts")
	return
}

// Attempt to parse the terrible undocumented format that mount(8) gives us.
// Return the 'device' (aka 'special' on OS X), the mount point, and a list of
// mount options encountered.
func parseArgs() (device string, mountPoint string, opts []Option, err error) {
	// Example invocation on OS X:
	//
	//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
	//
	// becomes the following arguments:
	//
	//     Arg 0: "/path/to/mount_gcsfuse"
	//     Arg 1: "-o"
	//     Arg 2: "key_file=/some file.json"
	//     Arg 3: "-o"
	//     Arg 4: "ro"
	//     Arg 5: "-o"
	//     Arg 6: "blah"
	//     Arg 7: "bucket"
	//     Arg 8: "/path/to/mp"
	//
	// On Linux, the fstab entry
	//
	//     bucket /path/to/mp porp user,key_file=/some\040file.json
	//
	// becomes
	//
	//     Arg 0: "/path/to/mount_gcsfuse"
	//     Arg 1: "bucket"
	//     Arg 2: "/path/to/mp"
	//     Arg 3: "-o"
	//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
	//

	// Linux and OS X differ on the position of the options. So scan all
	// arguments (aside from the name of the binary), and:
	//
	//  *  Treat the first argument not following "-o" as the device name.
	//  *  Treat the second argument not following "-o" as the mount point.
	//  *  Treat the third argument not following "-o" as an error.
	//  *  Treat all arguments following "-o" as comma-separated options lists.
	//
	rawArgs := 0
	for i, arg := range os.Args[1:] {
		// Skip "-o"; we will look back on the next iteration.
		if arg == "-o" {
			continue
		}

		// If the previous argument was "-o", this is a list of options.
		if os.Args[i-1] == "-o" {
			var tmp []Option
			tmp, err = parseOpts(arg)
			if err != nil {
				err = fmt.Errorf("parseOpts(%q): %v", arg, err)
				return
			}

			opts = append(opts, tmp...)
			continue
		}

		// Otherwise, have we found too many arguments?
		if rawArgs > 2 {
			break
		}

		switch rawArgs {
		case 0:
			device = arg

		case 1:
			mountPoint = arg

		default:
			break
		}

		rawArgs++
	}

	// Did we see all of the raw arguments we expected?
	if rawArgs != 2 {
		err = fmt.Errorf("Expected 2 non-option arguments; got %d", rawArgs)
	}

	err = errors.New("TODO: parseArgs")
	return
}

func main() {
	// Print out each argument.
	//
	// TODO(jacobsa): Get rid of some or all of the debug logging.
	for i, arg := range os.Args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs()
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
		return
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for _, opt := range opts {
		log.Printf("Option %q: %q", opt.Name, opt.Value)
	}

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

// mount_gcsfuse is a small helper for using gcsfuse with mount(8).
//
// mount_gcsfuse can be invoked using a command-line of the form expected for
// mount helpers. It calls the gcsfuse binary, which must be in $PATH, and
// waits for it to complete. The device is passed as --bucket, and other known
// options are converted to appropriate flags.
//
// mount_gcsfuse does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

import (
	"fmt"
	"log"
	"os"
	"strings"
)

// A 'name=value' mount option. If '=value' is not present, only the name will
// be filled in.
type Option struct {
	Name  string
	Value string
}

// Parse a single comma-separated list of mount options.
func parseOpts(s string) (opts []Option, err error) {
	// NOTE(jacobsa): The man pages don't define how escaping works, and as far
	// as I can tell there is no way to properly escape or quote a comma in the
	// options list for an fstab entry. So put our fingers in our ears and hope
	// that nobody needs a comma.
	for _, p := range strings.Split(s, ",") {
		var opt Option

		// Split on the first equals sign.
		if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 {
			opt.Name = p[:equalsIndex]
			opt.Value = p[equalsIndex+1:]
		} else {
			opt.Name = p
		}

		opts = append(opts, opt)
	}

	return
}

// Attempt to parse the terrible undocumented format that mount(8) gives us.
// Return the 'device' (aka 'special' on OS X), the mount point, and a list of
// mount options encountered.
func parseArgs() (device string, mountPoint string, opts []Option, err error) {
	// Example invocation on OS X:
	//
	//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
	//
	// becomes the following arguments:
	//
	//     Arg 0: "/path/to/mount_gcsfuse"
	//     Arg 1: "-o"
	//     Arg 2: "key_file=/some file.json"
	//     Arg 3: "-o"
	//     Arg 4: "ro"
	//     Arg 5: "-o"
	//     Arg 6: "blah"
	//     Arg 7: "bucket"
	//     Arg 8: "/path/to/mp"
	//
	// On Linux, the fstab entry
	//
	//     bucket /path/to/mp porp user,key_file=/some\040file.json
	//
	// becomes
	//
	//     Arg 0: "/path/to/mount_gcsfuse"
	//     Arg 1: "bucket"
	//     Arg 2: "/path/to/mp"
	//     Arg 3: "-o"
	//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
	//

	// Linux and OS X differ on the position of the options. So scan all
	// arguments (aside from the name of the binary), and:
	//
	//  *  Treat the first argument not following "-o" as the device name.
	//  *  Treat the second argument not following "-o" as the mount point.
	//  *  Treat the third argument not following "-o" as an error.
	//  *  Treat all arguments following "-o" as comma-separated options lists.
	//
	rawArgs := 0
	for i, arg := range os.Args[1:] {
		// Skip "-o"; we will look back on the next iteration.
		if arg == "-o" {
			continue
		}

		// If the previous argument was "-o", this is a list of options.
		if os.Args[i-1] == "-o" {
			var tmp []Option
			tmp, err = parseOpts(arg)
			if err != nil {
				err = fmt.Errorf("parseOpts(%q): %v", arg, err)
				return
			}

			opts = append(opts, tmp...)
			continue
		}

		// Otherwise, have we found too many arguments?
		if rawArgs > 2 {
			break
		}

		switch rawArgs {
		case 0:
			device = arg

		case 1:
			mountPoint = arg

		default:
			break
		}

		rawArgs++
	}

	// Did we see all of the raw arguments we expected?
	if rawArgs != 2 {
		err = fmt.Errorf("Expected 2 non-option arguments; got %d", rawArgs)
	}

	return
}

func main() {
	// Print out each argument.
	//
	// TODO(jacobsa): Get rid of some or all of the debug logging.
	for i, arg := range os.Args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs()
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
		return
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for _, opt := range opts {
		log.Printf("Option %q: %q", opt.Name, opt.Value)
	}

	os.Exit(1)
}
<|endoftext|>