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

package prometheus

import "github.com/prometheus/procfs"

type processCollector struct {
	pid             int
	collectFn       func(chan<- Metric)
	pidFn           func() (int, error)
	cpuTotal        Counter
	openFDs, maxFDs Gauge
	vsize, rss      Gauge
	startTime       Gauge
}

// NewProcessCollector returns a collector which exports the current state of
// process metrics including cpu, memory and file descriptor usage as well as
// the process start time for the given process id under the given namespace.
func NewProcessCollector(pid int, namespace string) *processCollector {
	return NewProcessCollectorPIDFn(
		func() (int, error) { return pid, nil },
		namespace,
	)
}

// NewProcessCollectorPIDFn returns a collector which exports the current state
// of process metrics including cpu, memory and file descriptor usage as well
// as the process start time under the given namespace. The given pidFn is
// called on each collect and is used to determine the process to export
// metrics for.
func NewProcessCollectorPIDFn(
	pidFn func() (int, error),
	namespace string,
) *processCollector {
	c := processCollector{
		pidFn:     pidFn,
		collectFn: func(chan<- Metric) {},

		cpuTotal: NewCounter(CounterOpts{
			Namespace: namespace,
			Name:      "process_cpu_seconds_total",
			Help:      "Total user and system CPU time spent in seconds.",
		}),
		openFDs: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_open_fds",
			Help:      "Number of open file descriptors.",
		}),
		maxFDs: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_max_fds",
			Help:      "Maximum number of open file descriptors.",
		}),
		vsize: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_virtual_memory_bytes",
			Help:      "Virtual memory size in bytes.",
		}),
		rss: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_resident_memory_bytes",
			Help:      "Resident memory size in bytes.",
		}),
		startTime: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_start_time_seconds",
			Help:      "Start time of the process since unix epoch in seconds.",
		}),
	}

	// Set up process metric collection if supported by the runtime.
	if processCollectSupported() {
		c.collectFn = c.processCollect
	}

	return &c
}

// Describe returns all descriptions of the collector.
func (c *processCollector) Describe(ch chan<- *Desc) {
	ch <- c.cpuTotal.Desc()
	ch <- c.openFDs.Desc()
	ch <- c.maxFDs.Desc()
	ch <- c.vsize.Desc()
	ch <- c.rss.Desc()
	ch <- c.startTime.Desc()
}

// Collect returns the current state of all metrics of the collector.
func (c *processCollector) Collect(ch chan<- Metric) {
	c.collectFn(ch)
}

func processCollectSupported() bool {
	if _, err := procfs.NewStat(); err == nil {
		return true
	}
	return false
}

// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
// client allows users to configure the error behavior.
func (c *processCollector) processCollect(ch chan<- Metric) {
	pid, err := c.pidFn()
	if err != nil {
		return
	}

	p, err := procfs.NewProc(pid)
	if err != nil {
		return
	}

	if stat, err := p.NewStat(); err == nil {
		c.cpuTotal.Set(stat.CPUTime())
		ch <- c.cpuTotal
		c.vsize.Set(float64(stat.VirtualMemory()))
		ch <- c.vsize
		c.rss.Set(float64(stat.ResidentMemory()))
		ch <- c.rss

		if startTime, err := stat.StartTime(); err == nil {
			c.startTime.Set(startTime)
			ch <- c.startTime
		}
	}

	if fds, err := p.FileDescriptorsLen(); err == nil {
		c.openFDs.Set(float64(fds))
		ch <- c.openFDs
	}

	if limits, err := p.NewLimits(); err == nil {
		c.maxFDs.Set(float64(limits.OpenFiles))
		ch <- c.maxFDs
	}
}
<commit_msg>Inline and remove processCollectSupported().<commit_after>// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheus

import "github.com/prometheus/procfs"

type processCollector struct {
	pid             int
	collectFn       func(chan<- Metric)
	pidFn           func() (int, error)
	cpuTotal        Counter
	openFDs, maxFDs Gauge
	vsize, rss      Gauge
	startTime       Gauge
}

// NewProcessCollector returns a collector which exports the current state of
// process metrics including cpu, memory and file descriptor usage as well as
// the process start time for the given process id under the given namespace.
func NewProcessCollector(pid int, namespace string) *processCollector {
	return NewProcessCollectorPIDFn(
		func() (int, error) { return pid, nil },
		namespace,
	)
}

// NewProcessCollectorPIDFn returns a collector which exports the current state
// of process metrics including cpu, memory and file descriptor usage as well
// as the process start time under the given namespace. The given pidFn is
// called on each collect and is used to determine the process to export
// metrics for.
func NewProcessCollectorPIDFn(
	pidFn func() (int, error),
	namespace string,
) *processCollector {
	c := processCollector{
		pidFn:     pidFn,
		collectFn: func(chan<- Metric) {},

		cpuTotal: NewCounter(CounterOpts{
			Namespace: namespace,
			Name:      "process_cpu_seconds_total",
			Help:      "Total user and system CPU time spent in seconds.",
		}),
		openFDs: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_open_fds",
			Help:      "Number of open file descriptors.",
		}),
		maxFDs: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_max_fds",
			Help:      "Maximum number of open file descriptors.",
		}),
		vsize: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_virtual_memory_bytes",
			Help:      "Virtual memory size in bytes.",
		}),
		rss: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_resident_memory_bytes",
			Help:      "Resident memory size in bytes.",
		}),
		startTime: NewGauge(GaugeOpts{
			Namespace: namespace,
			Name:      "process_start_time_seconds",
			Help:      "Start time of the process since unix epoch in seconds.",
		}),
	}

	// Set up process metric collection if supported by the runtime.
	if _, err := procfs.NewStat(); err == nil {
		c.collectFn = c.processCollect
	}

	return &c
}

// Describe returns all descriptions of the collector.
func (c *processCollector) Describe(ch chan<- *Desc) {
	ch <- c.cpuTotal.Desc()
	ch <- c.openFDs.Desc()
	ch <- c.maxFDs.Desc()
	ch <- c.vsize.Desc()
	ch <- c.rss.Desc()
	ch <- c.startTime.Desc()
}

// Collect returns the current state of all metrics of the collector.
func (c *processCollector) Collect(ch chan<- Metric) {
	c.collectFn(ch)
}

// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
// client allows users to configure the error behavior.
func (c *processCollector) processCollect(ch chan<- Metric) {
	pid, err := c.pidFn()
	if err != nil {
		return
	}

	p, err := procfs.NewProc(pid)
	if err != nil {
		return
	}

	if stat, err := p.NewStat(); err == nil {
		c.cpuTotal.Set(stat.CPUTime())
		ch <- c.cpuTotal
		c.vsize.Set(float64(stat.VirtualMemory()))
		ch <- c.vsize
		c.rss.Set(float64(stat.ResidentMemory()))
		ch <- c.rss

		if startTime, err := stat.StartTime(); err == nil {
			c.startTime.Set(startTime)
			ch <- c.startTime
		}
	}

	if fds, err := p.FileDescriptorsLen(); err == nil {
		c.openFDs.Set(float64(fds))
		ch <- c.openFDs
	}

	if limits, err := p.NewLimits(); err == nil {
		c.maxFDs.Set(float64(limits.OpenFiles))
		ch <- c.maxFDs
	}
}
<|endoftext|>


<commit_before>package sandbox

import (
	"github.com/Shopify/go-lua"
)

type WorldState struct {
	i int
}

type NewState struct {
	i int
}

type Node struct {
	resume  chan<- WorldState
	respond <-chan NewState
}

func (n *Node) Update(state WorldState) <-chan NewState {
	n.resume <- state
	return n.respond
}

func AddNode(program string) *Node {
	// Make the communication channels
	resume := make(chan WorldState)
	respond := make(chan NewState)

	n := Node{
		resume:  resume,
		respond: respond,
	}

	go runNode(internalNode{
		program: program,
		resume:  resume,
		respond: respond,
	})

	return &n
}

type internalNode struct {
	program string
	resume  <-chan WorldState
	respond chan<- NewState
}

type Direction int

const (
	Left Direction = iota
	Right
	Up
	Down
)

func addDirFunc(l *lua.State, name string, fn func(Direction) int) {
	l.PushGoFunction(func(l *lua.State) int {
		argCount := l.Top()
		if argCount != 1 {
			l.PushString("incorrect number of arguments") // XXX Include name of function
			l.Error()
			return 0
		}
		s, err := l.ToString(1) // toLowerCAse
		if err != nil {
			l.PushString("incorrect type of argument") // XXX Include name of function
			l.Error()
			return 0
		}

		if s == "left" {

		} else if s == "right" {
		} else if s == "up" {
		} else if s == "down" {
		}

	})
}

func runNode(node internalNode) {
	l := lua.NewState()
	l.PushGoFunction(func(l *lua.State) int {

	})
	lua.LoadString(l, node.program)
}
<commit_msg>Basic addDirFunc functionality<commit_after>package sandbox

import (
	"github.com/Shopify/go-lua"
)

type WorldState struct {
	lighting map[Direction]float64
}

type NewState struct {
	moveDir Direction
}

type Node struct {
	resume  chan<- WorldState
	respond <-chan NewState
}

func (n *Node) Update(state WorldState) <-chan NewState {
	n.resume <- state
	return n.respond
}

func AddNode(program string) *Node {
	// Make the communication channels
	resume := make(chan WorldState)
	respond := make(chan NewState)

	n := Node{
		resume:  resume,
		respond: respond,
	}

	go runNode(internalNode{
		program: program,
		resume:  resume,
		respond: respond,
	})

	return &n
}

type internalNode struct {
	program string
	resume  <-chan WorldState
	respond chan<- NewState
}

type Direction int

const (
	Left Direction = iota
	Right
	Up
	Down
)

func addIntFunc(l *lua.State, name string, fn func(*lua.State, int) int) {
	l.PushGoFunction(func(l *lua.State) int {
		if l.Top() != 1 {
			l.PushString("Wrong number of arguments")
			l.Error()
			return 0
		}

		i, ok := l.ToInteger(1)
		if !ok {
			l.PushString("Wrong argument type")
			l.Error()
			return 0
		}

		return fn(l, i)
	})

	l.SetGlobal(name)
}

func addVoidFunc(l *lua.State, name string, fn func(*lua.State) int) {
	l.PushGoFunction(func(l *lua.State) int {
		if l.Top() != 0 {
			l.PushString("Too many arguments to void function")
			l.Error()
			return 0
		}

		return fn(l)
	})

	l.SetGlobal(name)
}

func addDirFunc(l *lua.State, name string, fn func(*lua.State, Direction) int) {
	l.PushGoFunction(func(l *lua.State) int {
		argCount := l.Top()
		if argCount != 1 {
			l.PushString("incorrect number of arguments") // XXX Include name of function
			l.Error()
			return 0
		}

		s, ok := l.ToString(1)
		if !ok {
			l.PushString("incorrect type of argument") // XXX Include name of function
			l.Error()
			return 0
		}

		var d Direction
		if s == "left" {
			d = Left
		} else if s == "right" {
			d = Right
		} else if s == "up" {
			d = Up
		} else if s == "down" {
			d = Down
		}

		return fn(l, d)
	})

	l.SetGlobal(name)
}

func runNode(node internalNode) {
	l := lua.NewState()

	world := <-node.resume

	addDirFunc(l, "grow", func(l *lua.State, d Direction) int {
		var state NewState
		state.moveDir = d

		// Send a response and wait
		node.respond <- state
		world = <-node.resume

		return 0
	})

	addDirFunc(l, "lighting", func(l *lua.State, d Direction) int {
		l.PushNumber(world.lighting[d])
		return 1
	})

	lua.LoadString(l, node.program)
}
<|endoftext|>


<commit_before>// Package app provides a thin layer of support for writing web services. It
// integrates with the ardanlabs kit repo to provide support for logging,
// configuration, database, routing and application context. The base things
// you need to write a web service is provided.
package app

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/ardanlabs/kit/cfg"
	"github.com/ardanlabs/kit/log"
	"github.com/braintree/manners"
	"github.com/dimfeld/httptreemux"
	"github.com/pborman/uuid"
)

