<commit_before>package kontrol

import (
	"errors"
	"fmt"
	"time"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
	"github.com/koding/kite/kontrol/onceevery"
	kontrolprotocol "github.com/koding/kite/kontrol/protocol"
	"github.com/koding/kite/protocol"
)

func (k *Kontrol) handleRegister(r *kite.Request) (interface{}, error) {
	k.log.Info("Register request from: %s", r.Client.Kite)

	if r.Args.One().MustMap()["url"].MustString() == "" {
		return nil, errors.New("invalid url")
	}

	var args struct {
		URL string `json:"url"`
	}
	r.Args.One().MustUnmarshal(&args)
	if args.URL == "" {
		return nil, errors.New("empty url")
	}

	// Only accept requests with kiteKey because we need this info
	// for generating tokens for this kite.
	if r.Auth.Type != "kiteKey" {
		return nil, fmt.Errorf("Unexpected authentication type: %s", r.Auth.Type)
	}

	kiteURL := args.URL
	remote := r.Client

	if err := validateKiteKey(&remote.Kite); err != nil {
		return nil, err
	}

	value := &kontrolprotocol.RegisterValue{
		URL: kiteURL,
	}

	// Register first by adding the value to the storage. Return if there is
	// any error.
	if err := k.storage.Upsert(&remote.Kite, value); err != nil {
		k.log.Error("storage add '%s' error: %s", remote.Kite, err)
		return nil, errors.New("internal error - register")
	}

	every := onceevery.New(UpdateInterval)

	ping := make(chan struct{}, 1)
	closed := false

	updaterFunc := func() {
		for {
			select {
			case <-ping:
				k.log.Info("Kite is active, got a ping %s", remote.Kite)
				every.Do(func() {
					k.log.Info("Kite is active, updating the value %s", remote.Kite)
					err := k.storage.Update(&remote.Kite, value)
					if err != nil {
						k.log.Error("storage update '%s' error: %s", remote.Kite, err)
					}
				})
			case <-time.After(HeartbeatInterval + HeartbeatDelay):
				k.log.Info("Kite didn't sent any heartbeat %s.", remote.Kite)
				closed = true
				return
			}
		}
	}
	go updaterFunc()

	heartbeatArgs := []interface{}{
		HeartbeatInterval / time.Second,
		dnode.Callback(func(args *dnode.Partial) {
			k.log.Info("Kite send us an heartbeat. %s", remote.Kite)

			k.clientLocks.Get(remote.Kite.ID).Lock()
			defer k.clientLocks.Get(remote.Kite.ID).Unlock()

			select {
			case ping <- struct{}{}:
			default:
			}

			// seems we miss a heartbeat, so start it again!
			if closed {
				closed = false
				k.log.Warning("Updater was closed, but we are still getting heartbeats. Starting again %s",
					remote.Kite)

				// it might be removed because the ttl cleaner would come
				// before us, so try to add it again, the updater will than
				// continue to update it afterwards.
				k.storage.Upsert(&remote.Kite, value)
				go updaterFunc()
			}
		}),
	}

	// now trigger the remote kite so it sends us periodically an heartbeat
	remote.GoWithTimeout("kite.heartbeat", 4*time.Second, heartbeatArgs...)

	k.log.Info("Kite registered: %s", remote.Kite)

	remote.OnDisconnect(func() {
		k.log.Info("Kite disconnected: %s", remote.Kite)
		every.Stop()
	})

	// send response back to the kite, also identify him with the new name
	return &protocol.RegisterResult{URL: args.URL}, nil
}

func (k *Kontrol) handleGetKites(r *kite.Request) (interface{}, error) {
	// This type is here until inversion branch is merged.
	// Reason: We can't use the same struct for marshaling and unmarshaling.
	// TODO use the struct in protocol
	type GetKitesArgs struct {
		Query *protocol.KontrolQuery `json:"query"`
	}

	var args GetKitesArgs
	r.Args.One().MustUnmarshal(&args)

	query := args.Query

	// audience will go into the token as "aud" claim.
	audience := getAudience(query)

	// Generate token once here because we are using the same token for every
	// kite we return and generating many tokens is really slow.
	token, err := generateToken(audience, r.Username,
		k.Kite.Kite().Username, k.privateKey)
	if err != nil {
		return nil, err
	}

	// Get kites from the storage
	kites, err := k.storage.Get(query)
	if err != nil {
		return nil, err
	}

	// Attach tokens to kites
	kites.Attach(token)

	return &protocol.GetKitesResult{
		Kites: kites,
	}, nil
}

func (k *Kontrol) handleGetToken(r *kite.Request) (interface{}, error) {
	var query *protocol.KontrolQuery
	err := r.Args.One().Unmarshal(&query)
	if err != nil {
		return nil, errors.New("Invalid query")
	}

	// check if it's exist
	kites, err := k.storage.Get(query)
	if err != nil {
		return nil, err
	}

	if len(kites) > 1 {
		return nil, errors.New("query matches more than one kite")
	}

	audience := getAudience(query)

	return generateToken(audience, r.Username, k.Kite.Kite().Username, k.privateKey)
}

func (k *Kontrol) handleMachine(r *kite.Request) (interface{}, error) {
	if k.MachineAuthenticate != nil {
		if err := k.MachineAuthenticate(r); err != nil {
			return nil, errors.New("cannot authenticate user")
		}
	}

	username := r.Args.One().MustString() // username should be send as an argument
	return k.registerUser(username)
}
<commit_msg>kontrol/handlers: disable those logs which were debugs previously<commit_after>package kontrol

import (
	"errors"
	"fmt"
	"time"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
	"github.com/koding/kite/kontrol/onceevery"
	kontrolprotocol "github.com/koding/kite/kontrol/protocol"
	"github.com/koding/kite/protocol"
)

func (k *Kontrol) handleRegister(r *kite.Request) (interface{}, error) {
	k.log.Info("Register request from: %s", r.Client.Kite)

	if r.Args.One().MustMap()["url"].MustString() == "" {
		return nil, errors.New("invalid url")
	}

	var args struct {
		URL string `json:"url"`
	}
	r.Args.One().MustUnmarshal(&args)
	if args.URL == "" {
		return nil, errors.New("empty url")
	}

	// Only accept requests with kiteKey because we need this info
	// for generating tokens for this kite.
	if r.Auth.Type != "kiteKey" {
		return nil, fmt.Errorf("Unexpected authentication type: %s", r.Auth.Type)
	}

	kiteURL := args.URL
	remote := r.Client

	if err := validateKiteKey(&remote.Kite); err != nil {
		return nil, err
	}

	value := &kontrolprotocol.RegisterValue{
		URL: kiteURL,
	}

	// Register first by adding the value to the storage. Return if there is
	// any error.
	if err := k.storage.Upsert(&remote.Kite, value); err != nil {
		k.log.Error("storage add '%s' error: %s", remote.Kite, err)
		return nil, errors.New("internal error - register")
	}

	every := onceevery.New(UpdateInterval)

	ping := make(chan struct{}, 1)
	closed := false

	updaterFunc := func() {
		for {
			select {
			case <-ping:
				k.log.Debug("Kite is active, got a ping %s", remote.Kite)
				every.Do(func() {
					k.log.Info("Kite is active, updating the value %s", remote.Kite)
					err := k.storage.Update(&remote.Kite, value)
					if err != nil {
						k.log.Error("storage update '%s' error: %s", remote.Kite, err)
					}
				})
			case <-time.After(HeartbeatInterval + HeartbeatDelay):
				k.log.Info("Kite didn't sent any heartbeat %s.", remote.Kite)
				closed = true
				return
			}
		}
	}
	go updaterFunc()

	heartbeatArgs := []interface{}{
		HeartbeatInterval / time.Second,
		dnode.Callback(func(args *dnode.Partial) {
			k.log.Debug("Kite send us an heartbeat. %s", remote.Kite)

			k.clientLocks.Get(remote.Kite.ID).Lock()
			defer k.clientLocks.Get(remote.Kite.ID).Unlock()

			select {
			case ping <- struct{}{}:
			default:
			}

			// seems we miss a heartbeat, so start it again!
			if closed {
				closed = false
				k.log.Warning("Updater was closed, but we are still getting heartbeats. Starting again %s",
					remote.Kite)

				// it might be removed because the ttl cleaner would come
				// before us, so try to add it again, the updater will than
				// continue to update it afterwards.
				k.storage.Upsert(&remote.Kite, value)
				go updaterFunc()
			}
		}),
	}

	// now trigger the remote kite so it sends us periodically an heartbeat
	remote.GoWithTimeout("kite.heartbeat", 4*time.Second, heartbeatArgs...)

	k.log.Info("Kite registered: %s", remote.Kite)

	remote.OnDisconnect(func() {
		k.log.Info("Kite disconnected: %s", remote.Kite)
		every.Stop()
	})

	// send response back to the kite, also identify him with the new name
	return &protocol.RegisterResult{URL: args.URL}, nil
}

func (k *Kontrol) handleGetKites(r *kite.Request) (interface{}, error) {
	// This type is here until inversion branch is merged.
	// Reason: We can't use the same struct for marshaling and unmarshaling.
	// TODO use the struct in protocol
	type GetKitesArgs struct {
		Query *protocol.KontrolQuery `json:"query"`
	}

	var args GetKitesArgs
	r.Args.One().MustUnmarshal(&args)

	query := args.Query

	// audience will go into the token as "aud" claim.
	audience := getAudience(query)

	// Generate token once here because we are using the same token for every
	// kite we return and generating many tokens is really slow.
	token, err := generateToken(audience, r.Username,
		k.Kite.Kite().Username, k.privateKey)
	if err != nil {
		return nil, err
	}

	// Get kites from the storage
	kites, err := k.storage.Get(query)
	if err != nil {
		return nil, err
	}

	// Attach tokens to kites
	kites.Attach(token)

	return &protocol.GetKitesResult{
		Kites: kites,
	}, nil
}

func (k *Kontrol) handleGetToken(r *kite.Request) (interface{}, error) {
	var query *protocol.KontrolQuery
	err := r.Args.One().Unmarshal(&query)
	if err != nil {
		return nil, errors.New("Invalid query")
	}

	// check if it's exist
	kites, err := k.storage.Get(query)
	if err != nil {
		return nil, err
	}

	if len(kites) > 1 {
		return nil, errors.New("query matches more than one kite")
	}

	audience := getAudience(query)

	return generateToken(audience, r.Username, k.Kite.Kite().Username, k.privateKey)
}

func (k *Kontrol) handleMachine(r *kite.Request) (interface{}, error) {
	if k.MachineAuthenticate != nil {
		if err := k.MachineAuthenticate(r); err != nil {
			return nil, errors.New("cannot authenticate user")
		}
	}

	username := r.Args.One().MustString() // username should be send as an argument
	return k.registerUser(username)
}
<|endoftext|>


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

package kvscheduler

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/unrolled/render"

	"github.com/ligato/cn-infra/rpc/rest"
	. "github.com/ligato/cn-infra/kvscheduler/api"
	"github.com/ligato/cn-infra/kvscheduler/internal/graph"
)

