<commit_before>package types

import (
	"fmt"
	"time"
)

const (
	// DockerPrefix is the URL-like schema prefix used for Docker image references.
	DockerPrefix = "docker://"
)

// Registry is a service providing repositories.
type Registry interface {
	Repositories() []Repository
	Repository(ref string) Repository
	Lookup(term string) []Image // docker registry v1 only AFAICT, v2 can be built hacking with Images()
}

// Repository is a set of images.
type Repository interface {
	Images() []Image
	Image(ref string) Image // ref == image name w/o registry part
}

// Image is a Docker image in a repository.
type Image interface {
	// ref to repository?
	Layers(layers ...string) error // configure download directory? Call it DownloadLayers?
	Manifest() (ImageManifest, error)
	RawManifest(version string) ([]byte, error)
	DockerTar() ([]byte, error) // ??? also, configure output directory
}

// ImageManifest is the interesting subset of metadata about an Image.
// TODO(runcom)
type ImageManifest interface {
	String() string
}

// DockerImageManifest is a set of metadata describing Docker images and their manifest.json files.
// Note that this is not exactly manifest.json, e.g. some fields have been added.
type DockerImageManifest struct {
	Name          string
	Tag           string
	Digest        string
	RepoTags      []string
	Created       time.Time
	DockerVersion string
	Labels        map[string]string
	Architecture  string
	Os            string
	Layers        []string
}

func (m *DockerImageManifest) String() string {
	return fmt.Sprintf("%s:%s", m.Name, m.Tag)
}
<commit_msg>Add types.ImageSource and types.ImageDestination<commit_after>package types

import (
	"fmt"
	"io"
	"time"
)

const (
	// DockerPrefix is the URL-like schema prefix used for Docker image references.
	DockerPrefix = "docker://"
)

// Registry is a service providing repositories.
type Registry interface {
	Repositories() []Repository
	Repository(ref string) Repository
	Lookup(term string) []Image // docker registry v1 only AFAICT, v2 can be built hacking with Images()
}

// Repository is a set of images.
type Repository interface {
	Images() []Image
	Image(ref string) Image // ref == image name w/o registry part
}

// ImageSource is a service, possibly remote (= slow), to download components of a single image.
type ImageSource interface {
	GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error)
	GetLayer(digest string) (io.ReadCloser, error)
	GetSignatures() ([][]byte, error)
}

// ImageDestination is a service, possibly remote (= slow), to store components of a single image.
type ImageDestination interface {
	PutManifest([]byte) error
	PutLayer(digest string, stream io.Reader) error
	PutSignatures(signatures [][]byte) error
}

// Image is a Docker image in a repository.
type Image interface {
	// ref to repository?
	Layers(layers ...string) error // configure download directory? Call it DownloadLayers?
	Manifest() (ImageManifest, error)
	RawManifest(version string) ([]byte, error)
	DockerTar() ([]byte, error) // ??? also, configure output directory
}

// ImageManifest is the interesting subset of metadata about an Image.
// TODO(runcom)
type ImageManifest interface {
	String() string
}

// DockerImageManifest is a set of metadata describing Docker images and their manifest.json files.
// Note that this is not exactly manifest.json, e.g. some fields have been added.
type DockerImageManifest struct {
	Name          string
	Tag           string
	Digest        string
	RepoTags      []string
	Created       time.Time
	DockerVersion string
	Labels        map[string]string
	Architecture  string
	Os            string
	Layers        []string
}

func (m *DockerImageManifest) String() string {
	return fmt.Sprintf("%s:%s", m.Name, m.Tag)
}
<|endoftext|>


<commit_before>package sup

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"

	"github.com/goware/prefixer"
	"golang.org/x/crypto/ssh"
)

const VERSION = "0.4"

type Stackup struct {
	conf *Supfile
}

func New(conf *Supfile) (*Stackup, error) {
	return &Stackup{
		conf: conf,
	}, nil
}

// Run runs set of commands on multiple hosts defined by network sequentially.
// TODO: This megamoth method needs a big refactor and should be split
//       to multiple smaller methods.
func (sup *Stackup) Run(network *Network, commands ...*Command) error {
	if len(commands) == 0 {
		return errors.New("no commands to be run")
	}

	// Process all ENVs into a string of form
	// `export FOO="bar"; export BAR="baz";`.
	env := ``
	for _, v := range append(sup.conf.Env, network.Env...) {
		env += v.AsExport() + " "
	}

	// Create clients for every host (either SSH or Localhost).
	var (
		clients []Client
		bastion *SSHClient
	)

	if network.Bastion != "" {
		bastion = &SSHClient{}
		if err := bastion.Connect(network.Bastion); err != nil {
			return err
		}
	}

	for i, host := range network.Hosts {
		// Localhost client.
		if host == "localhost" {
			local := &LocalhostClient{
				env: env + `export SUP_HOST="` + host + `";`,
			}
			if err := local.Connect(host); err != nil {
				return err
			}
			clients = append(clients, local)
			continue
		}

		// SSH client.
		remote := &SSHClient{
			env:   env + `export SUP_HOST="` + host + `";`,
			color: Colors[i%len(Colors)],
		}

		if bastion != nil {
			if err := remote.ConnectWith(host, bastion.DialThrough); err != nil {
				return err
			}
		} else {
			if err := remote.Connect(host); err != nil {
				return err
			}
		}
		defer remote.Close()
		clients = append(clients, remote)
	}

	maxLen := 0
	for _, c := range clients {
		_, prefixLen := c.Prefix()
		if prefixLen > maxLen {
			maxLen = prefixLen
		}
	}

	// Run command or run multiple commands defined by target sequentially.
	for _, cmd := range commands {
		// Translate command into task(s).
		tasks, err := CreateTasks(cmd, clients, env)
		if err != nil {
			return fmt.Errorf("CreateTasks(): %s", err)
		}

		// Run tasks sequentially.
		for _, task := range tasks {
			var writers []io.Writer
			var wg sync.WaitGroup

			// Run tasks on the provided clients.
			for _, c := range task.Clients {
				prefix, prefixLen := c.Prefix()
				if len(prefix) < maxLen { // Left padding.
					prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
				}

				err := c.Run(task)
				if err != nil {
					return fmt.Errorf("%s%v", prefix, err)
				}

				// Copy over tasks's STDOUT.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stdout, prefixer.New(c.Stdout(), prefix))
					if err != nil && err != io.EOF {
						// TODO: io.Copy() should not return io.EOF at all.
						// Upstream bug? Or prefixer.WriteTo() bug?
						fmt.Fprintf(os.Stderr, "%sSTDOUT: %v", prefix, err)
					}
				}(c)

				// Copy over tasks's STDERR.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stderr, prefixer.New(c.Stderr(), prefix))
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "%sSTDERR: %v", prefix, err)
					}
				}(c)

				writers = append(writers, c.Stdin())
			}

			// Copy over task's STDIN.
			if task.Input != nil {
				go func() {
					writer := io.MultiWriter(writers...)
					_, err := io.Copy(writer, task.Input)
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "STDIN: %v", err)
					}
					// TODO: Use MultiWriteCloser (not in Stdlib), so we can writer.Close() instead?
					for _, c := range clients {
						c.WriteClose()
					}
				}()
			}

			// Wait for all I/O operations first.
			wg.Wait()

			// Make sure each client finishes the task, return on failure.
			for _, c := range task.Clients {
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					if err := c.Wait(); err != nil {
						prefix, prefixLen := c.Prefix()
						if len(prefix) < maxLen { // Left padding.
							prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
						}
						if e, ok := err.(*ssh.ExitError); ok && e.ExitStatus() != 15 {
							// TODO: Store all the errors, and print them after Wait().
							fmt.Fprintf(os.Stderr, "%sexit %v\n", prefix, e.ExitStatus())
							os.Exit(e.ExitStatus())
						}
						fmt.Fprintf(os.Stderr, "%s%v\n", prefix, err)
						os.Exit(1)
					}
				}(c)
			}

			// Wait for all commands to finish.
			wg.Wait()
		}
	}

	return nil
}
<commit_msg>Connect to all hosts in parallel<commit_after>package sup

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"

	"github.com/goware/prefixer"
	"golang.org/x/crypto/ssh"
)

const VERSION = "0.4"

type Stackup struct {
	conf *Supfile
}

func New(conf *Supfile) (*Stackup, error) {
	return &Stackup{
		conf: conf,
	}, nil
}

// Run runs set of commands on multiple hosts defined by network sequentially.
// TODO: This megamoth method needs a big refactor and should be split
//       to multiple smaller methods.
func (sup *Stackup) Run(network *Network, commands ...*Command) error {
	if len(commands) == 0 {
		return errors.New("no commands to be run")
	}

	// Process all ENVs into a string of form
	// `export FOO="bar"; export BAR="baz";`.
	env := ``
	for _, v := range append(sup.conf.Env, network.Env...) {
		env += v.AsExport() + " "
	}

	// Create clients for every host (either SSH or Localhost).
	var bastion *SSHClient
	if network.Bastion != "" {
		bastion = &SSHClient{}
		if err := bastion.Connect(network.Bastion); err != nil {
			return err
		}
	}

	var wg sync.WaitGroup
	clientCh := make(chan Client, len(network.Hosts))
	errCh := make(chan error, len(network.Hosts))

	for i, host := range network.Hosts {
		wg.Add(1)
		go func(i int, host string) {
			defer wg.Done()

			// Localhost client.
			if host == "localhost" {
				local := &LocalhostClient{
					env: env + `export SUP_HOST="` + host + `";`,
				}
				if err := local.Connect(host); err != nil {
					errCh <- err
					return
				}
				clientCh <- local
				return
			}

			// SSH client.
			remote := &SSHClient{
				env:   env + `export SUP_HOST="` + host + `";`,
				color: Colors[i%len(Colors)],
			}

			if bastion != nil {
				if err := remote.ConnectWith(host, bastion.DialThrough); err != nil {
					errCh <- err
					return
				}
			} else {
				if err := remote.Connect(host); err != nil {
					errCh <- err
					return
				}
			}
			clientCh <- remote
		}(i, host)
	}
	wg.Wait()
	close(clientCh)
	close(errCh)

	maxLen := 0
	var clients []Client
	for client := range clientCh {
		if remote, ok := client.(*SSHClient); ok {
			defer remote.Close()
		}
		_, prefixLen := client.Prefix()
		if prefixLen > maxLen {
			maxLen = prefixLen
		}
		clients = append(clients, client)
	}
	for err := range errCh {
		return err
	}

	// Run command or run multiple commands defined by target sequentially.
	for _, cmd := range commands {
		// Translate command into task(s).
		tasks, err := CreateTasks(cmd, clients, env)
		if err != nil {
			return fmt.Errorf("CreateTasks(): %s", err)
		}

		// Run tasks sequentially.
		for _, task := range tasks {
			var writers []io.Writer
			var wg sync.WaitGroup

			// Run tasks on the provided clients.
			for _, c := range task.Clients {
				prefix, prefixLen := c.Prefix()
				if len(prefix) < maxLen { // Left padding.
					prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
				}

				err := c.Run(task)
				if err != nil {
					return fmt.Errorf("%s%v", prefix, err)
				}

				// Copy over tasks's STDOUT.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stdout, prefixer.New(c.Stdout(), prefix))
					if err != nil && err != io.EOF {
						// TODO: io.Copy() should not return io.EOF at all.
						// Upstream bug? Or prefixer.WriteTo() bug?
						fmt.Fprintf(os.Stderr, "%sSTDOUT: %v", prefix, err)
					}
				}(c)

				// Copy over tasks's STDERR.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stderr, prefixer.New(c.Stderr(), prefix))
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "%sSTDERR: %v", prefix, err)
					}
				}(c)

				writers = append(writers, c.Stdin())
			}

			// Copy over task's STDIN.
			if task.Input != nil {
				go func() {
					writer := io.MultiWriter(writers...)
					_, err := io.Copy(writer, task.Input)
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "STDIN: %v", err)
					}
					// TODO: Use MultiWriteCloser (not in Stdlib), so we can writer.Close() instead?
					for _, c := range clients {
						c.WriteClose()
					}
				}()
			}

			// Wait for all I/O operations first.
			wg.Wait()

			// Make sure each client finishes the task, return on failure.
			for _, c := range task.Clients {
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					if err := c.Wait(); err != nil {
						prefix, prefixLen := c.Prefix()
						if len(prefix) < maxLen { // Left padding.
							prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
						}
						if e, ok := err.(*ssh.ExitError); ok && e.ExitStatus() != 15 {
							// TODO: Store all the errors, and print them after Wait().
							fmt.Fprintf(os.Stderr, "%sexit %v\n", prefix, e.ExitStatus())
							os.Exit(e.ExitStatus())
						}
						fmt.Fprintf(os.Stderr, "%s%v\n", prefix, err)
						os.Exit(1)
					}
				}(c)
			}

			// Wait for all commands to finish.
			wg.Wait()
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"strings"
	"sync/atomic"
	"unsafe"

	r "github.com/dancannon/gorethink"
	"github.com/jaracil/ei"
)

