<commit_before>/*
Copyright (c) 2016 EliteSystems. All rights reserved.

eliteConfiguration lets you Load/Save Configuration from/to files with JSON content.
*/
package eliteConfiguration

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"path"
	"path/filepath"
)

/*
RootPathKey is the Key to access the Configuration's RootPath
*/
const (
	RootPathKey = "RootPath"
)

/*
Configuration is the main packages's interface used to manipulate configurations structs
with a Name, a set of Values accessed by their Name and a Size
*/
type Configuration interface {
	Name() string
	SetName(name string) Configuration
	Value(name string) (interface{}, error)
	Add(name string, value interface{}) Configuration
	Remove(name string) Configuration
	Size() int
	Property(name string) Property
	HasProperty(name string) bool
	newProperty(name string, value interface{}) Property
	properties() map[string]Property
	Default(value interface{}) Configuration
}

/*
Property is the interface used to manipulate configurations 'properties'
with access to their Name and Value
*/
type Property interface {
	Name() string
	Value() interface{}
}

/*
New return a new default (immutable) Configuration with the required Name
*/
func New(requiredName string) Configuration {

	configuration := immutableConfiguration{iName: requiredName}
	return configuration
}

/*
New return a new mutable Configuration with the required Name
*/
func NewMutable(requiredName string) Configuration {

	configuration := &mutableConfiguration{iName: requiredName}
	return configuration
}

/*
newFromJSON return a new mutableConfiguration from the jsonContent
*/
func newFromJSON(jsonContent []byte) (configuration marshallableConfiguration, messageError error) {

	// Deserialize JSON content into Configuration struct
	if err := json.Unmarshal(jsonContent, &configuration); err != nil {
		messageError = newError("eliteConfiguration.newFromJSON()", err)
	}
	return
}

/*
Load fileName with valid JSON Content into a returned immutable Configuration
*/
func Load(fileName string) (Configuration, error) {

	// Read fileName
	jsonContent, err := ioutil.ReadFile(filepath.FromSlash(fileName))
	if err != nil {
		return nil, newError("ioutil.ReadFile("+fileName+")", err)
	}

	// Create new immutableConfiguration
	configuration, messageError := newFromJSON(jsonContent)
	if messageError == nil {
		var returnConfiguration Configuration = immutableConfiguration{iName: configuration.NameAttr}
		if configuration.PropertiesAttr != nil {
			for key, value := range configuration.PropertiesAttr {
				returnConfiguration = returnConfiguration.Add(key, value.ValueAttr)
			}
		}
		// Add/Replace RootPath to configuration
		return returnConfiguration.Add(RootPathKey, path.Dir(fileName)), nil
	}

	return nil, messageError
}

/*
Save a Configuration to fileName in indented JSON format
*/
func Save(configuration Configuration, fileName string) error {

	// Serialize Configuration struct to JSON
	jsonContent, messageError := toJSON(configuration)

	if messageError == nil {

		// Indent JSON content for better readability
		var jsonIndentedContent bytes.Buffer
		if err := json.Indent(&jsonIndentedContent, jsonContent, "", "  "); err != nil {
			messageError = newError("json.Indent()", err)
		}

		// Write JSON content to fileName
		if err := ioutil.WriteFile(filepath.FromSlash(fileName), jsonIndentedContent.Bytes(), 0600); err != nil {
			messageError = newError("ioutil.WriteFile("+fileName+")", err)
		}
	}

	return messageError
}

/*
toJSON return JSON's content from the Configuration
*/
func toJSON(configuration Configuration) ([]byte, error) {

	var messageError error
	jsonContent, err := json.Marshal(toMutable(configuration))
	if err != nil {
		messageError = newError("Configuration.toJSON()", err)
	}
	return jsonContent, messageError
}

func toMutable(configuration Configuration) marshallableConfiguration {

	returnConfiguration := marshallableConfiguration{NameAttr: configuration.Name(), PropertiesAttr: make(map[string]marshallableProperty)}
	if configuration.properties() != nil {
		for key, value := range configuration.properties() {
			returnConfiguration.PropertiesAttr[key] = marshallableProperty{NameAttr: value.Name(), ValueAttr: value.Value()}
		}
	}

	return returnConfiguration
}

/*
newError return a new configurationError with required message and optional cause
*/
func newError(requiredMessage string, optionalCause error) error {

	return configurationError{message: requiredMessage, cause: optionalCause}
}
<commit_msg>eliteConfiguration - MutableConfiguration<commit_after>/*
Copyright (c) 2016 EliteSystems. All rights reserved.

eliteConfiguration lets you Load/Save Configuration from/to files with JSON content.
*/
package eliteConfiguration

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"path"
	"path/filepath"
)

/*
RootPathKey is the Key to access the Configuration's RootPath
*/
const (
	RootPathKey = "RootPath"
)

/*
Configuration is the main packages's interface used to manipulate configurations structs
with a Name, a set of Values accessed by their Name and a Size
*/
type Configuration interface {
	Name() string
	SetName(name string) Configuration
	Value(name string) (interface{}, error)
	Add(name string, value interface{}) Configuration
	Remove(name string) Configuration
	Size() int
	Property(name string) Property
	HasProperty(name string) bool
	newProperty(name string, value interface{}) Property
	properties() map[string]Property
	Default(value interface{}) Configuration
}

/*
Property is the interface used to manipulate configurations 'properties'
with access to their Name and Value
*/
type Property interface {
	Name() string
	Value() interface{}
}

/*
New return a new default (immutable) Configuration with the required Name
*/
func New(requiredName string) Configuration {

	configuration := immutableConfiguration{iName: requiredName}
	return configuration
}

/*
New return a new mutable Configuration with the required Name
*/
func NewMutable(requiredName string) Configuration {

	configuration := &mutableConfiguration{iName: requiredName}
	return configuration
}

/*
newFromJSON return a new mutableConfiguration from the jsonContent
*/
func newFromJSON(jsonContent []byte) (configuration marshallableConfiguration, messageError error) {

	// Deserialize JSON content into Configuration struct
	if err := json.Unmarshal(jsonContent, &configuration); err != nil {
		messageError = newError("eliteConfiguration.newFromJSON()", err)
	}
	return
}

/*
Load fileName with valid JSON Content into a returned immutable Configuration
*/
func Load(fileName string) (Configuration, error) {

	jsonContent, err := readFile(fileName)
	if err != nil {
		return nil, err
	}

	// Get marshallableConfiguration from JSON
	configuration, messageError := newFromJSON(jsonContent)

	// Create new immutableConfiguration
	if messageError == nil {
		var returnConfiguration Configuration = New(configuration.NameAttr)
		if configuration.PropertiesAttr != nil {
			for key, value := range configuration.PropertiesAttr {
				returnConfiguration = returnConfiguration.Add(key, value.ValueAttr)
			}
		}
		// Add/Replace RootPath to configuration
		return returnConfiguration.Add(RootPathKey, path.Dir(fileName)), nil
	}

	return nil, messageError
}

/*
Save a Configuration to fileName in indented JSON format
*/
func Save(configuration Configuration, fileName string) error {

	// Serialize Configuration struct to JSON
	jsonContent, messageError := toJSON(configuration)

	if messageError == nil {

		// Indent JSON content for better readability
		var jsonIndentedContent bytes.Buffer
		if err := json.Indent(&jsonIndentedContent, jsonContent, "", "  "); err != nil {
			messageError = newError("json.Indent()", err)
		}

		// Write JSON content to fileName
		if err := ioutil.WriteFile(filepath.FromSlash(fileName), jsonIndentedContent.Bytes(), 0600); err != nil {
			messageError = newError("ioutil.WriteFile("+fileName+")", err)
		}
	}

	return messageError
}

/*
toJSON return JSON's content from the Configuration
*/
func toJSON(configuration Configuration) ([]byte, error) {

	var messageError error
	jsonContent, err := json.Marshal(toMarshallable(configuration))
	if err != nil {
		messageError = newError("Configuration.toJSON()", err)
	}
	return jsonContent, messageError
}

func toMarshallable(configuration Configuration) marshallableConfiguration {

	returnConfiguration := marshallableConfiguration{NameAttr: configuration.Name(), PropertiesAttr: make(map[string]marshallableProperty)}
	if configuration.properties() != nil {
		for key, value := range configuration.properties() {
			returnConfiguration.PropertiesAttr[key] = marshallableProperty{NameAttr: value.Name(), ValueAttr: value.Value()}
		}
	}

	return returnConfiguration
}

/*
newError return a new configurationError with required message and optional cause
*/
func newError(requiredMessage string, optionalCause error) error {

	return configurationError{message: requiredMessage, cause: optionalCause}
}

/*
readFile is an internal method to read the fileName content
*/
func readFile(fileName string) ([]byte, error) {

	fileContent, err := ioutil.ReadFile(filepath.FromSlash(fileName))
	if err != nil {
		return nil, newError("ioutil.ReadFile("+fileName+")", err)
	}
	return fileContent, nil
}
<|endoftext|>


<commit_before>// app contains the interfaces and structures for application type
// implementations for Otto. Applications are the components that
// know how to dev, build, and deploy a certain kind of application such
// as Rails, PHP, etc.
//
// All app implementations are built specific to a certain 3-tuple:
// (app type, infra type, infra flavor). For example:
// (rails, aws, vpc-public-private). The app implementation then only
// needs to know how to satisfy that specific 3-tuple.
//
// When building app plugins, it is possible for that plugin to support
// multiple matrix elements, but each implementation of the interface
// is expeced to only implement one.
package app

import (
	"github.com/hashicorp/otto/appfile"
	"github.com/hashicorp/otto/context"
)

// App is the interface that must be implemented by each
// (app type, infra type, infra flavor) 3-tuple.
type App interface {
	// Compile is called to compile the files that are used to manage
	// this application.
	Compile(*Context) (*CompileResult, error)

	// Build is called to build the deployable artifact for this
	// application.
	Build(*Context) error

	// Deploy is called to deploy this application. The existence of
	// a prior build artifact is confirmed before this is called.
	Deploy(*Context) error

	// Dev should manage a development environment for this app
	// type. This is called for the local, mutable dev environment
	// where this application is the main thing under development.
	Dev(*Context) error

	// DevDep is called when this app is an upstream dependency
	// of another application that is being developed. This app should
	// build itself for development, and configure the Vagrantfile so
	// that this dependency starts properly on boot.
	//
	// DevDep is given two contexts. The first is the destination
	// app (the one being developed), and the second is the source
	// app (this one that is an upstream dep).
	//
	// The results of this call are cached to speed up development
	// of the destination app until there is a change, which is detected
	// based on VCS.
	DevDep(dst *Context, src *Context) (*DevDep, error)
}