const (
	// prefix used for REST urls of the scheduler.
	urlPrefix = "/scheduler/"

	// txnHistoryURL is URL used to obtain the transaction history.
	txnHistoryURL = urlPrefix + "txn-history"

	// sinceArg is the name of the argument used to define the start of the time
	// window for the transaction history to display.
	sinceArg = "since"

	// untilArg is the name of the argument used to define the end of the time
	// window for the transaction history to display.
	untilArg = "until"

	// seqNumArg is the name of the argument used to define the sequence number
	// of the transaction to display (txnHistoryURL).
	seqNumArg = "seq-num"

	// keyTimelineURL is URL used to obtain timeline of value changes for a given key.
	keyTimelineURL = urlPrefix + "key-timeline"

	// keyArg is the name of the argument used to define key for "key-timeline" API.
	keyArg = "key"

	// graphSnapshotURL is URL used to obtain graph snapshot from a given point in time.
	graphSnapshotURL = urlPrefix + "graph-snapshot"

	// flagStatsURL is URL used to obtain flag statistics.
	flagStatsURL = urlPrefix + "flag-stats"

	// flagArg is the name of the argument used to define flag for "flag-stats" API.
	flagArg = "flag"

	// prefixArg is the name of the argument used to define prefix to filter keys
	// for "flag-stats" API.
	prefixArg = "prefix"

	// time is the name of the argument used to define point in time for a graph snapshot
	// to retrieve.
	timeArg = "time"

	// downstreamResyncURL is URL used to trigger downstream-resync.
	downstreamResyncURL = urlPrefix + "downstream-resync"

	// dumpURL is URL used to dump either SB or scheduler's internal state of kv-pairs
	// under the given descriptor.
	dumpURL = urlPrefix + "dump"

	// descriptorArg is the name of the argument used to define descriptor for "dump" API.
	descriptorArg = "descriptor"

	// internalArg is the name of the argument used for "dump" API to tell whether
	// to dump SB or the scheduler's internal view of SB.
	internalArg = "internal"
)

// registerHandlers registers all supported REST APIs.
func (scheduler *Scheduler) registerHandlers(http rest.HTTPHandlers) {
	if http == nil {
		scheduler.Log.Warn("No http handler provided, skipping registration of KVScheduler REST handlers")
		return
	}
	http.RegisterHTTPHandler(txnHistoryURL, scheduler.txnHistoryGetHandler, "GET")
	http.RegisterHTTPHandler(keyTimelineURL, scheduler.keyTimelineGetHandler, "GET")
	http.RegisterHTTPHandler(graphSnapshotURL, scheduler.graphSnapshotGetHandler, "GET")
	http.RegisterHTTPHandler(flagStatsURL, scheduler.flagStatsGetHandler, "GET")
	http.RegisterHTTPHandler(downstreamResyncURL, scheduler.downstreamResyncPostHandler, "POST")
	http.RegisterHTTPHandler(dumpURL, scheduler.dumpGetHandler, "GET")
}

// txnHistoryGetHandler is the GET handler for "txn-history" API.
func (scheduler *Scheduler) txnHistoryGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		var since, until time.Time
		var seqNum int
		args := req.URL.Query()

		// parse optional *seq-num* argument
		if seqNumStr, withSeqNum := args[seqNumArg]; withSeqNum && len(seqNumStr) == 1 {
			var err error
			seqNum, err = strconv.Atoi(seqNumStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}

			// sequence number takes precedence over the since-until time window
			txn := scheduler.getRecordedTransaction(uint(seqNum))
			if txn == nil {
				formatter.JSON(w, http.StatusNotFound, "transaction with such sequence is not recorded")
				return
			}

			formatter.Text(w, http.StatusOK, txn.StringWithOpts(false, 0))
			return
		}

		// parse optional *until* argument
		if untilStr, withUntil := args[untilArg]; withUntil && len(untilStr) == 1 {
			var err error
			until, err = stringToTime(untilStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		// parse optional *since* argument
		if sinceStr, withSince := args[sinceArg]; withSince && len(sinceStr) == 1 {
			var err error
			since, err = stringToTime(sinceStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		txnHistory := scheduler.getTransactionHistory(since, until)
		formatter.Text(w, http.StatusOK, txnHistory.StringWithOpts(false, 0))
	}
}

// keyTimelineGetHandler is the GET handler for "key-timeline" API.
func (scheduler *Scheduler) keyTimelineGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()

		// parse mandatory *key* argument
		if keys, withKey := args[keyArg]; withKey && len(keys) == 1 {
			graphR := scheduler.graph.Read()
			defer graphR.Release()

			timeline := graphR.GetNodeTimeline(keys[0])
			formatter.JSON(w, http.StatusOK, timeline)
			return
		}

		err := errors.New("missing key argument")
		formatter.JSON(w, http.StatusInternalServerError, err)
		return
	}
}

// graphSnapshotGetHandler is the GET handler for "graph-snapshot" API.
func (scheduler *Scheduler) graphSnapshotGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		timeVal := time.Now()
		args := req.URL.Query()

		// parse optional *time* argument
		if timeStr, withTime := args[timeArg]; withTime && len(timeStr) == 1 {
			var err error
			timeVal, err = stringToTime(timeStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		graphR := scheduler.graph.Read()
		defer graphR.Release()

		snapshot := graphR.GetSnapshot(timeVal)
		formatter.JSON(w, http.StatusOK, snapshot)
	}
}

// flagStatsGetHandler is the GET handler for "flag-stats" API.
func (scheduler *Scheduler) flagStatsGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()
		var prefixes []string

		// parse repeated *prefix* argument
		prefixes, _ = args[prefixArg]

		if flags, withFlag := args[flagArg]; withFlag && len(flags) == 1 {
			graphR := scheduler.graph.Read()
			defer graphR.Release()

			stats := graphR.GetFlagStats(flags[0], func(key string) bool {
				if len(prefixes) == 0 {
					return true
				}
				for _, prefix := range prefixes {
					if strings.HasPrefix(key, prefix) {
						return true
					}
				}
				return false
			})
			formatter.JSON(w, http.StatusOK, stats)
			return
		}

		err := errors.New("missing flag argument")
		formatter.JSON(w, http.StatusInternalServerError, err)
		return
	}
}

// downstreamResyncPostHandler is the POST handler for "downstream-resync" API.
func (scheduler *Scheduler) downstreamResyncPostHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		ctx := context.Background()
		ctx = WithDownstreamResync(ctx)
		kvErrors, txnError := scheduler.StartNBTransaction().Commit(ctx)
		if txnError != nil {
			formatter.JSON(w, http.StatusInternalServerError, txnError)
			return
		}
		if len(kvErrors) > 0 {
			formatter.JSON(w, http.StatusInternalServerError, kvErrors)
			return
		}
		formatter.Text(w, http.StatusOK, "SB was successfully synchronized with KVScheduler")
		return
	}
}

// dumpGetHandler is the GET handler for "dump" API.
func (scheduler *Scheduler) dumpGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()

		// parse mandatory *descriptor* argument
		descriptors, withDescriptor := args[descriptorArg]
		if !withDescriptor {
			err := errors.New("missing descriptor argument")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		if len(descriptors) != 1 {
			err := errors.New("descriptor argument listed more than once")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		descriptor := descriptors[0]

		// parse optional *internal* argument
		internalDump := false
		if internalStr, withInternal := args[internalArg]; withInternal && len(internalStr) == 1 {
			internalVal := internalStr[0]
			if internalVal == "true" || internalVal == "1" {
				internalDump = true
			}
		}

		// pause transaction processing
		if !internalDump {
			scheduler.txnLock.Lock()
			defer scheduler.txnLock.Unlock()
		}

		graphR := scheduler.graph.Read()
		defer graphR.Release()

		// dump from the in-memory graph first (for SB Dump it is used for correlation)
		inMemNodes := nodesToKVPairsWithMetadata(
			graphR.GetNodes(nil,
				graph.WithFlags(&DescriptorFlag{descriptor}),
				graph.WithoutFlags(&PendingFlag{}, &DerivedFlag{})))

		if internalDump {
			// return the scheduler's view of SB for the given descriptor
			formatter.JSON(w, http.StatusOK, inMemNodes)
			return
		}

		// obtain Dump handler from the descriptor
		kvDescriptor := scheduler.registry.GetDescriptor(descriptor)
		if kvDescriptor == nil {
			err := errors.New("descriptor is not registered")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		if kvDescriptor.Dump == nil {
			err := errors.New("descriptor does not support Dump operation")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}

		// dump the state directly from SB via descriptor
		dump, err := kvDescriptor.Dump(inMemNodes)
		if err != nil {
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		formatter.JSON(w, http.StatusOK, dump)
		return
	}
}

// stringToTime converts Unix timestamp from string to time.Time.
func stringToTime(s string) (time.Time, error) {
	sec, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return time.Time{}, err
	}
	return time.Unix(sec, 0), nil
}
<commit_msg>Add missing newline.<commit_after>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kvscheduler

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/unrolled/render"

	"github.com/ligato/cn-infra/rpc/rest"
	. "github.com/ligato/cn-infra/kvscheduler/api"
	"github.com/ligato/cn-infra/kvscheduler/internal/graph"
)

const (
	// prefix used for REST urls of the scheduler.
	urlPrefix = "/scheduler/"

	// txnHistoryURL is URL used to obtain the transaction history.
	txnHistoryURL = urlPrefix + "txn-history"

	// sinceArg is the name of the argument used to define the start of the time
	// window for the transaction history to display.
	sinceArg = "since"

	// untilArg is the name of the argument used to define the end of the time
	// window for the transaction history to display.
	untilArg = "until"

	// seqNumArg is the name of the argument used to define the sequence number
	// of the transaction to display (txnHistoryURL).
	seqNumArg = "seq-num"

	// keyTimelineURL is URL used to obtain timeline of value changes for a given key.
	keyTimelineURL = urlPrefix + "key-timeline"

	// keyArg is the name of the argument used to define key for "key-timeline" API.
	keyArg = "key"

	// graphSnapshotURL is URL used to obtain graph snapshot from a given point in time.
	graphSnapshotURL = urlPrefix + "graph-snapshot"

	// flagStatsURL is URL used to obtain flag statistics.
	flagStatsURL = urlPrefix + "flag-stats"

	// flagArg is the name of the argument used to define flag for "flag-stats" API.
	flagArg = "flag"

	// prefixArg is the name of the argument used to define prefix to filter keys
	// for "flag-stats" API.
	prefixArg = "prefix"

	// time is the name of the argument used to define point in time for a graph snapshot
	// to retrieve.
	timeArg = "time"

	// downstreamResyncURL is URL used to trigger downstream-resync.
	downstreamResyncURL = urlPrefix + "downstream-resync"

	// dumpURL is URL used to dump either SB or scheduler's internal state of kv-pairs
	// under the given descriptor.
	dumpURL = urlPrefix + "dump"

	// descriptorArg is the name of the argument used to define descriptor for "dump" API.
	descriptorArg = "descriptor"

	// internalArg is the name of the argument used for "dump" API to tell whether
	// to dump SB or the scheduler's internal view of SB.
	internalArg = "internal"
)

// registerHandlers registers all supported REST APIs.
func (scheduler *Scheduler) registerHandlers(http rest.HTTPHandlers) {
	if http == nil {
		scheduler.Log.Warn("No http handler provided, skipping registration of KVScheduler REST handlers")
		return
	}
	http.RegisterHTTPHandler(txnHistoryURL, scheduler.txnHistoryGetHandler, "GET")
	http.RegisterHTTPHandler(keyTimelineURL, scheduler.keyTimelineGetHandler, "GET")
	http.RegisterHTTPHandler(graphSnapshotURL, scheduler.graphSnapshotGetHandler, "GET")
	http.RegisterHTTPHandler(flagStatsURL, scheduler.flagStatsGetHandler, "GET")
	http.RegisterHTTPHandler(downstreamResyncURL, scheduler.downstreamResyncPostHandler, "POST")
	http.RegisterHTTPHandler(dumpURL, scheduler.dumpGetHandler, "GET")
}