// Web config environmental variables.
const (
	cfgLoggingLevel = "LOGGING_LEVEL"
	cfgHost         = "HOST"
)

var (
	// ErrNotAuthorized occurs when the call is not authorized.
	ErrNotAuthorized = errors.New("Not authorized")

	// ErrDBNotConfigured occurs when the DB is not initialized.
	ErrDBNotConfigured = errors.New("DB not initialized")

	// ErrNotFound is abstracting the mgo not found error.
	ErrNotFound = errors.New("Entity not found")

	// ErrInvalidID occurs when an ID is not in a valid form.
	ErrInvalidID = errors.New("ID is not in it's proper form")

	// ErrValidation occurs when there are validation errors.
	ErrValidation = errors.New("Validation errors occurred")
)

type (
	// A Handler is a type that handles an http request within our own little mini
	// framework. The fun part is that our context is fully controlled and
	// configured by us so we can extend the functionality of the Context whenever
	// we want.
	Handler func(*Context) error

	// A Middleware is a type that wraps a handler to remove boilerplate or other
	// concerns not direct to any given Handler.
	Middleware func(Handler) Handler
)

// app maintains some framework state.
var app = struct {
	userHeaders map[string]string
}{
	userHeaders: make(map[string]string),
}

//==============================================================================

// App is the entrypoint into our application and what configures our context
// object for each of our http handlers. Feel free to add any configuration
// data/logic on this App struct
type App struct {
	*httptreemux.TreeMux
	Ctx map[string]interface{}

	mw []Middleware
}

// New create an App value that handle a set of routes for the application.
// You can provide any number of middleware and they'll be used to wrap every
// request handler.
func New(mw ...Middleware) *App {
	return &App{
		TreeMux: httptreemux.New(),
		Ctx:     make(map[string]interface{}),
		mw:      mw,
	}
}

// Handle is our mechanism for mounting Handlers for a given HTTP verb and path
// pair, this makes for really easy, convenient routing.
func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {

	// The function to execute for each request.
	h := func(w http.ResponseWriter, r *http.Request, p map[string]string) {
		c := Context{
			ResponseWriter: w,
			Request:        r,
			Now:            time.Now(),
			Params:         p,
			SessionID:      uuid.New(),
			Ctx:            make(map[string]interface{}),
			App:            a,
		}

		log.User(c.SessionID, "Request", "Started : Method[%s] URL[%s] RADDR[%s]", c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr)

		// Wrap the handler in all associated middleware.
		wrap := func(h Handler) Handler {
			// Wrap up the application-wide first...
			for i := len(a.mw) - 1; i >= 0; i-- {
				h = a.mw[i](h)
			}

			// Then wrap with our route specific ones.
			for i := len(mw) - 1; i >= 0; i-- {
				h = mw[i](h)
			}

			return h
		}

		// Call the wrapped handler and handle any possible error.
		if err := wrap(handler)(&c); err != nil {
			c.Error(err)
		}

		log.User(c.SessionID, "Request", "Completed : Status[%d] Duration[%s]", c.Status, time.Since(c.Now))
	}

	// Add this handler for the specified verb and route.
	a.TreeMux.Handle(verb, path, h)
}

// CORS providing support for Cross-Origin Resource Sharing.
// https://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/
func (a *App) CORS() {
	h := func(w http.ResponseWriter, r *http.Request, p map[string]string) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Max-Age", "86400")
		w.Header().Set("Content-Type", "application/json")

		w.WriteHeader(http.StatusOK)
	}

	a.TreeMux.OptionsHandler = h

	app.userHeaders["Access-Control-Allow-Origin"] = "*"
}

//==============================================================================

// Init is called to initialize the application.
func Init(p cfg.Provider) {

	// Init the configuration system.
	if err := cfg.Init(p); err != nil {
		fmt.Println("Error initalizing configuration system", err)
		os.Exit(1)
	}

	// Init the log system.
	logLevel := func() int {
		ll, err := cfg.Int(cfgLoggingLevel)
		if err != nil {
			return log.USER
		}
		return ll
	}
	log.Init(os.Stderr, logLevel, log.Ldefault)

	// Log all the configuration options
	log.User("startup", "Init", "\n\nConfig Settings:\n%s\n", cfg.Log())

	// Load user defined custom headers. HEADERS should be key:value,key:value
	if hs, err := cfg.String("HEADERS"); err == nil {
		hdrs := strings.Split(hs, ",")
		for _, hdr := range hdrs {
			if kv := strings.Split(hdr, ":"); len(kv) == 2 {
				log.User("startup", "Init", "User Headers : %s:%s", kv[0], kv[1])
				app.userHeaders[kv[0]] = kv[1]
			}
		}
	}
}

// Run is called to start the web service.
func Run(defaultHost string, routes http.Handler) {
	log.Dev("startup", "Run", "Start : defaultHost[%s]", defaultHost)

	// Check for a configured host value.
	host, err := cfg.String(cfgHost)
	if err != nil {
		host = defaultHost
	}

	// Create this goroutine to run the web server.
	go func() {
		log.Dev("listener", "Run", "Listening on: %s", host)
		manners.ListenAndServe(host, routes)
	}()

	// Listen for an interrupt signal from the OS.
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)
	<-sigChan

	log.Dev("shutdown", "Run", "Starting shutdown...")
	manners.Close()

	log.Dev("shutdown", "Run", "Complete")
}
<commit_msg>Was handling the clean shutdown wrong.<commit_after>// Package app provides a thin layer of support for writing web services. It
// integrates with the ardanlabs kit repo to provide support for logging,
// configuration, database, routing and application context. The base things
// you need to write a web service is provided.
package app

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/ardanlabs/kit/cfg"
	"github.com/ardanlabs/kit/log"
	"github.com/braintree/manners"
	"github.com/dimfeld/httptreemux"
	"github.com/pborman/uuid"
)

// Web config environmental variables.
const (
	cfgLoggingLevel = "LOGGING_LEVEL"
	cfgHost         = "HOST"
)

var (
	// ErrNotAuthorized occurs when the call is not authorized.
	ErrNotAuthorized = errors.New("Not authorized")

	// ErrDBNotConfigured occurs when the DB is not initialized.
	ErrDBNotConfigured = errors.New("DB not initialized")

	// ErrNotFound is abstracting the mgo not found error.
	ErrNotFound = errors.New("Entity not found")

	// ErrInvalidID occurs when an ID is not in a valid form.
	ErrInvalidID = errors.New("ID is not in it's proper form")

	// ErrValidation occurs when there are validation errors.
	ErrValidation = errors.New("Validation errors occurred")
)

type (
	// A Handler is a type that handles an http request within our own little mini
	// framework. The fun part is that our context is fully controlled and
	// configured by us so we can extend the functionality of the Context whenever
	// we want.
	Handler func(*Context) error

	// A Middleware is a type that wraps a handler to remove boilerplate or other
	// concerns not direct to any given Handler.
	Middleware func(Handler) Handler
)

// app maintains some framework state.
var app = struct {
	userHeaders map[string]string
}{
	userHeaders: make(map[string]string),
}

//==============================================================================

// App is the entrypoint into our application and what configures our context
// object for each of our http handlers. Feel free to add any configuration
// data/logic on this App struct
type App struct {
	*httptreemux.TreeMux
	Ctx map[string]interface{}

	mw []Middleware
}

// New create an App value that handle a set of routes for the application.
// You can provide any number of middleware and they'll be used to wrap every
// request handler.
func New(mw ...Middleware) *App {
	return &App{
		TreeMux: httptreemux.New(),
		Ctx:     make(map[string]interface{}),
		mw:      mw,
	}
}

// Handle is our mechanism for mounting Handlers for a given HTTP verb and path
// pair, this makes for really easy, convenient routing.
func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {

	// The function to execute for each request.
	h := func(w http.ResponseWriter, r *http.Request, p map[string]string) {
		c := Context{
			ResponseWriter: w,
			Request:        r,
			Now:            time.Now(),
			Params:         p,
			SessionID:      uuid.New(),
			Ctx:            make(map[string]interface{}),
			App:            a,
		}

		log.User(c.SessionID, "Request", "Started : Method[%s] URL[%s] RADDR[%s]", c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr)

		// Wrap the handler in all associated middleware.
		wrap := func(h Handler) Handler {
			// Wrap up the application-wide first...
			for i := len(a.mw) - 1; i >= 0; i-- {
				h = a.mw[i](h)
			}

			// Then wrap with our route specific ones.
			for i := len(mw) - 1; i >= 0; i-- {
				h = mw[i](h)
			}

			return h
		}

		// Call the wrapped handler and handle any possible error.
		if err := wrap(handler)(&c); err != nil {
			c.Error(err)
		}

		log.User(c.SessionID, "Request", "Completed : Status[%d] Duration[%s]", c.Status, time.Since(c.Now))
	}

	// Add this handler for the specified verb and route.
	a.TreeMux.Handle(verb, path, h)
}

// CORS providing support for Cross-Origin Resource Sharing.
// https://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/
func (a *App) CORS() {
	h := func(w http.ResponseWriter, r *http.Request, p map[string]string) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Max-Age", "86400")
		w.Header().Set("Content-Type", "application/json")

		w.WriteHeader(http.StatusOK)
	}

	a.TreeMux.OptionsHandler = h

	app.userHeaders["Access-Control-Allow-Origin"] = "*"
}

//==============================================================================

// Init is called to initialize the application.
func Init(p cfg.Provider) {

	// Init the configuration system.
	if err := cfg.Init(p); err != nil {
		fmt.Println("Error initalizing configuration system", err)
		os.Exit(1)
	}

	// Init the log system.
	logLevel := func() int {
		ll, err := cfg.Int(cfgLoggingLevel)
		if err != nil {
			return log.USER
		}
		return ll
	}
	log.Init(os.Stderr, logLevel, log.Ldefault)

	// Log all the configuration options
	log.User("startup", "Init", "\n\nConfig Settings:\n%s\n", cfg.Log())

	// Load user defined custom headers. HEADERS should be key:value,key:value
	if hs, err := cfg.String("HEADERS"); err == nil {
		hdrs := strings.Split(hs, ",")
		for _, hdr := range hdrs {
			if kv := strings.Split(hdr, ":"); len(kv) == 2 {
				log.User("startup", "Init", "User Headers : %s:%s", kv[0], kv[1])
				app.userHeaders[kv[0]] = kv[1]
			}
		}
	}
}

// Run is called to start the web service.
func Run(defaultHost string, routes http.Handler) {
	log.Dev("startup", "Run", "Start : defaultHost[%s]", defaultHost)

	// Check for a configured host value.
	host, err := cfg.String(cfgHost)
	if err != nil {
		host = defaultHost
	}

	// Support for shutting down cleanly.
	go func() {

		// Listen for an interrupt signal from the OS.
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, os.Interrupt)
		<-sigChan

		// We have been asked to shutdown the server.
		log.Dev("shutdown", "Run", "Starting shutdown...")
		manners.Close()
	}()

	log.Dev("listener", "Run", "Listening on: %s", host)
	manners.ListenAndServe(host, routes)

	log.Dev("shutdown", "Run", "Complete")
}
<|endoftext|>


<commit_before>package scanner

import (
	"fmt"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"time"

	"github.com/composer22/pzscan/logger"
)

const (
	maxJobs = 10000 // The jobq maximum number of jobs to hold. We need something for non blocking.
)

var (
	maxIdleDuration = 15 * time.Second       // How long we should wait on empty queues before auto quitting.
	maxScannerSleep = 100 * time.Millisecond // How long should the scanner sleep before checking for results.
)

// Scanner is a manager of scanning jobs and evaluates the results of the workers.
type Scanner struct {
	mu         sync.Mutex        // For locking access.
	log        *logger.Logger    // Logger for writing final results.
	RootURL    *url.URL          // The original URL that we started the scan from.
	Tests      map[string]*Stats // URL test results go in here.
	MaxRunMin  int               // The Maximum number of minutes we want the scanner to run.
	MaxWorkers int               // The maximumm job workers we want in the pool.
	StartTime  time.Time         // When the scanner started runnning.
	ExpireTime time.Time         // The expire time: when the scanner should stop running.
	EndTime    time.Time         // When the scanner ended.
	jobq       chan *scanJob     // Channel to send jobs.
	doneCh     chan *scanJob     // Channel to receive done jobs.
	wg         sync.WaitGroup    // Synchronize close() of job channel.
	stopOnce   sync.Once         // Used to close down the system once and once only.
}