// Context is the context for operations on applications. Some of the
// fields in this struct are only available for certain operations.
type Context struct {
	context.Shared

	// Action is the sub-action to take when being executed.
	//
	// ActionArgs is the list of arguments for this action.
	//
	// Both of these fields will only be set for the Execute call.
	Action     string
	ActionArgs []string

	// Dir is the directory that the compilation is allowed to write to
	// for persistant storage of data that is available during task
	// execution. For tasks, this will be the directory that compilation
	// wrote to. Whenever a compilation is done, this directory is
	// cleared. Data that should be persistant across compilations should
	// be stored in the directory service.
	Dir string

	// CacheDir is the directory where data can be cached. This data
	// will persist across compiles of the same version of an Appfile.
	//
	// The App implementation should function under the assumption that
	// this cache directory can be cleared at any time between runs.
	CacheDir string

	// Tuple is the Tuple that identifies this application. This can be
	// used so that an implementatin of App can work with multiple tuple
	// types.
	Tuple Tuple

	// Appfile is the full appfile
	Appfile *appfile.File

	// Application is the application configuration itself from the appfile.
	Application *appfile.Application

	// DevDepFragments will be populated with the list of dev dep
	// Vagrantfile fragment paths. This will only be available in the Compile
	// call.
	DevDepFragments []string
}

// CompileResult is the structure containing compilation result values.
type CompileResult struct {
	// DevDepFragmentPath is the path to the Vagrantfile fragment that
	// should be added to other Vagrantfiles when this application is
	// used as a dependency.
	DevDepFragmentPath string
}
<commit_msg>app: update docs for DevDep<commit_after>// app contains the interfaces and structures for application type
// implementations for Otto. Applications are the components that
// know how to dev, build, and deploy a certain kind of application such
// as Rails, PHP, etc.
//
// All app implementations are built specific to a certain 3-tuple:
// (app type, infra type, infra flavor). For example:
// (rails, aws, vpc-public-private). The app implementation then only
// needs to know how to satisfy that specific 3-tuple.
//
// When building app plugins, it is possible for that plugin to support
// multiple matrix elements, but each implementation of the interface
// is expeced to only implement one.
package app

import (
	"github.com/hashicorp/otto/appfile"
	"github.com/hashicorp/otto/context"
)

// App is the interface that must be implemented by each
// (app type, infra type, infra flavor) 3-tuple.
type App interface {
	// Compile is called to compile the files that are used to manage
	// this application.
	Compile(*Context) (*CompileResult, error)

	// Build is called to build the deployable artifact for this
	// application.
	Build(*Context) error

	// Deploy is called to deploy this application. The existence of
	// a prior build artifact is confirmed before this is called.
	Deploy(*Context) error

	// Dev should manage a development environment for this app
	// type. This is called for the local, mutable dev environment
	// where this application is the main thing under development.
	Dev(*Context) error

	// DevDep is called when this app is an upstream dependency
	// of another application that is being developed. This app should
	// build itself for development, and configure the Vagrantfile so
	// that this dependency starts properly on boot.
	//
	// DevDep is given two contexts. The first is the destination
	// app (the one being developed), and the second is the source
	// app (this one that is an upstream dep).
	//
	// The results of this call are cached to speed up development
	// of the destination app until there is a change, which is detected
	// based on VCS.
	//
	// The resulting DevDep can be nil if nothing needs to be done that
	// is part of the DevDep structure. Any DevDepFragments from the
	// compilation will still be used, of course.
	DevDep(dst *Context, src *Context) (*DevDep, error)
}

// Context is the context for operations on applications. Some of the
// fields in this struct are only available for certain operations.
type Context struct {
	context.Shared

	// Action is the sub-action to take when being executed.
	//
	// ActionArgs is the list of arguments for this action.
	//
	// Both of these fields will only be set for the Execute call.
	Action     string
	ActionArgs []string

	// Dir is the directory that the compilation is allowed to write to
	// for persistant storage of data that is available during task
	// execution. For tasks, this will be the directory that compilation
	// wrote to. Whenever a compilation is done, this directory is
	// cleared. Data that should be persistant across compilations should
	// be stored in the directory service.
	Dir string

	// CacheDir is the directory where data can be cached. This data
	// will persist across compiles of the same version of an Appfile.
	//
	// The App implementation should function under the assumption that
	// this cache directory can be cleared at any time between runs.
	CacheDir string

	// Tuple is the Tuple that identifies this application. This can be
	// used so that an implementatin of App can work with multiple tuple
	// types.
	Tuple Tuple

	// Appfile is the full appfile
	Appfile *appfile.File

	// Application is the application configuration itself from the appfile.
	Application *appfile.Application

	// DevDepFragments will be populated with the list of dev dep
	// Vagrantfile fragment paths. This will only be available in the Compile
	// call.
	DevDepFragments []string
}