func (nc *NexusConn) handleSysReq(req *JsonRpcReq) {
	switch req.Method {
	case "sys.ping":
		req.Result("pong")
	case "sys.watchdog":
		wdt := ei.N(req.Params).Int64Z()
		if wdt < 10 {
			wdt = 10
		}
		atomic.StoreInt64(&nc.wdog, wdt)
		req.Result(ei.M{"ok": true, "watchdog": wdt})
	case "sys.login":
		user, err := ei.N(req.Params).M("user").String()
		if err != nil {
			req.Error(ErrInvalidParams, "user", nil)
			return
		}
		pass, err := ei.N(req.Params).M("pass").String()
		if err != nil {
			req.Error(ErrInvalidParams, "pass", nil)
			return
		}
		var suser string
		split := strings.Split(user, ">")
		switch len(split) {
		case 1:
		case 2:
			if len(split[0]) > 0 && len(split[1]) > 0 {
				user = split[0]
				suser = split[1]
			} else {
				req.Error(ErrInvalidParams, "", nil)
				return
			}
		default:
			req.Error(ErrInvalidParams, "", nil)
			return
		}
		ud := &UserData{}
		cur, err := r.Table("users").Get(strings.ToLower(user)).Run(db)
		if err != nil {
			req.Error(ErrInternal, "", nil)
			return
		}
		defer cur.Close()
		err = cur.One(ud)
		if err != nil {
			if err == r.ErrEmptyResult {
				req.Error(ErrPermissionDenied, "", nil)
				return
			}
			req.Error(ErrInternal, "", nil)
			return
		}
		dk, err := HashPass(pass, ud.Salt)
		if err != nil {
			req.Error(ErrInternal, "", nil)
			return
		}
		if ud.Pass != dk {
			req.Error(ErrPermissionDenied, "", nil)
			return
		}
		atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&nc.user)), unsafe.Pointer(ud))

		if suser != "" {
			tags := nc.getTags(suser)
			if !(ei.N(tags).M("@admin").BoolZ()) {
				req.Error(ErrPermissionDenied, "", nil)
				return
			}
			sud := &UserData{}
			scur, err := r.Table("users").Get(strings.ToLower(suser)).Run(db)
			if err != nil {
				req.Error(ErrInternal, "", nil)
				return
			}
			defer scur.Close()
			err = scur.One(sud)
			if err != nil {
				if err == r.ErrEmptyResult {
					req.Error(ErrPermissionDenied, "", nil)
					return
				}
				req.Error(ErrInternal, "", nil)
				return
			}
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&nc.user)), unsafe.Pointer(sud))
		}
		req.Result(ei.M{"ok": true, "user": nc.user.User})
	default:
		req.Error(ErrMethodNotFound, "", nil)
	}
}
<commit_msg>Get stats on tasks push/pulls for a prefix<commit_after>package main

import (
	"strings"
	"sync/atomic"
	"unsafe"

	r "github.com/dancannon/gorethink"
	"github.com/jaracil/ei"
)

func (nc *NexusConn) handleSysReq(req *JsonRpcReq) {
	switch req.Method {
	case "sys.ping":
		req.Result("pong")
	case "sys.watchdog":
		wdt := ei.N(req.Params).Int64Z()
		if wdt < 10 {
			wdt = 10
		}
		atomic.StoreInt64(&nc.wdog, wdt)
		req.Result(ei.M{"ok": true, "watchdog": wdt})
	case "sys.login":
		user, err := ei.N(req.Params).M("user").String()
		if err != nil {
			req.Error(ErrInvalidParams, "user", nil)
			return
		}
		pass, err := ei.N(req.Params).M("pass").String()
		if err != nil {
			req.Error(ErrInvalidParams, "pass", nil)
			return
		}
		var suser string
		split := strings.Split(user, ">")
		switch len(split) {
		case 1:
		case 2:
			if len(split[0]) > 0 && len(split[1]) > 0 {
				user = split[0]
				suser = split[1]
			} else {
				req.Error(ErrInvalidParams, "", nil)
				return
			}
		default:
			req.Error(ErrInvalidParams, "", nil)
			return
		}
		ud := &UserData{}
		cur, err := r.Table("users").Get(strings.ToLower(user)).Run(db)
		if err != nil {
			req.Error(ErrInternal, "", nil)
			return
		}
		defer cur.Close()
		err = cur.One(ud)
		if err != nil {
			if err == r.ErrEmptyResult {
				req.Error(ErrPermissionDenied, "", nil)
				return
			}
			req.Error(ErrInternal, "", nil)
			return
		}
		dk, err := HashPass(pass, ud.Salt)
		if err != nil {
			req.Error(ErrInternal, "", nil)
			return
		}
		if ud.Pass != dk {
			req.Error(ErrPermissionDenied, "", nil)
			return
		}
		atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&nc.user)), unsafe.Pointer(ud))

		if suser != "" {
			tags := nc.getTags(suser)
			if !(ei.N(tags).M("@admin").BoolZ()) {
				req.Error(ErrPermissionDenied, "", nil)
				return
			}
			sud := &UserData{}
			scur, err := r.Table("users").Get(strings.ToLower(suser)).Run(db)
			if err != nil {
				req.Error(ErrInternal, "", nil)
				return
			}
			defer scur.Close()
			err = scur.One(sud)
			if err != nil {
				if err == r.ErrEmptyResult {
					req.Error(ErrPermissionDenied, "", nil)
					return
				}
				req.Error(ErrInternal, "", nil)
				return
			}
			atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&nc.user)), unsafe.Pointer(sud))
		}
		req.Result(ei.M{"ok": true, "user": nc.user.User})

	case "sys.stats":
		prefix, err := ei.N(req.Params).M("prefix").String()
		if err != nil {
			req.Error(ErrInvalidParams, "prefix", nil)
			return
		}
		tags := nc.getTags(prefix)
		if !(ei.N(tags).M("@admin").BoolZ()) {
			req.Error(ErrPermissionDenied, "", nil)
			return
		}
		cur, err := r.Table("tasks").Pluck("path").Run(db)
		if err != nil {
			req.Error(ErrInternal, "", nil)
			return
		}
		pulls := make(map[string]int)
		pushs := make(map[string]int)
		var task Task
		for cur.Next(&task) {
			if strings.HasPrefix(task.Path, "@pull."+prefix) {
				p := strings.TrimPrefix(task.Path, "@pull.")
				pulls[p]++
			} else if strings.HasPrefix(task.Path, prefix) {
				pushs[task.Path]++
			}
		}
		ret := make(map[string]interface{})
		ret["pulls"] = pulls
		ret["pushes"] = pushs
		req.Result(ret)

	default:
		req.Error(ErrMethodNotFound, "", nil)
	}
}
<|endoftext|>


<commit_before>package have

import (
	"fmt"
	"unicode"
)

type TokenType int

type Token struct {
	Type   TokenType
	Offset int
	Value  interface{}
}

// Tells if a token is any of the comparison operators.
func (t *Token) IsCompOp() bool {
	switch t.Type {
	case TOKEN_EQUALS, TOKEN_NEQUALS,
		TOKEN_LT, TOKEN_GT,
		TOKEN_EQ_LT, TOKEN_EQ_GT:
		return true
	}
	return false
}

// Tells if a token is any of the order operators.
func (t *Token) IsOrderOp() bool {
	switch t.Type {
	case TOKEN_LT, TOKEN_GT,
		TOKEN_EQ_LT, TOKEN_EQ_GT:
		return true
	}
	return false
}

// Tells if operator's operands can only be boolean.
func (t *Token) IsLogicalOp() bool {
	switch t.Type {
	case TOKEN_AND, TOKEN_OR:
		return true
	}
	return false
}