// New is a factory function that creates a new Scanner instance.
func New(hostname string, maxRunMin int, maxWorkers int) *Scanner {
	u, _ := url.Parse(fmt.Sprintf("http://%s", hostname))
	return &Scanner{
		log:        logger.New(logger.UseDefault, false),
		RootURL:    u,
		Tests:      make(map[string]*Stats),
		MaxRunMin:  maxRunMin,
		MaxWorkers: maxWorkers,
		jobq:       make(chan *scanJob, maxJobs),
		doneCh:     make(chan *scanJob, maxJobs),
	}
}

// Run starts the scanner and manages the jobs.
func (s *Scanner) Run() {
	// Trap all signals to quit.
	s.handleSignals()

	s.mu.Lock()

	// Spin up the workers
	for i := 0; i < s.MaxWorkers; i++ {
		s.wg.Add(1)
		go scanWorker(s.jobq, s.doneCh, &s.wg)
	}
	s.StartTime = time.Now()
	s.ExpireTime = s.StartTime.Add(time.Duration(s.MaxRunMin) * time.Minute)
	s.mu.Unlock()

	// Main event loop.
	s.jobq <- scanJobNew(s.RootURL) // Create first job
	for {
		select {
		case doneJob, ok := <-s.doneCh:
			if !ok {
				s.Stop()
			}
			s.evaluate(doneJob)
		default:
			// Drop dead time reached?
			if time.Now().After(s.ExpireTime) {
				s.Stop()
				return
			}
			// Test a reasonable time for all jobs to be cleared, and then die if no more
			// jobs need to be done or are returned.
			if len(s.jobq) == 0 && len(s.doneCh) == 0 {
				time.Sleep(maxIdleDuration)
				if len(s.doneCh) == 0 {
					s.Stop()
					return
				}
			}
			time.Sleep(maxScannerSleep) // Sleep a while.
		}
	}
}

// Stop performs close out procedures.
func (s *Scanner) Stop() {
	s.stopOnce.Do(func() {
		s.EndTime = time.Now()
		close(s.doneCh)
		close(s.jobq)
		s.wg.Wait()
	})
}

// handleSignals responds to operating system interrupts such as application kills.
func (s *Scanner) handleSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			s.Stop()
			os.Exit(0)
		}
	}()
}

// evaluate examines the result of the job and launches new jobs if site children are found.
func (s *Scanner) evaluate(job *scanJob) {
	// Store the result of this scan.
	if _, ok := s.Tests[job.Stat.URL.Path]; !ok {
		s.Tests[job.Stat.URL.String()] = job.Stat
		s.log.Infof(fmt.Sprint(job.Stat))
	}

	// Check for valid chidren under this host, and if any is not already scanned,
	// then submit new a job for this additional URL.
	for _, c := range job.Childen {
		if strings.Contains(c.Host, s.RootURL.Host) {
			if _, ok := s.Tests[c.String()]; !ok {
				s.jobq <- scanJobNew(c)
			}
		}
	}
}

// Dump prints the results to the log output as json.
func (s *Scanner) Dump() {
	for _, v := range s.Tests {
		s.log.Infof(fmt.Sprint(v))
	}
}
<commit_msg>Moved fun<commit_after>package scanner

import (
	"fmt"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"time"

	"github.com/composer22/pzscan/logger"
)

const (
	maxJobs = 10000 // The jobq maximum number of jobs to hold. We need something for non blocking.
)

var (
	maxIdleDuration = 15 * time.Second       // How long we should wait on empty queues before auto quitting.
	maxScannerSleep = 100 * time.Millisecond // How long should the scanner sleep before checking for results.
)

// Scanner is a manager of scanning jobs and evaluates the results of the workers.
type Scanner struct {
	mu         sync.Mutex        // For locking access.
	log        *logger.Logger    // Logger for writing final results.
	RootURL    *url.URL          // The original URL that we started the scan from.
	Tests      map[string]*Stats // URL test results go in here.
	MaxRunMin  int               // The Maximum number of minutes we want the scanner to run.
	MaxWorkers int               // The maximumm job workers we want in the pool.
	StartTime  time.Time         // When the scanner started runnning.
	ExpireTime time.Time         // The expire time: when the scanner should stop running.
	EndTime    time.Time         // When the scanner ended.
	jobq       chan *scanJob     // Channel to send jobs.
	doneCh     chan *scanJob     // Channel to receive done jobs.
	wg         sync.WaitGroup    // Synchronize close() of job channel.
	stopOnce   sync.Once         // Used to close down the system once and once only.
}

// New is a factory function that creates a new Scanner instance.
func New(hostname string, maxRunMin int, maxWorkers int) *Scanner {
	u, _ := url.Parse(fmt.Sprintf("http://%s", hostname))
	return &Scanner{
		log:        logger.New(logger.UseDefault, false),
		RootURL:    u,
		Tests:      make(map[string]*Stats),
		MaxRunMin:  maxRunMin,
		MaxWorkers: maxWorkers,
		jobq:       make(chan *scanJob, maxJobs),
		doneCh:     make(chan *scanJob, maxJobs),
	}
}

// Run starts the scanner and manages the jobs.
func (s *Scanner) Run() {
	// Trap all signals to quit.
	s.handleSignals()

	s.mu.Lock()

	// Spin up the workers
	for i := 0; i < s.MaxWorkers; i++ {
		s.wg.Add(1)
		go scanWorker(s.jobq, s.doneCh, &s.wg)
	}
	s.StartTime = time.Now()
	s.ExpireTime = s.StartTime.Add(time.Duration(s.MaxRunMin) * time.Minute)
	s.mu.Unlock()

	// Main event loop.
	s.jobq <- scanJobNew(s.RootURL) // Create first job
	for {
		select {
		case doneJob, ok := <-s.doneCh:
			if !ok {
				s.Stop()
			}
			s.evaluate(doneJob)
		default:
			// Drop dead time reached?
			if time.Now().After(s.ExpireTime) {
				s.Stop()
				return
			}
			// Test a reasonable time for all jobs to be cleared, and then die if no more
			// jobs need to be done or are returned.
			if len(s.jobq) == 0 && len(s.doneCh) == 0 {
				time.Sleep(maxIdleDuration)
				if len(s.doneCh) == 0 {
					s.Stop()
					return
				}
			}
			time.Sleep(maxScannerSleep) // Sleep a while.
		}
	}
}

// Stop performs close out procedures.
func (s *Scanner) Stop() {
	s.stopOnce.Do(func() {
		s.EndTime = time.Now()
		close(s.doneCh)
		close(s.jobq)
		s.wg.Wait()
	})
}

// Dump prints the results to the log output as json.
func (s *Scanner) Dump() {
	for _, v := range s.Tests {
		s.log.Infof(fmt.Sprint(v))
	}
}

// handleSignals responds to operating system interrupts such as application kills.
func (s *Scanner) handleSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			s.Stop()
			os.Exit(0)
		}
	}()
}

// evaluate examines the result of the job and launches new jobs if site children are found.
func (s *Scanner) evaluate(job *scanJob) {
	// Store the result of this scan.
	if _, ok := s.Tests[job.Stat.URL.Path]; !ok {
		s.Tests[job.Stat.URL.String()] = job.Stat
		s.log.Infof(fmt.Sprint(job.Stat))
	}

	// Check for valid chidren under this host, and if any is not already scanned,
	// then submit new a job for this additional URL.
	for _, c := range job.Childen {
		if strings.Contains(c.Host, s.RootURL.Host) {
			if _, ok := s.Tests[c.String()]; !ok {
				s.jobq <- scanJobNew(c)
			}
		}
	}
}
<|endoftext|>


<commit_before>package scanner

import (
	"bufio"
	"io"

	"github.com/brettlangdon/gython/errorcode"
	"github.com/brettlangdon/gython/token"
)

var EOF rune = 0
var MAXINDENT int = 100

type Scanner struct {
	asyncDef            bool
	atBol               bool
	currentColumn       int
	currentLine         int
	currentPosition     *Position
	indentationAltStack []int
	indentationCurrent  int
	indentationLevel    int
	indentationPending  int
	indentationStack    []int
	positionBuffer      []*Position
	tokenBuffer         []*token.Token
	reader              *bufio.Reader
	state               errorcode.ErrorCode
	tabsize             int
	tabsizeAlt          int
}

func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		atBol:               true,
		currentColumn:       0,
		currentLine:         1,
		indentationAltStack: make([]int, MAXINDENT),
		indentationCurrent:  0,
		indentationLevel:    0,
		indentationPending:  0,
		indentationStack:    make([]int, MAXINDENT),
		positionBuffer:      make([]*Position, 0),
		tokenBuffer:         make([]*token.Token, 0),
		reader:              bufio.NewReader(r),
		state:               errorcode.E_OK,
		tabsize:             8,
	}
}

func (scanner *Scanner) nextPosition() *Position {
	if len(scanner.positionBuffer) > 0 {
		last := len(scanner.positionBuffer) - 1
		scanner.currentPosition = scanner.positionBuffer[last]
		scanner.positionBuffer = scanner.positionBuffer[0:last]
		return scanner.currentPosition
	}

	next, _, err := scanner.reader.ReadRune()
	if err != nil {
		scanner.state = errorcode.E_EOF
		next = EOF
		scanner.currentLine++
		scanner.currentColumn = 0
	}

	if next == '\n' || next == EOF {
		scanner.currentLine++
		scanner.currentColumn = 0
	}

	pos := &Position{
		Char:   next,
		Line:   scanner.currentLine,
		Column: scanner.currentColumn,
	}
	scanner.currentColumn++
	return pos
}

func (scanner *Scanner) unreadPosition(pos *Position) {
	scanner.positionBuffer = append(scanner.positionBuffer, pos)
}