// CompileResult is the structure containing compilation result values.
type CompileResult struct {
	// DevDepFragmentPath is the path to the Vagrantfile fragment that
	// should be added to other Vagrantfiles when this application is
	// used as a dependency.
	DevDepFragmentPath string
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package app 核心功能的实现
package app

import (
	"context"
	"errors"
	"net/http"
	"os"
	"os/signal"
	"strconv"

	"github.com/issue9/logs/v2"
	"github.com/issue9/middleware/compress"
	"github.com/issue9/middleware/recovery/errorhandler"
	"golang.org/x/text/language"
	"golang.org/x/text/message"

	"github.com/issue9/web/config"
	"github.com/issue9/web/internal/messages"
	"github.com/issue9/web/internal/webconfig"
	"github.com/issue9/web/mimetype"
	"github.com/issue9/web/module"
)

// 框加需要用到的配置文件名。
// 实际路径需要通过 App.File 获取。
const (
	ConfigFilename = "web.yaml"
	LogsFilename   = "logs.xml"
)

// App 程序运行实例
type App struct {
	*module.Modules

	webConfig     *webconfig.WebConfig
	server        *http.Server
	configs       *config.Manager
	logs          *logs.Logs
	errorhandlers *errorhandler.ErrorHandler
	mt            *mimetype.Mimetypes
	messages      *messages.Messages
	compresses    map[string]compress.WriterFunc

	// 当 shutdown 延时关闭时，通过此事件确定 Serve() 的返回时机。
	closed chan struct{}
}

// New 声明一个新的 App 实例
//
// 日志系统会在此处初始化。
func New(mgr *config.Manager) (*App, error) {
	logs := logs.New()
	if err := logs.InitFromXMLFile(mgr.File(LogsFilename)); err != nil {
		return nil, err
	}

	webconf := &webconfig.WebConfig{}
	if err := mgr.LoadFile(ConfigFilename, webconf); err != nil {
		if serr, ok := err.(*config.Error); ok {
			serr.File = LogsFilename
		}
		return nil, err
	}

	ms, err := module.NewModules(webconf)
	if err != nil {
		return nil, err
	}

	app := &App{
		Modules:       ms,
		webConfig:     webconf,
		closed:        make(chan struct{}, 1),
		mt:            mimetype.New(),
		configs:       mgr,
		logs:          logs,
		errorhandlers: errorhandler.New(),
		messages:      messages.New(),
		compresses:    make(map[string]compress.WriterFunc, 5),
		server: &http.Server{
			Addr:              ":" + strconv.Itoa(webconf.Port),
			ErrorLog:          logs.ERROR(),
			ReadTimeout:       webconf.ReadTimeout,
			WriteTimeout:      webconf.WriteTimeout,
			IdleTimeout:       webconf.IdleTimeout,
			ReadHeaderTimeout: webconf.ReadHeaderTimeout,
			MaxHeaderBytes:    webconf.MaxHeaderBytes,
		},
	}
	app.server.Handler = app

	// 加载固有的中间件，需要在 ms 初始化之后调用
	app.buildMiddlewares(webconf)

	return app, nil
}

// AddCompresses 添加压缩处理函数
func (app *App) AddCompresses(m map[string]compress.WriterFunc) error {
	for k, v := range m {
		if _, found := app.compresses[k]; found {
			return errors.New("已经存在")
		}

		app.compresses[k] = v
	}

	return nil
}

// IsDebug 是否处于调试模式
func (app *App) IsDebug() bool {
	return app.webConfig.Debug
}

// Path 获取中径部分的地址
//
// 基于 app.webConfig.URL 中的路径部分。
func (app *App) Path(p string) string {
	p = app.webConfig.URLPath + p
	if p != "" && p[0] != '/' {
		p = "/" + p
	}

	return p
}

// URL 构建一条基于 app.webconfig.URL 的完整 URL
func (app *App) URL(path string) string {
	if len(path) == 0 {
		return app.webConfig.URL
	}

	if path[0] != '/' {
		path = "/" + path
	}
	return app.webConfig.URL + path
}

// Serve 加载各个模块的数据，运行路由，执行监听程序。
//
// 当调用 Shutdown 关闭服务时，会等待其完成未完的服务，才返回 http.ErrServerClosed
func (app *App) Serve() (err error) {
	conf := app.webConfig

	if !conf.HTTPS {
		err = app.server.ListenAndServe()
	} else {
		err = app.server.ListenAndServeTLS(conf.CertFile, conf.KeyFile)
	}

	// 由 Shutdown() 或 Close() 主动触发的关闭事件，才需要等待其执行完成，
	// 其它错误直接返回，否则一些内部错误会永远卡在此处无法返回。
	if err == http.ErrServerClosed {
		<-app.closed
	}
	return err
}

// LocalPrinter 获取本地化的输出对象
func (app *App) LocalPrinter(tag language.Tag, opts ...message.Option) *message.Printer {
	return message.NewPrinter(tag, opts...)
}

// Close 关闭服务。
//
// 无论配置文件如果设置，此函数都是直接关闭服务，不会等待。
func (app *App) Close() error {
	app.Stop() // 关闭服务

	defer func() {
		app.closed <- struct{}{}
	}()
	return app.server.Close()
}

// Shutdown 关闭所有服务。
//
// 根据配置文件中的配置项，决定当前是直接关闭还是延时之后关闭。
func (app *App) Shutdown() error {
	if app.webConfig.ShutdownTimeout <= 0 {
		return app.Close()
	}

	app.Stop() // 关闭服务
	ctx, cancel := context.WithTimeout(context.Background(), app.webConfig.ShutdownTimeout)
	defer func() {
		cancel()
		app.closed <- struct{}{}
	}()
	return app.server.Shutdown(ctx)
}

// Server 获取 http.Server 实例
func (app *App) Server() *http.Server {
	return app.server
}

// Mimetypes 返回 mimetype.Mimetypes
func (app *App) Mimetypes() *mimetype.Mimetypes {
	return app.mt
}

// Config 获取 config.Manager 的实例
func (app *App) Config() *config.Manager {
	return app.configs
}

// ErrorHandlers 错误处理功能
func (app *App) ErrorHandlers() *errorhandler.ErrorHandler {
	return app.errorhandlers
}

// Messages 返回 messages.Messages 实例
func (app *App) Messages() *messages.Messages {
	return app.messages
}

// Logs 获取 logs.Logs 实例
func (app *App) Logs() *logs.Logs {
	return app.logs
}

// Grace 指定触发 Shutdown() 的信号，若为空，则任意信号都触发。
//
// 多次调用，则每次指定的信号都会起作用，如果由传递了相同的值，
// 则有可能多次触发 Shutdown()。
//
// NOTE: 传递空值，与不调用，其结果是不同的。
// 若是不调用，则不会处理任何信号；若是传递空值调用，则是处理任何要信号。
func Grace(app *App, sig ...os.Signal) {
	go func() {
		signalChannel := make(chan os.Signal)
		signal.Notify(signalChannel, sig...)

		<-signalChannel
		signal.Stop(signalChannel)

		if err := app.Shutdown(); err != nil {
			app.Logs().Error(err)
		}
		app.Logs().Flush() // 保证内容会被正常输出到日志。
		close(signalChannel)
	}()
}
<commit_msg>[app] 调整文件名取值错误<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package app 核心功能的实现
package app

import (
	"context"
	"errors"
	"net/http"
	"os"
	"os/signal"
	"strconv"

	"github.com/issue9/logs/v2"
	"github.com/issue9/middleware/compress"
	"github.com/issue9/middleware/recovery/errorhandler"
	"golang.org/x/text/language"
	"golang.org/x/text/message"

	"github.com/issue9/web/config"
	"github.com/issue9/web/internal/messages"
	"github.com/issue9/web/internal/webconfig"
	"github.com/issue9/web/mimetype"
	"github.com/issue9/web/module"
)

// 框加需要用到的配置文件名。
// 实际路径需要通过 App.File 获取。
const (
	ConfigFilename = "web.yaml"
	LogsFilename   = "logs.xml"
)

// App 程序运行实例
type App struct {
	*module.Modules

	webConfig     *webconfig.WebConfig
	server        *http.Server
	configs       *config.Manager
	logs          *logs.Logs
	errorhandlers *errorhandler.ErrorHandler
	mt            *mimetype.Mimetypes
	messages      *messages.Messages
	compresses    map[string]compress.WriterFunc

	// 当 shutdown 延时关闭时，通过此事件确定 Serve() 的返回时机。
	closed chan struct{}
}

// New 声明一个新的 App 实例
//
// 日志系统会在此处初始化。
func New(mgr *config.Manager) (*App, error) {
	logs := logs.New()
	if err := logs.InitFromXMLFile(mgr.File(LogsFilename)); err != nil {
		return nil, err
	}

	webconf := &webconfig.WebConfig{}
	if err := mgr.LoadFile(ConfigFilename, webconf); err != nil {
		if serr, ok := err.(*config.Error); ok {
			serr.File = ConfigFilename
		}
		return nil, err
	}

	ms, err := module.NewModules(webconf)
	if err != nil {
		return nil, err
	}

	app := &App{
		Modules:       ms,
		webConfig:     webconf,
		closed:        make(chan struct{}, 1),
		mt:            mimetype.New(),
		configs:       mgr,
		logs:          logs,
		errorhandlers: errorhandler.New(),
		messages:      messages.New(),
		compresses:    make(map[string]compress.WriterFunc, 5),
		server: &http.Server{
			Addr:              ":" + strconv.Itoa(webconf.Port),
			ErrorLog:          logs.ERROR(),
			ReadTimeout:       webconf.ReadTimeout,
			WriteTimeout:      webconf.WriteTimeout,
			IdleTimeout:       webconf.IdleTimeout,
			ReadHeaderTimeout: webconf.ReadHeaderTimeout,
			MaxHeaderBytes:    webconf.MaxHeaderBytes,
		},
	}
	app.server.Handler = app

	// 加载固有的中间件，需要在 ms 初始化之后调用
	app.buildMiddlewares(webconf)

	return app, nil
}

// AddCompresses 添加压缩处理函数
func (app *App) AddCompresses(m map[string]compress.WriterFunc) error {
	for k, v := range m {
		if _, found := app.compresses[k]; found {
			return errors.New("已经存在")
		}

		app.compresses[k] = v
	}

	return nil
}

// IsDebug 是否处于调试模式
func (app *App) IsDebug() bool {
	return app.webConfig.Debug
}

// Path 获取中径部分的地址
//
// 基于 app.webConfig.URL 中的路径部分。
func (app *App) Path(p string) string {
	p = app.webConfig.URLPath + p
	if p != "" && p[0] != '/' {
		p = "/" + p
	}

	return p
}

// URL 构建一条基于 app.webconfig.URL 的完整 URL
func (app *App) URL(path string) string {
	if len(path) == 0 {
		return app.webConfig.URL
	}

	if path[0] != '/' {
		path = "/" + path
	}
	return app.webConfig.URL + path
}

// Serve 加载各个模块的数据，运行路由，执行监听程序。
//
// 当调用 Shutdown 关闭服务时，会等待其完成未完的服务，才返回 http.ErrServerClosed
func (app *App) Serve() (err error) {
	conf := app.webConfig

	if !conf.HTTPS {
		err = app.server.ListenAndServe()
	} else {
		err = app.server.ListenAndServeTLS(conf.CertFile, conf.KeyFile)
	}

	// 由 Shutdown() 或 Close() 主动触发的关闭事件，才需要等待其执行完成，
	// 其它错误直接返回，否则一些内部错误会永远卡在此处无法返回。
	if err == http.ErrServerClosed {
		<-app.closed
	}
	return err
}

// LocalPrinter 获取本地化的输出对象
func (app *App) LocalPrinter(tag language.Tag, opts ...message.Option) *message.Printer {
	return message.NewPrinter(tag, opts...)
}

// Close 关闭服务。
//
// 无论配置文件如果设置，此函数都是直接关闭服务，不会等待。
func (app *App) Close() error {
	app.Stop() // 关闭服务

	defer func() {
		app.closed <- struct{}{}
	}()
	return app.server.Close()
}

// Shutdown 关闭所有服务。
//
// 根据配置文件中的配置项，决定当前是直接关闭还是延时之后关闭。
func (app *App) Shutdown() error {
	if app.webConfig.ShutdownTimeout <= 0 {
		return app.Close()
	}

	app.Stop() // 关闭服务
	ctx, cancel := context.WithTimeout(context.Background(), app.webConfig.ShutdownTimeout)
	defer func() {
		cancel()
		app.closed <- struct{}{}
	}()
	return app.server.Shutdown(ctx)
}

// Server 获取 http.Server 实例
func (app *App) Server() *http.Server {
	return app.server
}

// Mimetypes 返回 mimetype.Mimetypes
func (app *App) Mimetypes() *mimetype.Mimetypes {
	return app.mt
}

// Config 获取 config.Manager 的实例
func (app *App) Config() *config.Manager {
	return app.configs
}

// ErrorHandlers 错误处理功能
func (app *App) ErrorHandlers() *errorhandler.ErrorHandler {
	return app.errorhandlers
}

// Messages 返回 messages.Messages 实例
func (app *App) Messages() *messages.Messages {
	return app.messages
}

// Logs 获取 logs.Logs 实例
func (app *App) Logs() *logs.Logs {
	return app.logs
}

// Grace 指定触发 Shutdown() 的信号，若为空，则任意信号都触发。
//
// 多次调用，则每次指定的信号都会起作用，如果由传递了相同的值，
// 则有可能多次触发 Shutdown()。
//
// NOTE: 传递空值，与不调用，其结果是不同的。
// 若是不调用，则不会处理任何信号；若是传递空值调用，则是处理任何要信号。
func Grace(app *App, sig ...os.Signal) {
	go func() {
		signalChannel := make(chan os.Signal)
		signal.Notify(signalChannel, sig...)

		<-signalChannel
		signal.Stop(signalChannel)

		if err := app.Shutdown(); err != nil {
			app.Logs().Error(err)
		}
		app.Logs().Flush() // 保证内容会被正常输出到日志。
		close(signalChannel)
	}()
}
<|endoftext|>


<commit_before>// Package app is the "runtime" for the ps-top / ps-stats application packages
//
// This file contains the library routines related to running the app.
package app

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/sjmudd/anonymiser"
	"github.com/sjmudd/ps-top/connector"
	"github.com/sjmudd/ps-top/context"
	"github.com/sjmudd/ps-top/display"
	"github.com/sjmudd/ps-top/event"
	fsbi "github.com/sjmudd/ps-top/file_io_latency"
	"github.com/sjmudd/ps-top/global"
	"github.com/sjmudd/ps-top/lib"
	"github.com/sjmudd/ps-top/logger"
	"github.com/sjmudd/ps-top/memory_usage"
	ewsgben "github.com/sjmudd/ps-top/mutex_latency"
	"github.com/sjmudd/ps-top/p_s/ps_table"
	"github.com/sjmudd/ps-top/setup_instruments"
	essgben "github.com/sjmudd/ps-top/stages_latency"
	tiwsbt "github.com/sjmudd/ps-top/table_io_latency"
	tlwsbt "github.com/sjmudd/ps-top/table_lock_latency"
	"github.com/sjmudd/ps-top/user_latency"
	"github.com/sjmudd/ps-top/view"
	"github.com/sjmudd/ps-top/wait_info"
)

const (
	hostname = "HOSTNAME"
	uptime   = "UPTIME"
	version  = "VERSION"
)

// Flags for initialising the app
type Settings struct {
	Anonymise bool
	Conn      *connector.Connector
	Interval  int
	Count     int
	Stdout    bool
	View      string
	Disp      display.Display
}

// App holds the data needed by an application
type App struct {
	ctx                *context.Context
	count              int
	display            display.Display
	done               chan struct{}
	sigChan            chan os.Signal
	wi                 wait_info.WaitInfo
	finished           bool
	stdout             bool
	dbh                *sql.DB
	help               bool
	fsbi               ps_table.Tabler // ufsbi.File_summary_by_instance
	tiwsbt             tiwsbt.Object
	tlwsbt             ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
	ewsgben            ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
	essgben            ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
	memory             memory_usage.Object
	users              user_latency.Object
	view               view.View
	wait_info.WaitInfo // embedded
	setupInstruments   setup_instruments.SetupInstruments
	wantRelativeStats  bool
}