// txnHistoryGetHandler is the GET handler for "txn-history" API.
func (scheduler *Scheduler) txnHistoryGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		var since, until time.Time
		var seqNum int
		args := req.URL.Query()

		// parse optional *seq-num* argument
		if seqNumStr, withSeqNum := args[seqNumArg]; withSeqNum && len(seqNumStr) == 1 {
			var err error
			seqNum, err = strconv.Atoi(seqNumStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}

			// sequence number takes precedence over the since-until time window
			txn := scheduler.getRecordedTransaction(uint(seqNum))
			if txn == nil {
				formatter.JSON(w, http.StatusNotFound, "transaction with such sequence is not recorded")
				return
			}

			formatter.Text(w, http.StatusOK, txn.StringWithOpts(false, 0))
			return
		}

		// parse optional *until* argument
		if untilStr, withUntil := args[untilArg]; withUntil && len(untilStr) == 1 {
			var err error
			until, err = stringToTime(untilStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		// parse optional *since* argument
		if sinceStr, withSince := args[sinceArg]; withSince && len(sinceStr) == 1 {
			var err error
			since, err = stringToTime(sinceStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		txnHistory := scheduler.getTransactionHistory(since, until)
		formatter.Text(w, http.StatusOK, txnHistory.StringWithOpts(false, 0))
	}
}

// keyTimelineGetHandler is the GET handler for "key-timeline" API.
func (scheduler *Scheduler) keyTimelineGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()

		// parse mandatory *key* argument
		if keys, withKey := args[keyArg]; withKey && len(keys) == 1 {
			graphR := scheduler.graph.Read()
			defer graphR.Release()

			timeline := graphR.GetNodeTimeline(keys[0])
			formatter.JSON(w, http.StatusOK, timeline)
			return
		}

		err := errors.New("missing key argument")
		formatter.JSON(w, http.StatusInternalServerError, err)
		return
	}
}

// graphSnapshotGetHandler is the GET handler for "graph-snapshot" API.
func (scheduler *Scheduler) graphSnapshotGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		timeVal := time.Now()
		args := req.URL.Query()

		// parse optional *time* argument
		if timeStr, withTime := args[timeArg]; withTime && len(timeStr) == 1 {
			var err error
			timeVal, err = stringToTime(timeStr[0])
			if err != nil {
				formatter.JSON(w, http.StatusInternalServerError, err)
				return
			}
		}

		graphR := scheduler.graph.Read()
		defer graphR.Release()

		snapshot := graphR.GetSnapshot(timeVal)
		formatter.JSON(w, http.StatusOK, snapshot)
	}
}

// flagStatsGetHandler is the GET handler for "flag-stats" API.
func (scheduler *Scheduler) flagStatsGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()
		var prefixes []string

		// parse repeated *prefix* argument
		prefixes, _ = args[prefixArg]

		if flags, withFlag := args[flagArg]; withFlag && len(flags) == 1 {
			graphR := scheduler.graph.Read()
			defer graphR.Release()

			stats := graphR.GetFlagStats(flags[0], func(key string) bool {
				if len(prefixes) == 0 {
					return true
				}
				for _, prefix := range prefixes {
					if strings.HasPrefix(key, prefix) {
						return true
					}
				}
				return false
			})
			formatter.JSON(w, http.StatusOK, stats)
			return
		}

		err := errors.New("missing flag argument")
		formatter.JSON(w, http.StatusInternalServerError, err)
		return
	}
}

// downstreamResyncPostHandler is the POST handler for "downstream-resync" API.
func (scheduler *Scheduler) downstreamResyncPostHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		ctx := context.Background()
		ctx = WithDownstreamResync(ctx)
		kvErrors, txnError := scheduler.StartNBTransaction().Commit(ctx)
		if txnError != nil {
			formatter.JSON(w, http.StatusInternalServerError, txnError)
			return
		}
		if len(kvErrors) > 0 {
			formatter.JSON(w, http.StatusInternalServerError, kvErrors)
			return
		}
		formatter.Text(w, http.StatusOK, "SB was successfully synchronized with KVScheduler\n")
		return
	}
}

// dumpGetHandler is the GET handler for "dump" API.
func (scheduler *Scheduler) dumpGetHandler(formatter *render.Render) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		args := req.URL.Query()

		// parse mandatory *descriptor* argument
		descriptors, withDescriptor := args[descriptorArg]
		if !withDescriptor {
			err := errors.New("missing descriptor argument")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		if len(descriptors) != 1 {
			err := errors.New("descriptor argument listed more than once")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		descriptor := descriptors[0]

		// parse optional *internal* argument
		internalDump := false
		if internalStr, withInternal := args[internalArg]; withInternal && len(internalStr) == 1 {
			internalVal := internalStr[0]
			if internalVal == "true" || internalVal == "1" {
				internalDump = true
			}
		}

		// pause transaction processing
		if !internalDump {
			scheduler.txnLock.Lock()
			defer scheduler.txnLock.Unlock()
		}

		graphR := scheduler.graph.Read()
		defer graphR.Release()

		// dump from the in-memory graph first (for SB Dump it is used for correlation)
		inMemNodes := nodesToKVPairsWithMetadata(
			graphR.GetNodes(nil,
				graph.WithFlags(&DescriptorFlag{descriptor}),
				graph.WithoutFlags(&PendingFlag{}, &DerivedFlag{})))

		if internalDump {
			// return the scheduler's view of SB for the given descriptor
			formatter.JSON(w, http.StatusOK, inMemNodes)
			return
		}

		// obtain Dump handler from the descriptor
		kvDescriptor := scheduler.registry.GetDescriptor(descriptor)
		if kvDescriptor == nil {
			err := errors.New("descriptor is not registered")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		if kvDescriptor.Dump == nil {
			err := errors.New("descriptor does not support Dump operation")
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}

		// dump the state directly from SB via descriptor
		dump, err := kvDescriptor.Dump(inMemNodes)
		if err != nil {
			formatter.JSON(w, http.StatusInternalServerError, err)
			return
		}
		formatter.JSON(w, http.StatusOK, dump)
		return
	}
}

// stringToTime converts Unix timestamp from string to time.Time.
func stringToTime(s string) (time.Time, error) {
	sec, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return time.Time{}, err
	}
	return time.Unix(sec, 0), nil
}
<|endoftext|>


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

package signalfx

import (
	"bytes"
	"fmt"
	"github.com/intelsdi-x/snap-plugin-lib-go/v1/plugin"
	"github.com/signalfx/golib/datapoint"
	"github.com/signalfx/golib/sfxclient"
	"golang.org/x/net/context"
	"log"
	"os"
	"strings"
)

const (
	NS_VENDOR = "opsvision"		// plugin vendor
	NS_PLUGIN = "signalfx"		// plugin name
	VERSION   = 1			// plugin version
)

// Our SignalFx object
type SignalFx struct {
	token     string		// SignalFx API token
	hostname  string		// Hostname
	namespace string		// Metric namespace
}

// Constructor
func New() *SignalFx {
	return new(SignalFx)
}

/**
 * Returns the configPolicy for the plugin
 */
func (s *SignalFx) GetConfigPolicy() (plugin.ConfigPolicy, error) {
	policy := plugin.NewConfigPolicy()

	// The SignalFx token
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"token",
		true)

	// The hostname to use (defaults to local hostname)
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"hostname",
		false)

	// The file name to use when debugging
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"debug-file",
		false)

	return *policy, nil
}

/**
 * Publish metrics to SignalFx using the TOKEN found in the config
 */
func (s *SignalFx) Publish(mts []plugin.Metric, cfg plugin.Config) error {
	// Enable debugging if the debug-file config property was set
	fileName, err := cfg.GetString("debug-file")
	if err != nil {
		// Open the output file
		f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return err
		}
		defer f.Close()

		// Set logging output for debugging
		log.SetOutput(f)
	}

	// Fetch the token
	token, err := cfg.GetString("token")
	if err != nil {
		return err
	}
	s.token = token

	// Attempt to set the hostname
	hostname, err := cfg.GetString("hostname")
	if err != nil {
		hostname, err = os.Hostname()
		if err != nil {
			hostname = "localhost"
		}
	}
	s.hostname = hostname

	// Iterate over the supplied metrics
	for _, m := range mts {
		var buffer bytes.Buffer

		// Convert the namespace to dot notation
		fmt.Fprintf(&buffer, "snap.%s", strings.Join(m.Namespace.Strings(), "."))
		s.namespace = buffer.String()

		// Do some type conversion and send the data
		switch v := m.Data.(type) {
		case uint:
			s.sendIntValue(int64(v))
		case uint32:
			s.sendIntValue(int64(v))
		case uint64:
			s.sendIntValue(int64(v))
		case int:
			s.sendIntValue(int64(v))
		case int32:
			s.sendIntValue(int64(v))
		case int64:
			s.sendIntValue(int64(v))
		case float32:
			s.sendFloatValue(float64(v))
		case float64:
			s.sendFloatValue(float64(v))
		default:
			log.Printf("Ignoring %T: %v\n", v, v)
			log.Printf("Contact the plugin author if you think this is an error")
		}
	}

	return nil
}

/**
 * Method for sending int64 values to SignalFx
 */
func (s *SignalFx) sendIntValue(value int64) {
	log.Printf("Sending [int64] %s -> %v", s.namespace, value)

	client := sfxclient.NewHTTPDatapointSink()
	client.AuthToken = s.token
	ctx := context.Background()
	client.AddDatapoints(ctx, []*datapoint.Datapoint{
		sfxclient.Gauge(s.namespace, map[string]string{
			"host": s.hostname,
		}, value),
	})
}

/**
 * Method for sending float64 values to SignalFx
 */
func (s *SignalFx) sendFloatValue(value float64) {
	log.Printf("Sending [float64] %s -> %v", s.namespace, value)

	client := sfxclient.NewHTTPDatapointSink()
	client.AuthToken = s.token
	ctx := context.Background()
	client.AddDatapoints(ctx, []*datapoint.Datapoint{
		sfxclient.GaugeF(s.namespace, map[string]string{
			"host": s.hostname,
		}, value),
	})
}
<commit_msg>Fixed up the comments per lint<commit_after>/*
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * Copyright 2017 OpsVision Solutions
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package signalfx

import (
	"bytes"
	"fmt"
	"github.com/intelsdi-x/snap-plugin-lib-go/v1/plugin"
	"github.com/signalfx/golib/datapoint"
	"github.com/signalfx/golib/sfxclient"
	"golang.org/x/net/context"
	"log"
	"os"
	"strings"
)

const (
	NS_VENDOR = "opsvision"		// plugin vendor
	NS_PLUGIN = "signalfx"		// plugin name
	VERSION   = 1			// plugin version
)

// Our SignalFx object
type SignalFx struct {
	token     string		// SignalFx API token
	hostname  string		// Hostname
	namespace string		// Metric namespace
}

// New() - Constructor
func New() *SignalFx {
	return new(SignalFx)
}

/**
 * GetConfigPolicy() - Returns the configPolicy for the plugin
 */
func (s *SignalFx) GetConfigPolicy() (plugin.ConfigPolicy, error) {
	policy := plugin.NewConfigPolicy()

	// The SignalFx token
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"token",
		true)

	// The hostname to use (defaults to local hostname)
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"hostname",
		false)

	// The file name to use when debugging
	policy.AddNewStringRule([]string{NS_VENDOR, NS_PLUGIN},
		"debug-file",
		false)

	return *policy, nil
}

/**
 * Publish() - Publish metrics to SignalFx using the TOKEN found in the config
 */
func (s *SignalFx) Publish(mts []plugin.Metric, cfg plugin.Config) error {
	// Enable debugging if the debug-file config property was set
	fileName, err := cfg.GetString("debug-file")
	if err != nil {
		// Open the output file
		f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return err
		}
		defer f.Close()

		// Set logging output for debugging
		log.SetOutput(f)
	}

	// Fetch the token
	token, err := cfg.GetString("token")
	if err != nil {
		return err
	}
	s.token = token

	// Attempt to set the hostname
	hostname, err := cfg.GetString("hostname")
	if err != nil {
		hostname, err = os.Hostname()
		if err != nil {
			hostname = "localhost"
		}
	}
	s.hostname = hostname

	// Iterate over the supplied metrics
	for _, m := range mts {
		var buffer bytes.Buffer

		// Convert the namespace to dot notation
		fmt.Fprintf(&buffer, "snap.%s", strings.Join(m.Namespace.Strings(), "."))
		s.namespace = buffer.String()

		// Do some type conversion and send the data
		switch v := m.Data.(type) {
		case uint:
			s.sendIntValue(int64(v))
		case uint32:
			s.sendIntValue(int64(v))
		case uint64:
			s.sendIntValue(int64(v))
		case int:
			s.sendIntValue(int64(v))
		case int32:
			s.sendIntValue(int64(v))
		case int64:
			s.sendIntValue(int64(v))
		case float32:
			s.sendFloatValue(float64(v))
		case float64:
			s.sendFloatValue(float64(v))
		default:
			log.Printf("Ignoring %T: %v\n", v, v)
			log.Printf("Contact the plugin author if you think this is an error")
		}
	}

	return nil
}