//go:generate stringer -type=TokenType
const (
	TOKEN_EOF          TokenType = iota + 1
	TOKEN_INDENT                 // indent - []rune of whitespace characters
	TOKEN_FOR                    // the "for" keyword
	TOKEN_WORD                   // alphanumeric word, starts witn a letter
	TOKEN_ASSIGN                 // =
	TOKEN_EQUALS                 // ==
	TOKEN_NEQUALS                // !=
	TOKEN_GT                     // >
	TOKEN_LT                     // <
	TOKEN_EQ_LT                  // <=
	TOKEN_EQ_GT                  // >=
	TOKEN_NEGATE                 // !
	TOKEN_NUM                    // general token for all number literals
	TOKEN_STR                    // string literal
	TOKEN_DOT                    // .
	TOKEN_LPARENTH               // (
	TOKEN_RPARENTH               // )
	TOKEN_LBRACKET               // [
	TOKEN_RBRACKET               // ]
	TOKEN_LBRACE                 // {
	TOKEN_RBRACE                 // }
	TOKEN_PLUS                   // +
	TOKEN_PLUS_ASSIGN            // +=
	TOKEN_INCREMENT              // ++
	TOKEN_MINUS                  // -
	TOKEN_MINUS_ASSIGN           // -=
	TOKEN_DECREMENT              // --
	TOKEN_VAR                    // the "var" keyword
	TOKEN_IF                     // the "if" keyword
	TOKEN_ELSE                   // the "else" keyword
	TOKEN_ELIF                   // the "elif" keyword
	TOKEN_SWITCH                 // the "switch" keyword
	TOKEN_CASE                   // the "case" keyword
	TOKEN_RETURN                 // the "return" keyword
	TOKEN_TRUE                   // the "true" keyword
	TOKEN_FALSE                  // the "false" keyword
	TOKEN_STRUCT                 // the "struct" keyword
	TOKEN_MAP                    // the "map" keyword
	TOKEN_FUNC                   // the "func" keyword
	TOKEN_TYPE                   // the "type" keyword
	TOKEN_IN                     // the "in" keyword
	TOKEN_PASS                   // the "pass" keyword
	TOKEN_PACKAGE                // the "package" keyword
	TOKEN_BREAK                  // the "break" keyword
	TOKEN_CONTINUE               // the "continue" keyword
	TOKEN_FALLTHROUGH            // the "fallthrough" keyword
	TOKEN_GOTO                   // the "goto" keyword
	TOKEN_INTERFACE              // the "interface" keyword
	TOKEN_NIL                    // the "nil" keyword
	TOKEN_MUL                    // *
	TOKEN_DIV                    // /
	TOKEN_MUL_ASSIGN             // *=
	TOKEN_DIV_ASSIGN             // /=
	TOKEN_SHL                    // <<
	TOKEN_SHR                    // >>
	TOKEN_SEND                   // <-
	TOKEN_COMMA                  // ,
	TOKEN_COLON                  // :
	TOKEN_SEMICOLON              // ;
	TOKEN_AMP                    // &
	TOKEN_PIPE                   // |
	TOKEN_PERCENT                // %
	TOKEN_AND                    // &&
	TOKEN_OR                     // ||
)

type Lexer struct {
	// Characters not processed yet.
	buf []rune
	// Stack of opened indents.
	indentsStack []int
	// We don't want to emit indent tokens for blank lines,
	// so we need to postpone indent tokens for a while.
	tokenIndent *Token
	// How many characters we've processed.
	skipped int
	// Offset of currently processed token.
	curTokenPos int
}

func NewLexer(buf []rune) *Lexer {
	return &Lexer{buf: buf, indentsStack: []int{}}
}

// Advance lexer's buffer by skipping whitespace, except newlines.
func (l *Lexer) skipWhiteChars() []rune {
	i := 0
	for i < len(l.buf) && (unicode.IsSpace(l.buf[i]) && l.buf[i] != '\n') {
		i++
	}
	whitespace := l.buf[:i]
	l.skipBy(i)
	return whitespace
}

// Read an alphanumeric word from the buffer, advancing it.
func (l *Lexer) scanWord() []rune {
	i := 0
	for i < len(l.buf) && (unicode.IsLetter(l.buf[i]) || unicode.IsNumber(l.buf[i])) {
		i++
	}

	result := l.buf[:i]
	l.skipBy(i)
	return result
}

// Advance lexer's buffer by one character.
func (l *Lexer) skip() {
	l.skipped++
	l.buf = l.buf[1:]
}

// Advance lexer's buffer by N characters.
func (l *Lexer) skipBy(n int) {
	l.skipped += n
	l.buf = l.buf[n:]
}

// Tells if we've reached the end of the buffer.
func (l *Lexer) isEnd() bool {
	return len(l.buf) == 0
}

// Check which token is currently at the beginning of the buffer.
// Can be used to decide between tokens with the same beginning, e.g.
// "=", "==", "=<", ">=".
// Returns the first token matched, NOT the longest one, so order matters.
// E.g. instead of "=", "=="; rather use "==", "=".
func (l *Lexer) checkAlt(alts ...string) (alt string, ok bool) {
	for _, alt := range alts {
		if string(l.buf[:len(alt)]) == alt {
			l.skipBy(len(alt))
			return alt, true
		}
	}
	return "", false
}

func (l *Lexer) loadEscapedString() (string, error) {
	if len(l.buf) == 0 || l.buf[0] != '"' {
		return "", fmt.Errorf("String literal has to start with a double quote")
	}

	l.skip()

	i := 0
	for ; i < len(l.buf); i++ {
		switch l.buf[i] {
		case '\\':
			i++
			if i == len(l.buf) {
				return "", fmt.Errorf("Unexpected file end - middle of a string literal")
			}
		case '"':
			s := string(l.buf[:i])
			l.skipBy(i + 1)
			return s, nil
		}
	}
	return "", fmt.Errorf("Unterminated string literal")
}

func (l *Lexer) newToken(typ TokenType, val interface{}) *Token {
	return &Token{Type: typ, Offset: l.curTokenPos, Value: val}
}

// A convenience wrapper for newToken, handy in situations when a token
// is created just to be immediately returned with a nil error.
func (l *Lexer) retNewToken(typ TokenType, val interface{}) (*Token, error) {
	return l.newToken(typ, val), nil
}

func (l *Lexer) Next() (*Token, error) {
	l.curTokenPos = l.skipped

	if l.isEnd() || l.buf[0] != '\n' {
		if l.tokenIndent != nil {
			t := l.tokenIndent
			l.tokenIndent = nil
			return t, nil
		}
	}

	if l.isEnd() {
		return l.retNewToken(TOKEN_EOF, nil)
	}

	ch := l.buf[0]

	switch {
	case ch == '\n':
		l.skip()
		indent := string(l.skipWhiteChars())
		l.tokenIndent = l.newToken(TOKEN_INDENT, indent)
		return l.Next()
	case unicode.IsSpace(ch):
		l.skipWhiteChars()
		return l.Next()
	case unicode.IsLetter(ch):
		word := l.scanWord()
		switch s := string(word); s {
		case "for":
			return l.retNewToken(TOKEN_FOR, nil)
		case "pass":
			return l.retNewToken(TOKEN_PASS, nil)
		case "package":
			return l.retNewToken(TOKEN_PACKAGE, nil)
		case "var":
			return l.retNewToken(TOKEN_VAR, nil)
		case "if":
			return l.retNewToken(TOKEN_IF, nil)
		case "else":
			return l.retNewToken(TOKEN_ELSE, nil)
		case "elif":
			return l.retNewToken(TOKEN_ELIF, nil)
		case "switch":
			return l.retNewToken(TOKEN_SWITCH, nil)
		case "case":
			return l.retNewToken(TOKEN_CASE, nil)
		case "return", "ret":
			return l.retNewToken(TOKEN_RETURN, nil)
		case "true":
			return l.retNewToken(TOKEN_TRUE, nil)
		case "false":
			return l.retNewToken(TOKEN_FALSE, nil)
		case "struct":
			return l.retNewToken(TOKEN_STRUCT, nil)
		case "interface":
			return l.retNewToken(TOKEN_INTERFACE, nil)
		case "map":
			return l.retNewToken(TOKEN_MAP, nil)
		case "func":
			return l.retNewToken(TOKEN_FUNC, nil)
		case "type":
			return l.retNewToken(TOKEN_TYPE, nil)
		case "break":
			return l.retNewToken(TOKEN_BREAK, nil)
		case "continue":
			return l.retNewToken(TOKEN_CONTINUE, nil)
		case "fallthrough":
			return l.retNewToken(TOKEN_FALLTHROUGH, nil)
		case "goto":
			return l.retNewToken(TOKEN_GOTO, nil)
		case "nil":
			return l.retNewToken(TOKEN_NIL, nil)
		default:
			return l.retNewToken(TOKEN_WORD, s)
		}
	case ch == '=':
		alt, _ := l.checkAlt("==", "=")
		switch alt {
		case "=":
			return l.retNewToken(TOKEN_ASSIGN, alt)
		case "==":
			return l.retNewToken(TOKEN_EQUALS, alt)
		}
	case ch == '!':
		alt, _ := l.checkAlt("!=", "!")
		switch alt {
		case "!=":
			return l.retNewToken(TOKEN_NEQUALS, alt)
		case "!":
			return l.retNewToken(TOKEN_NEGATE, alt)
		}
	case ch == '+':
		alt, _ := l.checkAlt("++", "+=", "+")
		switch alt {
		case "+":
			return l.retNewToken(TOKEN_PLUS, alt)
		case "+=":
			return l.retNewToken(TOKEN_PLUS_ASSIGN, alt)
		case "++":
			return l.retNewToken(TOKEN_INCREMENT, alt)
		}
	case ch == '-':
		alt, _ := l.checkAlt("--", "-=", "-")
		switch alt {
		case "-":
			return l.retNewToken(TOKEN_MINUS, alt)
		case "-=":
			return l.retNewToken(TOKEN_MINUS_ASSIGN, alt)
		case "--":
			return l.retNewToken(TOKEN_DECREMENT, alt)
		}
	case ch == '<':
		alt, _ := l.checkAlt("<<", "<-", "<=", "<")
		switch alt {
		case "<":
			return l.retNewToken(TOKEN_LT, alt)
		case "<-":
			return l.retNewToken(TOKEN_SEND, alt)
		case "<<":
			return l.retNewToken(TOKEN_SHL, alt)
		case "<=":
			return l.retNewToken(TOKEN_EQ_LT, alt)
		}
	case ch == '>':
		alt, _ := l.checkAlt(">>", ">=", ">")
		switch alt {
		case ">":
			return l.retNewToken(TOKEN_GT, alt)
		case ">>":
			return l.retNewToken(TOKEN_SHR, alt)
		case ">=":
			return l.retNewToken(TOKEN_EQ_GT, alt)
		}
	case unicode.IsNumber(ch):
		word := l.scanWord()
		return l.retNewToken(TOKEN_NUM, string(word))
	case ch == '"':
		str, err := l.loadEscapedString()
		if err != nil {
			return nil, err
		}
		return l.retNewToken(TOKEN_STR, str)
	case ch == '(':
		l.skip()
		return l.retNewToken(TOKEN_LPARENTH, nil)
	case ch == ')':
		l.skip()
		return l.retNewToken(TOKEN_RPARENTH, nil)
	case ch == '[':
		l.skip()
		return l.retNewToken(TOKEN_LBRACKET, nil)
	case ch == ']':
		l.skip()
		return l.retNewToken(TOKEN_RBRACKET, nil)
	case ch == '{':
		l.skip()
		return l.retNewToken(TOKEN_LBRACE, nil)
	case ch == '}':
		l.skip()
		return l.retNewToken(TOKEN_RBRACE, nil)
	case ch == '.':
		l.skip()
		return l.retNewToken(TOKEN_DOT, nil)
	case ch == '*':
		alt, _ := l.checkAlt("*=", "*")
		switch alt {
		case "*":
			return l.retNewToken(TOKEN_MUL, alt)
		case "*=":
			return l.retNewToken(TOKEN_MUL_ASSIGN, alt)
		}
	case ch == '/':
		alt, _ := l.checkAlt("/=", "/")
		switch alt {
		case "/":
			return l.retNewToken(TOKEN_DIV, alt)
		case "/=":
			return l.retNewToken(TOKEN_DIV_ASSIGN, alt)
		}
	case ch == ',':
		l.skip()
		return l.retNewToken(TOKEN_COMMA, nil)
	case ch == ';':
		l.skip()
		return l.retNewToken(TOKEN_SEMICOLON, nil)
	case ch == ':':
		l.skip()
		return l.retNewToken(TOKEN_COLON, nil)
	case ch == '%':
		l.skip()
		return l.retNewToken(TOKEN_PERCENT, "%")
	case ch == '&':
		alt, _ := l.checkAlt("&&", "&")
		switch alt {
		case "&&":
			return l.retNewToken(TOKEN_AND, alt)
		case "&":
			return l.retNewToken(TOKEN_AMP, alt)
		}
	case ch == '|':
		alt, _ := l.checkAlt("||", "|")
		switch alt {
		case "||":
			return l.retNewToken(TOKEN_OR, alt)
		case "|":
			return l.retNewToken(TOKEN_PIPE, alt)
		}
	}

	return nil, fmt.Errorf("Don't know what to do, '%c'", ch)
}
<commit_msg>Use Go's scanner to parse number literals<commit_after>package have