// ensure performance_schema is enabled
// - if not will not return and will exit
func ensurePerformanceSchemaEnabled(variables *global.Variables) {
	if variables == nil {
		log.Fatal("ensurePerformanceSchemaEnabled() variables is nil")
	}

	// check that performance_schema = ON
	if value := variables.Get("performance_schema"); value != "ON" {
		log.Fatal(fmt.Sprintf("ensurePerformanceSchemaEnabled(): performance_schema = '%s'. Please configure performance_schema = 1 in /etc/my.cnf (or equivalent) and restart mysqld to use %s.",
			value, lib.MyName()))
	} else {
		logger.Println("performance_schema = ON check succeeds")
	}
}

// NewApp sets up the application given various parameters.
func NewApp(settings Settings) *App {
	logger.Println("app.NewApp()")
	app := new(App)

	anonymiser.Enable(settings.Anonymise) // not dynamic at the moment
	app.dbh = settings.Conn.Handle()

	status := global.NewStatus(app.dbh)
	variables := global.NewVariables(app.dbh)
	// Prior to setting up screen check that performance_schema is enabled.
	// On MariaDB this is not the default setting so it will confuse people.
	ensurePerformanceSchemaEnabled(variables)

	app.ctx = context.NewContext(status, variables)
	app.count = settings.Count
	app.finished = false

	app.stdout = settings.Stdout
	app.display = settings.Disp
	app.display.SetContext(app.ctx)
	app.SetHelp(false)

	if err := view.ValidateViews(app.dbh); err != nil {
		log.Fatal(err)
	}

	logger.Println("app.Setup() Setting the default view to:", settings.View)
	app.view.SetByName(settings.View) // if empty will use the default

	app.setupInstruments = setup_instruments.NewSetupInstruments(app.dbh)
	app.setupInstruments.EnableMonitoring()

	app.wi.SetWaitInterval(time.Second * time.Duration(settings.Interval))

	// setup to their initial types/values
	app.fsbi = fsbi.NewFileSummaryByInstance(variables)
	app.tlwsbt = new(tlwsbt.Object)
	app.ewsgben = new(ewsgben.Object)
	app.essgben = new(essgben.Object)

	app.SetWantRelativeStats(true)
	app.fixLatencySetting() // adjust to see ops/latency

	app.resetDBStatistics()

	return app
}

// Finished tells us if we have finished
func (app App) Finished() bool {
	return app.finished
}

// CollectAll collects all the stats together in one go
func (app *App) collectAll() {
	app.fsbi.Collect(app.dbh)
	app.tlwsbt.Collect(app.dbh)
	app.tiwsbt.Collect(app.dbh)
	app.users.Collect(app.dbh)
	app.essgben.Collect(app.dbh)
	app.ewsgben.Collect(app.dbh)
	app.memory.Collect(app.dbh)
}

func (app *App) WantRelativeStats() bool {
	return app.wantRelativeStats
}

// do a fresh collection of data and then update the initial values based on that.
func (app *App) resetDBStatistics() {
	app.collectAll()
	app.setInitialFromCurrent()
}

func (app *App) setInitialFromCurrent() {
	start := time.Now()
	app.fsbi.SetInitialFromCurrent()
	app.tlwsbt.SetInitialFromCurrent()
	app.tiwsbt.SetInitialFromCurrent()
	app.essgben.SetInitialFromCurrent()
	app.ewsgben.SetInitialFromCurrent()
	app.memory.SetInitialFromCurrent()
	logger.Println("app.setInitialFromCurrent() took", time.Duration(time.Since(start)).String())
}

// Collect the data we are looking at.
func (app *App) Collect() {
	start := time.Now()

	switch app.view.Get() {
	case view.ViewLatency, view.ViewOps:
		app.tiwsbt.Collect(app.dbh)
	case view.ViewIO:
		app.fsbi.Collect(app.dbh)
	case view.ViewLocks:
		app.tlwsbt.Collect(app.dbh)
	case view.ViewUsers:
		app.users.Collect(app.dbh)
	case view.ViewMutex:
		app.ewsgben.Collect(app.dbh)
	case view.ViewStages:
		app.essgben.Collect(app.dbh)
	case view.ViewMemory:
		app.memory.Collect(app.dbh)
	}
	app.wi.CollectedNow()
	logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
}

// SetHelp determines if we need to display help
func (app *App) SetHelp(newHelp bool) {
	app.help = newHelp

	app.display.ClearScreen()
}

// Help returns the internal help variable
func (app App) Help() bool {
	return app.help
}

// Display shows the output appropriate to the corresponding view and device
func (app *App) Display() {
	if app.help {
		app.display.DisplayHelp() // shouldn't get here if in --stdout mode
	} else {
		switch app.view.Get() {
		case view.ViewLatency, view.ViewOps:
			app.display.Display(app.tiwsbt)
		case view.ViewIO:
			app.display.Display(app.fsbi)
		case view.ViewLocks:
			app.display.Display(app.tlwsbt)
		case view.ViewUsers:
			app.display.Display(app.users)
		case view.ViewMutex:
			app.display.Display(app.ewsgben)
		case view.ViewStages:
			app.display.Display(app.essgben)
		case view.ViewMemory:
			app.display.Display(app.memory)
		}
	}
}

// fixLatencySetting() ensures the SetWantsLatency() value is
// correct. This needs to be done more cleanly.
func (app *App) fixLatencySetting() {
	if app.view.Get() == view.ViewLatency {
		app.tiwsbt.SetWantsLatency(true)
	}
	if app.view.Get() == view.ViewOps {
		app.tiwsbt.SetWantsLatency(false)
	}
}

// change to the previous display mode
func (app *App) displayPrevious() {
	app.view.SetPrev()
	app.fixLatencySetting()
	app.display.ClearScreen()
	app.Display()
}

// change to the next display mode
func (app *App) displayNext() {
	app.view.SetNext()
	app.fixLatencySetting()
	app.display.ClearScreen()
	app.Display()
}

// SetWantRelativeStats sets whether we want to see data that's relative or absolute
func (app *App) SetWantRelativeStats(want bool) {
	app.wantRelativeStats = want

	app.fsbi.SetWantRelativeStats(want)
	app.tlwsbt.SetWantRelativeStats(want)
	app.tiwsbt.SetWantRelativeStats(want)
	app.users.SetWantRelativeStats(want) // ignored
	app.essgben.SetWantRelativeStats(want)
	app.ewsgben.SetWantRelativeStats(want) // ignored
	app.memory.SetWantRelativeStats(want)
}

// Cleanup prepares  the application prior to shutting down
func (app *App) Cleanup() {
	app.display.Close()
	if app.dbh != nil {
		app.setupInstruments.RestoreConfiguration()
		_ = app.dbh.Close()
	}
	logger.Println("App.Cleanup completed")
}

// Run runs the application in a loop until we're ready to finish
func (app *App) Run() {
	logger.Println("app.Run()")

	app.sigChan = make(chan os.Signal, 10) // 10 entries
	signal.Notify(app.sigChan, syscall.SIGINT, syscall.SIGTERM)

	eventChan := app.display.EventChan()

	for !app.Finished() {
		select {
		case sig := <-app.sigChan:
			fmt.Println("Caught signal: ", sig)
			app.finished = true
		case <-app.wi.WaitNextPeriod():
			app.Collect()
			app.Display()
			if app.stdout {
				app.setInitialFromCurrent()
			}
		case inputEvent := <-eventChan:
			switch inputEvent.Type {
			case event.EventAnonymise:
				anonymiser.Enable(!anonymiser.Enabled()) // toggle current behaviour
			case event.EventFinished:
				app.finished = true
			case event.EventViewNext:
				app.displayNext()
			case event.EventViewPrev:
				app.displayPrevious()
			case event.EventDecreasePollTime:
				if app.wi.WaitInterval() > time.Second {
					app.wi.SetWaitInterval(app.wi.WaitInterval() - time.Second)
				}
			case event.EventIncreasePollTime:
				app.wi.SetWaitInterval(app.wi.WaitInterval() + time.Second)
			case event.EventHelp:
				app.SetHelp(!app.Help())
			case event.EventToggleWantRelative:
				app.SetWantRelativeStats(!app.WantRelativeStats())
				app.Display()
			case event.EventResetStatistics:
				app.resetDBStatistics()
				app.Display()
			case event.EventResizeScreen:
				width, height := inputEvent.Width, inputEvent.Height
				app.display.Resize(width, height)
				app.Display()
			case event.EventError:
				log.Fatalf("Quitting because of EventError error")
			}
		}
		// provide a hook to stop the application if the counter goes down to zero
		if app.stdout && app.count > 0 {
			app.count--
			if app.count == 0 {
				app.finished = true
			}
		}
	}
}
<commit_msg>ps-top/app: remove unneeded constants<commit_after>// Package app is the "runtime" for the ps-top / ps-stats application packages
//
// This file contains the library routines related to running the app.
package app

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/sjmudd/anonymiser"
	"github.com/sjmudd/ps-top/connector"
	"github.com/sjmudd/ps-top/context"
	"github.com/sjmudd/ps-top/display"
	"github.com/sjmudd/ps-top/event"
	fsbi "github.com/sjmudd/ps-top/file_io_latency"
	"github.com/sjmudd/ps-top/global"
	"github.com/sjmudd/ps-top/lib"
	"github.com/sjmudd/ps-top/logger"
	"github.com/sjmudd/ps-top/memory_usage"
	ewsgben "github.com/sjmudd/ps-top/mutex_latency"
	"github.com/sjmudd/ps-top/p_s/ps_table"
	"github.com/sjmudd/ps-top/setup_instruments"
	essgben "github.com/sjmudd/ps-top/stages_latency"
	tiwsbt "github.com/sjmudd/ps-top/table_io_latency"
	tlwsbt "github.com/sjmudd/ps-top/table_lock_latency"
	"github.com/sjmudd/ps-top/user_latency"
	"github.com/sjmudd/ps-top/view"
	"github.com/sjmudd/ps-top/wait_info"
)

// Flags for initialising the app
type Settings struct {
	Anonymise bool
	Conn      *connector.Connector
	Interval  int
	Count     int
	Stdout    bool
	View      string
	Disp      display.Display
}

// App holds the data needed by an application
type App struct {
	ctx                *context.Context
	count              int
	display            display.Display
	done               chan struct{}
	sigChan            chan os.Signal
	wi                 wait_info.WaitInfo
	finished           bool
	stdout             bool
	dbh                *sql.DB
	help               bool
	fsbi               ps_table.Tabler // ufsbi.File_summary_by_instance
	tiwsbt             tiwsbt.Object
	tlwsbt             ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
	ewsgben            ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
	essgben            ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
	memory             memory_usage.Object
	users              user_latency.Object
	view               view.View
	wait_info.WaitInfo // embedded
	setupInstruments   setup_instruments.SetupInstruments
	wantRelativeStats  bool
}