/**
 * Method for sending int64 values to SignalFx
 */
func (s *SignalFx) sendIntValue(value int64) {
	log.Printf("Sending [int64] %s -> %v", s.namespace, value)

	client := sfxclient.NewHTTPDatapointSink()
	client.AuthToken = s.token
	ctx := context.Background()
	client.AddDatapoints(ctx, []*datapoint.Datapoint{
		sfxclient.Gauge(s.namespace, map[string]string{
			"host": s.hostname,
		}, value),
	})
}

/**
 * Method for sending float64 values to SignalFx
 */
func (s *SignalFx) sendFloatValue(value float64) {
	log.Printf("Sending [float64] %s -> %v", s.namespace, value)

	client := sfxclient.NewHTTPDatapointSink()
	client.AuthToken = s.token
	ctx := context.Background()
	client.AddDatapoints(ctx, []*datapoint.Datapoint{
		sfxclient.GaugeF(s.namespace, map[string]string{
			"host": s.hostname,
		}, value),
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/fsnotify/fsnotify"
	log "github.com/sirupsen/logrus"

	"github.com/carlostrub/sisyphus"
	"gopkg.in/urfave/cli.v2"
)

var (
	version string
)

func main() {

	// Define App
	app := cli.NewApp()
	app.Name = "Sisyphus"
	app.Usage = "Intelligent Junk and Spam Mail Handler"
	app.UsageText = `sisyphus [global options] command [command options]

	Sisyphus applies artificial intelligence to filter
	Junk mail in an unobtrusive way. Both, classification and learning
	operate directly on the Maildir of a user in a fully transparent mode,
	without any need for configuration or active operation.`
	app.HelpName = "Intelligent Junk and Spam Mail Handler"
	app.Version = version
	app.Copyright = "(c) 2017, Carlo Strub. All rights reserved. This binary is licensed under a BSD 3-Clause License."
	app.Authors = []cli.Author{
		{
			Name:  "Carlo Strub",
			Email: "cs@carlostrub.ch",
		},
	}

	maildirPaths := cli.StringSlice([]string{})

	var pidfile *string
	pidfile = new(string)

	app.Flags = []cli.Flag{

		cli.StringSliceFlag{
			Name:   "maildir, d",
			Value:  &maildirPaths,
			EnvVar: "SISYPHUS_DIRS",
			Usage:  "Call multiple Maildirs by repeating this flag, i.e. --maildir \"./Maildir\" --maildir \"./Maildir2\"",
		},
		cli.StringFlag{
			Name:        "pidfile, p",
			Value:       "/tmp/sisyphus.pid",
			EnvVar:      "SISYPHUS_PID",
			Usage:       "Location of PID file",
			Destination: pidfile,
		},
	}

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{"u"},
			Usage:   "run sisyphus",
			Action: func(c *cli.Context) {

				// check if daemon already running.
				if _, err := os.Stat(*pidfile); err == nil {
					log.WithFields(log.Fields{
						"pidfile": *pidfile,
					}).Fatal("Already running or pidfile exists")
				}

				fmt.Print(`

	███████╗██╗███████╗██╗   ██╗██████╗ ██╗  ██╗██╗   ██╗███████╗
	██╔════╝██║██╔════╝╚██╗ ██╔╝██╔══██╗██║  ██║██║   ██║██╔════╝
	███████╗██║███████╗ ╚████╔╝ ██████╔╝███████║██║   ██║███████╗
	╚════██║██║╚════██║  ╚██╔╝  ██╔═══╝ ██╔══██║██║   ██║╚════██║
	███████║██║███████║   ██║   ██║     ██║  ██║╚██████╔╝███████║
	╚══════╝╚═╝╚══════╝   ╚═╝   ╚═╝     ╚═╝  ╚═╝ ╚═════╝ ╚══════╝

	by Carlo Strub <cs@carlostrub.ch>

`)
				// Make arrangement to remove PID file upon receiving the SIGTERM from kill command
				ch := make(chan os.Signal, 1)
				signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)

				go func() {
					signalType := <-ch
					signal.Stop(ch)
					log.Info("Exit command received. Exiting sisyphus...")

					// this is a good place to flush everything to disk
					// before terminating.
					log.WithFields(log.Fields{
						"signal": signalType,
					}).Info("Received signal")

					// remove PID file
					os.Remove(*pidfile)

					os.Exit(0)

				}()

				if len(maildirPaths) < 1 {
					log.Fatal("No Maildir set. Please check the manual.")
				}

				// Populate maildir with the maildirs given by setting the flag.
				var maildirs []sisyphus.Maildir
				for _, val := range maildirPaths {
					maildirs = append(maildirs, sisyphus.Maildir(val))
				}

				// Load all mails
				mails, err := sisyphus.LoadMails(maildirs)
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot load mails")
				}

				// Open all databases
				dbs, err := sisyphus.LoadDatabases(maildirs)
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot load databases")
				}
				defer sisyphus.CloseDatabases(dbs)

				// Learn at startup
				for _, d := range maildirs {
					db := dbs[d]
					m := mails[d]
					for _, val := range m {
						err := val.Learn(db, d)
						if err != nil {
							log.WithFields(log.Fields{
								"err":  err,
								"mail": val.Key,
							}).Warning("Cannot learn mail")
						}
					}
				}
				log.Info("All mails learned")

				// Classify whenever a mail arrives in "new"
				watcher, err := fsnotify.NewWatcher()
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot setup directory watcher")
				}
				defer watcher.Close()

				done := make(chan bool)
				go func() {
					for {
						select {
						case event := <-watcher.Events:
							if event.Op&fsnotify.Create == fsnotify.Create {
								path := strings.Split(event.Name, "/new/")
								m := sisyphus.Mail{
									Key: path[1],
								}

								err = m.Classify(dbs[sisyphus.Maildir(path[0])], sisyphus.Maildir(path[0]))
								if err != nil {
									log.WithFields(log.Fields{
										"err": err,
									}).Error("Classify mail")
								}

							}
						case err := <-watcher.Errors:
							log.WithFields(log.Fields{
								"err": err,
							}).Error("Problem with directory watcher")
						}
					}
				}()

				for _, val := range maildirPaths {
					err = watcher.Add(val + "/new")
					if err != nil {
						log.WithFields(log.Fields{
							"err": err,
							"dir": val + "/new",
						}).Error("Cannot watch directory")
					}
				}

				<-done
			},
		},
		{
			// See
			// https://www.socketloop.com/tutorials/golang-daemonizing-a-simple-web-server-process-example
			// for the process we are using to daemonize
			Name:    "start",
			Aliases: []string{"s"},
			Usage:   "start sisyphus daemon in the background",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonStart()

				return nil
			},
		},
		{
			Name:    "stop",
			Aliases: []string{"e"},
			Usage:   "stop sisyphus daemon",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonStop()

				return nil
			},
		},
		{
			Name:    "restart",
			Aliases: []string{"r"},
			Usage:   "restart sisyphus daemon",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonRestart()

				return nil
			},
		},
		{
			Name:    "status",
			Aliases: []string{"i"},
			Usage:   "status of sisyphus",
			Action: func(c *cli.Context) error {
				log.Info("here, we should get statistics from the db, TBD...")
				return nil
			},
		},
	}

	app.Run(os.Args)
}
<commit_msg>junk is just fine<commit_after>package main

import (
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/fsnotify/fsnotify"
	log "github.com/sirupsen/logrus"

	"github.com/carlostrub/sisyphus"
	"gopkg.in/urfave/cli.v2"
)

var (
	version string
)

func main() {

	// Define App
	app := cli.NewApp()
	app.Name = "Sisyphus"
	app.Usage = "Intelligent Junk Mail Handler"
	app.UsageText = `sisyphus [global options] command [command options]

	Sisyphus applies artificial intelligence to filter
	Junk mail in an unobtrusive way. Both, classification and learning
	operate directly on the Maildir of a user in a fully transparent mode,
	without any need for configuration or active operation.`
	app.HelpName = "Intelligent Junk Mail Handler"
	app.Version = version
	app.Copyright = "(c) 2017, Carlo Strub. All rights reserved. This binary is licensed under a BSD 3-Clause License."
	app.Authors = []cli.Author{
		{
			Name:  "Carlo Strub",
			Email: "cs@carlostrub.ch",
		},
	}

	maildirPaths := cli.StringSlice([]string{})

	var pidfile *string
	pidfile = new(string)

	app.Flags = []cli.Flag{

		cli.StringSliceFlag{
			Name:   "maildir, d",
			Value:  &maildirPaths,
			EnvVar: "SISYPHUS_DIRS",
			Usage:  "Call multiple Maildirs by repeating this flag, i.e. --maildir \"./Maildir\" --maildir \"./Maildir2\"",
		},
		cli.StringFlag{
			Name:        "pidfile, p",
			Value:       "/tmp/sisyphus.pid",
			EnvVar:      "SISYPHUS_PID",
			Usage:       "Location of PID file",
			Destination: pidfile,
		},
	}

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{"u"},
			Usage:   "run sisyphus",
			Action: func(c *cli.Context) {

				// check if daemon already running.
				if _, err := os.Stat(*pidfile); err == nil {
					log.WithFields(log.Fields{
						"pidfile": *pidfile,
					}).Fatal("Already running or pidfile exists")
				}

				fmt.Print(`

	███████╗██╗███████╗██╗   ██╗██████╗ ██╗  ██╗██╗   ██╗███████╗
	██╔════╝██║██╔════╝╚██╗ ██╔╝██╔══██╗██║  ██║██║   ██║██╔════╝
	███████╗██║███████╗ ╚████╔╝ ██████╔╝███████║██║   ██║███████╗
	╚════██║██║╚════██║  ╚██╔╝  ██╔═══╝ ██╔══██║██║   ██║╚════██║
	███████║██║███████║   ██║   ██║     ██║  ██║╚██████╔╝███████║
	╚══════╝╚═╝╚══════╝   ╚═╝   ╚═╝     ╚═╝  ╚═╝ ╚═════╝ ╚══════╝

	by Carlo Strub <cs@carlostrub.ch>

`)
				// Make arrangement to remove PID file upon receiving the SIGTERM from kill command
				ch := make(chan os.Signal, 1)
				signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)

				go func() {
					signalType := <-ch
					signal.Stop(ch)
					log.Info("Exit command received. Exiting sisyphus...")

					// this is a good place to flush everything to disk
					// before terminating.
					log.WithFields(log.Fields{
						"signal": signalType,
					}).Info("Received signal")

					// remove PID file
					os.Remove(*pidfile)

					os.Exit(0)

				}()

				if len(maildirPaths) < 1 {
					log.Fatal("No Maildir set. Please check the manual.")
				}

				// Populate maildir with the maildirs given by setting the flag.
				var maildirs []sisyphus.Maildir
				for _, val := range maildirPaths {
					maildirs = append(maildirs, sisyphus.Maildir(val))
				}

				// Load all mails
				mails, err := sisyphus.LoadMails(maildirs)
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot load mails")
				}

				// Open all databases
				dbs, err := sisyphus.LoadDatabases(maildirs)
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot load databases")
				}
				defer sisyphus.CloseDatabases(dbs)

				// Learn at startup
				for _, d := range maildirs {
					db := dbs[d]
					m := mails[d]
					for _, val := range m {
						err := val.Learn(db, d)
						if err != nil {
							log.WithFields(log.Fields{
								"err":  err,
								"mail": val.Key,
							}).Warning("Cannot learn mail")
						}
					}
				}
				log.Info("All mails learned")

				// Classify whenever a mail arrives in "new"
				watcher, err := fsnotify.NewWatcher()
				if err != nil {
					log.WithFields(log.Fields{
						"err": err,
					}).Fatal("Cannot setup directory watcher")
				}
				defer watcher.Close()

				done := make(chan bool)
				go func() {
					for {
						select {
						case event := <-watcher.Events:
							if event.Op&fsnotify.Create == fsnotify.Create {
								path := strings.Split(event.Name, "/new/")
								m := sisyphus.Mail{
									Key: path[1],
								}

								err = m.Classify(dbs[sisyphus.Maildir(path[0])], sisyphus.Maildir(path[0]))
								if err != nil {
									log.WithFields(log.Fields{
										"err": err,
									}).Error("Classify mail")
								}

							}
						case err := <-watcher.Errors:
							log.WithFields(log.Fields{
								"err": err,
							}).Error("Problem with directory watcher")
						}
					}
				}()

				for _, val := range maildirPaths {
					err = watcher.Add(val + "/new")
					if err != nil {
						log.WithFields(log.Fields{
							"err": err,
							"dir": val + "/new",
						}).Error("Cannot watch directory")
					}
				}

				<-done
			},
		},
		{
			// See
			// https://www.socketloop.com/tutorials/golang-daemonizing-a-simple-web-server-process-example
			// for the process we are using to daemonize
			Name:    "start",
			Aliases: []string{"s"},
			Usage:   "start sisyphus daemon in the background",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonStart()

				return nil
			},
		},
		{
			Name:    "stop",
			Aliases: []string{"e"},
			Usage:   "stop sisyphus daemon",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonStop()

				return nil
			},
		},
		{
			Name:    "restart",
			Aliases: []string{"r"},
			Usage:   "restart sisyphus daemon",
			Action: func(c *cli.Context) error {

				sisyphus.Pidfile(*pidfile).DaemonRestart()

				return nil
			},
		},
		{
			Name:    "status",
			Aliases: []string{"i"},
			Usage:   "status of sisyphus",
			Action: func(c *cli.Context) error {
				log.Info("here, we should get statistics from the db, TBD...")
				return nil
			},
		},
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package otp

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
)