import (
	"fmt"
	"unicode"

	goscanner "go/scanner"
	gotoken "go/token"
)

type TokenType int

type Token struct {
	Type   TokenType
	Offset int
	Value  interface{}
}

// Tells if a token is any of the comparison operators.
func (t *Token) IsCompOp() bool {
	switch t.Type {
	case TOKEN_EQUALS, TOKEN_NEQUALS,
		TOKEN_LT, TOKEN_GT,
		TOKEN_EQ_LT, TOKEN_EQ_GT:
		return true
	}
	return false
}

// Tells if a token is any of the order operators.
func (t *Token) IsOrderOp() bool {
	switch t.Type {
	case TOKEN_LT, TOKEN_GT,
		TOKEN_EQ_LT, TOKEN_EQ_GT:
		return true
	}
	return false
}

// Tells if operator's operands can only be boolean.
func (t *Token) IsLogicalOp() bool {
	switch t.Type {
	case TOKEN_AND, TOKEN_OR:
		return true
	}
	return false
}

//go:generate stringer -type=TokenType
const (
	TOKEN_EOF          TokenType = iota + 1
	TOKEN_INDENT                 // indent - []rune of whitespace characters
	TOKEN_FOR                    // the "for" keyword
	TOKEN_WORD                   // alphanumeric word, starts witn a letter
	TOKEN_ASSIGN                 // =
	TOKEN_EQUALS                 // ==
	TOKEN_NEQUALS                // !=
	TOKEN_GT                     // >
	TOKEN_LT                     // <
	TOKEN_EQ_LT                  // <=
	TOKEN_EQ_GT                  // >=
	TOKEN_NEGATE                 // !
	TOKEN_NUM                    // general token for all number literals
	TOKEN_STR                    // string literal
	TOKEN_DOT                    // .
	TOKEN_LPARENTH               // (
	TOKEN_RPARENTH               // )
	TOKEN_LBRACKET               // [
	TOKEN_RBRACKET               // ]
	TOKEN_LBRACE                 // {
	TOKEN_RBRACE                 // }
	TOKEN_PLUS                   // +
	TOKEN_PLUS_ASSIGN            // +=
	TOKEN_INCREMENT              // ++
	TOKEN_MINUS                  // -
	TOKEN_MINUS_ASSIGN           // -=
	TOKEN_DECREMENT              // --
	TOKEN_VAR                    // the "var" keyword
	TOKEN_IF                     // the "if" keyword
	TOKEN_ELSE                   // the "else" keyword
	TOKEN_ELIF                   // the "elif" keyword
	TOKEN_SWITCH                 // the "switch" keyword
	TOKEN_CASE                   // the "case" keyword
	TOKEN_RETURN                 // the "return" keyword
	TOKEN_TRUE                   // the "true" keyword
	TOKEN_FALSE                  // the "false" keyword
	TOKEN_STRUCT                 // the "struct" keyword
	TOKEN_MAP                    // the "map" keyword
	TOKEN_FUNC                   // the "func" keyword
	TOKEN_TYPE                   // the "type" keyword
	TOKEN_IN                     // the "in" keyword
	TOKEN_PASS                   // the "pass" keyword
	TOKEN_PACKAGE                // the "package" keyword
	TOKEN_BREAK                  // the "break" keyword
	TOKEN_CONTINUE               // the "continue" keyword
	TOKEN_FALLTHROUGH            // the "fallthrough" keyword
	TOKEN_GOTO                   // the "goto" keyword
	TOKEN_INTERFACE              // the "interface" keyword
	TOKEN_NIL                    // the "nil" keyword
	TOKEN_MUL                    // *
	TOKEN_DIV                    // /
	TOKEN_MUL_ASSIGN             // *=
	TOKEN_DIV_ASSIGN             // /=
	TOKEN_SHL                    // <<
	TOKEN_SHR                    // >>
	TOKEN_SEND                   // <-
	TOKEN_COMMA                  // ,
	TOKEN_COLON                  // :
	TOKEN_SEMICOLON              // ;
	TOKEN_AMP                    // &
	TOKEN_PIPE                   // |
	TOKEN_PERCENT                // %
	TOKEN_AND                    // &&
	TOKEN_OR                     // ||
)

type Lexer struct {
	// Characters not processed yet.
	buf []rune
	// Stack of opened indents.
	indentsStack []int
	// We don't want to emit indent tokens for blank lines,
	// so we need to postpone indent tokens for a while.
	tokenIndent *Token
	// How many characters we've processed.
	skipped int
	// Offset of currently processed token.
	curTokenPos int
}

func NewLexer(buf []rune) *Lexer {
	return &Lexer{buf: buf, indentsStack: []int{}}
}

// Advance lexer's buffer by skipping whitespace, except newlines.
func (l *Lexer) skipWhiteChars() []rune {
	i := 0
	for i < len(l.buf) && (unicode.IsSpace(l.buf[i]) && l.buf[i] != '\n') {
		i++
	}
	whitespace := l.buf[:i]
	l.skipBy(i)
	return whitespace
}

// Read an alphanumeric word from the buffer, advancing it.
func (l *Lexer) scanWord() []rune {
	i := 0
	for i < len(l.buf) && (unicode.IsLetter(l.buf[i]) || unicode.IsNumber(l.buf[i])) {
		i++
	}

	result := l.buf[:i]
	l.skipBy(i)
	return result
}

// Advance lexer's buffer by one character.
func (l *Lexer) skip() {
	l.skipped++
	l.buf = l.buf[1:]
}

// Advance lexer's buffer by N characters.
func (l *Lexer) skipBy(n int) {
	l.skipped += n
	l.buf = l.buf[n:]
}

// Tells if we've reached the end of the buffer.
func (l *Lexer) isEnd() bool {
	return len(l.buf) == 0
}

// Check which token is currently at the beginning of the buffer.
// Can be used to decide between tokens with the same beginning, e.g.
// "=", "==", "=<", ">=".
// Returns the first token matched, NOT the longest one, so order matters.
// E.g. instead of "=", "=="; rather use "==", "=".
func (l *Lexer) checkAlt(alts ...string) (alt string, ok bool) {
	for _, alt := range alts {
		if string(l.buf[:len(alt)]) == alt {
			l.skipBy(len(alt))
			return alt, true
		}
	}
	return "", false
}

func (l *Lexer) loadEscapedString() (string, error) {
	if len(l.buf) == 0 || l.buf[0] != '"' {
		return "", fmt.Errorf("String literal has to start with a double quote")
	}

	l.skip()

	i := 0
	for ; i < len(l.buf); i++ {
		switch l.buf[i] {
		case '\\':
			i++
			if i == len(l.buf) {
				return "", fmt.Errorf("Unexpected file end - middle of a string literal")
			}
		case '"':
			s := string(l.buf[:i])
			l.skipBy(i + 1)
			return s, nil
		}
	}
	return "", fmt.Errorf("Unterminated string literal")
}

func (l *Lexer) newToken(typ TokenType, val interface{}) *Token {
	return &Token{Type: typ, Offset: l.curTokenPos, Value: val}
}

// A convenience wrapper for newToken, handy in situations when a token
// is created just to be immediately returned with a nil error.
func (l *Lexer) retNewToken(typ TokenType, val interface{}) (*Token, error) {
	return l.newToken(typ, val), nil
}

func (l *Lexer) scanGoToken() (token gotoken.Token, lit string, err error) {
	// TODO: We shouldn't be setting everything up from scratch every time.

	fs := gotoken.NewFileSet()
	code := make([]byte, len(l.buf))

	// TODO: Don't use []rune, if Golang doesn't need it neither do we and it leads
	// to stuff like this.
	for i := 0; i < len(l.buf); i++ {
		code[i] = byte(l.buf[i])
	}

	f := fs.AddFile("", fs.Base(), len(code))
	s := &goscanner.Scanner{}

	errorHandler := func(pos gotoken.Position, msg string) {
		err = fmt.Errorf("Scanner error: %s, %s", pos, msg)
	}

	s.Init(f, []byte(code), errorHandler, 0)
	_, tok, lit := s.Scan()
	l.skipBy(len(lit))

	return tok, lit, err
}

func (l *Lexer) fromGoToken(token gotoken.Token, lit string) (*Token, error) {
	switch token {
	case gotoken.INT, gotoken.FLOAT, gotoken.IMAG:
		// TODO: Don't lump everything together
		return l.retNewToken(TOKEN_NUM, lit)
	}
	return nil, fmt.Errorf("Unexpected Go token: %s", token)
}