// ensure performance_schema is enabled
// - if not will not return and will exit
func ensurePerformanceSchemaEnabled(variables *global.Variables) {
	if variables == nil {
		log.Fatal("ensurePerformanceSchemaEnabled() variables is nil")
	}

	// check that performance_schema = ON
	if value := variables.Get("performance_schema"); value != "ON" {
		log.Fatal(fmt.Sprintf("ensurePerformanceSchemaEnabled(): performance_schema = '%s'. Please configure performance_schema = 1 in /etc/my.cnf (or equivalent) and restart mysqld to use %s.",
			value, lib.MyName()))
	} else {
		logger.Println("performance_schema = ON check succeeds")
	}
}

// NewApp sets up the application given various parameters.
func NewApp(settings Settings) *App {
	logger.Println("app.NewApp()")
	app := new(App)

	anonymiser.Enable(settings.Anonymise) // not dynamic at the moment
	app.dbh = settings.Conn.Handle()

	status := global.NewStatus(app.dbh)
	variables := global.NewVariables(app.dbh)
	// Prior to setting up screen check that performance_schema is enabled.
	// On MariaDB this is not the default setting so it will confuse people.
	ensurePerformanceSchemaEnabled(variables)

	app.ctx = context.NewContext(status, variables)
	app.count = settings.Count
	app.finished = false

	app.stdout = settings.Stdout
	app.display = settings.Disp
	app.display.SetContext(app.ctx)
	app.SetHelp(false)

	if err := view.ValidateViews(app.dbh); err != nil {
		log.Fatal(err)
	}

	logger.Println("app.Setup() Setting the default view to:", settings.View)
	app.view.SetByName(settings.View) // if empty will use the default

	app.setupInstruments = setup_instruments.NewSetupInstruments(app.dbh)
	app.setupInstruments.EnableMonitoring()

	app.wi.SetWaitInterval(time.Second * time.Duration(settings.Interval))

	// setup to their initial types/values
	app.fsbi = fsbi.NewFileSummaryByInstance(variables)
	app.tlwsbt = new(tlwsbt.Object)
	app.ewsgben = new(ewsgben.Object)
	app.essgben = new(essgben.Object)

	app.SetWantRelativeStats(true)
	app.fixLatencySetting() // adjust to see ops/latency

	app.resetDBStatistics()

	return app
}

// Finished tells us if we have finished
func (app App) Finished() bool {
	return app.finished
}

// CollectAll collects all the stats together in one go
func (app *App) collectAll() {
	app.fsbi.Collect(app.dbh)
	app.tlwsbt.Collect(app.dbh)
	app.tiwsbt.Collect(app.dbh)
	app.users.Collect(app.dbh)
	app.essgben.Collect(app.dbh)
	app.ewsgben.Collect(app.dbh)
	app.memory.Collect(app.dbh)
}

func (app *App) WantRelativeStats() bool {
	return app.wantRelativeStats
}

// do a fresh collection of data and then update the initial values based on that.
func (app *App) resetDBStatistics() {
	app.collectAll()
	app.setInitialFromCurrent()
}

func (app *App) setInitialFromCurrent() {
	start := time.Now()
	app.fsbi.SetInitialFromCurrent()
	app.tlwsbt.SetInitialFromCurrent()
	app.tiwsbt.SetInitialFromCurrent()
	app.essgben.SetInitialFromCurrent()
	app.ewsgben.SetInitialFromCurrent()
	app.memory.SetInitialFromCurrent()
	logger.Println("app.setInitialFromCurrent() took", time.Duration(time.Since(start)).String())
}

// Collect the data we are looking at.
func (app *App) Collect() {
	start := time.Now()

	switch app.view.Get() {
	case view.ViewLatency, view.ViewOps:
		app.tiwsbt.Collect(app.dbh)
	case view.ViewIO:
		app.fsbi.Collect(app.dbh)
	case view.ViewLocks:
		app.tlwsbt.Collect(app.dbh)
	case view.ViewUsers:
		app.users.Collect(app.dbh)
	case view.ViewMutex:
		app.ewsgben.Collect(app.dbh)
	case view.ViewStages:
		app.essgben.Collect(app.dbh)
	case view.ViewMemory:
		app.memory.Collect(app.dbh)
	}
	app.wi.CollectedNow()
	logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
}

// SetHelp determines if we need to display help
func (app *App) SetHelp(newHelp bool) {
	app.help = newHelp

	app.display.ClearScreen()
}

// Help returns the internal help variable
func (app App) Help() bool {
	return app.help
}

// Display shows the output appropriate to the corresponding view and device
func (app *App) Display() {
	if app.help {
		app.display.DisplayHelp() // shouldn't get here if in --stdout mode
	} else {
		switch app.view.Get() {
		case view.ViewLatency, view.ViewOps:
			app.display.Display(app.tiwsbt)
		case view.ViewIO:
			app.display.Display(app.fsbi)
		case view.ViewLocks:
			app.display.Display(app.tlwsbt)
		case view.ViewUsers:
			app.display.Display(app.users)
		case view.ViewMutex:
			app.display.Display(app.ewsgben)
		case view.ViewStages:
			app.display.Display(app.essgben)
		case view.ViewMemory:
			app.display.Display(app.memory)
		}
	}
}

// fixLatencySetting() ensures the SetWantsLatency() value is
// correct. This needs to be done more cleanly.
func (app *App) fixLatencySetting() {
	if app.view.Get() == view.ViewLatency {
		app.tiwsbt.SetWantsLatency(true)
	}
	if app.view.Get() == view.ViewOps {
		app.tiwsbt.SetWantsLatency(false)
	}
}

// change to the previous display mode
func (app *App) displayPrevious() {
	app.view.SetPrev()
	app.fixLatencySetting()
	app.display.ClearScreen()
	app.Display()
}

// change to the next display mode
func (app *App) displayNext() {
	app.view.SetNext()
	app.fixLatencySetting()
	app.display.ClearScreen()
	app.Display()
}

// SetWantRelativeStats sets whether we want to see data that's relative or absolute
func (app *App) SetWantRelativeStats(want bool) {
	app.wantRelativeStats = want

	app.fsbi.SetWantRelativeStats(want)
	app.tlwsbt.SetWantRelativeStats(want)
	app.tiwsbt.SetWantRelativeStats(want)
	app.users.SetWantRelativeStats(want) // ignored
	app.essgben.SetWantRelativeStats(want)
	app.ewsgben.SetWantRelativeStats(want) // ignored
	app.memory.SetWantRelativeStats(want)
}

// Cleanup prepares  the application prior to shutting down
func (app *App) Cleanup() {
	app.display.Close()
	if app.dbh != nil {
		app.setupInstruments.RestoreConfiguration()
		_ = app.dbh.Close()
	}
	logger.Println("App.Cleanup completed")
}

// Run runs the application in a loop until we're ready to finish
func (app *App) Run() {
	logger.Println("app.Run()")

	app.sigChan = make(chan os.Signal, 10) // 10 entries
	signal.Notify(app.sigChan, syscall.SIGINT, syscall.SIGTERM)

	eventChan := app.display.EventChan()

	for !app.Finished() {
		select {
		case sig := <-app.sigChan:
			fmt.Println("Caught signal: ", sig)
			app.finished = true
		case <-app.wi.WaitNextPeriod():
			app.Collect()
			app.Display()
			if app.stdout {
				app.setInitialFromCurrent()
			}
		case inputEvent := <-eventChan:
			switch inputEvent.Type {
			case event.EventAnonymise:
				anonymiser.Enable(!anonymiser.Enabled()) // toggle current behaviour
			case event.EventFinished:
				app.finished = true
			case event.EventViewNext:
				app.displayNext()
			case event.EventViewPrev:
				app.displayPrevious()
			case event.EventDecreasePollTime:
				if app.wi.WaitInterval() > time.Second {
					app.wi.SetWaitInterval(app.wi.WaitInterval() - time.Second)
				}
			case event.EventIncreasePollTime:
				app.wi.SetWaitInterval(app.wi.WaitInterval() + time.Second)
			case event.EventHelp:
				app.SetHelp(!app.Help())
			case event.EventToggleWantRelative:
				app.SetWantRelativeStats(!app.WantRelativeStats())
				app.Display()
			case event.EventResetStatistics:
				app.resetDBStatistics()
				app.Display()
			case event.EventResizeScreen:
				width, height := inputEvent.Width, inputEvent.Height
				app.display.Resize(width, height)
				app.Display()
			case event.EventError:
				log.Fatalf("Quitting because of EventError error")
			}
		}
		// provide a hook to stop the application if the counter goes down to zero
		if app.stdout && app.count > 0 {
			app.count--
			if app.count == 0 {
				app.finished = true
			}
		}
	}
}
<|endoftext|>


<commit_before>// Package app contains the basic application logic and assembles together all
// the pieces of CheeseGull.
package app

import (
	"fmt"

	"github.com/osuripple/cheesegull"
)

// App is the basic struct of the application which contains all the methods
// to make it run smoothly.
type App struct {
	Downloader    cheesegull.BeatmapDownloader
	Source        cheesegull.BeatmapInfoSource
	Service       cheesegull.BeatmapService
	FileResolver  cheesegull.FileResolver
	Communication cheesegull.CommunicationService
	// handles non-critical errors
	ErrorHandler func(err error)
	download     chan cheesegull.BeatmapSet
}

const chunkSize = 200

// Start starts the application as a whole. Start should never return, unless
// there's a critical error.
func (a *App) Start(n int) error {
	// initialisation
	a.download = make(chan cheesegull.BeatmapSet, 64)
	if n < 1 {
		n = 1
	}
	for i := 0; i < n; i++ {
		go a.Worker()
	}

	ch, err := a.Communication.BeatmapRequestsChannel()
	if err != nil {
		return err
	}
	go a.communicationSync(a.download, ch)

	offset := 0
	for {
		sets, err := a.Service.ChunkOfSets(offset, chunkSize, cheesegull.SortLastChecked)
		if err != nil {
			return err
		}

		if len(sets) == 0 {
			err := a.discoverNew()
			if err != nil {
				return err
			}
			// repeat from the beginning
			offset = 0
			continue
		}

		// check beatmaps are good and if they are, add them to the download
		// queue.
		for _, s := range sets {
			b, err := a.CheckGood(&s)
			if err != nil {
				a.handle(err)
				continue
			}

			if b {
				a.download <- s
			}
		}

		offset += chunkSize
	}
}