var methods = []string{"totp", "hotp"}

// Supported hash algorithms.
var Hashes = []Hash{sha1.New, sha256.New, sha512.New, md5.New}

// Key defines a set of parameters required for code generation as well as metadata.
type Key struct {
	Method  string // Initialization method. Acceptable values are either 'totp' or 'hotp' for time-based or counter-based, respectively.
	Label   string // Label for the key.
	Secret  string // String representation of base32 encoded integer.
	Issuer  string // The issuer of the key.
	Algo    Hash   // The hash algorithm used in the HMAC. SHA1, SHA256, SHA512, and MD5 are supported.
	Digits  int    // The length of the code. 6 or 8 are acceptable.
	Period  int    // The number of seconds the code is valid for. Applies only to 'totp'.
	Counter int    // The initial counter value. Applies only to 'hotp'.
}

// GetCode accepts an initialization value and returns a corresponding code.
func (k Key) GetCode(iv int64) (string, error) {
	code, err := GetCode(k.Secret, iv, k.Algo, k.Digits)
	return code, err
}
<commit_msg>key docs<commit_after>package otp

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
)

var methods = []string{"totp", "hotp"}

// Supported hash algorithms.
var Hashes = []Hash{sha1.New, sha256.New, sha512.New, md5.New}

// Key represents a one-time password. It supports time-based (totp) and HMAC-based (hotp) approaches.
type Key struct {
	Method  string // Initialization method. Either 'totp' or 'hotp'.
	Label   string // Descriptive label..
	Secret  string // Base32-encoded secret key.
	Issuer  string // Key issuer.
	Algo    Hash   // Hash algorithm. See Hashes.
	Digits  int    // Length of the code. Either 6 or 8.
	Period  int    // Seconds code is valid for. Applies only to 'totp'.
	Counter int    // Initial counter value. Applies only to 'hotp'.
}

// GetCode returns a one-time password code an initial value..
func (k Key) GetCode(iv int64) (string, error) {
	code, err := GetCode(k.Secret, iv, k.Algo, k.Digits)
	return code, err
}
<|endoftext|>


<commit_before>package models

import (
	"time"

	envmanModels "github.com/bitrise-io/envman/models"
	stepmanModels "github.com/bitrise-io/stepman/models"
)

const (
	// StepRunStatusCodeSuccess ...
	StepRunStatusCodeSuccess = 0
	// StepRunStatusCodeFailed ...
	StepRunStatusCodeFailed = 1
	// StepRunStatusCodeFailedSkippable ...
	StepRunStatusCodeFailedSkippable = 2
	// StepRunStatusCodeSkipped ...
	StepRunStatusCodeSkipped = 3
	// StepRunStatusCodeSkippedWithRunIf ...
	StepRunStatusCodeSkippedWithRunIf = 4

	// Version ...
	Version = "1.0.0"
)

// StepListItemModel ...
type StepListItemModel map[string]stepmanModels.StepModel

// WorkflowModel ...
type WorkflowModel struct {
	Title        string                              `json:"title,omitempty" yaml:"title,omitempty"`
	Description  string                              `json:"description,omitempty" yaml:"description,omitempty"`
	Summary      string                              `json:"summary,omitempty" yaml:"summary,omitempty"`
	BeforeRun    []string                            `json:"before_run,omitempty" yaml:"before_run,omitempty"`
	AfterRun     []string                            `json:"after_run,omitempty" yaml:"after_run,omitempty"`
	Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"`
	Steps        []StepListItemModel                 `json:"steps,omitempty" yaml:"steps,omitempty"`
}

// AppModel ...
type AppModel struct {
	Title        string                              `json:"title,omitempty" yaml:"title,omitempty"`
	Description  string                              `json:"description,omitempty" yaml:"description,omitempty"`
	Summary      string                              `json:"summary,omitempty" yaml:"summary,omitempty"`
	Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"`
}

// TriggerMapItemModel ...
type TriggerMapItemModel struct {
	Pattern              string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
	IsPullRequestAllowed bool   `json:"is_pull_request_allowed,omitempty" yaml:"is_pull_request_allowed,omitempty"`
	WorkflowID           string `json:"workflow,omitempty" yaml:"workflow,omitempty"`
}

// BitriseDataModel ...
type BitriseDataModel struct {
	FormatVersion        string                   `json:"format_version" yaml:"format_version"`
	DefaultStepLibSource string                   `json:"default_step_lib_source,omitempty" yaml:"default_step_lib_source,omitempty"`
	TriggerMap           []TriggerMapItemModel    `json:"trigger_map,omitempty" yaml:"trigger_map,omitempty"`
	App                  AppModel                 `json:"app,omitempty" yaml:"app,omitempty"`
	Workflows            map[string]WorkflowModel `json:"workflows,omitempty" yaml:"workflows,omitempty"`
}

// StepIDData ...
// structured representation of a composite-step-id
//  a composite step id is: step-lib-source::step-id@1.0.0
type StepIDData struct {
	// IDOrURI : ID if steplib is provided, URI if local step or in case a direct git url provided
	IDorURI string
	// Version : version in the steplib, or in case of a direct git step the tag-or-branch to use
	Version string
	// SteplibSource : steplib source uri, or in case of local path just "path", and in case of direct git url just "git"
	SteplibSource string
}

// BuildRunResultsModel ...
type BuildRunResultsModel struct {
	StartTime            time.Time
	SuccessSteps         []StepRunResultsModel
	FailedSteps          []StepRunResultsModel
	FailedSkippableSteps []StepRunResultsModel
	SkippedSteps         []StepRunResultsModel
}

// StepRunResultsModel ...
type StepRunResultsModel struct {
	StepName string
	Status   int
	Idx      int
	RunTime  time.Duration
	Error    error
	ExitCode int
}
<commit_msg>Title, Summary, Description added to AppModel (main config model) & reordered the three, to be in this order in every model.<commit_after>package models

import (
	"time"

	envmanModels "github.com/bitrise-io/envman/models"
	stepmanModels "github.com/bitrise-io/stepman/models"
)

const (
	// StepRunStatusCodeSuccess ...
	StepRunStatusCodeSuccess = 0
	// StepRunStatusCodeFailed ...
	StepRunStatusCodeFailed = 1
	// StepRunStatusCodeFailedSkippable ...
	StepRunStatusCodeFailedSkippable = 2
	// StepRunStatusCodeSkipped ...
	StepRunStatusCodeSkipped = 3
	// StepRunStatusCodeSkippedWithRunIf ...
	StepRunStatusCodeSkippedWithRunIf = 4

	// Version ...
	Version = "1.0.1"
)

// StepListItemModel ...
type StepListItemModel map[string]stepmanModels.StepModel

// WorkflowModel ...
type WorkflowModel struct {
	Title        string                              `json:"title,omitempty" yaml:"title,omitempty"`
	Summary      string                              `json:"summary,omitempty" yaml:"summary,omitempty"`
	Description  string                              `json:"description,omitempty" yaml:"description,omitempty"`
	BeforeRun    []string                            `json:"before_run,omitempty" yaml:"before_run,omitempty"`
	AfterRun     []string                            `json:"after_run,omitempty" yaml:"after_run,omitempty"`
	Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"`
	Steps        []StepListItemModel                 `json:"steps,omitempty" yaml:"steps,omitempty"`
}

// AppModel ...
type AppModel struct {
	Title        string                              `json:"title,omitempty" yaml:"title,omitempty"`
	Summary      string                              `json:"summary,omitempty" yaml:"summary,omitempty"`
	Description  string                              `json:"description,omitempty" yaml:"description,omitempty"`
	Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"`
}

// TriggerMapItemModel ...
type TriggerMapItemModel struct {
	Pattern              string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
	IsPullRequestAllowed bool   `json:"is_pull_request_allowed,omitempty" yaml:"is_pull_request_allowed,omitempty"`
	WorkflowID           string `json:"workflow,omitempty" yaml:"workflow,omitempty"`
}

// BitriseDataModel ...
type BitriseDataModel struct {
	FormatVersion        string                   `json:"format_version" yaml:"format_version"`
	DefaultStepLibSource string                   `json:"default_step_lib_source,omitempty" yaml:"default_step_lib_source,omitempty"`
	Title                string                   `json:"title,omitempty" yaml:"title,omitempty"`
	Summary              string                   `json:"summary,omitempty" yaml:"summary,omitempty"`
	Description          string                   `json:"description,omitempty" yaml:"description,omitempty"`
	TriggerMap           []TriggerMapItemModel    `json:"trigger_map,omitempty" yaml:"trigger_map,omitempty"`
	App                  AppModel                 `json:"app,omitempty" yaml:"app,omitempty"`
	Workflows            map[string]WorkflowModel `json:"workflows,omitempty" yaml:"workflows,omitempty"`
}

// StepIDData ...
// structured representation of a composite-step-id
//  a composite step id is: step-lib-source::step-id@1.0.0
type StepIDData struct {
	// IDOrURI : ID if steplib is provided, URI if local step or in case a direct git url provided
	IDorURI string
	// Version : version in the steplib, or in case of a direct git step the tag-or-branch to use
	Version string
	// SteplibSource : steplib source uri, or in case of local path just "path", and in case of direct git url just "git"
	SteplibSource string
}

// BuildRunResultsModel ...
type BuildRunResultsModel struct {
	StartTime            time.Time
	SuccessSteps         []StepRunResultsModel
	FailedSteps          []StepRunResultsModel
	FailedSkippableSteps []StepRunResultsModel
	SkippedSteps         []StepRunResultsModel
}

// StepRunResultsModel ...
type StepRunResultsModel struct {
	StepName string
	Status   int
	Idx      int
	RunTime  time.Duration
	Error    error
	ExitCode int
}
<|endoftext|>


<commit_before>package models

import (
	"time"
)

// List of possible "volumes" (book types)
const (
	VolumePaper   = "paperbook"
	VolumeElectro = "ebook"
	VolumeAudio   = "audiobook"
)

// GetVolumes returns list of possible "volumes" (book types)
func GetVolumes() []string {
	volumes := []string{VolumeAudio, VolumeElectro, VolumePaper}
	return volumes
}