func (scanner *Scanner) parseNumber(positions *Positions, nextChar rune) *token.Token {
	pos := scanner.nextPosition()
	switch ch := pos.Char; {
	case nextChar == '0' && (ch == 'j' || ch == 'J'):
		// Imaginary
		positions.Append(pos)
	case nextChar == '0' && (ch == 'x' || ch == 'X'):
		// Hex
		positions.Append(pos)
		pos = scanner.nextPosition()
		if !IsXDigit(pos.Char) {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for IsXDigit(pos.Char) {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	case nextChar == '0' && (ch == 'b' || ch == 'B'):
		// Binary
		positions.Append(pos)
		pos = scanner.nextPosition()
		if pos.Char != '0' && pos.Char != '1' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for pos.Char == '0' || pos.Char == '1' {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	case nextChar == '0' && (ch == 'o' || ch == 'O'):
		// Octal
		positions.Append(pos)
		pos = scanner.nextPosition()
		if pos.Char < '0' || pos.Char >= '8' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for pos.Char >= '0' && pos.Char < '8' {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	default:
		decimal := nextChar == '.'
		imaginary := false
		exponent := false
		for {
			if pos.Char == '.' && decimal {
				break
			} else if pos.Char == '.' && !decimal {
				decimal = true
			} else if (pos.Char == 'j' || pos.Char == 'J') && !imaginary {
				imaginary = true
			} else if (pos.Char == 'e' || pos.Char == 'E') && !exponent {
				exponent = true
				positions.Append(pos)
				pos2 := scanner.nextPosition()
				if pos2.Char == '-' || pos2.Char == '+' {
					pos3 := scanner.nextPosition()
					if !IsDigit(pos3.Char) {
						return positions.AsToken(token.ERRORTOKEN)
					}
					scanner.unreadPosition(pos3)
					positions.Append(pos2)
				} else if !IsDigit(pos2.Char) {
					return positions.AsToken(token.ERRORTOKEN)
				} else {
					scanner.unreadPosition(pos2)
				}
				pos = scanner.nextPosition()
				continue
			} else if !IsDigit(pos.Char) {
				break
			}
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	}

	return positions.AsToken(token.NUMBER)
}

func (scanner *Scanner) parseQuoted(positions *Positions, quote rune) *token.Token {
	// Determine quote size, 1 or 3 (e.g. 'string',  '''string''')
	quoteSize := 1
	endQuoteSize := 0
	pos := scanner.nextPosition()
	if pos.Char == quote {
		pos2 := scanner.nextPosition()
		if pos2.Char == quote {
			positions.Append(pos)
			positions.Append(pos2)
			quoteSize = 3
		} else {
			scanner.unreadPosition(pos2)
			endQuoteSize = 1
		}
	} else {
		scanner.unreadPosition(pos)
	}

	for {
		if endQuoteSize == quoteSize {
			break
		}
		pos = scanner.nextPosition()
		positions.Append(pos)
		if pos.Char == EOF {
			return positions.AsToken(token.ERRORTOKEN)
		}
		if quoteSize == 1 && pos.Char == '\n' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		if pos.Char == quote {
			endQuoteSize += 1
		} else {
			endQuoteSize = 0
			if pos.Char == '\\' {
				pos = scanner.nextPosition()
			}
		}
	}
	return positions.AsToken(token.STRING)
}

func (scanner *Scanner) unreadToken(tok *token.Token) {
	scanner.tokenBuffer = append(scanner.tokenBuffer, tok)
}

func (scanner *Scanner) NextToken() *token.Token {
	if len(scanner.tokenBuffer) > 0 {
		last := len(scanner.tokenBuffer) - 1
		nextToken := scanner.tokenBuffer[last]
		scanner.tokenBuffer = scanner.tokenBuffer[0:last]
		return nextToken
	}

	blankline := false
	positions := NewPositions()
	var pos *Position

	if scanner.atBol {
		// Get indentation level
		col := 0
		altcol := 0
		scanner.atBol = false
		pos = scanner.nextPosition()
		for {
			if pos.Char == ' ' {
				col++
				altcol++
			} else if pos.Char == '\t' {
				col = (col/scanner.tabsize + 1) * scanner.tabsize
				altcol = (altcol/scanner.tabsizeAlt + 1) * scanner.tabsizeAlt
			} else {
				break
			}
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)

		if pos.Char == '#' || pos.Char == '\n' {
			// Lines with only newline or comment, shouldn't affect indentation
			if col == 0 && pos.Char == '\n' {
				blankline = false
			} else {
				blankline = true
			}
		}
		if !blankline && scanner.indentationLevel == 0 {
			if col == scanner.indentationStack[scanner.indentationCurrent] {
				if altcol != scanner.indentationAltStack[scanner.indentationCurrent] {
					return positions.AsToken(token.ERRORTOKEN)
				}
			} else if col > scanner.indentationStack[scanner.indentationCurrent] {
				if scanner.indentationCurrent+1 >= MAXINDENT {
					return positions.AsToken(token.ERRORTOKEN)
				}
				if altcol <= scanner.indentationAltStack[scanner.indentationCurrent] {
					return positions.AsToken(token.ERRORTOKEN)
				}
				scanner.indentationPending++
				scanner.indentationCurrent++
				scanner.indentationStack[scanner.indentationCurrent] = col
				scanner.indentationAltStack[scanner.indentationCurrent] = altcol

			} else {
				for scanner.indentationCurrent > 0 && col < scanner.indentationStack[scanner.indentationCurrent] {
					scanner.indentationPending--
					scanner.indentationCurrent--
				}
				if col != scanner.indentationStack[scanner.indentationCurrent] {
					return positions.AsToken(token.ERRORTOKEN)
				}
				if altcol != scanner.indentationAltStack[scanner.indentationCurrent] {
					return positions.AsToken(token.ERRORTOKEN)
				}
			}
		}
	}

	if scanner.indentationPending != 0 {
		if scanner.indentationPending < 0 {
			scanner.indentationPending++
			pos = scanner.currentPosition
			return &token.Token{
				ID:          token.DEDENT,
				LineStart:   pos.Line,
				ColumnStart: pos.Column,
				LineEnd:     pos.Line,
				ColumnEnd:   pos.Column,
				Literal:     "",
			}
		} else {
			scanner.indentationPending--
			pos = scanner.currentPosition
			return &token.Token{
				ID:          token.INDENT,
				LineStart:   pos.Line,
				ColumnStart: pos.Column,
				LineEnd:     pos.Line,
				ColumnEnd:   pos.Column + 4,
				Literal:     "    ",
			}
		}
	}

	pos = scanner.nextPosition()
	// skip spaces
	for {
		if pos.Char != ' ' && pos.Char != '\t' {
			break
		}
		pos = scanner.nextPosition()
	}

	// skip comments
	if pos.Char == '#' {
		for {
			pos = scanner.nextPosition()
			if pos.Char == EOF || pos.Char == '\n' {
				break
			}
		}
	}

	positions.Append(pos)
	switch ch := pos.Char; {
	case ch == EOF:
		id := token.ENDMARKER
		if scanner.state != errorcode.E_EOF {
			id = token.ERRORTOKEN
		}
		return positions.AsToken(id)
	case IsIdentifierStart(ch):
		// Parse Identifier
		saw_b, saw_r, saw_u := false, false, false
		for {
			if !(saw_b || saw_u) && (ch == 'b' || ch == 'B') {
				saw_b = true
			} else if !(saw_b || saw_u || saw_r) && (ch == 'u' || ch == 'U') {
				saw_u = true
			} else if !(saw_r || saw_u) && (ch == 'r' || ch == 'R') {
				saw_r = true
			} else {
				break
			}
			pos = scanner.nextPosition()
			if IsQuote(pos.Char) {
				positions.Append(pos)
				return scanner.parseQuoted(positions, pos.Char)
			}
		}
		pos = scanner.nextPosition()
		for IsIdentifierChar(pos.Char) {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)

		// Check for async/await
		literal := positions.String()
		if literal == "async" || literal == "await" {
			if scanner.asyncDef {
				switch literal {
				case "async":
					return positions.AsToken(token.ASYNC)
				case "await":
					return positions.AsToken(token.AWAIT)
				}
			} else if literal == "async" {
				nextToken := scanner.NextToken()
				if nextToken.ID == token.NAME && nextToken.Literal == "def" {
					scanner.asyncDef = true
					scanner.unreadToken(nextToken)
					return positions.AsToken(token.ASYNC)
				}
				scanner.unreadToken(nextToken)
			}
		}

		return positions.AsToken(token.NAME)
	case ch == '\n':
		scanner.atBol = true
		return positions.AsToken(token.NEWLINE)
	case ch == '.':
		pos2 := scanner.nextPosition()
		if IsDigit(pos2.Char) {
			positions.Append(pos2)
			return scanner.parseNumber(positions, pos2.Char)
		} else if pos2.Char == '.' {
			positions.Append(pos2)
			pos3 := scanner.nextPosition()
			if pos3.Char == '.' {
				positions.Append(pos3)
				return positions.AsToken(token.ELLIPSIS)
			}
			scanner.unreadPosition(pos3)
		}
		scanner.unreadPosition(pos2)

		return positions.AsToken(token.DOT)
	case IsDigit(ch):
		// Parse Number
		return scanner.parseNumber(positions, ch)
	case IsQuote(ch):
		// Parse String
		return scanner.parseQuoted(positions, ch)
	case ch == '\\':
		// Parse Continuation
	default:
		// Two and Three character operators
		pos2 := scanner.nextPosition()
		op2Id := GetTwoCharTokenID(pos.Char, pos2.Char)
		if op2Id != token.OP {
			positions.Append(pos2)
			pos3 := scanner.nextPosition()
			op3Id := GetThreeCharTokenID(pos.Char, pos2.Char, pos3.Char)
			if op3Id != token.OP {
				positions.Append(pos3)
				return positions.AsToken(op3Id)
			}
			scanner.unreadPosition(pos3)
			return positions.AsToken(op2Id)
		}
		scanner.unreadPosition(pos2)
	}
	switch pos.Char {
	case '(', '[', '{':
		// Increment indentation level
		// scanner.indentationLevel++
		break
	case ')', ']', '}':
		// Decrement indentation level
		// scanner.indentationLevel--
		break
	}

	opId := GetOneCharTokenID(pos.Char)
	return positions.AsToken(opId)
}
<commit_msg>fix up indentation levels<commit_after>package scanner

import (
	"bufio"
	"io"

	"github.com/brettlangdon/gython/errorcode"
	"github.com/brettlangdon/gython/token"
)

var EOF rune = 0
var MAXINDENT int = 100

type Scanner struct {
	asyncDef            bool
	atBol               bool
	currentColumn       int
	currentLine         int
	currentPosition     *Position
	indentationAltStack []int
	indentationCurrent  int
	indentationLevel    int
	indentationPending  int
	indentationStack    []int
	positionBuffer      []*Position
	tokenBuffer         []*token.Token
	reader              *bufio.Reader
	state               errorcode.ErrorCode
	tabsize             int
	tabsizeAlt          int
}

func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		atBol:               true,
		currentColumn:       0,
		currentLine:         1,
		indentationAltStack: make([]int, MAXINDENT),
		indentationCurrent:  0,
		indentationLevel:    0,
		indentationPending:  0,
		indentationStack:    make([]int, MAXINDENT),
		positionBuffer:      make([]*Position, 0),
		tokenBuffer:         make([]*token.Token, 0),
		reader:              bufio.NewReader(r),
		state:               errorcode.E_OK,
		tabsize:             8,
	}
}

func (scanner *Scanner) State() errorcode.ErrorCode {
	return scanner.state
}

func (scanner *Scanner) nextPosition() *Position {
	if len(scanner.positionBuffer) > 0 {
		last := len(scanner.positionBuffer) - 1
		scanner.currentPosition = scanner.positionBuffer[last]
		scanner.positionBuffer = scanner.positionBuffer[0:last]
		return scanner.currentPosition
	}

	next, _, err := scanner.reader.ReadRune()
	if err != nil {
		scanner.state = errorcode.E_EOF
		next = EOF
		scanner.currentLine++
		scanner.currentColumn = 0
	}

	if next == '\n' || next == EOF {
		scanner.currentLine++
		scanner.currentColumn = 0
	}

	pos := &Position{
		Char:   next,
		Line:   scanner.currentLine,
		Column: scanner.currentColumn,
	}
	scanner.currentColumn++
	return pos
}

func (scanner *Scanner) unreadPosition(pos *Position) {
	scanner.positionBuffer = append(scanner.positionBuffer, pos)
}

func (scanner *Scanner) parseNumber(positions *Positions, nextChar rune) *token.Token {
	pos := scanner.nextPosition()
	switch ch := pos.Char; {
	case nextChar == '0' && (ch == 'j' || ch == 'J'):
		// Imaginary
		positions.Append(pos)
	case nextChar == '0' && (ch == 'x' || ch == 'X'):
		// Hex
		positions.Append(pos)
		pos = scanner.nextPosition()
		if !IsXDigit(pos.Char) {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for IsXDigit(pos.Char) {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	case nextChar == '0' && (ch == 'b' || ch == 'B'):
		// Binary
		positions.Append(pos)
		pos = scanner.nextPosition()
		if pos.Char != '0' && pos.Char != '1' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for pos.Char == '0' || pos.Char == '1' {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	case nextChar == '0' && (ch == 'o' || ch == 'O'):
		// Octal
		positions.Append(pos)
		pos = scanner.nextPosition()
		if pos.Char < '0' || pos.Char >= '8' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		for pos.Char >= '0' && pos.Char < '8' {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	default:
		decimal := nextChar == '.'
		imaginary := false
		exponent := false
		for {
			if pos.Char == '.' && decimal {
				break
			} else if pos.Char == '.' && !decimal {
				decimal = true
			} else if (pos.Char == 'j' || pos.Char == 'J') && !imaginary {
				imaginary = true
			} else if (pos.Char == 'e' || pos.Char == 'E') && !exponent {
				exponent = true
				positions.Append(pos)
				pos2 := scanner.nextPosition()
				if pos2.Char == '-' || pos2.Char == '+' {
					pos3 := scanner.nextPosition()
					if !IsDigit(pos3.Char) {
						return positions.AsToken(token.ERRORTOKEN)
					}
					scanner.unreadPosition(pos3)
					positions.Append(pos2)
				} else if !IsDigit(pos2.Char) {
					return positions.AsToken(token.ERRORTOKEN)
				} else {
					scanner.unreadPosition(pos2)
				}
				pos = scanner.nextPosition()
				continue
			} else if !IsDigit(pos.Char) {
				break
			}
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)
	}

	return positions.AsToken(token.NUMBER)
}

func (scanner *Scanner) parseQuoted(positions *Positions, quote rune) *token.Token {
	// Determine quote size, 1 or 3 (e.g. 'string',  '''string''')
	quoteSize := 1
	endQuoteSize := 0
	pos := scanner.nextPosition()
	if pos.Char == quote {
		pos2 := scanner.nextPosition()
		if pos2.Char == quote {
			positions.Append(pos)
			positions.Append(pos2)
			quoteSize = 3
		} else {
			scanner.unreadPosition(pos2)
			endQuoteSize = 1
		}
	} else {
		scanner.unreadPosition(pos)
	}

	for {
		if endQuoteSize == quoteSize {
			break
		}
		pos = scanner.nextPosition()
		positions.Append(pos)
		if pos.Char == EOF {
			return positions.AsToken(token.ERRORTOKEN)
		}
		if quoteSize == 1 && pos.Char == '\n' {
			return positions.AsToken(token.ERRORTOKEN)
		}
		if pos.Char == quote {
			endQuoteSize += 1
		} else {
			endQuoteSize = 0
			if pos.Char == '\\' {
				pos = scanner.nextPosition()
			}
		}
	}
	return positions.AsToken(token.STRING)
}

func (scanner *Scanner) unreadToken(tok *token.Token) {
	scanner.tokenBuffer = append(scanner.tokenBuffer, tok)
}

func (scanner *Scanner) NextToken() *token.Token {
next_line:
	if len(scanner.tokenBuffer) > 0 {
		last := len(scanner.tokenBuffer) - 1
		nextToken := scanner.tokenBuffer[last]
		scanner.tokenBuffer = scanner.tokenBuffer[0:last]
		return nextToken
	}

	blankline := false
	positions := NewPositions()
	var pos *Position

	if scanner.atBol {
		// Get indentation level
		col := 0
		altcol := 0
		scanner.atBol = false
		pos = scanner.nextPosition()
		for {
			if pos.Char == ' ' {
				col++
				altcol++
			} else if pos.Char == '\t' {
				col = (col/scanner.tabsize + 1) * scanner.tabsize
				altcol = (altcol/scanner.tabsizeAlt + 1) * scanner.tabsizeAlt
			} else {
				break
			}
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)

		if pos.Char == '#' || pos.Char == '\n' {
			// Lines with only newline or comment, shouldn't affect indentation
			// TODO: Handle prompt
			if col == 0 && pos.Char == '\n' && false {
				blankline = false
			} else {
				blankline = true
			}
		}
		if !blankline && scanner.indentationLevel == 0 {
			if col == scanner.indentationStack[scanner.indentationCurrent] {
				if altcol != scanner.indentationAltStack[scanner.indentationCurrent] {
					pos = scanner.currentPosition
					return &token.Token{
						ID:          token.ERRORTOKEN,
						LineStart:   pos.Line,
						ColumnStart: pos.Column,
						LineEnd:     pos.Line,
						ColumnEnd:   pos.Column,
						Literal:     "",
					}
				}
			} else if col > scanner.indentationStack[scanner.indentationCurrent] {
				if scanner.indentationCurrent+1 >= MAXINDENT {
					scanner.state = errorcode.E_TOODEEP
					pos = scanner.currentPosition
					return &token.Token{
						ID:          token.ERRORTOKEN,
						LineStart:   pos.Line,
						ColumnStart: pos.Column,
						LineEnd:     pos.Line,
						ColumnEnd:   pos.Column,
						Literal:     "",
					}
				}
				if altcol <= scanner.indentationAltStack[scanner.indentationCurrent] {
					pos = scanner.currentPosition
					return &token.Token{
						ID:          token.ERRORTOKEN,
						LineStart:   pos.Line,
						ColumnStart: pos.Column,
						LineEnd:     pos.Line,
						ColumnEnd:   pos.Column,
						Literal:     "",
					}
				}
				scanner.indentationPending++
				scanner.indentationCurrent++
				scanner.indentationStack[scanner.indentationCurrent] = col
				scanner.indentationAltStack[scanner.indentationCurrent] = altcol

			} else {
				for scanner.indentationCurrent > 0 && col < scanner.indentationStack[scanner.indentationCurrent] {
					scanner.indentationPending--
					scanner.indentationCurrent--
				}
				if col != scanner.indentationStack[scanner.indentationCurrent] {
					scanner.state = errorcode.E_DEDENT
					pos = scanner.currentPosition
					return &token.Token{
						ID:          token.ERRORTOKEN,
						LineStart:   pos.Line,
						ColumnStart: pos.Column,
						LineEnd:     pos.Line,
						ColumnEnd:   pos.Column,
						Literal:     "",
					}
				}
				if altcol != scanner.indentationAltStack[scanner.indentationCurrent] {
					scanner.state = errorcode.E_DEDENT
					pos = scanner.currentPosition
					return &token.Token{
						ID:          token.ERRORTOKEN,
						LineStart:   pos.Line,
						ColumnStart: pos.Column,
						LineEnd:     pos.Line,
						ColumnEnd:   pos.Column,
						Literal:     "",
					}
				}
			}
		}
	}

	if scanner.indentationPending != 0 {
		if scanner.indentationPending < 0 {
			scanner.indentationPending++
			pos = scanner.currentPosition
			return &token.Token{
				ID:          token.DEDENT,
				LineStart:   pos.Line,
				ColumnStart: pos.Column,
				LineEnd:     pos.Line,
				ColumnEnd:   pos.Column,
				Literal:     "",
			}
		} else {
			scanner.indentationPending--
			pos = scanner.currentPosition
			return &token.Token{
				ID:          token.INDENT,
				LineStart:   pos.Line,
				ColumnStart: pos.Column,
				LineEnd:     pos.Line,
				ColumnEnd:   pos.Column + 4,
				Literal:     "    ",
			}
		}
	}

	pos = scanner.nextPosition()
	// skip spaces
	for {
		if pos.Char != ' ' && pos.Char != '\t' {
			break
		}
		pos = scanner.nextPosition()
	}

	// skip comments
	if pos.Char == '#' {
		for {
			pos = scanner.nextPosition()
			if pos.Char == EOF || pos.Char == '\n' {
				break
			}
		}
	}

	positions.Append(pos)
	switch ch := pos.Char; {
	case ch == EOF:
		id := token.ENDMARKER
		if scanner.state != errorcode.E_EOF {
			id = token.ERRORTOKEN
		}
		return positions.AsToken(id)
	case IsIdentifierStart(ch):
		// Parse Identifier
		saw_b, saw_r, saw_u := false, false, false
		for {
			if !(saw_b || saw_u) && (ch == 'b' || ch == 'B') {
				saw_b = true
			} else if !(saw_b || saw_u || saw_r) && (ch == 'u' || ch == 'U') {
				saw_u = true
			} else if !(saw_r || saw_u) && (ch == 'r' || ch == 'R') {
				saw_r = true
			} else {
				break
			}
			pos = scanner.nextPosition()
			if IsQuote(pos.Char) {
				positions.Append(pos)
				return scanner.parseQuoted(positions, pos.Char)
			}
		}
		pos = scanner.nextPosition()
		for IsIdentifierChar(pos.Char) {
			positions.Append(pos)
			pos = scanner.nextPosition()
		}
		scanner.unreadPosition(pos)

		// Check for async/await
		literal := positions.String()
		if literal == "async" || literal == "await" {
			if scanner.asyncDef {
				switch literal {
				case "async":
					return positions.AsToken(token.ASYNC)
				case "await":
					return positions.AsToken(token.AWAIT)
				}
			} else if literal == "async" {
				nextToken := scanner.NextToken()
				if nextToken.ID == token.NAME && nextToken.Literal == "def" {
					scanner.asyncDef = true
					scanner.unreadToken(nextToken)
					return positions.AsToken(token.ASYNC)
				}
				scanner.unreadToken(nextToken)
			}
		}

		return positions.AsToken(token.NAME)
	case ch == '\n':
		scanner.atBol = true
		if blankline || scanner.indentationCurrent > 0 {
			goto next_line
		}
		return positions.AsToken(token.NEWLINE)
	case ch == '.':
		pos2 := scanner.nextPosition()
		if IsDigit(pos2.Char) {
			positions.Append(pos2)
			return scanner.parseNumber(positions, pos2.Char)
		} else if pos2.Char == '.' {
			positions.Append(pos2)
			pos3 := scanner.nextPosition()
			if pos3.Char == '.' {
				positions.Append(pos3)
				return positions.AsToken(token.ELLIPSIS)
			}
			scanner.unreadPosition(pos3)
		}
		scanner.unreadPosition(pos2)

		return positions.AsToken(token.DOT)
	case IsDigit(ch):
		// Parse Number
		return scanner.parseNumber(positions, ch)
	case IsQuote(ch):
		// Parse String
		return scanner.parseQuoted(positions, ch)
	case ch == '\\':
		// Parse Continuation
	default:
		// Two and Three character operators
		pos2 := scanner.nextPosition()
		op2Id := GetTwoCharTokenID(pos.Char, pos2.Char)
		if op2Id != token.OP {
			positions.Append(pos2)
			pos3 := scanner.nextPosition()
			op3Id := GetThreeCharTokenID(pos.Char, pos2.Char, pos3.Char)
			if op3Id != token.OP {
				positions.Append(pos3)
				return positions.AsToken(op3Id)
			}
			scanner.unreadPosition(pos3)
			return positions.AsToken(op2Id)
		}
		scanner.unreadPosition(pos2)
	}
	switch pos.Char {
	case '(', '[', '{':
		// Increment indentation level
		scanner.indentationLevel++
		break
	case ')', ']', '}':
		// Decrement indentation level
		scanner.indentationLevel--
		break
	}

	opId := GetOneCharTokenID(pos.Char)
	return positions.AsToken(opId)
}
<|endoftext|>


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

import (
	"context"
	"fmt"
	"io/ioutil"
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/runtime"

	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/core/resource"
	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/services"
	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/testservices"
)

func TestAuthorizeAccess(t *testing.T) {
	ctx := context.Background()
	clusterName := "test-cluster"
	clusterLocation := "us-east1-b"
	clusterProject := "my-project"
	gs := &testservices.TestGcloud{
		ContainerClustersGetCredentialsErr: nil,
	}

	if err := AuthorizeAccess(ctx, clusterName, clusterLocation, clusterProject, gs); err != nil {
		t.Errorf("AuthorizeAccess(ctx, %s, %s, gs) = %v; want <nil>", clusterName, clusterLocation, err)
	}
}

func TestAuthorizeAccessErrors(t *testing.T) {
	ctx := context.Background()
	clusterName := "test-cluster"
	clusterLocation := "us-east1-b"
	clusterProject := "my-project"
	gs := &testservices.TestGcloud{
		ContainerClustersGetCredentialsErr: fmt.Errorf("failed to get credentials of cluster"),
	}

	if err := AuthorizeAccess(ctx, clusterName, clusterLocation, clusterProject, gs); err == nil {
		t.Errorf("AuthorizeAccess(ctx, %s, %s, gs) = <nil>; want error", clusterName, clusterLocation)
	}
}

func TestApplyConfigFromString(t *testing.T) {
	configString := string(fileContents(t, "testing/deployment.yaml"))
	namespace := "default"
	ks := &testservices.TestKubectl{
		ApplyFromStringResponse: map[string]error{
			configString: nil,
		},
	}

	if err := ApplyConfigFromString(configString, namespace, ks); err != nil {
		t.Errorf("ApplyConfigFromString(%s, %s, ks) = %v; want <nil>", configString, namespace, err)
	}
}

func TestApplyConfigFromStringErrors(t *testing.T) {
	configString := string(fileContents(t, "testing/deployment.yaml"))
	namespace := "default"
	ks := &testservices.TestKubectl{
		ApplyFromStringResponse: map[string]error{
			configString: fmt.Errorf("failed to apply kubernetes manifests to cluster"),
		},
	}

	if err := ApplyConfigFromString(configString, namespace, ks); err == nil {
		t.Errorf("ApplyConfigs(%s, %s, ks) = <nil>; want error", configString, namespace)
	}
}

func TestGetDeployedObject(t *testing.T) {
	ctx := context.Background()

	testDeploymentFile := "testing/deployment.yaml"
	testServiceFile := "testing/service.yaml"

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService

		want runtime.Object
	}{{
		name: "Get deployed deployment",

		kind:      "Deployment",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string]*testservices.GetResponse{
				"Deployment": {
					"test-app": {
						Res: []string{
							string(fileContents(t, testDeploymentFile)),
						},
						Err: []error{
							nil,
						},
					},
				},
			},
		},

		want: newObjectFromFile(t, testDeploymentFile),
	}, {
		name: "Get deployed service",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string]*testservices.GetResponse{
				"Service": {
					"test-app": {
						Res: []string{
							string(fileContents(t, testServiceFile)),
						},
						Err: []error{
							nil,
						},
					},
				},
			},
		},

		want: newObjectFromFile(t, testServiceFile),
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := GetDeployedObject(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); !reflect.DeepEqual(got, tc.want) || err != nil {
				t.Errorf("GetDeployedObject(ctx, %s, %s, %s, ks,) = %v, %v; want %v, <nil>", tc.kind, tc.objName, tc.namespace, got, err, tc.want)
			}
		})
	}
}

func TestDeployedObjectExists(t *testing.T) {
	ctx := context.Background()

	testDeploymentFile := "testing/deployment.yaml"

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService

		want bool
	}{{
		name: "Deployed deployment exists",

		kind:      "Deployment",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string]*testservices.GetResponse{
				"Deployment": {
					"test-app": {
						Res: []string{
							string(fileContents(t, testDeploymentFile)),
						},
						Err: []error{
							nil,
						},
					},
				},
			},
		},

		want: true,
	}, {
		name: "Deployed service does not exist",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string]*testservices.GetResponse{
				"Service": {
					"test-app": {
						Res: []string{
							"",
						},
						Err: []error{
							nil,
						},
					},
				},
			},
		},

		want: false,
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := DeployedObjectExists(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); got != tc.want || err != nil {
				t.Errorf("DeployedObjectExists(ctx, %s, %s, %s, ks,) = %t, %v; want %t, <nil>", tc.kind, tc.objName, tc.namespace, got, err, tc.want)
			}
		})
	}
}