// discoverNew is the part where we discover new beatmaps that are available to download.
func (a *App) discoverNew() error {
	i, err := a.Service.HighestBeatmapSetID()
	if err != nil {
		return err
	}
	i -= 5000
	if i < 1 {
		i = 1
	}
	// number of consecutive beatmaps not found
	var notFound int
	for {
		i++
		if notFound > 5000 {
			return nil
		}

		s := cheesegull.BeatmapSet{
			SetID: i,
		}
		b, err := a.CheckGood(&s)
		if err != nil {
			return err
		}

		if !b {
			notFound++
			continue
		}

		a.download <- s
	}
}

func (a *App) handle(err error) {
	if a.ErrorHandler == nil {
		fmt.Println(err)
		return
	}
	a.ErrorHandler(err)
}

func (a *App) communicationSync(dst chan<- cheesegull.BeatmapSet, src <-chan int) {
	for i := range src {
		fmt.Println("Adding", i)

		s := cheesegull.BeatmapSet{
			SetID: i,
		}
		b, err := a.CheckGood(&s)
		if err != nil {
			fmt.Println(err)
			continue
		}

		// if it's false, it means that the beatmap could not be found
		if b {
			dst <- s
		}
	}
}
<commit_msg>add some logging<commit_after>// Package app contains the basic application logic and assembles together all
// the pieces of CheeseGull.
package app

import (
	"fmt"

	"github.com/osuripple/cheesegull"
)

// App is the basic struct of the application which contains all the methods
// to make it run smoothly.
type App struct {
	Downloader    cheesegull.BeatmapDownloader
	Source        cheesegull.BeatmapInfoSource
	Service       cheesegull.BeatmapService
	FileResolver  cheesegull.FileResolver
	Communication cheesegull.CommunicationService
	// handles non-critical errors
	ErrorHandler func(err error)
	download     chan cheesegull.BeatmapSet
}

const chunkSize = 200

// Start starts the application as a whole. Start should never return, unless
// there's a critical error.
func (a *App) Start(n int) error {
	// initialisation
	a.download = make(chan cheesegull.BeatmapSet, 64)
	if n < 1 {
		n = 1
	}
	for i := 0; i < n; i++ {
		go a.Worker()
	}

	ch, err := a.Communication.BeatmapRequestsChannel()
	if err != nil {
		return err
	}
	go a.communicationSync(a.download, ch)

	offset := 0
	for {
		sets, err := a.Service.ChunkOfSets(offset, chunkSize, cheesegull.SortLastChecked)
		if err != nil {
			return err
		}

		if len(sets) == 0 {
			fmt.Println("Starting discoverNew")
			err := a.discoverNew()
			if err != nil {
				return err
			}
			// repeat from the beginning
			offset = 0
			continue
		}

		// check beatmaps are good and if they are, add them to the download
		// queue.
		for _, s := range sets {
			fmt.Println("Checking whether", s.SetID, "is good...")
			b, err := a.CheckGood(&s)
			if err != nil {
				a.handle(err)
				continue
			}

			if b {
				fmt.Println("Queueing", s.SetID)
				a.download <- s
			}
		}

		offset += chunkSize
	}
}

// discoverNew is the part where we discover new beatmaps that are available to download.
func (a *App) discoverNew() error {
	i, err := a.Service.HighestBeatmapSetID()
	if err != nil {
		return err
	}
	i -= 5000
	if i < 1 {
		i = 1
	}
	// number of consecutive beatmaps not found
	var notFound int
	for {
		i++
		if notFound > 5000 {
			return nil
		}

		s := cheesegull.BeatmapSet{
			SetID: i,
		}
		b, err := a.CheckGood(&s)
		if err != nil {
			return err
		}

		if !b {
			notFound++
			continue
		}

		a.download <- s
	}
}

func (a *App) handle(err error) {
	if a.ErrorHandler == nil {
		fmt.Println(err)
		return
	}
	a.ErrorHandler(err)
}

func (a *App) communicationSync(dst chan<- cheesegull.BeatmapSet, src <-chan int) {
	for i := range src {
		fmt.Println("Adding", i)

		s := cheesegull.BeatmapSet{
			SetID: i,
		}
		b, err := a.CheckGood(&s)
		if err != nil {
			fmt.Println(err)
			continue
		}

		// if it's false, it means that the beatmap could not be found
		if b {
			dst <- s
		}
	}
}
<|endoftext|>


<commit_before>// Package topk implements the Filtered Space-Saving TopK streaming algorithm
/*

The original Space-Saving algorithm:
https://icmi.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf

The Filtered Space-Saving enhancement:
http://www.l2f.inesc-id.pt/~fmmb/wiki/uploads/Work/misnis.ref0a.pdf

This implementation follows the algorithm of the FSS paper, but not the
suggested implementation.  Specifically, we use a heap instead of a sorted list
of monitored items, and since we are also using a map to provide O(1) access on
update also don't need the c_i counters in the hash table.

Licensed under the MIT license.

*/
package topk

import (
	"bytes"
	"container/heap"
	"encoding/gob"
	"hash/fnv"
	"sort"
)

// Element is a TopK item
type Element struct {
	Key   string
	Count int
	Error int
}

type elementsByCountDescending []Element

func (elts elementsByCountDescending) Len() int { return len(elts) }
func (elts elementsByCountDescending) Less(i, j int) bool {
	return (elts[i].Count >= elts[j].Count) || (elts[i].Count == elts[j].Count && elts[i].Key < elts[i].Key)
}
func (elts elementsByCountDescending) Swap(i, j int) { elts[i], elts[j] = elts[j], elts[i] }

type keys struct {
	m    map[string]int
	elts []Element
}

// Implement the container/heap interface

func (tk *keys) Len() int { return len(tk.elts) }
func (tk *keys) Less(i, j int) bool {
	return (tk.elts[i].Count < tk.elts[j].Count) || (tk.elts[i].Count == tk.elts[j].Count && tk.elts[i].Error > tk.elts[j].Error)
}
func (tk *keys) Swap(i, j int) {

	tk.elts[i], tk.elts[j] = tk.elts[j], tk.elts[i]

	tk.m[tk.elts[i].Key] = i
	tk.m[tk.elts[j].Key] = j
}

func (tk *keys) Push(x interface{}) {
	e := x.(Element)
	tk.m[e.Key] = len(tk.elts)
	tk.elts = append(tk.elts, e)
}

func (tk *keys) Pop() interface{} {
	var e Element
	e, tk.elts = tk.elts[len(tk.elts)-1], tk.elts[:len(tk.elts)-1]

	delete(tk.m, e.Key)

	return e
}

// Stream calculates the TopK elements for a stream
type Stream struct {
	n      int
	k      keys
	alphas []int
}

// New returns a Stream estimating the top n most frequent elements
func New(n int) *Stream {
	return &Stream{
		n:      n,
		k:      keys{m: make(map[string]int), elts: make([]Element, 0, n)},
		alphas: make([]int, n*6), // 6 is the multiplicative constant from the paper
	}
}

// Insert adds an element to the stream to be tracked
func (s *Stream) Insert(x string, count int) {

	h := fnv.New32a()
	h.Write([]byte(x))
	xhash := int(h.Sum32()) % len(s.alphas)

	// are we tracking this element?
	if idx, ok := s.k.m[x]; ok {
		s.k.elts[idx].Count += count
		heap.Fix(&s.k, idx)
		return
	}

	// can we track more elements?
	if len(s.k.elts) < s.n {
		// there is free space
		heap.Push(&s.k, Element{Key: x, Count: count})
		return
	}

	if s.alphas[xhash]+count < s.k.elts[0].Count {
		s.alphas[xhash] += count
		return
	}

	// replace the current minimum element
	minKey := s.k.elts[0].Key

	h.Reset()
	h.Write([]byte(minKey))
	mkhash := int(h.Sum32()) % len(s.alphas)
	s.alphas[mkhash] = s.k.elts[0].Count

	s.k.elts[0].Key = x
	s.k.elts[0].Error = s.alphas[xhash]
	s.k.elts[0].Count = s.alphas[xhash] + count

	// we're not longer monitoring minKey
	delete(s.k.m, minKey)
	// but 'x' is as array position 0
	s.k.m[x] = 0

	heap.Fix(&s.k, 0)
}

// Keys returns the current estimates for the most frequent elements
func (s *Stream) Keys() []Element {
	elts := append([]Element(nil), s.k.elts...)
	sort.Sort(elementsByCountDescending(elts))
	return elts
}

func (s *Stream) GobEncode() ([]byte, error) {
	buf := bytes.Buffer{}
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(s.n); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.k.m); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.k.elts); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.alphas); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (s *Stream) GobDecode(b []byte) error {
	dec := gob.NewDecoder(bytes.NewBuffer(b))
	if err := dec.Decode(&s.n); err != nil {
		return err
	}
	if err := dec.Decode(&s.k.m); err != nil {
		return err
	}
	if err := dec.Decode(&s.k.elts); err != nil {
		return err
	}
	if err := dec.Decode(&s.alphas); err != nil {
		return err
	}
	return nil
}
<commit_msg>fix copy-paste error on indexing<commit_after>// Package topk implements the Filtered Space-Saving TopK streaming algorithm
/*

The original Space-Saving algorithm:
https://icmi.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf

The Filtered Space-Saving enhancement:
http://www.l2f.inesc-id.pt/~fmmb/wiki/uploads/Work/misnis.ref0a.pdf

This implementation follows the algorithm of the FSS paper, but not the
suggested implementation.  Specifically, we use a heap instead of a sorted list
of monitored items, and since we are also using a map to provide O(1) access on
update also don't need the c_i counters in the hash table.

Licensed under the MIT license.

*/
package topk

import (
	"bytes"
	"container/heap"
	"encoding/gob"
	"hash/fnv"
	"sort"
)

// Element is a TopK item
type Element struct {
	Key   string
	Count int
	Error int
}

type elementsByCountDescending []Element

func (elts elementsByCountDescending) Len() int { return len(elts) }
func (elts elementsByCountDescending) Less(i, j int) bool {
	return (elts[i].Count >= elts[j].Count) || (elts[i].Count == elts[j].Count && elts[i].Key < elts[j].Key)
}
func (elts elementsByCountDescending) Swap(i, j int) { elts[i], elts[j] = elts[j], elts[i] }

type keys struct {
	m    map[string]int
	elts []Element
}

// Implement the container/heap interface