// CheckVolume
func CheckVolume(volume string) bool {
	switch volume {
	case VolumePaper:
	case VolumeElectro:
	case VolumeAudio:
	default:
		return false
	}
	return true
}

//go:generate reform

// Volume describes "type" of book - paperbook, ebook or audiobook.
//reform:volumes
type Volume struct {
	ID        int32     `reform:"id,pk"`
	BookID    int32     `reform:"book_id"`
	Type      string    `reform:"type"`
	CreatedAt time.Time `reform:"created_at"`
	UpdatedAt time.Time `reform:"updated_at"`
}

// BeforeInsert set CreatedAt and UpdatedAt.
func (v *Volume) BeforeInsert() error {
	v.CreatedAt = time.Now().UTC().Truncate(time.Second)
	v.UpdatedAt = v.CreatedAt
	return nil
}

// BeforeUpdate set UpdatedAt.
func (v *Volume) BeforeUpdate() error {
	v.UpdatedAt = time.Now().UTC().Truncate(time.Second)
	return nil
}
<commit_msg>Documentation & spelling<commit_after>package models

import (
	"time"
)

// List of possible "volumes" (book types)
const (
	VolumePaper   = "paperbook"
	VolumeElectro = "ebook"
	VolumeAudio   = "audiobook"
)

// GetVolumes returns list of possible "volumes" (book types)
func GetVolumes() []string {
	volumes := []string{VolumeAudio, VolumeElectro, VolumePaper}
	return volumes
}

// CheckVolume checks if the given volume is supported
func CheckVolume(volume string) bool {
	switch volume {
	case VolumePaper:
	case VolumeElectro:
	case VolumeAudio:
	default:
		return false
	}
	return true
}

//go:generate reform

// Volume describes "type" of book - paperbook, ebook or audiobook.
//reform:volumes
type Volume struct {
	ID        int32     `reform:"id,pk"`
	BookID    int32     `reform:"book_id"`
	Type      string    `reform:"type"`
	CreatedAt time.Time `reform:"created_at"`
	UpdatedAt time.Time `reform:"updated_at"`
}

// BeforeInsert set CreatedAt and UpdatedAt.
func (v *Volume) BeforeInsert() error {
	v.CreatedAt = time.Now().UTC().Truncate(time.Second)
	v.UpdatedAt = v.CreatedAt
	return nil
}

// BeforeUpdate set UpdatedAt.
func (v *Volume) BeforeUpdate() error {
	v.UpdatedAt = time.Now().UTC().Truncate(time.Second)
	return nil
}
<|endoftext|>


<commit_before>package entity

import (
	"fmt"
	//	"fmt"
	"strconv"
	"testing"

	"github.com/gfandada/gserver/util"
)

// 一万个可行走点测试aoi
const (
	X = 100
	Y = 0
	Z = 100
)

func Test_space(t *testing.T) {
	// 构建一个场景
	space := NewSpace(1, new(Space))
	RegisterSpace(space)
	for i := 0; i < X; i++ {
		for j := 0; j < Z; j++ {
			entity := NewEntity(1, "entity"+strconv.Itoa(i)+":"+strconv.Itoa(j),
				true, true)
			entity.BindIentity(new(Entity))
			RegisterEntity(entity)
			entity.EnterSpace(space.Id, Vector3{
				X: Coord(i),
				Y: Coord(0),
				Z: Coord(j),
			})
		}
	}
	// 主进程调度
	for i := 0; i < 1; i++ {
		for key := range space.entities {
			x := util.RandInterval(0, X-1)
			z := util.RandInterval(0, Z-1)
			//fmt.Println("调度时的随机坐标", x, ":", z)
			key.MoveSpace(Vector3{
				X: Coord(x),
				Y: Coord(0),
				Z: Coord(z),
			})
		}
		for key := range space.entities {
			fmt.Println("我是", key.aoi.pos, key.Desc.Name, "我有", len(key.Neighbors()), "个邻居")
		}
	}
}
<commit_msg>update space test<commit_after>package entity

import (
	//	"fmt"
	//	"fmt"
	//	"fmt"
	"strconv"
	"testing"

	//"github.com/beefsack/go-astar"
	"github.com/gfandada/gserver/util"
)

// 一万个可行走点测试aoi
const (
	X = 100
	Y = 0
	Z = 100
)

func Test_space(t *testing.T) {
	// 构建一个场景
	space := NewSpace(1, new(Space))
	RegisterSpace(space)
	for i := 0; i < X; i++ {
		for j := 0; j < Z; j++ {
			entity := NewEntity(1, "entity"+strconv.Itoa(i)+":"+strconv.Itoa(j),
				true, true)
			entity.BindIentity(new(Entity))
			RegisterEntity(entity)
			entity.EnterSpace(space.Id, Vector3{
				X: Coord(i),
				Y: Coord(0),
				Z: Coord(j),
			})
		}
	}
	// 主进程调度
	for i := 0; i < 1; i++ {
		for key := range space.entities {
			x := util.RandInterval(0, X-1)
			z := util.RandInterval(0, Z-1)
			//fmt.Println("调度时的随机坐标", x, ":", z)
			key.MoveSpace(Vector3{
				X: Coord(x),
				Y: Coord(0),
				Z: Coord(z),
			})
		}
		//		for key := range space.entities {
		//			fmt.Println("我是", key.aoi.pos, key.Desc.Name, "我有", len(key.Neighbors()), "个邻居")
		//		}
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

var mountains [8]int

type ship struct {
	x, y, target int
}

func (s *ship) GetTarget() {
	y := 0
	s.target = -1
	for i, mountain := range mountains {
		if mountain >= y {
			y = mountain
			s.target = i
		}
	}
}

func (s *ship) FireOrHold() string {
	if s.target == -1 {
		s.GetTarget()
	}

	if s.x == s.target {
		s.target = -1
		return "FIRE"
	}

	return "HOLD"
}

func main() {
	hero := ship{0, 0, -1}

	for {
		fmt.Scanf("%d %d\n", &hero.x, &hero.y)
		for i := range mountains {
			fmt.Scanf("%d\n", &mountains[i])
		}

		fmt.Println(hero.FireOrHold())
	}
}
<commit_msg>kirk solution works for first 5 input files<commit_after>package main

import (
	"fmt"
	"github.com/glendc/cgreader"
)

var mountains [8]int

type ship struct {
	x, y, target int
}

func (s *ship) GetTarget() {
	y := 0
	s.target = -1
	for i, mountain := range mountains {
		if mountain >= y {
			y = mountain
			s.target = i
		}
	}
}

func (s *ship) FireOrHold() string {
	if s.target == -1 {
		s.GetTarget()
	}

	if s.x == s.target {
		s.target = -1
		return "FIRE"
	}

	return "HOLD"
}

var hero ship

func Initialize(input <-chan string) {
	hero = ship{0, 0, -1}
}

func Update(input <-chan string, output chan string) {
	fmt.Sscanf(<-input, "%d %d", &hero.x, &hero.y)
	for i := range mountains {
		fmt.Sscanf(<-input, "%d", &mountains[i])
	}

	output <- hero.FireOrHold()
}

func main() {
	cgreader.SetFrameRate(5)
	cgreader.RunKirkProgram("../../input/kirk_6.txt", true, Initialize, Update)
	/*cgreader.RunKirkPrograms(
	cgreader.GetFileList("../../input/kirk_%d.txt", 6),
	false,
	Initialize,
	Update)*/
}
<|endoftext|>


<commit_before>package game_engine

import (
    "fmt"
    "encoding/json"
    "errors"
    "requests"
)

type Game struct {
    terrain [][]terrain
    unitMap map[location]*unit
    players []*player
    numPlayers int
    turnOwner int
}

func NewGame(playerIds []int, worldId int) (*Game, error) {
    numPlayers := len(playerIds)
    if numPlayers > 4 || numPlayers < 1 {
        return nil, errors.New("must have between 1 and 4 players")
    }
    players := make([]*player, numPlayers)
    for i, playerId := range(playerIds) {
        players[i] = newPlayer(playerId, nation(i), team(i))
    }
    ret_game := &Game{
        terrain: [][]terrain{
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains}},
        unitMap: make(map[location]*unit),
        players: players,
        numPlayers: numPlayers,
        turnOwner: 0}
    if worldId == 0 {
        ret_game.AddUnit(newLocation(0, 0), tank(red))
    }
    return ret_game, nil
}

func (game *Game) verifyTurnOwner(playerId int) error {
    if playerId != game.players[game.turnOwner].playerId {
        return errors.New("Not the turn owner")
    }
    return nil
}

func (game *Game) AddUnit(location location, unit *unit) error {
    fmt.Println("adding unit")
    _, ok := game.unitMap[location]; if !ok {
        game.unitMap[location] = unit
        fmt.Println("added unit")
        return nil
    } else {
        fmt.Println("failed to add unit")
        return errors.New("location already occupied")
    }
}

func (game *Game) EndTurn(playerId int) error {
    ownerError := game.verifyTurnOwner(playerId)
    if ownerError != nil {
        return ownerError
    }
    nextOwner := game.turnOwner + 1
    if nextOwner >= game.numPlayers {
        game.turnOwner = 0
    } else {
        game.turnOwner = nextOwner
    }
    return nil
}

func (game *Game) MoveUnit(playerId int, rawLocations []requests.LocationStruct) error {
    ownerError := game.verifyTurnOwner(playerId)
    if ownerError != nil {
        return ownerError
    }
    locations := make([]location, len(rawLocations))
    for i, location := range(rawLocations) {
        locations[i] = newLocation(location.X, location.Y)
    }
    if len(locations) < 1 {
        message := "must supply more than zero  locations"
        fmt.Println(message)
        return errors.New(message)
    }

    tiles := make([]terrain, len(locations))
    for i, location := range(locations) {
        tiles[i] = game.terrain[location.x][location.y]
    }
    //fmt.Println(tiles)
    //fmt.Println(game.unitMap)
    //fmt.Println(locations)
    unit, ok := game.unitMap[locations[0]]; if ok {
        moveErr := validMove(
            unit.movement.distance,
            unit.movement, tiles, locations)
        if moveErr == nil {
            end := len(locations)
            unit = game.unitMap[newLocation(locations[0].x, locations[0].y)]
            game.unitMap[newLocation(locations[end-1].x, locations[end-1].y)] = unit
            delete(game.unitMap, newLocation(locations[0].x, locations[0].y))
            return nil
        } else {
            fmt.Println(moveErr)
            return moveErr
        }
    } else {
        message := "Invalid starting location"
        fmt.Println(message)
        return errors.New(message)
    }
}

func (game *Game) Serialize(playerId int) ([]byte, error) {
    players := make([]*requests.PlayerStruct, len(game.players))
    for i, player := range(game.players) {
        players[i] = player.serialize()
    }
    terrainInts := make([][]int, len(game.terrain))
    for i, t := range(game.terrain) {
        thoriz := make([]int, len(t))
        for j, t_ := range(t) {
            thoriz[j] = int(t_)
        }
        terrainInts[i] = thoriz
    }
    units := make([]*requests.UnitStruct, len(game.unitMap))
    i := 0
    for location, unit := range(game.unitMap) {
        units[i] = unit.serialize(location)
        i += 1
    }
    return json.Marshal(requests.WorldStruct{
        Terrain: terrainInts,
        Units: units,
        Players: players,
        TurnOwner: game.players[game.turnOwner].playerId})
}
<commit_msg>Reorder functions in world.go<commit_after>package game_engine

import (
    "fmt"
    "encoding/json"
    "errors"
    "requests"
)

type Game struct {
    terrain [][]terrain
    unitMap map[location]*unit
    players []*player
    numPlayers int
    turnOwner int
}

func NewGame(playerIds []int, worldId int) (*Game, error) {
    numPlayers := len(playerIds)
    if numPlayers > 4 || numPlayers < 1 {
        return nil, errors.New("must have between 1 and 4 players")
    }
    players := make([]*player, numPlayers)
    for i, playerId := range(playerIds) {
        players[i] = newPlayer(playerId, nation(i), team(i))
    }
    ret_game := &Game{
        terrain: [][]terrain{
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains},
            []terrain{plains, plains, plains, plains, plains, plains, plains, plains}},
        unitMap: make(map[location]*unit),
        players: players,
        numPlayers: numPlayers,
        turnOwner: 0}
    if worldId == 0 {
        ret_game.AddUnit(newLocation(0, 0), tank(red))
    }
    return ret_game, nil
}