func (l *Lexer) Next() (*Token, error) {
	l.curTokenPos = l.skipped

	if l.isEnd() || l.buf[0] != '\n' {
		if l.tokenIndent != nil {
			t := l.tokenIndent
			l.tokenIndent = nil
			return t, nil
		}
	}

	if l.isEnd() {
		return l.retNewToken(TOKEN_EOF, nil)
	}

	ch := l.buf[0]

	switch {
	case ch == '\n':
		l.skip()
		indent := string(l.skipWhiteChars())
		l.tokenIndent = l.newToken(TOKEN_INDENT, indent)
		return l.Next()
	case unicode.IsSpace(ch):
		l.skipWhiteChars()
		return l.Next()
	case unicode.IsLetter(ch):
		word := l.scanWord()
		switch s := string(word); s {
		case "for":
			return l.retNewToken(TOKEN_FOR, nil)
		case "pass":
			return l.retNewToken(TOKEN_PASS, nil)
		case "package":
			return l.retNewToken(TOKEN_PACKAGE, nil)
		case "var":
			return l.retNewToken(TOKEN_VAR, nil)
		case "if":
			return l.retNewToken(TOKEN_IF, nil)
		case "else":
			return l.retNewToken(TOKEN_ELSE, nil)
		case "elif":
			return l.retNewToken(TOKEN_ELIF, nil)
		case "switch":
			return l.retNewToken(TOKEN_SWITCH, nil)
		case "case":
			return l.retNewToken(TOKEN_CASE, nil)
		case "return", "ret":
			return l.retNewToken(TOKEN_RETURN, nil)
		case "true":
			return l.retNewToken(TOKEN_TRUE, nil)
		case "false":
			return l.retNewToken(TOKEN_FALSE, nil)
		case "struct":
			return l.retNewToken(TOKEN_STRUCT, nil)
		case "interface":
			return l.retNewToken(TOKEN_INTERFACE, nil)
		case "map":
			return l.retNewToken(TOKEN_MAP, nil)
		case "func":
			return l.retNewToken(TOKEN_FUNC, nil)
		case "type":
			return l.retNewToken(TOKEN_TYPE, nil)
		case "break":
			return l.retNewToken(TOKEN_BREAK, nil)
		case "continue":
			return l.retNewToken(TOKEN_CONTINUE, nil)
		case "fallthrough":
			return l.retNewToken(TOKEN_FALLTHROUGH, nil)
		case "goto":
			return l.retNewToken(TOKEN_GOTO, nil)
		case "nil":
			return l.retNewToken(TOKEN_NIL, nil)
		default:
			return l.retNewToken(TOKEN_WORD, s)
		}
	case ch == '=':
		alt, _ := l.checkAlt("==", "=")
		switch alt {
		case "=":
			return l.retNewToken(TOKEN_ASSIGN, alt)
		case "==":
			return l.retNewToken(TOKEN_EQUALS, alt)
		}
	case ch == '!':
		alt, _ := l.checkAlt("!=", "!")
		switch alt {
		case "!=":
			return l.retNewToken(TOKEN_NEQUALS, alt)
		case "!":
			return l.retNewToken(TOKEN_NEGATE, alt)
		}
	case ch == '+':
		alt, _ := l.checkAlt("++", "+=", "+")
		switch alt {
		case "+":
			return l.retNewToken(TOKEN_PLUS, alt)
		case "+=":
			return l.retNewToken(TOKEN_PLUS_ASSIGN, alt)
		case "++":
			return l.retNewToken(TOKEN_INCREMENT, alt)
		}
	case ch == '-':
		alt, _ := l.checkAlt("--", "-=", "-")
		switch alt {
		case "-":
			return l.retNewToken(TOKEN_MINUS, alt)
		case "-=":
			return l.retNewToken(TOKEN_MINUS_ASSIGN, alt)
		case "--":
			return l.retNewToken(TOKEN_DECREMENT, alt)
		}
	case ch == '<':
		alt, _ := l.checkAlt("<<", "<-", "<=", "<")
		switch alt {
		case "<":
			return l.retNewToken(TOKEN_LT, alt)
		case "<-":
			return l.retNewToken(TOKEN_SEND, alt)
		case "<<":
			return l.retNewToken(TOKEN_SHL, alt)
		case "<=":
			return l.retNewToken(TOKEN_EQ_LT, alt)
		}
	case ch == '>':
		alt, _ := l.checkAlt(">>", ">=", ">")
		switch alt {
		case ">":
			return l.retNewToken(TOKEN_GT, alt)
		case ">>":
			return l.retNewToken(TOKEN_SHR, alt)
		case ">=":
			return l.retNewToken(TOKEN_EQ_GT, alt)
		}
	case unicode.IsNumber(ch):
		gotok, lit, err := l.scanGoToken()
		if err != nil {
			return nil, err
		}
		return l.fromGoToken(gotok, lit)
	case ch == '"':
		str, err := l.loadEscapedString()
		if err != nil {
			return nil, err
		}
		return l.retNewToken(TOKEN_STR, str)
	case ch == '(':
		l.skip()
		return l.retNewToken(TOKEN_LPARENTH, nil)
	case ch == ')':
		l.skip()
		return l.retNewToken(TOKEN_RPARENTH, nil)
	case ch == '[':
		l.skip()
		return l.retNewToken(TOKEN_LBRACKET, nil)
	case ch == ']':
		l.skip()
		return l.retNewToken(TOKEN_RBRACKET, nil)
	case ch == '{':
		l.skip()
		return l.retNewToken(TOKEN_LBRACE, nil)
	case ch == '}':
		l.skip()
		return l.retNewToken(TOKEN_RBRACE, nil)
	case ch == '.':
		l.skip()
		return l.retNewToken(TOKEN_DOT, nil)
	case ch == '*':
		alt, _ := l.checkAlt("*=", "*")
		switch alt {
		case "*":
			return l.retNewToken(TOKEN_MUL, alt)
		case "*=":
			return l.retNewToken(TOKEN_MUL_ASSIGN, alt)
		}
	case ch == '/':
		alt, _ := l.checkAlt("/=", "/")
		switch alt {
		case "/":
			return l.retNewToken(TOKEN_DIV, alt)
		case "/=":
			return l.retNewToken(TOKEN_DIV_ASSIGN, alt)
		}
	case ch == ',':
		l.skip()
		return l.retNewToken(TOKEN_COMMA, nil)
	case ch == ';':
		l.skip()
		return l.retNewToken(TOKEN_SEMICOLON, nil)
	case ch == ':':
		l.skip()
		return l.retNewToken(TOKEN_COLON, nil)
	case ch == '%':
		l.skip()
		return l.retNewToken(TOKEN_PERCENT, "%")
	case ch == '&':
		alt, _ := l.checkAlt("&&", "&")
		switch alt {
		case "&&":
			return l.retNewToken(TOKEN_AND, alt)
		case "&":
			return l.retNewToken(TOKEN_AMP, alt)
		}
	case ch == '|':
		alt, _ := l.checkAlt("||", "|")
		switch alt {
		case "||":
			return l.retNewToken(TOKEN_OR, alt)
		case "|":
			return l.retNewToken(TOKEN_PIPE, alt)
		}
	}

	return nil, fmt.Errorf("Don't know what to do, '%c'", ch)
}
<|endoftext|>


<commit_before>package mark
<commit_msg>feat(lexer): add tokens<commit_after>package mark

// Token represent token or string returned from the lexer(scanner)
type Token struct {
	Type Type   // The type of this item
	Line int    // The line number on thich this token apears
	Text string // The text of this item
}

// Type identifies the type of lex items
type Type int

const (
	EOF   Type = iota // Zero value so closed channel delivers EOF
	Error             // Error occurred; value is text of error
	NewLine
	// Intersting things
	Paragraph // Simple paragraph
	Heading   // Simple heading
	LHeading  // Line heading
	Hr        // Thematic break

)
<|endoftext|>


<commit_before>package objconv

import "strings"

// tag represents the result of parsing the tag of a struct field.
type tag struct {
	// Name is the field name that should be used when serializing.
	name string

	// Omitempty is true if the tag had `omitempty` set.
	omitempty bool

	// Omitzero is true if the tag had `omitzero` set.
	omitzero bool
}

// parseTag parses a raw tag obtained from a struct field, returning the results
// as a tag value.
func parseTag(s string) tag {
	var tokens [2]string
	var omitzero bool
	var omitempty bool

	name, s := parseNextTagToken(s)
	tokens[0], s = parseNextTagToken(s)
	tokens[1], _ = parseNextTagToken(s)

	for _, t := range tokens {
		switch t {
		case "omitempty":
			omitempty = true
		case "omitzero":
			omitzero = true
		}
	}

	return tag{
		name:      name,
		omitempty: omitempty,
		omitzero:  omitzero,
	}
}

func parseNextTagToken(s string) (token string, next string) {
	if split := strings.IndexByte(s, ','); split < 0 {
		token = s
	} else {
		token, next = s[:split], s[split+1:]
	}
	return
}
<commit_msg>add ParseTag<commit_after>package objconv

import "strings"

// tag represents the result of parsing the tag of a struct field.
type tag struct {
	// Name is the field name that should be used when serializing.
	name string

	// Omitempty is true if the tag had `omitempty` set.
	omitempty bool

	// Omitzero is true if the tag had `omitzero` set.
	omitzero bool
}

// ParseTag parses a struct field tag in s, returning the name of the field and
// its properties.
func ParseTag(s string) (name string, omitempty bool, omitzero bool) {
	t := parseTag(s)
	return t.name, t.omitempty, t.omitzero
}

// parseTag parses a raw tag obtained from a struct field, returning the results
// as a tag value.
func parseTag(s string) tag {
	var tokens [2]string
	var omitzero bool
	var omitempty bool

	name, s := parseNextTagToken(s)
	tokens[0], s = parseNextTagToken(s)
	tokens[1], _ = parseNextTagToken(s)

	for _, t := range tokens {
		switch t {
		case "omitempty":
			omitempty = true
		case "omitzero":
			omitzero = true
		}
	}

	return tag{
		name:      name,
		omitempty: omitempty,
		omitzero:  omitzero,
	}
}

func parseNextTagToken(s string) (token string, next string) {
	if split := strings.IndexByte(s, ','); split < 0 {
		token = s
	} else {
		token, next = s[:split], s[split+1:]
	}
	return
}
<|endoftext|>


<commit_before>package carchivum

// tar implements the tape archive format.

import (
	"archive/tar"
	"compress/bzip2"
	"compress/gzip"
	"compress/lzw"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/MichaelTJones/walk"
	"github.com/pierrec/lz4"
)

// Tar is a struct for a tar, tape archive.
type Tar struct {
	Car
	*tar.Writer
	Format
	sources []string
}

// NewTar returns an initialized Tar struct ready for use.
func NewTar() *Tar {
	return &Tar{Car: Car{t0: time.Now()}, Format: defaultFormat, sources: []string{}}
}