func (tk *keys) Len() int { return len(tk.elts) }
func (tk *keys) Less(i, j int) bool {
	return (tk.elts[i].Count < tk.elts[j].Count) || (tk.elts[i].Count == tk.elts[j].Count && tk.elts[i].Error > tk.elts[j].Error)
}
func (tk *keys) Swap(i, j int) {

	tk.elts[i], tk.elts[j] = tk.elts[j], tk.elts[i]

	tk.m[tk.elts[i].Key] = i
	tk.m[tk.elts[j].Key] = j
}

func (tk *keys) Push(x interface{}) {
	e := x.(Element)
	tk.m[e.Key] = len(tk.elts)
	tk.elts = append(tk.elts, e)
}

func (tk *keys) Pop() interface{} {
	var e Element
	e, tk.elts = tk.elts[len(tk.elts)-1], tk.elts[:len(tk.elts)-1]

	delete(tk.m, e.Key)

	return e
}

// Stream calculates the TopK elements for a stream
type Stream struct {
	n      int
	k      keys
	alphas []int
}

// New returns a Stream estimating the top n most frequent elements
func New(n int) *Stream {
	return &Stream{
		n:      n,
		k:      keys{m: make(map[string]int), elts: make([]Element, 0, n)},
		alphas: make([]int, n*6), // 6 is the multiplicative constant from the paper
	}
}

// Insert adds an element to the stream to be tracked
func (s *Stream) Insert(x string, count int) {

	h := fnv.New32a()
	h.Write([]byte(x))
	xhash := int(h.Sum32()) % len(s.alphas)

	// are we tracking this element?
	if idx, ok := s.k.m[x]; ok {
		s.k.elts[idx].Count += count
		heap.Fix(&s.k, idx)
		return
	}

	// can we track more elements?
	if len(s.k.elts) < s.n {
		// there is free space
		heap.Push(&s.k, Element{Key: x, Count: count})
		return
	}

	if s.alphas[xhash]+count < s.k.elts[0].Count {
		s.alphas[xhash] += count
		return
	}

	// replace the current minimum element
	minKey := s.k.elts[0].Key

	h.Reset()
	h.Write([]byte(minKey))
	mkhash := int(h.Sum32()) % len(s.alphas)
	s.alphas[mkhash] = s.k.elts[0].Count

	s.k.elts[0].Key = x
	s.k.elts[0].Error = s.alphas[xhash]
	s.k.elts[0].Count = s.alphas[xhash] + count

	// we're not longer monitoring minKey
	delete(s.k.m, minKey)
	// but 'x' is as array position 0
	s.k.m[x] = 0

	heap.Fix(&s.k, 0)
}

// Keys returns the current estimates for the most frequent elements
func (s *Stream) Keys() []Element {
	elts := append([]Element(nil), s.k.elts...)
	sort.Sort(elementsByCountDescending(elts))
	return elts
}

func (s *Stream) GobEncode() ([]byte, error) {
	buf := bytes.Buffer{}
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(s.n); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.k.m); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.k.elts); err != nil {
		return nil, err
	}
	if err := enc.Encode(s.alphas); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (s *Stream) GobDecode(b []byte) error {
	dec := gob.NewDecoder(bytes.NewBuffer(b))
	if err := dec.Decode(&s.n); err != nil {
		return err
	}
	if err := dec.Decode(&s.k.m); err != nil {
		return err
	}
	if err := dec.Decode(&s.k.elts); err != nil {
		return err
	}
	if err := dec.Decode(&s.alphas); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package app

import (
  "github.com/stretchr/goweb"
  "github.com/stretchr/goweb/context"
  "github.com/stretchr/goweb/handlers"

  "github.com/tgreiser/victr/controllers"
)

func Init() {
  handler := handlers.NewHttpHandler(goweb.CodecService)

  //handler.Map("GET", "/", func (c context.Context) error {
  //  return goweb.Respond.With(c, 200, []byte("Hey planet, what's up"))
  //})

  content := new(controllers.ContentController)
  handler.MapController(content)
  handler.Map("GET", "/content/new", func (c context.Context) error {
    return content.New(c)
  })
  handler.Map("POST", "/content/publish", func (c context.Context) error {
    return content.Publish(c)
  })

  // failover handler
  /*
  handler.Map(func(c context.Context) error {
    return NotFound(c)
  })

  */
  http.Handle("/content", handler)
}

func NotFound(c context.Context) error {
  return goweb.Respond.With(c, 404, []byte("File not found"))
}

func SystemError(c context.Context) error {
  return goweb.Respond.With(c, 500, []byte("Server error, please try again in a moment."))
}
<commit_msg>fixed http reference<commit_after>package app

import (
  "net/http"

  "github.com/stretchr/goweb"
  "github.com/stretchr/goweb/context"
  "github.com/stretchr/goweb/handlers"

  "github.com/tgreiser/victr/controllers"
)

func Init() {
  handler := handlers.NewHttpHandler(goweb.CodecService)

  //handler.Map("GET", "/", func (c context.Context) error {
  //  return goweb.Respond.With(c, 200, []byte("Hey planet, what's up"))
  //})

  content := new(controllers.ContentController)
  handler.MapController(content)
  handler.Map("GET", "/content/new", func (c context.Context) error {
    return content.New(c)
  })
  handler.Map("POST", "/content/publish", func (c context.Context) error {
    return content.Publish(c)
  })

  // failover handler
  /*
  handler.Map(func(c context.Context) error {
    return NotFound(c)
  })

  */
  http.Handle("/content", handler)
}

func NotFound(c context.Context) error {
  return goweb.Respond.With(c, 404, []byte("File not found"))
}

func SystemError(c context.Context) error {
  return goweb.Respond.With(c, 500, []byte("Server error, please try again in a moment."))
}
<|endoftext|>


<commit_before>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "time"
  "strconv"
  "regexp"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

var (
  SCALE_NUM_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  HOSTS_REG, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client
}

func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
  }
}

func (t *Town) ReadFile(name string) {
  var pathLocs = [...]string{
    name + ".yml",
    "/etc/town/" + name + ".yml",
  }

  for _, path := range pathLocs {
    if _, err := os.Stat(path); err == nil {
      t.cluster = cluster.NewCluster(path)
      t.cluster.ReadFile()
      return
    }
  }

  log.Println("ERROR: Could not find file ", name, ".yml")
}

func (t *Town) Connect() {
  endpoint := t.cluster.Application.Docker.Hosts[0] // at the moment use only first
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

func (t *Town) Provision(checkChanged bool) {
  // update containers
  for _, node := range t.cluster.Nodes {
    var buf bytes.Buffer
    var image = strings.Split(node.Container.Image, ":")

    opts := dockerapi.PullImageOptions{
        Repository: image[0],
        // Registry:     "docker.tsuru.io",
        // Tag:          "latest",
        OutputStream: &buf,
    }

    if len(image) > 1 {
      opts.Tag = image[1]
    }
    err := t.docker.PullImage(opts, dockerapi.AuthConfiguration{});
    if err != nil {
      log.Println("Could not pull image ", image)
    }
  }

  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  if err == nil {
    for _, listing := range allContainers {
      container, err := t.docker.InspectContainer(listing.ID)
      if err == nil {
        name := container.Name[1:]
        node, index := t.cluster.FindNodeByName(name)
        if node != nil && index > 0 {
          if node.Container.Exist == nil {
            node.Container.Exist = []cluster.ExistContainer{}
          }
          runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
          if checkChanged {
            node.Container.Changed = t.isChangedImage(node, container)
          } else {
            node.Container.Changed = true
          }
          node.Container.Exist = append(node.Container.Exist, runningContainer)
        }
      } else {
        log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
      }
    }

    if checkChanged {
      t.cluster.AddChangeDependant()
    }
  } else {
    log.Println("[ERROR] Can't start provision")
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    } else {
      log.Println("[ERROR] Could not inspect image ", node.Container.Name)
    }
  } else {
    log.Println("[ERROR] Could not inspect image ", imageName)
  }
  return false
}

func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Start failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName
        } else {
          log.Println("Inpect ", container.ID, " error ", inspectError)
        }
        //retry = 0
        break;
      }
    }
  } else {
    log.Println("error: ", err);
  }

  return "", ""
}

func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      log.Println(node.Container.Name, "  image: ", node.Container.Image)
      for i := 1; i <= node.Container.Scale; i++ {

        _, host := t.CreateContainer(node, i) //id

        if len(node.Container.Validate) > 0 {
          t.bashCommand(host, node.Container.Validate)
        }

        ids = append(ids, host)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")
          t.bashCommand(id, buffer.String() )
        }
      }

      time.Sleep(1000 * time.Millisecond)
    }
  }
}

func (t *Town) bashCommand(id string, command string)  {
  config := dockerapi.CreateExecOptions{
    Container:    id,
    AttachStdin:  true,
    AttachStdout: true,
    AttachStderr: false,
    Tty:          false,
    Cmd:          []string{"bash", "-c", command},
  }
  execObj, err := t.docker.CreateExec(config)
  if err == nil {
    config := dockerapi.StartExecOptions{
      Detach: true,
    }
    err := t.docker.StartExec(execObj.ID, config)
    if err != nil {
      log.Println("Container ", id, " command failed with error: ", err)
    }
  } else {
    log.Println("Container ", id, " command failed with error: ", err)
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := SCALE_NUM_REG.FindAllStringSubmatch(replace, -1)
  hostMatch := HOSTS_REG.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<commit_msg>add container name<commit_after>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "time"
  "strconv"
  "regexp"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

var (
  SCALE_NUM_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  HOSTS_REG, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client
}

func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
  }
}

func (t *Town) ReadFile(name string) {
  var pathLocs = [...]string{
    name + ".yml",
    "/etc/town/" + name + ".yml",
  }

  for _, path := range pathLocs {
    if _, err := os.Stat(path); err == nil {
      t.cluster = cluster.NewCluster(path)
      t.cluster.ReadFile()
      return
    }
  }

  log.Println("ERROR: Could not find file ", name, ".yml")
}