func TestDeployedObjectExistsErrors(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService
	}{{
		name: "Failed to check if object exists",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string]*testservices.GetResponse{
				"Service": {
					"test-app": {
						Res: []string{
							"",
						},
						Err: []error{
							fmt.Errorf("failed to get service"),
						},
					},
				},
			},
		},
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := DeployedObjectExists(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); err == nil {
				t.Errorf("DeployedObjectExists(ctx, %s, %s, %s, ks,) = %t, <nil>; want false, error", tc.kind, tc.objName, tc.namespace, got)
			}
		})
	}
}

func newObjectFromFile(t *testing.T, filename string) runtime.Object {
	contents := fileContents(t, filename)
	obj, err := resource.DecodeFromYAML(nil, contents)
	if err != nil {
		t.Fatalf("failed to decode resource from file %s", filename)
	}
	return obj
}

func fileContents(t *testing.T, filename string) []byte {
	contents, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatalf("failed to read file %s", filename)
	}
	return contents
}
<commit_msg>Update gke-deploy cluster test to use list of res-err pairs<commit_after>/*
Copyright 2019 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster

import (
	"context"
	"fmt"
	"io/ioutil"
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/runtime"

	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/core/resource"
	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/services"
	"github.com/GoogleCloudPlatform/cloud-builders/gke-deploy/testservices"
)

func TestAuthorizeAccess(t *testing.T) {
	ctx := context.Background()
	clusterName := "test-cluster"
	clusterLocation := "us-east1-b"
	clusterProject := "my-project"
	gs := &testservices.TestGcloud{
		ContainerClustersGetCredentialsErr: nil,
	}

	if err := AuthorizeAccess(ctx, clusterName, clusterLocation, clusterProject, gs); err != nil {
		t.Errorf("AuthorizeAccess(ctx, %s, %s, gs) = %v; want <nil>", clusterName, clusterLocation, err)
	}
}

func TestAuthorizeAccessErrors(t *testing.T) {
	ctx := context.Background()
	clusterName := "test-cluster"
	clusterLocation := "us-east1-b"
	clusterProject := "my-project"
	gs := &testservices.TestGcloud{
		ContainerClustersGetCredentialsErr: fmt.Errorf("failed to get credentials of cluster"),
	}

	if err := AuthorizeAccess(ctx, clusterName, clusterLocation, clusterProject, gs); err == nil {
		t.Errorf("AuthorizeAccess(ctx, %s, %s, gs) = <nil>; want error", clusterName, clusterLocation)
	}
}

func TestApplyConfigFromString(t *testing.T) {
	configString := string(fileContents(t, "testing/deployment.yaml"))
	namespace := "default"
	ks := &testservices.TestKubectl{
		ApplyFromStringResponse: map[string][]error{
			configString: {nil},
		},
	}

	if err := ApplyConfigFromString(configString, namespace, ks); err != nil {
		t.Errorf("ApplyConfigFromString(%s, %s, ks) = %v; want <nil>", configString, namespace, err)
	}
}

func TestApplyConfigFromStringErrors(t *testing.T) {
	configString := string(fileContents(t, "testing/deployment.yaml"))
	namespace := "default"
	ks := &testservices.TestKubectl{
		ApplyFromStringResponse: map[string][]error{
			configString: {fmt.Errorf("failed to apply kubernetes manifests to cluster")},
		},
	}

	if err := ApplyConfigFromString(configString, namespace, ks); err == nil {
		t.Errorf("ApplyConfigs(%s, %s, ks) = <nil>; want error", configString, namespace)
	}
}

func TestGetDeployedObject(t *testing.T) {
	ctx := context.Background()

	testDeploymentFile := "testing/deployment.yaml"
	testServiceFile := "testing/service.yaml"

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService

		want runtime.Object
	}{{
		name: "Get deployed deployment",

		kind:      "Deployment",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string][]testservices.GetResponse{
				"Deployment": {
					"test-app": {
						{
							Res: string(fileContents(t, testDeploymentFile)),
							Err: nil,
						},
					},
				},
			},
		},

		want: newObjectFromFile(t, testDeploymentFile),
	}, {
		name: "Get deployed service",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string][]testservices.GetResponse{
				"Service": {
					"test-app": {
						{
							Res: string(fileContents(t, testServiceFile)),
							Err: nil,
						},
					},
				},
			},
		},

		want: newObjectFromFile(t, testServiceFile),
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := GetDeployedObject(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); !reflect.DeepEqual(got, tc.want) || err != nil {
				t.Errorf("GetDeployedObject(ctx, %s, %s, %s, ks,) = %v, %v; want %v, <nil>", tc.kind, tc.objName, tc.namespace, got, err, tc.want)
			}
		})
	}
}

func TestDeployedObjectExists(t *testing.T) {
	ctx := context.Background()

	testDeploymentFile := "testing/deployment.yaml"

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService

		want bool
	}{{
		name: "Deployed deployment exists",

		kind:      "Deployment",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string][]testservices.GetResponse{
				"Deployment": {
					"test-app": {
						{
							Res: string(fileContents(t, testDeploymentFile)),
							Err: nil,
						},
					},
				},
			},
		},

		want: true,
	}, {
		name: "Deployed service does not exist",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string][]testservices.GetResponse{
				"Service": {
					"test-app": {
						{
							Res: "",
							Err: nil,
						},
					},
				},
			},
		},

		want: false,
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := DeployedObjectExists(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); got != tc.want || err != nil {
				t.Errorf("DeployedObjectExists(ctx, %s, %s, %s, ks,) = %t, %v; want %t, <nil>", tc.kind, tc.objName, tc.namespace, got, err, tc.want)
			}
		})
	}
}

func TestDeployedObjectExistsErrors(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name string

		kind      string
		objName   string
		namespace string
		ks        services.KubectlService
	}{{
		name: "Failed to check if object exists",

		kind:      "Service",
		objName:   "test-app",
		namespace: "default",
		ks: &testservices.TestKubectl{
			GetResponse: map[string]map[string][]testservices.GetResponse{
				"Service": {
					"test-app": {
						{
							Res: "",
							Err: fmt.Errorf("failed to get service"),
						},
					},
				},
			},
		},
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, err := DeployedObjectExists(ctx, tc.kind, tc.objName, tc.namespace, tc.ks); err == nil {
				t.Errorf("DeployedObjectExists(ctx, %s, %s, %s, ks,) = %t, <nil>; want false, error", tc.kind, tc.objName, tc.namespace, got)
			}
		})
	}
}

func newObjectFromFile(t *testing.T, filename string) runtime.Object {
	contents := fileContents(t, filename)
	obj, err := resource.DecodeFromYAML(nil, contents)
	if err != nil {
		t.Fatalf("failed to decode resource from file %s", filename)
	}
	return obj
}

func fileContents(t *testing.T, filename string) []byte {
	contents, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatalf("failed to read file %s", filename)
	}
	return contents
}
<|endoftext|>


<commit_before>package standard

import (
	"fmt"
	G "github.com/ionous/sashimi/game"
	. "github.com/ionous/sashimi/script"
	"github.com/ionous/sashimi/util/lang"
	"strings"
)

// InitStandardLibrary to ensure all standard library scripts are properly created.
func InitStandardLibrary() *Script {
	// the side effect of importing the standard library
	// adds the scripts to the list of those which get initialized.
	return InitScripts()
}

// FIX: there's no error testing here and its definitely possible to screw things up.
func AssignTo(prop G.IObject, rel string, dest G.IObject) {
	// sure hope there's no errors, would relation by value remove the need for transaction?
	if _, parentRel := prop.ParentRelation(); parentRel != "" {
		// note: an object like the fishFood isnt "in the world", and doest have an owner field to clear.
		prop.Set(parentRel, nil)
	}
	prop.Set(rel, dest)
}

//
func init() {
	AddScript(func(s *Script) {
		// FIX: hierarchy is a work in progress.
		// kinds> stories, rooms, objects > actors (> animals),  props(> openers(> doors,containers), supporters, devices)

		// FIX: IObject and Instance, ObjectList are confusing versus kind/object
		// Instance might be better...? or some better name for the class of objects
		// i tried "subjects" but hated it more.
		s.The("kinds",
			// printed plural name (text),
			Have("printed name", "text"),
			Have("indefinite article", "text"), //
			AreEither("singular-named").Or("plural-named"),
			AreEither("common-named").Or("proper-named"), //a name used for an individual person, place, or organization, spelled with initial capital letters, e.g., Larry, Mexico, and Boston Red Sox.
			//not normally preceded by an article or other limiting modifier, as any or some. Nor are they usually pluralized
		)

		// vs. descriptions in "kind"
		// it seems to make sense for now to have two separate description fields.
		// rooms like to say their description, while objects like to say their brief initial appearance ( or name, if there's none. )
		s.The("rooms",
			Have("description", "text"))

		s.The("objects",
			Have("description", "text"),
			Have("brief", "text"))

		// inform's rooms: lighted, dark; unvisited, visited; description, region
		s.The("kinds",
			Called("rooms"),
			AreEither("visited").Or("unvisited").Usually("unvisited"),
		)

		// the class hierarchy means rooms cant contain other rooms.
		s.The("rooms",
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("whereabouts", "room")))

		// things		unlit, lit
		// 	inedible, edible
		//
		// 	unmarked for listing, marked for listing
		// 	described, undescribed : i think, whether to appear in any room descriptions
		// 	mentioned, unmentioned : i think, whether it has appeared in a room description
		// bool	scenery
		// 	wearable
		// 	pushable between rooms
		// 	.handled
		// 	.description (in objects and rooms)
		// 	.initial appearance (brief)
		// 	matching key
		s.The("kinds",
			Called("objects"),
			Exist())

		// hrmmm.... are actors really scenery? handled?
		s.The("objects",
			AreEither("unhandled").Or("handled"),
			AreEither("scenery").Or("not scenery").Usually("not scenery"))

		s.The("openers",
			Called("doors"),
			Exist())

		// CAN WE DEFAULT (USUALLY(X)) DOORS TO fixed-in-place???
		s.The("doors", Before("reporting take").Always(func(g G.Play) {
			g.Say("It is fixed in place.")
			g.StopHere()
		}))

		// nothing special: just a handy name to mirror inform's.
		s.The("actors",
			Called("animals"),
			Exist())

		// hrmm.... implies actors are takeable.
		s.The("objects",
			Called("actors"),
			HaveMany("clothing", "objects").
				Implying("objects", HaveOne("wearer", "actor")),
			HaveMany("inventory", "objects").
				Implying("objects", HaveOne("owner", "actor")))

		// changed: used to have "equipment" for held objects
		// will either implement some sort of "relation with value"
		// or will add a "held","holdable", state to objects.

		s.The("objects",
			Called("props"),
			AreEither("portable").Or("fixed in place"),
		)

		// Usually opaque not transparent, open not closed, unopenable not openable, unlocked not locked.
		// Usually not enterable, lockable.
		// Can have carrying capacity (number)
		s.The("openers",
			Called("containers"),
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("enclosure", "container")),
			AreEither("opaque").Or("transparent"),
			//AreEither("enterable").Or("not enterable"),
			AreEither("lockable").Or("not lockable").Usually("not lockable"),
			AreEither("locked").Or("unlocked").Usually("unlocked"),
		)

		s.The("props",
			Called("supporters"),
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("support", "supporter")))
	})
}

// reflect to the passed action passing the actors's current whereabouts.
// will have to become more sophisticated for being inside a box.
func ReflectToLocation(g G.Play, action string) {
	actor := g.The("actor")
	target := actor.Object("whereabouts")
	//g.Log("reflecting", action, actor, target)
	target.Go(action, actor)
}

// ReflectToTarget runs the passed action, flipping the source and target.
func ReflectToTarget(g G.Play, action string) {
	source := g.The("action.Source")
	target := g.The("action.Target")
	//g.Log("reflecting", action, source, target)
	target.Go(action, source)

}

// ReflectWithContext runs the passed action, shifting to target, context, source.
// FIX: i think it'd be better to first use ReflectToTarget, keeping the context as the third parameter
// and then ReflectToContext, possibly re-swapping source and target.
func ReflectWithContext(g G.Play, action string) {
	source := g.The("action.Source")
	target := g.The("action.Target")
	context := g.The("action.Context")
	//g.Log("reflecting", action, source, target, context)
	target.Go(action, context, source)
}

func init() {
	AddScript(func(s *Script) {

		// one visible thing, and requring light
		s.The("actors",
			Can("look").And("looking").RequiresNothing(),
			To("look",
				// func( g G.Play) { ReflectToLocation(g,"report the view") }
				// reflect to location will send the actor as a parameter,
				// but report the view doesnt expect parameters.
				func(g G.Play) {
					actor := g.The("actor")
					target := actor.Object("whereabouts")
					target.Go("report the view")
				}),
		)

		// one visible thing and requiring light.
		s.The("actors",
			Can("look under it").And("looking under it").RequiresOne("object"),
			To("look under it", func(g G.Play) { ReflectToTarget(g, "report look under") }),
		)

		// FIX: should generate a report/response instead?
		s.The("actors",
			Can("impress").And("impressing").RequiresNothing(),
			To("impress", func(g G.Play) {
				g.Say(lang.Capitalize(ArticleName(g, "actor", nil)), "is unimpressed.")
			}))

		// "taking inventory" in inform
		// again, as with some other actions: for players this happens in carry out, for npcs in report.
		// i'm sure that's useful... somehow....
		s.The("actors",
			Can("report inventory").And("reporting inventory").RequiresNothing(),
			To("report inventory", func(g G.Play) {
				this := g.The("actor")
				source := []string{"Clothing", "Inventory"}
				for _, s := range source {
					contents := this.ObjectList(s)
					if len(contents) > 0 {
						g.Say(s + ":")
						for _, obj := range contents {
							obj.Go("print name")
						}
					} else {
						g.Say(s, "(none).")
					}
				}
			}),
		)

		// FIX: for some reason, the order must be biggest match to smallest, the other way doesnt work.
		s.Execute("report inventory", Matching("inventory|inv|i"))
		s.Execute("look", Matching("look|l"))
		s.Execute("look under it", Matching("look under {{something}}"))
	})
}

//
// when is the right time for functions versus callbacks?
func ListContents(g G.Play, header string, obj G.IObject) (printed bool) {
	// if something described which is not scenery is on the noun and something which is not the player is on the noun:
	// obviously a filterd callback, visitor, would be nice FilterList("contents", func() ... )
	// FIX: if something has scenery objets, they appear as contents,
	// but then the list is empty. ( ex. lab coat, but it might happen elsewhere )
	// we'd maybe need to know if something printed?
	if contents := obj.ObjectList("contents"); len(contents) > 0 {
		g.Say(header, obj.Text("Name"), "is:")
		for _, content := range contents {
			content.Go("print description")
		}
		printed = true
	}
	return printed
}

func NameFullStop(G.IObject) string {
	return ""
}

type NameStatus func(obj G.IObject) string

func ArticleName(g G.Play, which string, status NameStatus) string {
	return articleName(g, which, false, status)
}
func DefiniteName(g G.Play, which string, status NameStatus) string {
	return articleName(g, which, true, status)
}
func articleName(g G.Play, which string, definite bool, status NameStatus) string {
	obj := g.The(which)
	text := obj.Text("Name")
	if obj.Is("proper-named") {
		text = lang.Titleize(text)
	} else {
		article := ""
		if definite {
			article = "the"
		} else {
			article = obj.Text("indefinite article")
			if article == "" {
				if obj.Is("plural-named") {
					article = "some"
				} else if lang.StartsWithVowel(text) {
					article = "an"
				} else {
					article = "a"
				}
			}
		}
		text = strings.Join([]string{article, strings.ToLower(text)}, " ")
	}
	if status != nil {
		if s := status(obj); s != "" {
			text = fmt.Sprintf("%s (%s)", text, s)
		} else {
			text = text + "."
		}
	}
	return text
}

//
// System actions
func init() {
	AddScript(func(s *Script) {

		// inform has two entries for some actions (looking under as an example, jumping as a counter example):
		// 1. carry out an actor looking under: if the player
		// 2. report an actor looking under: if not the player
		// its not exactly clear to me why, the docs give guidelines for this, but not rationale.
		// it might be interesting to queue says, if they need to be cancelled or held back.
		// keep in mind, most of these really want to be animations, and only sometimes voice.
		s.The("objects",
			Can("report look under").And("reporting look under").RequiresOne("actor"),
			To("report look under", func(g G.Play) {
				source, actor := g.The("action.Source"), g.The("action.Target")
				if g.The("player") == actor {
					g.Say("You find nothing of interest.")
				} else {
					g.Say(actor.Text("Name"), "looks under the", source.Text("Name"), ".")
				}
			}))

		s.The("objects",
			Can("print name").And("printing name text").RequiresNothing(),
			To("print name", func(g G.Play) {
				if text := ArticleName(g, "object", NameFullStop); len(text) > 0 {
					text = lang.Capitalize(text)
					g.Say(text)
				}
			}))

		s.The("containers",
			When("printing name text").
				Always(func(g G.Play) {
				// FIX: conditional return instead of Always?
				// or some way ( dependency injection ) to get at the event object
				// of course, rules producing values and stacks might work too.
				// FIX: a container is an opener... where do we print the opener status name
				// put this on doors for now.
				text := ArticleName(g, "container", func(obj G.IObject) (status string) {
					if obj.Is("closed") {
						if obj.Is("hinged") {
							status = "closed"
						}
					} else if list := obj.ObjectList("contents"); len(list) == 0 {
						if obj.Is("transparent") || obj.Is("open") {
							status = "empty"
						}
					}
					return status
				})
				text = lang.Capitalize(text)
				g.Say(text)
				g.StopHere()
			}))

		s.The("doors",
			When("printing name text").
				Always(func(g G.Play) {
				text := ArticleName(g, "door", func(obj G.IObject) (status string) {
					if obj.Is("hinged") {
						if obj.Is("open") {
							status = "open"
						} else {
							status = "closed"
						}
					}
					return status
				})
				text = lang.Capitalize(text)
				g.Say(text)
				g.StopHere()
			}))

		s.The("rooms",
			Can("report the view").And("reporting the view").RequiresNothing(),
			After("reporting the view").Always(func(g G.Play) {
				room := g.The("room")
				room.IsNow("visited")
			}),
			To("report the view", func(g G.Play) {
				room := g.The("room")
				// sometines a blank like is printed without this
				// (maybe certain directions? or going through doors?)
				// not sure why, so leaving this for consistency
				g.Say(Lines("", room.Text("Name")))
				g.Say(Lines(room.Text("description"), ""))
				// FIX? uses 1 to exclude the player....
				// again, this happens because we dont know if print description actually did anything (re:scenery, etc.)
				if contents := room.ObjectList("contents"); len(contents) > 1 {
					for _, obj := range contents {
						obj.Go("print description")
						g.Say("")
					}
				}
				// FIX: duplicated in stories describe the first room
				g.The("status bar").SetText("left", lang.Titleize(room.Text("Name")))
			}))
	})
}
<commit_msg>minor: remove empty line<commit_after>package standard

import (
	"fmt"
	G "github.com/ionous/sashimi/game"
	. "github.com/ionous/sashimi/script"
	"github.com/ionous/sashimi/util/lang"
	"strings"
)

// InitStandardLibrary to ensure all standard library scripts are properly created.
func InitStandardLibrary() *Script {
	// the side effect of importing the standard library
	// adds the scripts to the list of those which get initialized.
	return InitScripts()
}

// FIX: there's no error testing here and its definitely possible to screw things up.
func AssignTo(prop G.IObject, rel string, dest G.IObject) {
	// sure hope there's no errors, would relation by value remove the need for transaction?
	if _, parentRel := prop.ParentRelation(); parentRel != "" {
		// note: an object like the fishFood isnt "in the world", and doest have an owner field to clear.
		prop.Set(parentRel, nil)
	}
	prop.Set(rel, dest)
}

//
func init() {
	AddScript(func(s *Script) {
		// FIX: hierarchy is a work in progress.
		// kinds> stories, rooms, objects > actors (> animals),  props(> openers(> doors,containers), supporters, devices)

		// FIX: IObject and Instance, ObjectList are confusing versus kind/object
		// Instance might be better...? or some better name for the class of objects
		// i tried "subjects" but hated it more.
		s.The("kinds",
			// printed plural name (text),
			Have("printed name", "text"),
			Have("indefinite article", "text"), //
			AreEither("singular-named").Or("plural-named"),
			AreEither("common-named").Or("proper-named"), //a name used for an individual person, place, or organization, spelled with initial capital letters, e.g., Larry, Mexico, and Boston Red Sox.
			//not normally preceded by an article or other limiting modifier, as any or some. Nor are they usually pluralized
		)

		// vs. descriptions in "kind"
		// it seems to make sense for now to have two separate description fields.
		// rooms like to say their description, while objects like to say their brief initial appearance ( or name, if there's none. )
		s.The("rooms",
			Have("description", "text"))

		s.The("objects",
			Have("description", "text"),
			Have("brief", "text"))

		// inform's rooms: lighted, dark; unvisited, visited; description, region
		s.The("kinds",
			Called("rooms"),
			AreEither("visited").Or("unvisited").Usually("unvisited"),
		)

		// the class hierarchy means rooms cant contain other rooms.
		s.The("rooms",
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("whereabouts", "room")))

		// things		unlit, lit
		// 	inedible, edible
		//
		// 	unmarked for listing, marked for listing
		// 	described, undescribed : i think, whether to appear in any room descriptions
		// 	mentioned, unmentioned : i think, whether it has appeared in a room description
		// bool	scenery
		// 	wearable
		// 	pushable between rooms
		// 	.handled
		// 	.description (in objects and rooms)
		// 	.initial appearance (brief)
		// 	matching key
		s.The("kinds",
			Called("objects"),
			Exist())

		// hrmmm.... are actors really scenery? handled?
		s.The("objects",
			AreEither("unhandled").Or("handled"),
			AreEither("scenery").Or("not scenery").Usually("not scenery"))

		s.The("openers",
			Called("doors"),
			Exist())

		// CAN WE DEFAULT (USUALLY(X)) DOORS TO fixed-in-place???
		s.The("doors", Before("reporting take").Always(func(g G.Play) {
			g.Say("It is fixed in place.")
			g.StopHere()
		}))

		// nothing special: just a handy name to mirror inform's.
		s.The("actors",
			Called("animals"),
			Exist())

		// hrmm.... implies actors are takeable.
		s.The("objects",
			Called("actors"),
			HaveMany("clothing", "objects").
				Implying("objects", HaveOne("wearer", "actor")),
			HaveMany("inventory", "objects").
				Implying("objects", HaveOne("owner", "actor")))

		// changed: used to have "equipment" for held objects
		// will either implement some sort of "relation with value"
		// or will add a "held","holdable", state to objects.

		s.The("objects",
			Called("props"),
			AreEither("portable").Or("fixed in place"),
		)

		// Usually opaque not transparent, open not closed, unopenable not openable, unlocked not locked.
		// Usually not enterable, lockable.
		// Can have carrying capacity (number)
		s.The("openers",
			Called("containers"),
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("enclosure", "container")),
			AreEither("opaque").Or("transparent"),
			//AreEither("enterable").Or("not enterable"),
			AreEither("lockable").Or("not lockable").Usually("not lockable"),
			AreEither("locked").Or("unlocked").Usually("unlocked"),
		)

		s.The("props",
			Called("supporters"),
			HaveMany("contents", "objects").
				Implying("objects", HaveOne("support", "supporter")))
	})
}

// reflect to the passed action passing the actors's current whereabouts.
// will have to become more sophisticated for being inside a box.
func ReflectToLocation(g G.Play, action string) {
	actor := g.The("actor")
	target := actor.Object("whereabouts")
	//g.Log("reflecting", action, actor, target)
	target.Go(action, actor)
}

// ReflectToTarget runs the passed action, flipping the source and target.
func ReflectToTarget(g G.Play, action string) {
	source := g.The("action.Source")
	target := g.The("action.Target")
	//g.Log("reflecting", action, source, target)
	target.Go(action, source)
}

// ReflectWithContext runs the passed action, shifting to target, context, source.
// FIX: i think it'd be better to first use ReflectToTarget, keeping the context as the third parameter
// and then ReflectToContext, possibly re-swapping source and target.
func ReflectWithContext(g G.Play, action string) {
	source := g.The("action.Source")
	target := g.The("action.Target")
	context := g.The("action.Context")
	//g.Log("reflecting", action, source, target, context)
	target.Go(action, context, source)
}

func init() {
	AddScript(func(s *Script) {

		// one visible thing, and requring light
		s.The("actors",
			Can("look").And("looking").RequiresNothing(),
			To("look",
				// func( g G.Play) { ReflectToLocation(g,"report the view") }
				// reflect to location will send the actor as a parameter,
				// but report the view doesnt expect parameters.
				func(g G.Play) {
					actor := g.The("actor")
					target := actor.Object("whereabouts")
					target.Go("report the view")
				}),
		)

		// one visible thing and requiring light.
		s.The("actors",
			Can("look under it").And("looking under it").RequiresOne("object"),
			To("look under it", func(g G.Play) { ReflectToTarget(g, "report look under") }),
		)

		// FIX: should generate a report/response instead?
		s.The("actors",
			Can("impress").And("impressing").RequiresNothing(),
			To("impress", func(g G.Play) {
				g.Say(lang.Capitalize(ArticleName(g, "actor", nil)), "is unimpressed.")
			}))

		// "taking inventory" in inform
		// again, as with some other actions: for players this happens in carry out, for npcs in report.
		// i'm sure that's useful... somehow....
		s.The("actors",
			Can("report inventory").And("reporting inventory").RequiresNothing(),
			To("report inventory", func(g G.Play) {
				this := g.The("actor")
				source := []string{"Clothing", "Inventory"}
				for _, s := range source {
					contents := this.ObjectList(s)
					if len(contents) > 0 {
						g.Say(s + ":")
						for _, obj := range contents {
							obj.Go("print name")
						}
					} else {
						g.Say(s, "(none).")
					}
				}
			}),
		)

		// FIX: for some reason, the order must be biggest match to smallest, the other way doesnt work.
		s.Execute("report inventory", Matching("inventory|inv|i"))
		s.Execute("look", Matching("look|l"))
		s.Execute("look under it", Matching("look under {{something}}"))
	})
}

//
// when is the right time for functions versus callbacks?
func ListContents(g G.Play, header string, obj G.IObject) (printed bool) {
	// if something described which is not scenery is on the noun and something which is not the player is on the noun:
	// obviously a filterd callback, visitor, would be nice FilterList("contents", func() ... )
	// FIX: if something has scenery objets, they appear as contents,
	// but then the list is empty. ( ex. lab coat, but it might happen elsewhere )
	// we'd maybe need to know if something printed?
	if contents := obj.ObjectList("contents"); len(contents) > 0 {
		g.Say(header, obj.Text("Name"), "is:")
		for _, content := range contents {
			content.Go("print description")
		}
		printed = true
	}
	return printed
}

func NameFullStop(G.IObject) string {
	return ""
}

type NameStatus func(obj G.IObject) string

func ArticleName(g G.Play, which string, status NameStatus) string {
	return articleName(g, which, false, status)
}
func DefiniteName(g G.Play, which string, status NameStatus) string {
	return articleName(g, which, true, status)
}
func articleName(g G.Play, which string, definite bool, status NameStatus) string {
	obj := g.The(which)
	text := obj.Text("Name")
	if obj.Is("proper-named") {
		text = lang.Titleize(text)
	} else {
		article := ""
		if definite {
			article = "the"
		} else {
			article = obj.Text("indefinite article")
			if article == "" {
				if obj.Is("plural-named") {
					article = "some"
				} else if lang.StartsWithVowel(text) {
					article = "an"
				} else {
					article = "a"
				}
			}
		}
		text = strings.Join([]string{article, strings.ToLower(text)}, " ")
	}
	if status != nil {
		if s := status(obj); s != "" {
			text = fmt.Sprintf("%s (%s)", text, s)
		} else {
			text = text + "."
		}
	}
	return text
}

//
// System actions
func init() {
	AddScript(func(s *Script) {

		// inform has two entries for some actions (looking under as an example, jumping as a counter example):
		// 1. carry out an actor looking under: if the player
		// 2. report an actor looking under: if not the player
		// its not exactly clear to me why, the docs give guidelines for this, but not rationale.
		// it might be interesting to queue says, if they need to be cancelled or held back.
		// keep in mind, most of these really want to be animations, and only sometimes voice.
		s.The("objects",
			Can("report look under").And("reporting look under").RequiresOne("actor"),
			To("report look under", func(g G.Play) {
				source, actor := g.The("action.Source"), g.The("action.Target")
				if g.The("player") == actor {
					g.Say("You find nothing of interest.")
				} else {
					g.Say(actor.Text("Name"), "looks under the", source.Text("Name"), ".")
				}
			}))

		s.The("objects",
			Can("print name").And("printing name text").RequiresNothing(),
			To("print name", func(g G.Play) {
				if text := ArticleName(g, "object", NameFullStop); len(text) > 0 {
					text = lang.Capitalize(text)
					g.Say(text)
				}
			}))

		s.The("containers",
			When("printing name text").
				Always(func(g G.Play) {
				// FIX: conditional return instead of Always?
				// or some way ( dependency injection ) to get at the event object
				// of course, rules producing values and stacks might work too.
				// FIX: a container is an opener... where do we print the opener status name
				// put this on doors for now.
				text := ArticleName(g, "container", func(obj G.IObject) (status string) {
					if obj.Is("closed") {
						if obj.Is("hinged") {
							status = "closed"
						}
					} else if list := obj.ObjectList("contents"); len(list) == 0 {
						if obj.Is("transparent") || obj.Is("open") {
							status = "empty"
						}
					}
					return status
				})
				text = lang.Capitalize(text)
				g.Say(text)
				g.StopHere()
			}))

		s.The("doors",
			When("printing name text").
				Always(func(g G.Play) {
				text := ArticleName(g, "door", func(obj G.IObject) (status string) {
					if obj.Is("hinged") {
						if obj.Is("open") {
							status = "open"
						} else {
							status = "closed"
						}
					}
					return status
				})
				text = lang.Capitalize(text)
				g.Say(text)
				g.StopHere()
			}))

		s.The("rooms",
			Can("report the view").And("reporting the view").RequiresNothing(),
			After("reporting the view").Always(func(g G.Play) {
				room := g.The("room")
				room.IsNow("visited")
			}),
			To("report the view", func(g G.Play) {
				room := g.The("room")
				// sometines a blank like is printed without this
				// (maybe certain directions? or going through doors?)
				// not sure why, so leaving this for consistency
				g.Say(Lines("", room.Text("Name")))
				g.Say(Lines(room.Text("description"), ""))
				// FIX? uses 1 to exclude the player....
				// again, this happens because we dont know if print description actually did anything (re:scenery, etc.)
				if contents := room.ObjectList("contents"); len(contents) > 1 {
					for _, obj := range contents {
						obj.Go("print description")
						g.Say("")
					}
				}
				// FIX: duplicated in stories describe the first room
				g.The("status bar").SetText("left", lang.Titleize(room.Text("Name")))
			}))
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	logging "github.com/op/go-logging"
	"koding/tools/logger"
)

var (
	log *logging.Logger
)

func init() {
	log = logger.CreateLogger("PostMigrator", "debug")
}

func GetMigrationCompletedReport(m *Migrator) {
	result := fmt.Sprintf("Migration Completed for \"%s\"", m.PostType)
	result = fmt.Sprintf("%s with %d success", result, m.SuccessCount)
	result = fmt.Sprintf("%s and %d error", result, m.ErrorCount)
	totalCount := m.ErrorCount + m.SuccessCount
	result = fmt.Sprintf("%s with total of %d documents", result, totalCount)
	log.Notice(result)
	m.ResetCounters()
}

func ReportSuccess(m *Migrator) {
	log.Info("%d. %s Post Migrated", m.Index, m.Id)
	m.AddSuccessReport()
	if err := publish(*m); err != nil {
		log.Error(err.Error())
	}
}

func ReportError(m *Migrator, err error) {
	log.Error("%d. %s received error: %s", m.Index, m.Id, err.Error())
	m.AddErrorReport(err)
	// do not send already migrated post info to queue
	if err == ErrAlreadyMigrated {
		return
	}
	if err := publish(*m); err != nil {
		log.Error(err.Error())
	}
}
<commit_msg>Migrator: style issue<commit_after>package main

import (
	"fmt"
	"koding/tools/logger"

	logging "github.com/op/go-logging"
)

var (
	log *logging.Logger
)

func init() {
	log = logger.CreateLogger("PostMigrator", "debug")
}

func GetMigrationCompletedReport(m *Migrator) {
	result := fmt.Sprintf("Migration Completed for \"%s\"", m.PostType)
	result = fmt.Sprintf("%s with %d success", result, m.SuccessCount)
	result = fmt.Sprintf("%s and %d error", result, m.ErrorCount)
	totalCount := m.ErrorCount + m.SuccessCount
	result = fmt.Sprintf("%s with total of %d documents", result, totalCount)
	log.Notice(result)
	m.ResetCounters()
}

func ReportSuccess(m *Migrator) {
	log.Info("%d. %s Post Migrated", m.Index, m.Id)
	m.AddSuccessReport()
	if err := publish(*m); err != nil {
		log.Error(err.Error())
	}
}

func ReportError(m *Migrator, err error) {
	log.Error("%d. %s received error: %s", m.Index, m.Id, err.Error())
	m.AddErrorReport(err)
	// do not send already migrated post info to queue
	if err == ErrAlreadyMigrated {
		return
	}
	if err := publish(*m); err != nil {
		log.Error(err.Error())
	}
}
<|endoftext|>