// Create creates a tarfile from the passed src('s) and saves it to the dst.
func (t *Tar) Create(src ...string) (cnt int, err error) {
	// If there isn't a destination, return err
	if t.Name == "" {
		err = fmt.Errorf("destination required to create a tar archive")
		log.Print(err)
		return 0, err
	}
	// If there aren't any sources, return err
	if len(src) == 0 {
		err = fmt.Errorf("a source is required to create a tar archive")
		log.Print(err)
		return 0, err
	}
	t.sources = src
	// See if we can create the destination file before processing
	tball, err := os.OpenFile(t.Name, os.O_RDWR|os.O_CREATE, 0744)
	if err != nil {
		log.Print(err)
		return 0, err
	}
	defer func() {
		cerr := tball.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	switch t.Format {
	case GzipFmt:
		err = t.CreateGzip(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	case Bzip2Fmt:
		err = fmt.Errorf("Bzip2 compression is not supported")
		return 0, err
	case LZWFmt:
		err = t.CreateZ(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	case LZ4Fmt:
		err = t.CreateLZ4(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	default:
		err = fmt.Errorf("Unsupported compression format: %s", t.Format.String())
		return 0, err
	}
	if t.DeleteArchived {
		err := t.removeFiles()
		if err != nil {
			err = fmt.Errorf("an error was encountered while deleting the archived files; some files may not be deleted: %q", err)
			log.Print(err)
			return 0, err
		}
	}
	t.setDelta()
	return int(t.Car.files), nil
}

func (t *Tar) removeFiles() error {
	for _, file := range t.deleteList {
		err := os.Remove(file)
		if err != nil {
			log.Print(err)
			return err
		}
	}
	return nil
}

// CreateGzip creates a GZip using the passed writer.
func (t *Tar) CreateGzip(w io.Writer) (err error) {
	zw := gzip.NewWriter(w)
	// Close the file with error handling
	defer func() {
		cerr := zw.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.writeTar(zw)
	return err
}

// CreateLZW compresses using LZW and LSB order using the passed writer.
// TODO: address order so that it doesn't necessarily default to LSB
func (t *Tar) CreateZ(w io.Writer) (err error) {
	zw := lzw.NewWriter(w, lzw.LSB, 8)
	// Close the file with error handling
	defer func() {
		cerr := zw.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.writeTar(zw)
	return err
}

// CreateLZ4 creates a LZ4 compressed tarball using the passed writer.
func (t *Tar) CreateLZ4(w io.Writer) (err error) {
	lzW := lz4.NewWriter(w)
	err = t.writeTar(lzW)
	lzW.Close()
	return err
}

func (t *Tar) writeTar(w io.Writer) (err error) {
	t.Writer = tar.NewWriter(w)
	defer func() {
		cerr := t.Writer.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	t.FileCh = make(chan *os.File)
	wait, err := t.Write()
	if err != nil {
		log.Print(err)
		return err
	}
	var fullPath string
	visitor := func(p string, fi os.FileInfo, err error) error {
		return t.AddFile(fullPath, p, fi, err)
	}
	var wg sync.WaitGroup
	wg.Add(len(t.sources) - 1)
	for _, source := range t.sources {
		fullPath, err = filepath.Abs(source)
		if err != nil {
			log.Print(err)
			return err
		}
		err = walk.Walk(fullPath, visitor)
		if err != nil {
			log.Print(err)
			return err
		}
	}
	wg.Wait()
	close(t.FileCh)
	wait.Wait()
	return err
}

// Write adds the files received on the channel to the tarball.
func (t *Tar) Write() (*sync.WaitGroup, error) {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() error {
		defer wg.Done()
		for f := range t.FileCh {
			info, err := f.Stat()
			if err != nil {
				log.Print(err)
				return err
			}
			if info.IsDir() {
				continue
			}
			header, err := tar.FileInfoHeader(info, "")
			if err != nil {
				log.Print(err)
				return err
			}
			header.Name = f.Name()
			// See if any header overrides need to be done
			if t.Owner > 0 {
				header.Uid = t.Owner
			}
			if t.Group > 0 {
				header.Gid = t.Group
			}
			if t.FileMode > 0 {
				header.Mode = int64(t.FileMode)
			} else {
				header.Mode = int64(info.Mode().Perm())
			}
			header.ModTime = info.ModTime()
			err = t.Writer.WriteHeader(header)
			if err != nil {
				log.Print(err)
				return err
			}
			_, err = io.Copy(t.Writer, f)
			if err != nil {
				log.Print(err)
				return err
			}
			err = f.Close()
			if err != nil {
				log.Print(err)
				return err
			}
		}
		return nil
	}()
	return &wg, nil
}

// Delete is not implemented
func (t *Tar) Delete() error {
	return nil
}

// Extract extracts the files from src and writes them to the dst.
func (t *Tar) Extract(dst string, src io.Reader) error {
	switch t.Format {
	case TarFmt:
		return t.ExtractTar(dst, src)
	case GzipFmt:
		return t.ExtractTgz(dst, src)
	case Bzip2Fmt:
		return t.ExtractTbz(dst, src)
	case LZWFmt:
		return t.ExtractZ(dst, src)
	case LZ4Fmt:
		return t.ExtractLZ4(dst, src)
	default:
		return UnsupportedFmt.NotSupportedError()
	}
	return nil
}

// ExtractTar extracts a tar file using the passed reader
func (t *Tar) ExtractTar(dst string, src io.Reader) (err error) {
	tr := tar.NewReader(src)
	for {
		header, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Print(err)
			return err
		}
		fname := header.Name
		// extract is always relative to cwd, for now
		fname = filepath.Join(dst, fname)
		fmt.Printf("%s %s\n", fname, strconv.Itoa(int(header.Mode)))
		switch header.Typeflag {
		case tar.TypeDir:
			err = os.MkdirAll(fname, 0744)
			if err != nil {
				log.Print(err)
				return err
			}
			// set the final element to the appropriate permission
			err = os.Chmod(fname, os.FileMode(header.Mode))
			if err != nil {
				log.Print(err)
				return err
			}
		case tar.TypeReg:
			// create the parent directory if necessary
			pdir := filepath.Dir(fname)
			err = os.MkdirAll(pdir, 0744)
			if err != nil {
				log.Print(err)
				return err
			}
			if err != nil {
				log.Print(err)
				return err
			}
			w, err := os.Create(fname)
			if err != nil {
				log.Print(err)
				return err
			}
			io.Copy(w, tr)
			err = os.Chmod(fname, os.FileMode(header.Mode))
			if err != nil {
				log.Print(err)
				return err
			}
			w.Close()
		default:
			err = fmt.Errorf("Unable to extract type: %c in file %s", header.Typeflag, fname)
			log.Print(err)
			return err
		}
	}
	return nil
}

// ExtractGzip reads a GZip using the passed reader.
func (t *Tar) ExtractGzip(dst string, src io.Reader) (err error) {
	gR, err := gzip.NewReader(src)
	if err != nil {
		log.Print(err)
		return err
	}
	// Close the file with error handling
	defer func() {
		cerr := gR.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.ExtractTar(dst, gR)
	return err
}

// ExtractTgz extracts GZip'd tarballs.
func (t *Tar) ExtractTgz(dst string, src io.Reader) error {
	gr, err := gzip.NewReader(src)
	if err != nil {
		log.Print(err)
		return err
	}
	defer gr.Close()
	err = t.ExtractTar(dst, gr)
	return err
}

// ExtractTbz extracts Bzip2 compressed tarballs.
func (t *Tar) ExtractTbz(dst string, src io.Reader) error {
	zR := bzip2.NewReader(src)
	return t.ExtractTar(dst, zR)
}

// ExtractZ extracts tarballs compressed with LZW, typically .Z extension.
// TODO fix so that order and width get properly set. Assuming order isn't
// good.
func (t *Tar) ExtractZ(dst string, src io.Reader) error {
	zR := lzw.NewReader(src, lzw.LSB, 8)
	defer zR.Close()
	return t.ExtractTar(dst, zR)
}

// ExtractLZ4 extracts LZ4 compressed tarballs.
func (t *Tar) ExtractLZ4(dst string, src io.Reader) error {
	lzR := lz4.NewReader(src)
	tr := tar.NewReader(lzR)
	err := t.ExtractTar(dst, tr)
	return err
}

func extractTarFile(hdr *tar.Header, dst string, src io.Reader) error {
	fP := filepath.Join(dst, hdr.Name)
	fI := hdr.FileInfo()
	err := os.MkdirAll(filepath.Dir(fP), fI.Mode())
	if err != nil {
		log.Print(err)
		return err
	}
	if fI.IsDir() {
		return nil
	}
	if fI.Mode()&os.ModeSymlink != 0 {
		return os.Symlink(hdr.Linkname, fP)
	}

	dF, err := os.OpenFile(fP, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fI.Mode())
	if err != nil {
		log.Print(err)
		return err
	}
	defer dF.Close()

	_, err = io.Copy(dF, src)
	if err != nil {
		log.Print(err)
		return err
	}
	return nil
}
<commit_msg>elide extraneous wg: not necessary and bug as written<commit_after>package carchivum

// tar implements the tape archive format.

import (
	"archive/tar"
	"compress/bzip2"
	"compress/gzip"
	"compress/lzw"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/MichaelTJones/walk"
	"github.com/pierrec/lz4"
)

// Tar is a struct for a tar, tape archive.
type Tar struct {
	Car
	*tar.Writer
	Format
	sources []string
}

// NewTar returns an initialized Tar struct ready for use.
func NewTar() *Tar {
	return &Tar{Car: Car{t0: time.Now()}, Format: defaultFormat, sources: []string{}}
}

// Create creates a tarfile from the passed src('s) and saves it to the dst.
func (t *Tar) Create(src ...string) (cnt int, err error) {
	// If there isn't a destination, return err
	if t.Name == "" {
		err = fmt.Errorf("destination required to create a tar archive")
		log.Print(err)
		return 0, err
	}
	// If there aren't any sources, return err
	if len(src) == 0 {
		err = fmt.Errorf("a source is required to create a tar archive")
		log.Print(err)
		return 0, err
	}
	t.sources = src
	// See if we can create the destination file before processing
	tball, err := os.OpenFile(t.Name, os.O_RDWR|os.O_CREATE, 0744)
	if err != nil {
		log.Print(err)
		return 0, err
	}
	defer func() {
		cerr := tball.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	switch t.Format {
	case GzipFmt:
		err = t.CreateGzip(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	case Bzip2Fmt:
		err = fmt.Errorf("Bzip2 compression is not supported")
		return 0, err
	case LZWFmt:
		err = t.CreateZ(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	case LZ4Fmt:
		err = t.CreateLZ4(tball)
		if err != nil {
			log.Print(err)
			return 0, err
		}
	default:
		err = fmt.Errorf("Unsupported compression format: %s", t.Format.String())
		return 0, err
	}
	if t.DeleteArchived {
		err := t.removeFiles()
		if err != nil {
			err = fmt.Errorf("an error was encountered while deleting the archived files; some files may not be deleted: %q", err)
			log.Print(err)
			return 0, err
		}
	}
	t.setDelta()
	return int(t.Car.files), nil
}

func (t *Tar) removeFiles() error {
	for _, file := range t.deleteList {
		err := os.Remove(file)
		if err != nil {
			log.Print(err)
			return err
		}
	}
	return nil
}

// CreateGzip creates a GZip using the passed writer.
func (t *Tar) CreateGzip(w io.Writer) (err error) {
	zw := gzip.NewWriter(w)
	// Close the file with error handling
	defer func() {
		cerr := zw.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.writeTar(zw)
	return err
}

// CreateLZW compresses using LZW and LSB order using the passed writer.
// TODO: address order so that it doesn't necessarily default to LSB
func (t *Tar) CreateZ(w io.Writer) (err error) {
	zw := lzw.NewWriter(w, lzw.LSB, 8)
	// Close the file with error handling
	defer func() {
		cerr := zw.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.writeTar(zw)
	return err
}

// CreateLZ4 creates a LZ4 compressed tarball using the passed writer.
func (t *Tar) CreateLZ4(w io.Writer) (err error) {
	lzW := lz4.NewWriter(w)
	err = t.writeTar(lzW)
	lzW.Close()
	return err
}

func (t *Tar) writeTar(w io.Writer) (err error) {
	t.Writer = tar.NewWriter(w)
	defer func() {
		cerr := t.Writer.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	t.FileCh = make(chan *os.File)
	wait, err := t.Write()
	if err != nil {
		log.Print(err)
		return err
	}
	var fullPath string
	visitor := func(p string, fi os.FileInfo, err error) error {
		return t.AddFile(fullPath, p, fi, err)
	}
	for _, source := range t.sources {
		fullPath, err = filepath.Abs(source)
		if err != nil {
			log.Print(err)
			return err
		}
		err = walk.Walk(fullPath, visitor)
		if err != nil {
			log.Print(err)
			return err
		}
	}
	close(t.FileCh)
	wait.Wait()
	return err
}

// Write adds the files received on the channel to the tarball.
func (t *Tar) Write() (*sync.WaitGroup, error) {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() error {
		defer wg.Done()
		for f := range t.FileCh {
			info, err := f.Stat()
			if err != nil {
				log.Print(err)
				return err
			}
			if info.IsDir() {
				continue
			}
			header, err := tar.FileInfoHeader(info, "")
			if err != nil {
				log.Print(err)
				return err
			}
			header.Name = f.Name()
			// See if any header overrides need to be done
			if t.Owner > 0 {
				header.Uid = t.Owner
			}
			if t.Group > 0 {
				header.Gid = t.Group
			}
			if t.FileMode > 0 {
				header.Mode = int64(t.FileMode)
			} else {
				header.Mode = int64(info.Mode().Perm())
			}
			header.ModTime = info.ModTime()
			err = t.Writer.WriteHeader(header)
			if err != nil {
				log.Print(err)
				return err
			}
			_, err = io.Copy(t.Writer, f)
			if err != nil {
				log.Print(err)
				return err
			}
			err = f.Close()
			if err != nil {
				log.Print(err)
				return err
			}
		}
		return nil
	}()
	return &wg, nil
}

// Delete is not implemented
func (t *Tar) Delete() error {
	return nil
}

// Extract extracts the files from src and writes them to the dst.
func (t *Tar) Extract(dst string, src io.Reader) error {
	switch t.Format {
	case TarFmt:
		return t.ExtractTar(dst, src)
	case GzipFmt:
		return t.ExtractTgz(dst, src)
	case Bzip2Fmt:
		return t.ExtractTbz(dst, src)
	case LZWFmt:
		return t.ExtractZ(dst, src)
	case LZ4Fmt:
		return t.ExtractLZ4(dst, src)
	default:
		return UnsupportedFmt.NotSupportedError()
	}
	return nil
}

// ExtractTar extracts a tar file using the passed reader
func (t *Tar) ExtractTar(dst string, src io.Reader) (err error) {
	tr := tar.NewReader(src)
	for {
		header, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Print(err)
			return err
		}
		fname := header.Name
		// extract is always relative to cwd, for now
		fname = filepath.Join(dst, fname)
		fmt.Printf("%s %s\n", fname, strconv.Itoa(int(header.Mode)))
		switch header.Typeflag {
		case tar.TypeDir:
			err = os.MkdirAll(fname, 0744)
			if err != nil {
				log.Print(err)
				return err
			}
			// set the final element to the appropriate permission
			err = os.Chmod(fname, os.FileMode(header.Mode))
			if err != nil {
				log.Print(err)
				return err
			}
		case tar.TypeReg:
			// create the parent directory if necessary
			pdir := filepath.Dir(fname)
			err = os.MkdirAll(pdir, 0744)
			if err != nil {
				log.Print(err)
				return err
			}
			if err != nil {
				log.Print(err)
				return err
			}
			w, err := os.Create(fname)
			if err != nil {
				log.Print(err)
				return err
			}
			io.Copy(w, tr)
			err = os.Chmod(fname, os.FileMode(header.Mode))
			if err != nil {
				log.Print(err)
				return err
			}
			w.Close()
		default:
			err = fmt.Errorf("Unable to extract type: %c in file %s", header.Typeflag, fname)
			log.Print(err)
			return err
		}
	}
	return nil
}

// ExtractGzip reads a GZip using the passed reader.
func (t *Tar) ExtractGzip(dst string, src io.Reader) (err error) {
	gR, err := gzip.NewReader(src)
	if err != nil {
		log.Print(err)
		return err
	}
	// Close the file with error handling
	defer func() {
		cerr := gR.Close()
		if cerr != nil && err == nil {
			log.Print(cerr)
			err = cerr
		}
	}()
	err = t.ExtractTar(dst, gR)
	return err
}

// ExtractTgz extracts GZip'd tarballs.
func (t *Tar) ExtractTgz(dst string, src io.Reader) error {
	gr, err := gzip.NewReader(src)
	if err != nil {
		log.Print(err)
		return err
	}
	defer gr.Close()
	err = t.ExtractTar(dst, gr)
	return err
}

// ExtractTbz extracts Bzip2 compressed tarballs.
func (t *Tar) ExtractTbz(dst string, src io.Reader) error {
	zR := bzip2.NewReader(src)
	return t.ExtractTar(dst, zR)
}

// ExtractZ extracts tarballs compressed with LZW, typically .Z extension.
// TODO fix so that order and width get properly set. Assuming order isn't
// good.
func (t *Tar) ExtractZ(dst string, src io.Reader) error {
	zR := lzw.NewReader(src, lzw.LSB, 8)
	defer zR.Close()
	return t.ExtractTar(dst, zR)
}

// ExtractLZ4 extracts LZ4 compressed tarballs.
func (t *Tar) ExtractLZ4(dst string, src io.Reader) error {
	lzR := lz4.NewReader(src)
	tr := tar.NewReader(lzR)
	err := t.ExtractTar(dst, tr)
	return err
}

func extractTarFile(hdr *tar.Header, dst string, src io.Reader) error {
	fP := filepath.Join(dst, hdr.Name)
	fI := hdr.FileInfo()
	err := os.MkdirAll(filepath.Dir(fP), fI.Mode())
	if err != nil {
		log.Print(err)
		return err
	}
	if fI.IsDir() {
		return nil
	}
	if fI.Mode()&os.ModeSymlink != 0 {
		return os.Symlink(hdr.Linkname, fP)
	}

	dF, err := os.OpenFile(fP, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fI.Mode())
	if err != nil {
		log.Print(err)
		return err
	}
	defer dF.Close()

	_, err = io.Copy(dF, src)
	if err != nil {
		log.Print(err)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package putio

import (
	"encoding/json"
)

// AccountService is the service to gather information about user account.
type AccountService struct {
	client *Client
}

// Info retrieves user account information.
func (a *AccountService) Info() (Info, error) {
	req, err := a.client.NewRequest("GET", "/v2/account/info", nil)
	if err != nil {
		return Info{}, nil
	}
	resp, err := a.client.Do(req)
	if err != nil {
		return Info{}, err
	}
	defer resp.Body.Close()

	var r struct {
		Info   Info
		Status string
	}
	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return Info{}, err
	}

	return r.Info, nil
}

// Settings retrieves user preferences.
func (a *AccountService) Settings() (Settings, error) {
	req, err := a.client.NewRequest("GET", "/v2/account/settings", nil)
	if err != nil {
		return Settings{}, nil
	}
	resp, err := a.client.Do(req)
	if err != nil {
		return Settings{}, err
	}
	defer resp.Body.Close()

	var r struct {
		Settings Settings
		Status   string
	}
	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return Settings{}, err
	}

	return r.Settings, nil
}

// FIXME: fill
func (a *AccountService) UpdateSettings() error {
	panic("not implemented yet")
}
<commit_msg>account: dont export update-settings method yet<commit_after>package putio

import (
	"encoding/json"
)

// AccountService is the service to gather information about user account.
type AccountService struct {
	client *Client
}

// Info retrieves user account information.
func (a *AccountService) Info() (Info, error) {
	req, err := a.client.NewRequest("GET", "/v2/account/info", nil)
	if err != nil {
		return Info{}, nil
	}
	resp, err := a.client.Do(req)
	if err != nil {
		return Info{}, err
	}
	defer resp.Body.Close()

	var r struct {
		Info   Info
		Status string
	}
	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return Info{}, err
	}

	return r.Info, nil
}

// Settings retrieves user preferences.
func (a *AccountService) Settings() (Settings, error) {
	req, err := a.client.NewRequest("GET", "/v2/account/settings", nil)
	if err != nil {
		return Settings{}, nil
	}
	resp, err := a.client.Do(req)
	if err != nil {
		return Settings{}, err
	}
	defer resp.Body.Close()

	var r struct {
		Settings Settings
		Status   string
	}
	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return Settings{}, err
	}

	return r.Settings, nil
}

// FIXME: fill
func (a *AccountService) updateSettings() error {
	panic("not implemented yet")
}
<|endoftext|>


<commit_before>package utils

import (
	"log"
	"os"
)

func CheckError(err error, messages ...string) {
	if err != nil {
		log.Println(err)
		if len(messages) > 0 {
			log.Println(messages)
		}

		os.Exit(-1)
	}
}
<commit_msg>fix #3<commit_after>package utils

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

func CheckError(err error, messages ...string) {
	if err != nil {
		log.Println(err)
		if len(messages) > 0 {
			log.Println(strings.Join(messages, " "))
		}

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


<commit_before>package utils

// WorkQueue/utils - Go utilities for WorkQueue
//
// Copyright (c) 2017 - Valentin Kuznetsov <vkuznet@gmail.com>

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"os/user"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/vkuznet/x509proxy"
)

// ResponseType structure is what we expect to get for our URL call.
// It contains a request URL, the data chunk and possible error from remote
type ResponseType struct {
	Url        string // response url
	Data       []byte // response data, i.e. what we got with Body of the response
	Error      error  // http error, a non-2xx return code is not an error
	Status     string // http status string
	StatusCode int    // http status code
}

// UrlRequest structure holds details about url request's attributes
type UrlRequest struct {
	rurl string
	args string
	out  chan<- ResponseType
	ts   int64
}

// VERBOSE variable control verbosity level of client's utilities
var VERBOSE int

// create global HTTP client and re-use it through the code
var client = HttpClient()

// UserDN function parses user Distinguished Name (DN) from client's HTTP request
func UserDN(r *http.Request) string {
	var names []interface{}
	for _, cert := range r.TLS.PeerCertificates {
		for _, name := range cert.Subject.Names {
			switch v := name.Value.(type) {
			case string:
				names = append(names, v)
			}
		}
	}
	parts := names[:7]
	return fmt.Sprintf("/DC=%s/DC=%s/OU=%s/OU=%s/CN=%s/CN=%s/CN=%s", parts...)
}

// client X509 certificates
func tlsCerts() ([]tls.Certificate, error) {
	uproxy := os.Getenv("X509_USER_PROXY")
	uckey := os.Getenv("X509_USER_KEY")
	ucert := os.Getenv("X509_USER_CERT")

	// check if /tmp/x509up_u$UID exists, if so setup X509_USER_PROXY env
	u, err := user.Current()
	if err == nil {
		fname := fmt.Sprintf("/tmp/x509up_u%s", u.Uid)
		if _, err := os.Stat(fname); err == nil {
			uproxy = fname
		}
	}
	if VERBOSE > 1 {
		log.Println("uproxy", uproxy)
		log.Println("uckey", uckey)
		log.Println("ucert", ucert)
	}

	if uproxy == "" && uckey == "" { // user doesn't have neither proxy or user certs
		return nil, nil
	}
	if uproxy != "" {
		// use local implementation of LoadX409KeyPair instead of tls one
		x509cert, err := x509proxy.LoadX509Proxy(uproxy)
		if err != nil {
			return nil, fmt.Errorf("failed to parse proxy X509 proxy set by X509_USER_PROXY: %v", err)
		}
		return []tls.Certificate{x509cert}, nil
	}
	x509cert, err := tls.LoadX509KeyPair(ucert, uckey)
	if err != nil {
		return nil, fmt.Errorf("failed to parse user X509 certificate: %v", err)
	}
	return []tls.Certificate{x509cert}, nil
}

// HttpClient is HTTP client for urlfetch server
func HttpClient() (client *http.Client) {
	// get X509 certs
	certs, err := tlsCerts()
	if err != nil {
		panic(err.Error())
	}
	if len(certs) == 0 {
		client = &http.Client{}
		return
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{Certificates: certs,
			InsecureSkipVerify: true},
	}
	client = &http.Client{Transport: tr}
	return
}

func (r *ResponseType) String() string {
	return fmt.Sprintf("<Response: url=%s data=%s error=%v>", r.Url, string(r.Data), r.Error)
}

// FetchResponse fetches data for provided URL, args is a json dump of arguments
func FetchResponse(rurl, args string) ResponseType {
	startTime := time.Now()
	var response ResponseType
	response.Url = rurl
	if validateUrl(rurl) == false {
		response.Error = errors.New("Invalid URL")
		return response
	}
	var req *http.Request
	var e error
	if len(args) > 0 {
		jsonStr := []byte(args)
		req, e = http.NewRequest("POST", rurl, bytes.NewBuffer(jsonStr))
		req.Header.Set("Content-Type", "application/json")
	} else {
		req, e = http.NewRequest("GET", rurl, nil)
		if e != nil {
			log.Println("Unable to make GET request", e)
		}
		req.Header.Set("Accept", "application/json")
	}
	if VERBOSE > 1 {
		dump1, err1 := httputil.DumpRequestOut(req, true)
		log.Println("HTTP request", req, string(dump1), err1)
	}
	resp, err := client.Do(req)
	if err != nil {
		log.Println("HTTP ERROR", err)
		response.Error = err
		return response
	}
	response.Status = resp.Status
	response.StatusCode = resp.StatusCode
	if VERBOSE > 0 {
		if len(args) > 0 {
			log.Println("HTTP POST", rurl, string(args), err, time.Now().Sub(startTime))
		} else {
			log.Println("HTTP GET", rurl, string(args), err, time.Now().Sub(startTime))
		}
	}
	if VERBOSE > 1 {
		dump2, err2 := httputil.DumpResponse(resp, true)
		log.Println("HTTP response", string(dump2), err2)
	}
	if err != nil {
		response.Error = err
		return response
	}
	defer resp.Body.Close()
	response.Data, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		response.Error = err
	}
	return response
}

// Fetch data for provided URL and redirect results to given channel
func Fetch(rurl, args string, ch chan<- ResponseType) {
	urlRetry := 3
	var resp, r ResponseType
	resp = FetchResponse(rurl, args)
	if resp.Error != nil {
		log.Println("workqueue WARNING, fail to fetch data", rurl, "error", resp.Error)
		for i := 1; i <= urlRetry; i++ {
			sleep := time.Duration(i) * time.Second
			time.Sleep(sleep)
			r = FetchResponse(rurl, args)
			if r.Error == nil {
				break
			}
			log.Println("workqueue WARNING", rurl, "retry", i, "error", r.Error)
		}
		resp = r
	}
	if resp.Error != nil {
		log.Println("workqueue ERROR, fail to fetch data", rurl, "retries", urlRetry, "error", resp.Error)
	}
	ch <- resp
}

// Helper function which validates given URL
func validateUrl(rurl string) bool {
	if len(rurl) > 0 {
		if PatternUrl.MatchString(rurl) {
			return true
		}
		log.Error("invalid URL: ", rurl)
	}
	return false
}

// Response represents final response in a form of JSON structure
// we use custorm representation
func Response(rurl string, data []byte) []byte {
	b := []byte(`{"url":`)
	u := []byte(rurl)
	c := []byte(",")
	d := []byte(`"data":`)
	e := []byte(`}`)
	a := [][]byte{b, u, c, d, data, e}
	s := []byte(" ")
	r := bytes.Join(a, s)
	return r

}
<commit_msg>Read certs once and make Http client<commit_after>package utils

// WorkQueue/utils - Go utilities for WorkQueue
//
// Copyright (c) 2017 - Valentin Kuznetsov <vkuznet@gmail.com>

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"os/user"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/vkuznet/x509proxy"
)

// ResponseType structure is what we expect to get for our URL call.
// It contains a request URL, the data chunk and possible error from remote
type ResponseType struct {
	Url        string // response url
	Data       []byte // response data, i.e. what we got with Body of the response
	Error      error  // http error, a non-2xx return code is not an error
	Status     string // http status string
	StatusCode int    // http status code
}

// UrlRequest structure holds details about url request's attributes
type UrlRequest struct {
	rurl string
	args string
	out  chan<- ResponseType
	ts   int64
}

// VERBOSE variable control verbosity level of client's utilities
var VERBOSE int

// create global HTTP client and re-use it through the code
var _client = HttpClient()
var _certs []tls.Certificate

// UserDN function parses user Distinguished Name (DN) from client's HTTP request
func UserDN(r *http.Request) string {
	var names []interface{}
	for _, cert := range r.TLS.PeerCertificates {
		for _, name := range cert.Subject.Names {
			switch v := name.Value.(type) {
			case string:
				names = append(names, v)
			}
		}
	}
	parts := names[:7]
	return fmt.Sprintf("/DC=%s/DC=%s/OU=%s/OU=%s/CN=%s/CN=%s/CN=%s", parts...)
}

// client X509 certificates
func tlsCerts() ([]tls.Certificate, error) {
	if len(_certs) != 0 {
		return _certs, nil // use cached certs
	}
	uproxy := os.Getenv("X509_USER_PROXY")
	uckey := os.Getenv("X509_USER_KEY")
	ucert := os.Getenv("X509_USER_CERT")

	// check if /tmp/x509up_u$UID exists, if so setup X509_USER_PROXY env
	u, err := user.Current()
	if err == nil {
		fname := fmt.Sprintf("/tmp/x509up_u%s", u.Uid)
		if _, err := os.Stat(fname); err == nil {
			uproxy = fname
		}
	}
	if VERBOSE > 1 {
		log.Println("uproxy", uproxy)
		log.Println("uckey", uckey)
		log.Println("ucert", ucert)
	}

	if uproxy == "" && uckey == "" { // user doesn't have neither proxy or user certs
		return nil, nil
	}
	if uproxy != "" {
		// use local implementation of LoadX409KeyPair instead of tls one
		x509cert, err := x509proxy.LoadX509Proxy(uproxy)
		if err != nil {
			return nil, fmt.Errorf("failed to parse proxy X509 proxy set by X509_USER_PROXY: %v", err)
		}
		_certs = []tls.Certificate{x509cert}
		return _certs, nil
	}
	x509cert, err := tls.LoadX509KeyPair(ucert, uckey)
	if err != nil {
		return nil, fmt.Errorf("failed to parse user X509 certificate: %v", err)
	}
	_certs = []tls.Certificate{x509cert}
	return _certs, nil
}

// HttpClient provides HTTP client
func HttpClient() *http.Client {
	// get X509 certs
	certs, err := tlsCerts()
	if err != nil {
		panic(err.Error())
	}
	if len(certs) == 0 {
		return &http.Client{}
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{Certificates: certs,
			InsecureSkipVerify: true},
	}
	return &http.Client{Transport: tr}
}

func (r *ResponseType) String() string {
	return fmt.Sprintf("<Response: url=%s data=%s error=%v>", r.Url, string(r.Data), r.Error)
}

// FetchResponse fetches data for provided URL, args is a json dump of arguments
func FetchResponse(rurl, args string) ResponseType {
	startTime := time.Now()
	var response ResponseType
	response.Url = rurl
	if validateUrl(rurl) == false {
		response.Error = errors.New("Invalid URL")
		return response
	}
	var req *http.Request
	var e error
	if len(args) > 0 {
		jsonStr := []byte(args)
		req, e = http.NewRequest("POST", rurl, bytes.NewBuffer(jsonStr))
		req.Header.Set("Content-Type", "application/json")
	} else {
		req, e = http.NewRequest("GET", rurl, nil)
		if e != nil {
			log.Println("Unable to make GET request", e)
		}
		req.Header.Set("Accept", "application/json")
	}
	if VERBOSE > 1 {
		dump1, err1 := httputil.DumpRequestOut(req, true)
		log.Println("HTTP request", req, string(dump1), err1)
	}
	resp, err := _client.Do(req)
	if err != nil {
		log.Println("HTTP ERROR", err)
		response.Error = err
		return response
	}
	response.Status = resp.Status
	response.StatusCode = resp.StatusCode
	if VERBOSE > 0 {
		if len(args) > 0 {
			log.Println("HTTP POST", _client, rurl, string(args), err, time.Now().Sub(startTime))
		} else {
			log.Println("HTTP GET", _client, rurl, err, time.Now().Sub(startTime))
		}
	}
	if VERBOSE > 1 {
		dump2, err2 := httputil.DumpResponse(resp, true)
		log.Println("HTTP response", string(dump2), err2)
	}
	if err != nil {
		response.Error = err
		return response
	}
	defer resp.Body.Close()
	response.Data, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		response.Error = err
	}
	return response
}

// Fetch data for provided URL and redirect results to given channel
func Fetch(rurl, args string, ch chan<- ResponseType) {
	urlRetry := 3
	var resp, r ResponseType
	resp = FetchResponse(rurl, args)
	if resp.Error != nil {
		log.Println("workqueue WARNING, fail to fetch data", rurl, "error", resp.Error)
		for i := 1; i <= urlRetry; i++ {
			sleep := time.Duration(i) * time.Second
			time.Sleep(sleep)
			r = FetchResponse(rurl, args)
			if r.Error == nil {
				break
			}
			log.Println("workqueue WARNING", rurl, "retry", i, "error", r.Error)
		}
		resp = r
	}
	if resp.Error != nil {
		log.Println("workqueue ERROR, fail to fetch data", rurl, "retries", urlRetry, "error", resp.Error)
	}
	ch <- resp
}

// Helper function which validates given URL
func validateUrl(rurl string) bool {
	if len(rurl) > 0 {
		if PatternUrl.MatchString(rurl) {
			return true
		}
		log.Error("invalid URL: ", rurl)
	}
	return false
}

// Response represents final response in a form of JSON structure
// we use custorm representation
func Response(rurl string, data []byte) []byte {
	b := []byte(`{"url":`)
	u := []byte(rurl)
	c := []byte(",")
	d := []byte(`"data":`)
	e := []byte(`}`)
	a := [][]byte{b, u, c, d, data, e}
	s := []byte(" ")
	r := bytes.Join(a, s)
	return r

}
<|endoftext|>


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

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return true, err
			}

			light.Automatic = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return true, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

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

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

const initializationDuration = 10 * time.Second

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Initializing     bool       `json:"-"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return true, err
			}

			light.Automatic = true
			light.Initializing = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true
			light.Initializing = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Are we in initialization phase of the light? Keep setting light state
	if light.Initializing {
		err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
		if err != nil {
			return true, err
		}
		log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Initialization)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

		// Disable initialization phase after certain time
		if time.Now().After(light.Appearance.Add(initializationDuration)) {
			log.Debugf("💡 Light %s - Ending initialization phase after appearance at %v", light.Name, light.Appearance)
			light.Initializing = false
		}
		return true, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return true, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
}
<|endoftext|>