func (t *Town) Connect() {
  endpoint := t.cluster.Application.Docker.Hosts[0] // at the moment use only first
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

func (t *Town) Provision(checkChanged bool) {
  // update containers
  for _, node := range t.cluster.Nodes {
    var buf bytes.Buffer
    var image = strings.Split(node.Container.Image, ":")

    opts := dockerapi.PullImageOptions{
        Repository: image[0],
        // Registry:     "docker.tsuru.io",
        // Tag:          "latest",
        OutputStream: &buf,
    }

    if len(image) > 1 {
      opts.Tag = image[1]
    }
    err := t.docker.PullImage(opts, dockerapi.AuthConfiguration{});
    if err != nil {
      log.Println("Could not pull image ", image)
    }
  }

  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  if err == nil {
    for _, listing := range allContainers {
      container, err := t.docker.InspectContainer(listing.ID)
      if err == nil {
        name := container.Name[1:]
        node, index := t.cluster.FindNodeByName(name)
        if node != nil && index > 0 {
          if node.Container.Exist == nil {
            node.Container.Exist = []cluster.ExistContainer{}
          }
          runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
          if checkChanged {
            node.Container.Changed = t.isChangedImage(node, container)
          } else {
            node.Container.Changed = true
          }
          node.Container.Exist = append(node.Container.Exist, runningContainer)
        }
      } else {
        log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
      }
    }

    if checkChanged {
      t.cluster.AddChangeDependant()
    }
  } else {
    log.Println("[ERROR] Can't start provision")
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    } else {
      log.Println("[ERROR] Could not inspect image ", node.Container.Name)
    }
  } else {
    log.Println("[ERROR] Could not inspect image ", imageName)
  }
  return false
}

func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Start failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName, containerName
        } else {
          log.Println("Inpect ", container.ID, " error ", inspectError)
        }
        //retry = 0
        break;
      }
    }
  } else {
    log.Println("error: ", err);
  }

  return "", "", ""
}

func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      log.Println(node.Container.Name, "  image: ", node.Container.Image)
      for i := 1; i <= node.Container.Scale; i++ {

        _, host, containerName := t.CreateContainer(node, i) //id

        if len(node.Container.Validate) > 0 {
          t.bashCommand(containerName, node.Container.Validate)
        }

        ids = append(ids, containerName)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")
          t.bashCommand(id, buffer.String() )
        }
      }

      time.Sleep(1000 * time.Millisecond)
    }
  }
}

func (t *Town) bashCommand(id string, command string)  {
  config := dockerapi.CreateExecOptions{
    Container:    id,
    AttachStdin:  true,
    AttachStdout: true,
    AttachStderr: false,
    Tty:          false,
    Cmd:          []string{"bash", "-c", command},
  }
  execObj, err := t.docker.CreateExec(config)
  if err == nil {
    config := dockerapi.StartExecOptions{
      Detach: true,
    }
    err := t.docker.StartExec(execObj.ID, config)
    if err != nil {
      log.Println("Container ", id, " command failed with error: ", err)
    }
  } else {
    log.Println("Container ", id, " command failed with error: ", err)
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := SCALE_NUM_REG.FindAllStringSubmatch(replace, -1)
  hostMatch := HOSTS_REG.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
)

type NodeType int

const (
	ndError NodeType = iota
	ndArrayAccess
	ndBlock
	ndCharacter
	ndExtVarDecl
	ndExtVarInit
	ndFunction
	ndFunctionCall
	ndIdent
	ndInteger
	ndNull
	ndParen
	ndString
	ndUnary
	ndVarDecl
)

type Node interface {
	Type() NodeType
	String() string
}

type ArrayAccessNode struct {
	array Node
	index Node
}

func (a ArrayAccessNode) Type() NodeType { return ndArrayAccess }
func (a ArrayAccessNode) String() string {
	return fmt.Sprintf("%s[%s]", a.array, a.index)
}

// '{' node* '}'
type BlockNode struct {
	nodes []Node
}

func (b BlockNode) Type() NodeType { return ndBlock }
func (b BlockNode) String() string {
	str := "{\n"

	for _, node := range b.nodes {
		str += fmt.Sprintf("\t%v\n", node)
	}

	str += "}"
	return str
}

type CharacterNode struct {
	value string
}

func (c CharacterNode) Type() NodeType { return ndCharacter }
func (c CharacterNode) String() string { return fmt.Sprintf("'%s'", c.value) }

type ExternVarDeclNode struct {
	names []string
}

func (e ExternVarDeclNode) Type() NodeType { return ndExtVarDecl }
func (e ExternVarDeclNode) String() string {
	return fmt.Sprintf("extrn %s;", strings.Join(e.names, ", "))
}

// name value ';'
type ExternVarInitNode struct {
	name  string
	value Node
}

func (e ExternVarInitNode) Type() NodeType { return ndExtVarInit }
func (e ExternVarInitNode) String() string {
	return fmt.Sprintf("%s %v;", e.name, e.value)
}

// name '(' (var (',' var)*) ? ')' block
type FunctionNode struct {
	name   string
	params []string
	block  BlockNode
}

func (f FunctionNode) Type() NodeType { return ndFunction }
func (f FunctionNode) String() string {
	return fmt.Sprintf("%s(%s) %s",
		f.name, strings.Join(f.params, ", "), f.block)
}

type FunctionCallNode struct {
	callable Node
	args     []Node
}

func (f FunctionCallNode) Type() NodeType { return ndFunctionCall }
func (f FunctionCallNode) String() string {
	args := make([]string, len(f.args), len(f.args))
	for i, arg := range f.args {
		args[i] = arg.String()
	}

	return fmt.Sprintf("%s(%s)", f.callable, strings.Join(args, ", "))
}

type IdentNode struct {
	value string
}

func (i IdentNode) Type() NodeType { return ndIdent }
func (i IdentNode) String() string { return i.value }

type IntegerNode struct {
	value string
}

func (i IntegerNode) Type() NodeType { return ndInteger }
func (i IntegerNode) String() string { return i.value }

type NullNode struct{}

func (n NullNode) Type() NodeType { return ndNull }
func (n NullNode) String() string { return "" }

type ParenNode struct{ node Node }

func (p ParenNode) Type() NodeType { return ndParen }
func (p ParenNode) String() string { return "(" + p.node.String() + ")" }

type StringNode struct {
	value string
}

func (s StringNode) Type() NodeType { return ndString }
func (s StringNode) String() string { return fmt.Sprintf("\"%s\"", s.value) }

type UnaryNode struct {
	oper    string
	node    Node
	postfix bool
}

func (u UnaryNode) Type() NodeType { return ndUnary }
func (u UnaryNode) String() string {
	if u.postfix {
		return fmt.Sprintf("%v%s", u.node, u.oper)
	}
	return fmt.Sprintf("%s%v", u.oper, u.node)
}

type VarDeclNode struct {
	vars []string
}

func (v VarDeclNode) Type() NodeType { return ndVarDecl }
func (v VarDeclNode) String() string {
	return fmt.Sprintf("auto %s;", strings.Join(v.vars, ", "))
}
<commit_msg>Add BinaryNode<commit_after>package main

import (
	"fmt"
	"strings"
)

type NodeType int

const (
	ndError NodeType = iota
	ndArrayAccess
	ndBinary
	ndBlock
	ndCharacter
	ndExtVarDecl
	ndExtVarInit
	ndFunction
	ndFunctionCall
	ndIdent
	ndInteger
	ndNull
	ndParen
	ndString
	ndUnary
	ndVarDecl
)

type Node interface {
	Type() NodeType
	String() string
}

type ArrayAccessNode struct {
	array Node
	index Node
}

func (a ArrayAccessNode) Type() NodeType { return ndArrayAccess }
func (a ArrayAccessNode) String() string {
	return fmt.Sprintf("%s[%s]", a.array, a.index)
}

type BinaryNode struct {
	left, right Node
	oper        string
}

func (b BinaryNode) Type() NodeType { return ndBinary }
func (b BinaryNode) String() string {
	return fmt.Sprintf("%v %s %v",
		b.left, b.oper, b.right)
}

// '{' node* '}'
type BlockNode struct {
	nodes []Node
}

func (b BlockNode) Type() NodeType { return ndBlock }
func (b BlockNode) String() string {
	str := "{\n"

	for _, node := range b.nodes {
		str += fmt.Sprintf("\t%v\n", node)
	}

	str += "}"
	return str
}

type CharacterNode struct {
	value string
}

func (c CharacterNode) Type() NodeType { return ndCharacter }
func (c CharacterNode) String() string { return fmt.Sprintf("'%s'", c.value) }

type ExternVarDeclNode struct {
	names []string
}

func (e ExternVarDeclNode) Type() NodeType { return ndExtVarDecl }
func (e ExternVarDeclNode) String() string {
	return fmt.Sprintf("extrn %s;", strings.Join(e.names, ", "))
}

// name value ';'
type ExternVarInitNode struct {
	name  string
	value Node
}

func (e ExternVarInitNode) Type() NodeType { return ndExtVarInit }
func (e ExternVarInitNode) String() string {
	return fmt.Sprintf("%s %v;", e.name, e.value)
}

// name '(' (var (',' var)*) ? ')' block
type FunctionNode struct {
	name   string
	params []string
	block  BlockNode
}

func (f FunctionNode) Type() NodeType { return ndFunction }
func (f FunctionNode) String() string {
	return fmt.Sprintf("%s(%s) %s",
		f.name, strings.Join(f.params, ", "), f.block)
}

type FunctionCallNode struct {
	callable Node
	args     []Node
}

func (f FunctionCallNode) Type() NodeType { return ndFunctionCall }
func (f FunctionCallNode) String() string {
	args := make([]string, len(f.args), len(f.args))
	for i, arg := range f.args {
		args[i] = arg.String()
	}

	return fmt.Sprintf("%s(%s)", f.callable, strings.Join(args, ", "))
}

type IdentNode struct {
	value string
}

func (i IdentNode) Type() NodeType { return ndIdent }
func (i IdentNode) String() string { return i.value }

type IntegerNode struct {
	value string
}

func (i IntegerNode) Type() NodeType { return ndInteger }
func (i IntegerNode) String() string { return i.value }

type NullNode struct{}

func (n NullNode) Type() NodeType { return ndNull }
func (n NullNode) String() string { return "" }

type ParenNode struct{ node Node }

func (p ParenNode) Type() NodeType { return ndParen }
func (p ParenNode) String() string { return "(" + p.node.String() + ")" }

type StringNode struct {
	value string
}

func (s StringNode) Type() NodeType { return ndString }
func (s StringNode) String() string { return fmt.Sprintf("\"%s\"", s.value) }

type UnaryNode struct {
	oper    string
	node    Node
	postfix bool
}

func (u UnaryNode) Type() NodeType { return ndUnary }
func (u UnaryNode) String() string {
	if u.postfix {
		return fmt.Sprintf("%v%s", u.node, u.oper)
	}
	return fmt.Sprintf("%s%v", u.oper, u.node)
}

type VarDeclNode struct {
	vars []string
}

func (v VarDeclNode) Type() NodeType { return ndVarDecl }
func (v VarDeclNode) String() string {
	return fmt.Sprintf("auto %s;", strings.Join(v.vars, ", "))
}
<|endoftext|>