func (game *Game) verifyTurnOwner(playerId int) error {
    if playerId != game.players[game.turnOwner].playerId {
        return errors.New("Not the turn owner")
    }
    return nil
}

func (game *Game) AddUnit(location location, unit *unit) error {
    fmt.Println("adding unit")
    _, ok := game.unitMap[location]; if !ok {
        game.unitMap[location] = unit
        fmt.Println("added unit")
        return nil
    } else {
        fmt.Println("failed to add unit")
        return errors.New("location already occupied")
    }
}

func (game *Game) Serialize(playerId int) ([]byte, error) {
    players := make([]*requests.PlayerStruct, len(game.players))
    for i, player := range(game.players) {
        players[i] = player.serialize()
    }
    terrainInts := make([][]int, len(game.terrain))
    for i, t := range(game.terrain) {
        thoriz := make([]int, len(t))
        for j, t_ := range(t) {
            thoriz[j] = int(t_)
        }
        terrainInts[i] = thoriz
    }
    units := make([]*requests.UnitStruct, len(game.unitMap))
    i := 0
    for location, unit := range(game.unitMap) {
        units[i] = unit.serialize(location)
        i += 1
    }
    return json.Marshal(requests.WorldStruct{
        Terrain: terrainInts,
        Units: units,
        Players: players,
        TurnOwner: game.players[game.turnOwner].playerId})
}

func (game *Game) MoveUnit(playerId int, rawLocations []requests.LocationStruct) error {
    ownerError := game.verifyTurnOwner(playerId)
    if ownerError != nil {
        return ownerError
    }
    locations := make([]location, len(rawLocations))
    for i, location := range(rawLocations) {
        locations[i] = newLocation(location.X, location.Y)
    }
    if len(locations) < 1 {
        message := "must supply more than zero  locations"
        fmt.Println(message)
        return errors.New(message)
    }

    tiles := make([]terrain, len(locations))
    for i, location := range(locations) {
        tiles[i] = game.terrain[location.x][location.y]
    }
    //fmt.Println(tiles)
    //fmt.Println(game.unitMap)
    //fmt.Println(locations)
    unit, ok := game.unitMap[locations[0]]; if ok {
        moveErr := validMove(
            unit.movement.distance,
            unit.movement, tiles, locations)
        if moveErr == nil {
            end := len(locations)
            unit = game.unitMap[newLocation(locations[0].x, locations[0].y)]
            game.unitMap[newLocation(locations[end-1].x, locations[end-1].y)] = unit
            delete(game.unitMap, newLocation(locations[0].x, locations[0].y))
            return nil
        } else {
            fmt.Println(moveErr)
            return moveErr
        }
    } else {
        message := "Invalid starting location"
        fmt.Println(message)
        return errors.New(message)
    }
}

func (game *Game) EndTurn(playerId int) error {
    ownerError := game.verifyTurnOwner(playerId)
    if ownerError != nil {
        return ownerError
    }
    nextOwner := game.turnOwner + 1
    if nextOwner >= game.numPlayers {
        game.turnOwner = 0
    } else {
        game.turnOwner = nextOwner
    }
    return nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/davecgh/go-spew/spew"
)

// token represents the basic lexicographical units of the language.
type token struct {
	kind tokenType // The kind of token with which we're dealing.
	pos  Pos       // The byte offset of the beginning of the token with respect to the beginning of the input.
	val  string    // The token's value. Error message for lexError; otherwise, the token's constituent text.
}

// Defining the String function satisfies the Stinger interface.
// Satisfying Stringer allows package fmt to pretty-print our tokens.
// func (t *token) String() string {
// 	switch {
// 	case t.kind == tokError:
// 		return t.val
// 	case t.kind == tokEOF:
// 		return "EOF"
// 	case t.kind > tokKeyword:
// 		return fmt.Sprintf("<%s>", t.val)
// 	case len(t.val) > 10:
// 		return fmt.Sprintf("%.10q...", t.val) // Limit the max width for long tokens
// 	case t.kind == tokSpace:
// 		return "_"
// 	default:
// 		return t.val
// 	}
// }

// tokenType identifies the type of a token.
type tokenType int

// The list of tokenTypes.
const (
	// special
	tokError tokenType = iota // error occurred
	tokEOF
	tokComment

	// punctuation
	tokSpace
	tokSemicolon
	tokComma
	tokLeftParen
	tokRightParen

	// literals
	tokNumber

	// identifiers
	tokIdentifier

	// keywords
	tokKeyword // used to delineate keywords
	tokDefine
	tokExtern
	tokIf
	tokThen
	tokElse
	tokFor
	tokIn
	tokBinary
	tokUnary
	tokVariable

	// operators
	tokUserUnaryOp // additionally used to delineate operators
	tokUserBinaryOp
	tokEqual
	tokPlus
	tokMinus
	tokStar
	tokSlash
	tokLessThan
)

var key = map[string]tokenType{
	"def":    tokDefine,
	"extern": tokExtern,
	"if":     tokIf,
	"then":   tokThen,
	"else":   tokElse,
	"for":    tokFor,
	"in":     tokIn,
	"binary": tokBinary,
	"unary":  tokUnary,
	"var":    tokVariable,
}

// op maps built-in operators to tokenTypes
// As this should never be written to, it is safe to share between lexer goroutines.
var op = map[rune]tokenType{
	'=': tokEqual,
	'+': tokPlus,
	'-': tokMinus,
	'*': tokStar,
	'/': tokSlash,
	'<': tokLessThan,
}

// userOpType differentiates a user-defined unary, binary or not found operator.
type userOpType int

const (
	uopNOP userOpType = iota // Signals that the rune is not a user operator.
	uopUnaryOp
	uopBinaryOp
)

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

// lexer holds the state of the scanner.
type lexer struct {
	name          string              // name of input file; used in error reports
	input         string              // input being scanned
	state         stateFn             // next lexing function to be called
	pos           Pos                 // current position in input
	start         Pos                 // beginning position of the current token
	width         Pos                 // width of last rune read from input
	tokens        chan token          // channel of lexed items
	userOperators map[rune]userOpType // userOperators maps user defined operators to number of operands (Implication: no multi-char operators)
	parenDepth    int                 // nested layers of paren expressions
	printTokens   bool                // print tokens before sending
}

// NewLex creates and runs a new lexer from the input string.
func NewLex(name, input string, printTokens bool) *lexer {
	l := &lexer{
		name:          name,
		input:         input,
		tokens:        make(chan token, 10),
		userOperators: map[rune]userOpType{},
	}
	go l.run()
	return l
}

// l.next() returns eof to signal end of file to a stateFn.
const eof = -1

// word returns the value of the token that would be emitted if
// l.emit() were to be called.
func (l *lexer) word() string {
	return l.input[l.start:l.pos]
}

// next returns the next rune from the input and advances the scan.
// It returns the eof constant (-1) if the scanner is at the end of
// the input.
func (l *lexer) next() rune {
	if int(l.pos) >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	l.width = Pos(w)
	l.pos += l.width
	return r
}

// peek returns the next rune without moving the scan forward.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// backup moves the scan back one rune.
func (l *lexer) backup() {
	l.pos -= l.width
}

// ignore skips the pending input before this point.
func (l *lexer) ignore() {
	l.start = l.pos
}

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

// lineNumber returns the line on which a given rune occurred.
// If we need the line number for many tokens, it'd be better to
// track this with l.next() and l.backup(). However, we only use
// this to report lexing & parsing errors—something that is hopefully
// rare compared to the number of valid tokens and parse nodes.
func (l *lexer) lineNumber(p Pos) int {
	return 1 + strings.Count(l.input[:p], "\n")
}

// errorf sending an error token and terminates the scan by passing nil as the next stateFn
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	t := token{
		kind: tokError,
		pos:  l.start,
		val:  fmt.Sprintf(format, args...)}
	spew.Dump(t)
	l.tokens <- t
	return nil
}

// emit passes the current token.
func (l *lexer) emit(tt tokenType) {
	t := token{
		kind: tt,
		pos:  l.start,
		val:  l.word(),
	}
	spew.Dump(t)
	l.tokens <- t
	l.start = l.pos
}

// run runs the state machine for the lexer.
func (l *lexer) run() {
	for l.state = lexTopLevel; l.state != nil; {
		l.state = l.state(l)
		// Println(runtime.FuncForPC(reflect.ValueOf(l.state).Pointer()).Name())
	}
	close(l.tokens) // Tells the client no more tokens will be delivered.
}

// State Functions

// lexTopLevel lexes any top level statement. Because our language is simple,
// our lexer rarely needs to know its prior state and therefore this amounts
// to the giant-switch style of lexing. Nevertheless, the stateFn technique
// allows us to easy extend our lexer to more complex grammars.
func lexTopLevel(l *lexer) stateFn {
	// Either whitespace, an empty line, a comment,
	// a number, a paren, identifier, or unary operator.
	r := l.next()
	switch {
	case r == eof:
		l.emit(tokEOF)
		return nil
	case isSpace(r):
		l.backup()
		return lexSpace
	case isEOL(r):
		l.start = l.pos
		return lexTopLevel
	case r == ';':
		l.emit(tokSemicolon)
		return lexTopLevel
	case r == ',':
		l.emit(tokComma)
		return lexTopLevel
	case r == '#':
		return lexComment
	case r == '(':
		l.parenDepth++
		l.emit(tokLeftParen)
		return lexTopLevel
	case r == ')':
		l.parenDepth--
		l.emit(tokRightParen)
		if l.parenDepth < 0 {
			return l.errorf("unexpected right paren")
		}
		return lexTopLevel
	case '0' <= r && r <= '9':
		l.backup()
		return lexNumber
	case isAlphaNumeric(r):
		l.backup()
		return lexIdentifer
	case op[r] > tokUserBinaryOp:
		l.emit(op[r])
		return lexTopLevel
	case l.userOperators[r] == uopBinaryOp:
		l.emit(tokUserBinaryOp)
		return lexTopLevel
	case l.userOperators[r] == uopUnaryOp:
		l.emit(tokUserUnaryOp)
		return lexTopLevel
	default:
		return l.errorf("unrecognized character: %#U", r)
	}
}

// lexSpace globs contiguous whitespace.
func lexSpace(l *lexer) stateFn {
	globWhitespace(l)
	return lexTopLevel
}

// globWhitespace globs contiguous whitespace, but sometimes we
// don't want to return to lexTopLevel after doing this.
func globWhitespace(l *lexer) {
	for isSpace(l.next()) {
	}
	l.backup()
	if l.start != l.pos {
		l.emit(tokSpace)
	}
}

// lexComment runs from '#' to the end of line or end of file.
func lexComment(l *lexer) stateFn {
	for !isEOL(l.next()) {
	}
	l.backup()
	l.emit(tokComment)
	return lexTopLevel
}

// lexNumber globs potential number-like strings. We let the parser
// verify that the token is actually a valid number.
// e.g. "3.A.8" could be emitted by this function.
func lexNumber(l *lexer) stateFn {
	numberish := "0123456789.xabcdefABCDEF" // Implication: cannot have "." operator
	l.acceptRun(numberish)
	// if isAlphaNumeric(l.peek()) { // probably a mistyped identifier
	// 	l.next()
	// 	return l.errorf("bad number syntax: %q", l.word())
	// }
	l.emit(tokNumber)
	return lexTopLevel
}

// lexIdentfier globs unicode alpha-numerics, determines if they
// represent a keyword or identifier, and output the appropriate
// token. For the "binary" & "unary" keywords, we need to add their
// associated user-defined operator to our map so that we can
// identify it later.
func lexIdentifer(l *lexer) stateFn {
Loop:
	for {
		switch r := l.next(); {
		case isAlphaNumeric(r):
			// absorb
		default:
			l.backup()
			word := l.word()
			if key[word] > tokKeyword { // We already know it's not an operator.
				l.emit(key[word])
				switch word {
				case "binary":
					return lexUserBinaryOp
				case "unary":
					return lexUserUnaryOp
				}
			} else {
				l.emit(tokIdentifier)
			}
			break Loop
		}
	}
	return lexTopLevel
}

// lexUserBinaryOp checks for spaces and then identifies and maps.
// the newly defined user operator.
func lexUserBinaryOp(l *lexer) stateFn {
	globWhitespace(l)
	r := l.next() // Implication: no multi-char operators
	l.userOperators[r] = uopBinaryOp
	l.emit(tokUserBinaryOp)
	return lexTopLevel
}

// lexUserBinaryOp checks for spaces and then identifies and maps.
// the newly defined user operator.
func lexUserUnaryOp(l *lexer) stateFn {
	globWhitespace(l)
	r := l.next() // Implication: no multi-char operators
	l.userOperators[r] = uopUnaryOp
	l.emit(tokUserUnaryOp)
	return lexTopLevel
}

// Helper Functions

// isSpace reports whether r is whitespace.
func isSpace(r rune) bool {
	return r == ' ' || r == '\t'
}

// isEOL reports whether r is an end-of-line character or an EOF.
func isEOL(r rune) bool {
	return r == '\n' || r == '\r' || r == eof
}

// isValidIdefRune reports if r may be part of an identifier name.
func isAlphaNumeric(r rune) bool {
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
<commit_msg>Bugfix: Set and check for -tok flag<commit_after>package main

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

	"github.com/davecgh/go-spew/spew"
)

// token represents the basic lexicographical units of the language.
type token struct {
	kind tokenType // The kind of token with which we're dealing.
	pos  Pos       // The byte offset of the beginning of the token with respect to the beginning of the input.
	val  string    // The token's value. Error message for lexError; otherwise, the token's constituent text.
}

// Defining the String function satisfies the Stinger interface.
// Satisfying Stringer allows package fmt to pretty-print our tokens.
// func (t *token) String() string {
// 	switch {
// 	case t.kind == tokError:
// 		return t.val
// 	case t.kind == tokEOF:
// 		return "EOF"
// 	case t.kind > tokKeyword:
// 		return fmt.Sprintf("<%s>", t.val)
// 	case len(t.val) > 10:
// 		return fmt.Sprintf("%.10q...", t.val) // Limit the max width for long tokens
// 	case t.kind == tokSpace:
// 		return "_"
// 	default:
// 		return t.val
// 	}
// }

// tokenType identifies the type of a token.
type tokenType int

// The list of tokenTypes.
const (
	// special
	tokError tokenType = iota // error occurred
	tokEOF
	tokComment

	// punctuation
	tokSpace
	tokSemicolon
	tokComma
	tokLeftParen
	tokRightParen

	// literals
	tokNumber

	// identifiers
	tokIdentifier

	// keywords
	tokKeyword // used to delineate keywords
	tokDefine
	tokExtern
	tokIf
	tokThen
	tokElse
	tokFor
	tokIn
	tokBinary
	tokUnary
	tokVariable

	// operators
	tokUserUnaryOp // additionally used to delineate operators
	tokUserBinaryOp
	tokEqual
	tokPlus
	tokMinus
	tokStar
	tokSlash
	tokLessThan
)

var key = map[string]tokenType{
	"def":    tokDefine,
	"extern": tokExtern,
	"if":     tokIf,
	"then":   tokThen,
	"else":   tokElse,
	"for":    tokFor,
	"in":     tokIn,
	"binary": tokBinary,
	"unary":  tokUnary,
	"var":    tokVariable,
}

// op maps built-in operators to tokenTypes
// As this should never be written to, it is safe to share between lexer goroutines.
var op = map[rune]tokenType{
	'=': tokEqual,
	'+': tokPlus,
	'-': tokMinus,
	'*': tokStar,
	'/': tokSlash,
	'<': tokLessThan,
}

// userOpType differentiates a user-defined unary, binary or not found operator.
type userOpType int

const (
	uopNOP userOpType = iota // Signals that the rune is not a user operator.
	uopUnaryOp
	uopBinaryOp
)

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

// lexer holds the state of the scanner.
type lexer struct {
	name          string              // name of input file; used in error reports
	input         string              // input being scanned
	state         stateFn             // next lexing function to be called
	pos           Pos                 // current position in input
	start         Pos                 // beginning position of the current token
	width         Pos                 // width of last rune read from input
	tokens        chan token          // channel of lexed items
	userOperators map[rune]userOpType // userOperators maps user defined operators to number of operands (Implication: no multi-char operators)
	parenDepth    int                 // nested layers of paren expressions
	printTokens   bool                // print tokens before sending
}

// NewLex creates and runs a new lexer from the input string.
func NewLex(name, input string, printTokens bool) *lexer {
	l := &lexer{
		name:          name,
		input:         input,
		tokens:        make(chan token, 10),
		userOperators: map[rune]userOpType{},
		printTokens:   printTokens,
	}
	go l.run()
	return l
}

// l.next() returns eof to signal end of file to a stateFn.
const eof = -1

// word returns the value of the token that would be emitted if
// l.emit() were to be called.
func (l *lexer) word() string {
	return l.input[l.start:l.pos]
}

// next returns the next rune from the input and advances the scan.
// It returns the eof constant (-1) if the scanner is at the end of
// the input.
func (l *lexer) next() rune {
	if int(l.pos) >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	l.width = Pos(w)
	l.pos += l.width
	return r
}

// peek returns the next rune without moving the scan forward.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// backup moves the scan back one rune.
func (l *lexer) backup() {
	l.pos -= l.width
}

// ignore skips the pending input before this point.
func (l *lexer) ignore() {
	l.start = l.pos
}

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

// lineNumber returns the line on which a given rune occurred.
// If we need the line number for many tokens, it'd be better to
// track this with l.next() and l.backup(). However, we only use
// this to report lexing & parsing errors—something that is hopefully
// rare compared to the number of valid tokens and parse nodes.
func (l *lexer) lineNumber(p Pos) int {
	return 1 + strings.Count(l.input[:p], "\n")
}

// errorf sending an error token and terminates the scan by passing nil as the next stateFn
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	t := token{
		kind: tokError,
		pos:  l.start,
		val:  fmt.Sprintf(format, args...)}
	if l.printTokens {
		spew.Dump(t)
	}
	l.tokens <- t
	return nil
}

// emit passes the current token.
func (l *lexer) emit(tt tokenType) {
	t := token{
		kind: tt,
		pos:  l.start,
		val:  l.word(),
	}
	if l.printTokens {
		spew.Dump(t)
	}
	l.tokens <- t
	l.start = l.pos
}

// run runs the state machine for the lexer.
func (l *lexer) run() {
	for l.state = lexTopLevel; l.state != nil; {
		l.state = l.state(l)
		// Println(runtime.FuncForPC(reflect.ValueOf(l.state).Pointer()).Name())
	}
	close(l.tokens) // Tells the client no more tokens will be delivered.
}

// State Functions

// lexTopLevel lexes any top level statement. Because our language is simple,
// our lexer rarely needs to know its prior state and therefore this amounts
// to the giant-switch style of lexing. Nevertheless, the stateFn technique
// allows us to easy extend our lexer to more complex grammars.
func lexTopLevel(l *lexer) stateFn {
	// Either whitespace, an empty line, a comment,
	// a number, a paren, identifier, or unary operator.
	r := l.next()
	switch {
	case r == eof:
		l.emit(tokEOF)
		return nil
	case isSpace(r):
		l.backup()
		return lexSpace
	case isEOL(r):
		l.start = l.pos
		return lexTopLevel
	case r == ';':
		l.emit(tokSemicolon)
		return lexTopLevel
	case r == ',':
		l.emit(tokComma)
		return lexTopLevel
	case r == '#':
		return lexComment
	case r == '(':
		l.parenDepth++
		l.emit(tokLeftParen)
		return lexTopLevel
	case r == ')':
		l.parenDepth--
		l.emit(tokRightParen)
		if l.parenDepth < 0 {
			return l.errorf("unexpected right paren")
		}
		return lexTopLevel
	case '0' <= r && r <= '9':
		l.backup()
		return lexNumber
	case isAlphaNumeric(r):
		l.backup()
		return lexIdentifer
	case op[r] > tokUserBinaryOp:
		l.emit(op[r])
		return lexTopLevel
	case l.userOperators[r] == uopBinaryOp:
		l.emit(tokUserBinaryOp)
		return lexTopLevel
	case l.userOperators[r] == uopUnaryOp:
		l.emit(tokUserUnaryOp)
		return lexTopLevel
	default:
		return l.errorf("unrecognized character: %#U", r)
	}
}

// lexSpace globs contiguous whitespace.
func lexSpace(l *lexer) stateFn {
	globWhitespace(l)
	return lexTopLevel
}

// globWhitespace globs contiguous whitespace, but sometimes we
// don't want to return to lexTopLevel after doing this.
func globWhitespace(l *lexer) {
	for isSpace(l.next()) {
	}
	l.backup()
	if l.start != l.pos {
		l.emit(tokSpace)
	}
}

// lexComment runs from '#' to the end of line or end of file.
func lexComment(l *lexer) stateFn {
	for !isEOL(l.next()) {
	}
	l.backup()
	l.emit(tokComment)
	return lexTopLevel
}

// lexNumber globs potential number-like strings. We let the parser
// verify that the token is actually a valid number.
// e.g. "3.A.8" could be emitted by this function.
func lexNumber(l *lexer) stateFn {
	numberish := "0123456789.xabcdefABCDEF" // Implication: cannot have "." operator
	l.acceptRun(numberish)
	// if isAlphaNumeric(l.peek()) { // probably a mistyped identifier
	// 	l.next()
	// 	return l.errorf("bad number syntax: %q", l.word())
	// }
	l.emit(tokNumber)
	return lexTopLevel
}

// lexIdentfier globs unicode alpha-numerics, determines if they
// represent a keyword or identifier, and output the appropriate
// token. For the "binary" & "unary" keywords, we need to add their
// associated user-defined operator to our map so that we can
// identify it later.
func lexIdentifer(l *lexer) stateFn {
Loop:
	for {
		switch r := l.next(); {
		case isAlphaNumeric(r):
			// absorb
		default:
			l.backup()
			word := l.word()
			if key[word] > tokKeyword { // We already know it's not an operator.
				l.emit(key[word])
				switch word {
				case "binary":
					return lexUserBinaryOp
				case "unary":
					return lexUserUnaryOp
				}
			} else {
				l.emit(tokIdentifier)
			}
			break Loop
		}
	}
	return lexTopLevel
}

// lexUserBinaryOp checks for spaces and then identifies and maps.
// the newly defined user operator.
func lexUserBinaryOp(l *lexer) stateFn {
	globWhitespace(l)
	r := l.next() // Implication: no multi-char operators
	l.userOperators[r] = uopBinaryOp
	l.emit(tokUserBinaryOp)
	return lexTopLevel
}

// lexUserBinaryOp checks for spaces and then identifies and maps.
// the newly defined user operator.
func lexUserUnaryOp(l *lexer) stateFn {
	globWhitespace(l)
	r := l.next() // Implication: no multi-char operators
	l.userOperators[r] = uopUnaryOp
	l.emit(tokUserUnaryOp)
	return lexTopLevel
}

// Helper Functions

// isSpace reports whether r is whitespace.
func isSpace(r rune) bool {
	return r == ' ' || r == '\t'
}

// isEOL reports whether r is an end-of-line character or an EOF.
func isEOL(r rune) bool {
	return r == '\n' || r == '\r' || r == eof
}

// isValidIdefRune reports if r may be part of an identifier name.
func isAlphaNumeric(r rune) bool {
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
<|endoftext|>