<commit_before>// Package ini implements functions for reading and writing INI files.
package ini

import (
	"bufio"
	"errors"
	"io"
	"regexp"
)

var (
	commentRegexp  = regexp.MustCompile(`^;.*`)
	propertyRegexp = regexp.MustCompile(`^(.+)=(.*)`)
	sectionRegexp  = regexp.MustCompile(`^\[(.+)\]$`)
)

// A Section is a map of property names to values in a section of an INI file.
type Section map[string]string

// A File is a map of section names to Sections. The "global" section
// (properties declared before a section declaration) can be accessed by using
// an empty string as the map key.
type File map[string]Section

// Read reads r as an INI file. Each line of r must either be a property
// ("name=value"), section ("[section]"), comment ("; comment"), or blank line.
// Property names are case-sensitive.
func Read(r io.Reader) (File, error) {
	scanner := bufio.NewScanner(r)
	file := make(File)
	section := ""
	file[section] = make(Section)

	for scanner.Scan() {
		line := scanner.Text()
		if m := propertyRegexp.FindStringSubmatch(line); m != nil {
			file[section][m[1]] = m[2]
		} else if line == "" || commentRegexp.MatchString(line) {
			// do nothing with blank lines and comment lines
		} else if m := sectionRegexp.FindStringSubmatch(line); m != nil {
			section = m[1]
			file[section] = make(Section)
		} else {
			return nil, errors.New("ini.Read(): invalid line: " + line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return file, nil
}

// Write writes f to w in INI format.
func (f File) Write(w io.Writer) error {
	buf := bufio.NewWriter(w)

	// write global properties
	if section := f[""]; section != nil {
		for name, value := range section {
			_, err := buf.WriteString(name + "=" + value + "\r\n")
			if err != nil {
				return err
			}
		}
	}

	// write sections
	for sectionName, section := range f {
		if sectionName == "" {
			continue // since we already wrote global properties
		}

		if _, err := buf.WriteString("[" + sectionName + "]\r\n"); err != nil {
			return err
		}

		for name, value := range section {
			_, err := buf.WriteString(name + "=" + value + "\r\n")
			if err != nil {
				return err
			}
		}
	}

	return buf.Flush()
}
<commit_msg>Update doc comment for ini.Read()<commit_after>// Package ini implements functions for reading and writing INI files.
package ini

import (
	"bufio"
	"errors"
	"io"
	"regexp"
)

var (
	commentRegexp  = regexp.MustCompile(`^;.*`)
	propertyRegexp = regexp.MustCompile(`^(.+)=(.*)`)
	sectionRegexp  = regexp.MustCompile(`^\[(.+)\]$`)
)

// A Section is a map of property names to values in a section of an INI file.
type Section map[string]string

// A File is a map of section names to Sections. The "global" section
// (properties declared before a section declaration) can be accessed by using
// an empty string as the map key.
type File map[string]Section

// Read reads r as an INI file. Each line of r must either be a property
// ("name=value"), section ("[section]"), comment (";comment"), or blank line.
// Property names are case-sensitive.
func Read(r io.Reader) (File, error) {
	scanner := bufio.NewScanner(r)
	file := make(File)
	section := ""
	file[section] = make(Section)

	for scanner.Scan() {
		line := scanner.Text()
		if m := propertyRegexp.FindStringSubmatch(line); m != nil {
			file[section][m[1]] = m[2]
		} else if line == "" || commentRegexp.MatchString(line) {
			// do nothing with blank lines and comment lines
		} else if m := sectionRegexp.FindStringSubmatch(line); m != nil {
			section = m[1]
			file[section] = make(Section)
		} else {
			return nil, errors.New("ini.Read(): invalid line: " + line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return file, nil
}

// Write writes f to w in INI format.
func (f File) Write(w io.Writer) error {
	buf := bufio.NewWriter(w)

	// write global properties
	if section := f[""]; section != nil {
		for name, value := range section {
			_, err := buf.WriteString(name + "=" + value + "\r\n")
			if err != nil {
				return err
			}
		}
	}

	// write sections
	for sectionName, section := range f {
		if sectionName == "" {
			continue // since we already wrote global properties
		}

		if _, err := buf.WriteString("[" + sectionName + "]\r\n"); err != nil {
			return err
		}

		for name, value := range section {
			_, err := buf.WriteString(name + "=" + value + "\r\n")
			if err != nil {
				return err
			}
		}
	}

	return buf.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"time"

	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dchest/uniuri"
	"github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"github.com/raggaer/castro/app/database"
	"github.com/raggaer/castro/app/lua"
	"github.com/raggaer/castro/app/models"
	"github.com/raggaer/castro/app/util"
	glua "github.com/yuin/gopher-lua"
	"io/ioutil"
	"path/filepath"
	"strings"
)

const (
	// configFileName the name of the application configuration file
	configFileName = "config.toml"

	// znoteTableName name of the znote table
	znoteTableName = "znote"
)

// znoteTable main znote installation table to look for
type znoteTable struct {
	Version   int
	Installed int64
}

// znoteAccount main znote accounts table
type znoteAccount struct {
	ID         uint64
	Account_id uint64
	Points     uint
}

// isInstalled check if application is installed
func isInstalled() bool {
	// Check if file exists
	_, err := os.Stat(configFileName)

	return err == nil
}

// isZnoteInstalled checks if znote_aac is already installed
func isZnoteInstalled(db *sqlx.DB) (bool, error) {
	// Check if table exists
	if _, err := db.Exec("DESCRIBE " + znoteTableName); err != nil {

		// Convert error to MySQL error type
		mErr, ok := err.(*mysql.MySQLError)

		// Check if table is installed
		if ok && mErr.Number == 1146 {

			return false, nil
		}

		return false, err
	}

	return true, nil
}

// installApplication runs the installation process
func installApplication() error {

	// Ask user for server directory location
	fmt.Print("Insert your server location: ")

	// Location holder
	var location string

	// Get user response
	_, err := fmt.Scanln(&location)

	if err != nil {
		return err
	}

	// Load config.lua file
	if err := lua.LoadConfig(
		filepath.Join(filepath.Join(location, "config.lua")),
	); err != nil {
		return err
	}

	// Connect to database
	db, err := database.Open(lua.Config.GetGlobal("mysqlUser").String(), lua.Config.GetGlobal("mysqlPass").String(), lua.Config.GetGlobal("mysqlDatabase").String())

	if err != nil {
		return err
	}

	// Ping database
	if err := db.Ping(); err != nil {
		return err
	}

	// Close database handle
	defer db.Close()

	// Get all sql files
	tables, err := ioutil.ReadDir(filepath.Join("install"))

	if err != nil {
		return err
	}

	// Loop files
	for _, table := range tables {

		// Check if table exists
		if _, err := db.Exec("DESCRIBE " + strings.TrimSuffix(table.Name(), ".sql")); err != nil {

			// Convert error to MySQL error type
			mErr, ok := err.(*mysql.MySQLError)

			// Check if table is installed
			if ok && mErr.Number == 1146 {

				// Read file
				buff, err := ioutil.ReadFile(filepath.Join("install", table.Name()))

				if err != nil {
					return err
				}

				// Execute query
				if _, err := db.Exec(string(buff)); err != nil {
					return err
				}

				continue
			}

			return err
		}
	}

	// Check if znote is installed
	z, err := isZnoteInstalled(db)

	if err != nil {
		return err
	}

	if z {

		// Znote accounts placeholder
		znoteAccounts := []znoteAccount{}

		// Get znote accounts
		if err := db.Select(&znoteAccounts, "SELECT id, account_id, points FROM znote_accounts ORDER BY id"); err != nil {
			return err
		}

		// Loop znote accounts
		for _, acc := range znoteAccounts {

			// Insert castro account from znote account
			if _, err := db.Exec("INSERT INTO castro_accounts (account_id, points) VALUES (?, ?)", acc.Account_id, acc.Points); err != nil {
				return err
			}
		}
	} else {

		// Normal accounts placeholder
		accountList := []models.Account{}

		// Get all accounts
		if err := db.Select(&accountList, "SELECT id FROM accounts ORDER BY id"); err != nil {
			return err
		}

		// Loop accounts
		for _, acc := range accountList {

			// Insert castro account from normal account
			if _, err := db.Exec("INSERT INTO castro_accounts (account_id) VALUES (?)", acc.ID); err != nil {
				return err
			}
		}
	}

	// Encode server map
	if err := util.EncodeMap(
		filepath.Join(location, "data", "world", lua.Config.GetGlobal("mapName").String() + ".otbm"),
		filepath.Join("engine", lua.Config.GetGlobal("mapName").String() + ".castro"),
	); err != nil {
		return err
	}

	// Create configuration file
	return createConfigFile(configFileName, location)
}

// createConfigFile encodes a configuration file with the given name and location
func createConfigFile(name, location string) error {
	// Get lua state
	luaState := glua.NewState()

	// Close state
	defer luaState.Close()

	// Create events metatable
	lua.SetEventsMetaTable(luaState)

	// Create storage metatable
	lua.SetStorageMetaTable(luaState)

	// Create time metatable
	lua.SetTimeMetaTable(luaState)

	// Create url metatable
	lua.SetURLMetaTable(luaState)

	// Create debug metatable
	lua.SetDebugMetaTable(luaState)

	// Create XML metatable
	lua.SetXMLMetaTable(luaState)

	// Create captcha metatable
	lua.SetCaptchaMetaTable(luaState)

	// Create crypto metatable
	lua.SetCryptoMetaTable(luaState)

	// Create validator metatable
	lua.SetValidatorMetaTable(luaState)

	// Create database metatable
	lua.SetDatabaseMetaTable(luaState)

	// Create config metatable
	lua.SetConfigMetaTable(luaState)

	// Create map metatable
	lua.SetMapMetaTable(luaState)

	// Create mail metatable
	lua.SetMailMetaTable(luaState)

	// Create cache metatable
	lua.SetCacheMetaTable(luaState)

	// Create reflect metatable
	lua.SetReflectMetaTable(luaState)

	// Create json metatable
	lua.SetJSONMetaTable(luaState)

	// Set config metatable
	lua.SetConfigGlobal(luaState)

	// Execute init file
	if err := luaState.DoFile(filepath.Join("engine", "install.lua")); err != nil {
		return err
	}

	// Create configuration file handle
	configFile, err := os.Create(name)
	if err != nil {
		return err
	}

	// Close file handle
	defer configFile.Close()

	// Get lua file table
	tbl := luaState.ToTable(-1)

	// Encode the given configuration struct into the file
	return toml.NewEncoder(configFile).Encode(util.Configuration{
		Version:      VERSION,
		CheckUpdates: true,
		Mode:         "dev",
		Port:         8080,
		URL:          "localhost",
		Datapack:     location,
		Cookies: util.CookieConfig{
			Name:     fmt.Sprintf("castro-%v", uniuri.NewLen(5)),
			MaxAge:   1000000,
			HashKey:  uniuri.NewLen(32),
			BlockKey: uniuri.NewLen(32),
		},
		Cache: util.CacheConfig{
			Default: time.Minute * 5,
			Purge:   time.Minute,
		},
		RateLimit: util.RateLimiterConfig{
			Number: 100,
			Time:   time.Minute,
		},
		Security: util.SecurityConfig{
			NonceEnabled: true,
			STS: "max-age=10000",
			XSS: "1; mode=block",
			Frame: "DENY",
			ContentType: "nosniff",
			ReferrerPolicy: "origin",
			CrossDomainPolicy: "none",
			CSP: util.ContentSecurityPolicyConfig{
				Default: []string{"none"},
				Frame: util.ContentSecurityPolicyType{
					SRC: []string{"https://www.google.com"},
				},
				Script: util.ContentSecurityPolicyType{
					Default: []string{"self"},
					SRC: []string{"https://www.google.com", "https://code.jquery.com", "https://cdn.datatables.net", "https://www.gstatic.com"},
				},
				Font: util.ContentSecurityPolicyType{
					Default: []string{"self"},
					SRC: []string{"http://fonts.gstatic.com", "http://fonts.googleapis.com"},
				},
				Connect: util.ContentSecurityPolicyType{
					Default: []string{"self"},
				},
				Style: util.ContentSecurityPolicyType{
					Default: []string{"unsafe-inline", "self"},
					SRC: []string{"http://fonts.googleapis.com", "https://cdn.datatables.net"},
				},
				Image: util.ContentSecurityPolicyType{
					Default: []string{"self"},
				},
			},
		},
		Custom: lua.TableToMap(tbl),
	})
}
<commit_msg>gofmt<commit_after>package main

import (
	"os"
	"time"

	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dchest/uniuri"
	"github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"github.com/raggaer/castro/app/database"
	"github.com/raggaer/castro/app/lua"
	"github.com/raggaer/castro/app/models"
	"github.com/raggaer/castro/app/util"
	glua "github.com/yuin/gopher-lua"
	"io/ioutil"
	"path/filepath"
	"strings"
)

const (
	// configFileName the name of the application configuration file
	configFileName = "config.toml"

	// znoteTableName name of the znote table
	znoteTableName = "znote"
)

// znoteTable main znote installation table to look for
type znoteTable struct {
	Version   int
	Installed int64
}

// znoteAccount main znote accounts table
type znoteAccount struct {
	ID         uint64
	Account_id uint64
	Points     uint
}

// isInstalled check if application is installed
func isInstalled() bool {
	// Check if file exists
	_, err := os.Stat(configFileName)

	return err == nil
}

// isZnoteInstalled checks if znote_aac is already installed
func isZnoteInstalled(db *sqlx.DB) (bool, error) {
	// Check if table exists
	if _, err := db.Exec("DESCRIBE " + znoteTableName); err != nil {

		// Convert error to MySQL error type
		mErr, ok := err.(*mysql.MySQLError)

		// Check if table is installed
		if ok && mErr.Number == 1146 {

			return false, nil
		}

		return false, err
	}

	return true, nil
}

// installApplication runs the installation process
func installApplication() error {

	// Ask user for server directory location
	fmt.Print("Insert your server location: ")

	// Location holder
	var location string

	// Get user response
	_, err := fmt.Scanln(&location)

	if err != nil {
		return err
	}

	// Load config.lua file
	if err := lua.LoadConfig(
		filepath.Join(filepath.Join(location, "config.lua")),
	); err != nil {
		return err
	}

	// Connect to database
	db, err := database.Open(lua.Config.GetGlobal("mysqlUser").String(), lua.Config.GetGlobal("mysqlPass").String(), lua.Config.GetGlobal("mysqlDatabase").String())

	if err != nil {
		return err
	}

	// Ping database
	if err := db.Ping(); err != nil {
		return err
	}

	// Close database handle
	defer db.Close()

	// Get all sql files
	tables, err := ioutil.ReadDir(filepath.Join("install"))

	if err != nil {
		return err
	}

	// Loop files
	for _, table := range tables {

		// Check if table exists
		if _, err := db.Exec("DESCRIBE " + strings.TrimSuffix(table.Name(), ".sql")); err != nil {

			// Convert error to MySQL error type
			mErr, ok := err.(*mysql.MySQLError)

			// Check if table is installed
			if ok && mErr.Number == 1146 {

				// Read file
				buff, err := ioutil.ReadFile(filepath.Join("install", table.Name()))

				if err != nil {
					return err
				}

				// Execute query
				if _, err := db.Exec(string(buff)); err != nil {
					return err
				}

				continue
			}

			return err
		}
	}

	// Check if znote is installed
	z, err := isZnoteInstalled(db)

	if err != nil {
		return err
	}

	if z {

		// Znote accounts placeholder
		znoteAccounts := []znoteAccount{}

		// Get znote accounts
		if err := db.Select(&znoteAccounts, "SELECT id, account_id, points FROM znote_accounts ORDER BY id"); err != nil {
			return err
		}

		// Loop znote accounts
		for _, acc := range znoteAccounts {

			// Insert castro account from znote account
			if _, err := db.Exec("INSERT INTO castro_accounts (account_id, points) VALUES (?, ?)", acc.Account_id, acc.Points); err != nil {
				return err
			}
		}
	} else {

		// Normal accounts placeholder
		accountList := []models.Account{}

		// Get all accounts
		if err := db.Select(&accountList, "SELECT id FROM accounts ORDER BY id"); err != nil {
			return err
		}

		// Loop accounts
		for _, acc := range accountList {

			// Insert castro account from normal account
			if _, err := db.Exec("INSERT INTO castro_accounts (account_id) VALUES (?)", acc.ID); err != nil {
				return err
			}
		}
	}

	// Encode server map
	if err := util.EncodeMap(
		filepath.Join(location, "data", "world", lua.Config.GetGlobal("mapName").String()+".otbm"),
		filepath.Join("engine", lua.Config.GetGlobal("mapName").String()+".castro"),
	); err != nil {
		return err
	}

	// Create configuration file
	return createConfigFile(configFileName, location)
}

// createConfigFile encodes a configuration file with the given name and location
func createConfigFile(name, location string) error {
	// Get lua state
	luaState := glua.NewState()

	// Close state
	defer luaState.Close()

	// Create events metatable
	lua.SetEventsMetaTable(luaState)

	// Create storage metatable
	lua.SetStorageMetaTable(luaState)

	// Create time metatable
	lua.SetTimeMetaTable(luaState)

	// Create url metatable
	lua.SetURLMetaTable(luaState)

	// Create debug metatable
	lua.SetDebugMetaTable(luaState)

	// Create XML metatable
	lua.SetXMLMetaTable(luaState)

	// Create captcha metatable
	lua.SetCaptchaMetaTable(luaState)

	// Create crypto metatable
	lua.SetCryptoMetaTable(luaState)

	// Create validator metatable
	lua.SetValidatorMetaTable(luaState)

	// Create database metatable
	lua.SetDatabaseMetaTable(luaState)

	// Create config metatable
	lua.SetConfigMetaTable(luaState)

	// Create map metatable
	lua.SetMapMetaTable(luaState)

	// Create mail metatable
	lua.SetMailMetaTable(luaState)

	// Create cache metatable
	lua.SetCacheMetaTable(luaState)

	// Create reflect metatable
	lua.SetReflectMetaTable(luaState)

	// Create json metatable
	lua.SetJSONMetaTable(luaState)

	// Set config metatable
	lua.SetConfigGlobal(luaState)

	// Execute init file
	if err := luaState.DoFile(filepath.Join("engine", "install.lua")); err != nil {
		return err
	}

	// Create configuration file handle
	configFile, err := os.Create(name)
	if err != nil {
		return err
	}

	// Close file handle
	defer configFile.Close()

	// Get lua file table
	tbl := luaState.ToTable(-1)

	// Encode the given configuration struct into the file
	return toml.NewEncoder(configFile).Encode(util.Configuration{
		Version:      VERSION,
		CheckUpdates: true,
		Mode:         "dev",
		Port:         8080,
		URL:          "localhost",
		Datapack:     location,
		Cookies: util.CookieConfig{
			Name:     fmt.Sprintf("castro-%v", uniuri.NewLen(5)),
			MaxAge:   1000000,
			HashKey:  uniuri.NewLen(32),
			BlockKey: uniuri.NewLen(32),
		},
		Cache: util.CacheConfig{
			Default: time.Minute * 5,
			Purge:   time.Minute,
		},
		RateLimit: util.RateLimiterConfig{
			Number: 100,
			Time:   time.Minute,
		},
		Security: util.SecurityConfig{
			NonceEnabled:      true,
			STS:               "max-age=10000",
			XSS:               "1; mode=block",
			Frame:             "DENY",
			ContentType:       "nosniff",
			ReferrerPolicy:    "origin",
			CrossDomainPolicy: "none",
			CSP: util.ContentSecurityPolicyConfig{
				Default: []string{"none"},
				Frame: util.ContentSecurityPolicyType{
					SRC: []string{"https://www.google.com"},
				},
				Script: util.ContentSecurityPolicyType{
					Default: []string{"self"},
					SRC:     []string{"https://www.google.com", "https://code.jquery.com", "https://cdn.datatables.net", "https://www.gstatic.com"},
				},
				Font: util.ContentSecurityPolicyType{
					Default: []string{"self"},
					SRC:     []string{"http://fonts.gstatic.com", "http://fonts.googleapis.com"},
				},
				Connect: util.ContentSecurityPolicyType{
					Default: []string{"self"},
				},
				Style: util.ContentSecurityPolicyType{
					Default: []string{"unsafe-inline", "self"},
					SRC:     []string{"http://fonts.googleapis.com", "https://cdn.datatables.net"},
				},
				Image: util.ContentSecurityPolicyType{
					Default: []string{"self"},
				},
			},
		},
		Custom: lua.TableToMap(tbl),
	})
}
<|endoftext|>


<commit_before>package agent

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
	"time"

	"github.com/hashicorp/nomad/testutil"
	"github.com/mitchellh/cli"
	"github.com/stretchr/testify/require"
)

type MockDiscover struct {
	ReceivedAddrs string
}

const stubAddress = "127.0.0.1"

func (m *MockDiscover) Addrs(s string, l *log.Logger) ([]string, error) {
	m.ReceivedAddrs = s
	return []string{stubAddress}, nil
}
func (m *MockDiscover) Help() string { return "" }
func (m *MockDiscover) Names() []string {
	return []string{""}
}

func TestRetryJoin_Integration(t *testing.T) {
	t.Parallel()

	// Create two agents and have one retry join the other
	agent := NewTestAgent(t, t.Name(), nil)
	defer agent.Shutdown()

	agent2 := NewTestAgent(t, t.Name(), func(c *Config) {
		c.NodeName = "foo"
		if c.Server.ServerJoin == nil {
			c.Server.ServerJoin = &ServerJoin{}
		}
		c.Server.ServerJoin.RetryJoin = []string{agent.Config.normalizedAddrs.Serf}
		c.Server.ServerJoin.RetryInterval = 1 * time.Second
	})
	defer agent2.Shutdown()

	// Create a fake command and have it wrap the second agent and run the retry
	// join handler
	cmd := &Command{
		Ui: &cli.BasicUi{
			Reader:      os.Stdin,
			Writer:      os.Stdout,
			ErrorWriter: os.Stderr,
		},
		agent: agent2.Agent,
	}

	if err := cmd.handleRetryJoin(agent2.Config); err != nil {
		t.Fatalf("handleRetryJoin failed: %v", err)
	}

	// Ensure the retry join occured.
	testutil.WaitForResult(func() (bool, error) {
		mem := agent.server.Members()
		if len(mem) != 2 {
			return false, fmt.Errorf("bad :%#v", mem)
		}
		return true, nil
	}, func(err error) {
		t.Fatalf(err.Error())
	})
}

func TestRetryJoin_Server_NonCloud(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	joiner := retryJoiner{
		discover:      &MockDiscover{},
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Server_Cloud(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"provider=aws, tag_value=foo"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	mockDiscover := &MockDiscover{}
	joiner := retryJoiner{
		discover:      mockDiscover,
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal("provider=aws, tag_value=foo", mockDiscover.ReceivedAddrs)
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Server_MixedProvider(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"provider=aws, tag_value=foo", "127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	mockDiscover := &MockDiscover{}
	joiner := retryJoiner{
		discover:      mockDiscover,
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(2, len(output))
	require.Equal("provider=aws, tag_value=foo", mockDiscover.ReceivedAddrs)
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Client(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	joiner := retryJoiner{
		discover:      &MockDiscover{},
		clientJoin:    mockJoin,
		clientEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Validate(t *testing.T) {
	t.Parallel()
	type validateExpect struct {
		config  *Config
		isValid bool
		reason  string
	}

	scenarios := []*validateExpect{
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					RetryJoin:        []string{"127.0.0.1"},
					RetryMaxAttempts: 0,
					RetryInterval:    0,
					StartJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_join is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					StartJoin:        []string{"127.0.0.1"},
					RetryMaxAttempts: 0,
					RetryInterval:    0,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if start_join is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					StartJoin:        []string{},
					RetryMaxAttempts: 1,
					RetryInterval:    0,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_max_attempts is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    time.Duration(1),
						StartJoin:        []string{},
					},
					StartJoin:        []string{},
					RetryMaxAttempts: 0,
					RetryInterval:    3 * time.Second,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_interval is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{"127.0.0.1"},
					},
				},
			},
			isValid: false,
			reason:  "start_join and retry_join should not both be defined",
		},
		{
			config: &Config{
				Client: &ClientConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{"127.0.0.1"},
					},
				},
			},
			isValid: false,
			reason:  "start_join should not be defined on the client",
		},
		{
			config: &Config{
				Client: &ClientConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
					},
				},
			},
			isValid: true,
			reason:  "client server_join should be valid",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 1,
						RetryInterval:    1,
						StartJoin:        []string{},
					},
				},
			},
			isValid: true,
			reason:  "server server_join should be valid",
		},
	}

	joiner := retryJoiner{}
	for _, scenario := range scenarios {
		t.Run(scenario.reason, func(t *testing.T) {
			err := joiner.Validate(scenario.config)
			if scenario.isValid {
				require.NoError(t, err)
			} else {
				require.Error(t, err)
			}
		})
	}
}
<commit_msg>spelling<commit_after>package agent

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
	"time"

	"github.com/hashicorp/nomad/testutil"
	"github.com/mitchellh/cli"
	"github.com/stretchr/testify/require"
)

type MockDiscover struct {
	ReceivedAddrs string
}

const stubAddress = "127.0.0.1"

func (m *MockDiscover) Addrs(s string, l *log.Logger) ([]string, error) {
	m.ReceivedAddrs = s
	return []string{stubAddress}, nil
}
func (m *MockDiscover) Help() string { return "" }
func (m *MockDiscover) Names() []string {
	return []string{""}
}

func TestRetryJoin_Integration(t *testing.T) {
	t.Parallel()

	// Create two agents and have one retry join the other
	agent := NewTestAgent(t, t.Name(), nil)
	defer agent.Shutdown()

	agent2 := NewTestAgent(t, t.Name(), func(c *Config) {
		c.NodeName = "foo"
		if c.Server.ServerJoin == nil {
			c.Server.ServerJoin = &ServerJoin{}
		}
		c.Server.ServerJoin.RetryJoin = []string{agent.Config.normalizedAddrs.Serf}
		c.Server.ServerJoin.RetryInterval = 1 * time.Second
	})
	defer agent2.Shutdown()

	// Create a fake command and have it wrap the second agent and run the retry
	// join handler
	cmd := &Command{
		Ui: &cli.BasicUi{
			Reader:      os.Stdin,
			Writer:      os.Stdout,
			ErrorWriter: os.Stderr,
		},
		agent: agent2.Agent,
	}

	if err := cmd.handleRetryJoin(agent2.Config); err != nil {
		t.Fatalf("handleRetryJoin failed: %v", err)
	}

	// Ensure the retry join occurred.
	testutil.WaitForResult(func() (bool, error) {
		mem := agent.server.Members()
		if len(mem) != 2 {
			return false, fmt.Errorf("bad :%#v", mem)
		}
		return true, nil
	}, func(err error) {
		t.Fatalf(err.Error())
	})
}

func TestRetryJoin_Server_NonCloud(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	joiner := retryJoiner{
		discover:      &MockDiscover{},
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Server_Cloud(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"provider=aws, tag_value=foo"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	mockDiscover := &MockDiscover{}
	joiner := retryJoiner{
		discover:      mockDiscover,
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal("provider=aws, tag_value=foo", mockDiscover.ReceivedAddrs)
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Server_MixedProvider(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"provider=aws, tag_value=foo", "127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	mockDiscover := &MockDiscover{}
	joiner := retryJoiner{
		discover:      mockDiscover,
		serverJoin:    mockJoin,
		serverEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(2, len(output))
	require.Equal("provider=aws, tag_value=foo", mockDiscover.ReceivedAddrs)
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Client(t *testing.T) {
	t.Parallel()
	require := require.New(t)

	serverJoin := &ServerJoin{
		RetryMaxAttempts: 1,
		RetryJoin:        []string{"127.0.0.1"},
	}

	var output []string

	mockJoin := func(s []string) (int, error) {
		output = s
		return 0, nil
	}

	joiner := retryJoiner{
		discover:      &MockDiscover{},
		clientJoin:    mockJoin,
		clientEnabled: true,
		logger:        log.New(ioutil.Discard, "", 0),
		errCh:         make(chan struct{}),
	}

	joiner.RetryJoin(serverJoin)

	require.Equal(1, len(output))
	require.Equal(stubAddress, output[0])
}

func TestRetryJoin_Validate(t *testing.T) {
	t.Parallel()
	type validateExpect struct {
		config  *Config
		isValid bool
		reason  string
	}

	scenarios := []*validateExpect{
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					RetryJoin:        []string{"127.0.0.1"},
					RetryMaxAttempts: 0,
					RetryInterval:    0,
					StartJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_join is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					StartJoin:        []string{"127.0.0.1"},
					RetryMaxAttempts: 0,
					RetryInterval:    0,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if start_join is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{},
					},
					StartJoin:        []string{},
					RetryMaxAttempts: 1,
					RetryInterval:    0,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_max_attempts is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    time.Duration(1),
						StartJoin:        []string{},
					},
					StartJoin:        []string{},
					RetryMaxAttempts: 0,
					RetryInterval:    3 * time.Second,
					RetryJoin:        []string{},
				},
			},
			isValid: false,
			reason:  "server_join cannot be defined if retry_interval is defined on the server stanza",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{"127.0.0.1"},
					},
				},
			},
			isValid: false,
			reason:  "start_join and retry_join should not both be defined",
		},
		{
			config: &Config{
				Client: &ClientConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
						StartJoin:        []string{"127.0.0.1"},
					},
				},
			},
			isValid: false,
			reason:  "start_join should not be defined on the client",
		},
		{
			config: &Config{
				Client: &ClientConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 0,
						RetryInterval:    0,
					},
				},
			},
			isValid: true,
			reason:  "client server_join should be valid",
		},
		{
			config: &Config{
				Server: &ServerConfig{
					ServerJoin: &ServerJoin{
						RetryJoin:        []string{"127.0.0.1"},
						RetryMaxAttempts: 1,
						RetryInterval:    1,
						StartJoin:        []string{},
					},
				},
			},
			isValid: true,
			reason:  "server server_join should be valid",
		},
	}

	joiner := retryJoiner{}
	for _, scenario := range scenarios {
		t.Run(scenario.reason, func(t *testing.T) {
			err := joiner.Validate(scenario.config)
			if scenario.isValid {
				require.NoError(t, err)
			} else {
				require.Error(t, err)
			}
		})
	}
}
<|endoftext|>


<commit_before>package command_runner

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
)

type CommandRunner interface {
	Run(*exec.Cmd) error
	Start(*exec.Cmd) error
	Wait(*exec.Cmd) error
	Kill(*exec.Cmd) error
	ServerRoot() string
}

type RealCommandRunner struct {
	debug bool
}

type CommandNotRunningError struct {
	cmd *exec.Cmd
}

func (e CommandNotRunningError) Error() string {
	return fmt.Sprintf("command is not running: %#v", e.cmd)
}

func New(debug bool) *RealCommandRunner {
	return &RealCommandRunner{debug}
}

func (r *RealCommandRunner) Run(cmd *exec.Cmd) error {
	if r.debug {
		log.Printf("\x1b[40;36mexecuting: %s\x1b[0m\n", prettyCommand(cmd))
		r.tee(cmd)
	}

	err := r.resolve(cmd).Run()

	if r.debug {
		if err != nil {
			log.Printf("\x1b[40;31mcommand failed: %s\x1b[0m\n", err)
		} else {
			log.Printf("\x1b[40;32mcommand succeeded\x1b[0m\n")
		}
	}

	return err
}

func (r *RealCommandRunner) Start(cmd *exec.Cmd) error {
	if r.debug {
		log.Printf("\x1b[40;36mspawning: %s\x1b[0m\n", prettyCommand(cmd))
		r.tee(cmd)
	}

	err := r.resolve(cmd).Start()

	if r.debug {
		if err != nil {
			log.Printf("\x1b[40;31mspawning failed: %s\x1b[0m\n", err)
		} else {
			log.Printf("\x1b[40;32mspawning succeeded\x1b[0m\n")
		}
	}

	return err
}

func (r *RealCommandRunner) Wait(cmd *exec.Cmd) error {
	return cmd.Wait()
}

func (r *RealCommandRunner) Kill(cmd *exec.Cmd) error {
	if cmd.Process == nil {
		return CommandNotRunningError{cmd}
	}

	return cmd.Process.Kill()
}

func (r *RealCommandRunner) ServerRoot() string {
	return "/"
}

func (r *RealCommandRunner) tee(cmd *exec.Cmd) {
	if cmd.Stderr == nil {
		cmd.Stderr = os.Stderr
	} else if cmd.Stderr != nil {
		cmd.Stderr = io.MultiWriter(cmd.Stderr, os.Stderr)
	}

	if cmd.Stdout == nil {
		cmd.Stdout = os.Stdout
	} else if cmd.Stdout != nil {
		cmd.Stdout = io.MultiWriter(cmd.Stdout, os.Stdout)
	}
}

func (r *RealCommandRunner) resolve(cmd *exec.Cmd) *exec.Cmd {
	originalPath := cmd.Path

	path, err := exec.LookPath(cmd.Path)
	if err != nil {
		path = cmd.Path
	}

	cmd.Path = path

	cmd.Args = append([]string{originalPath}, cmd.Args...)

	return cmd
}

func prettyCommand(cmd *exec.Cmd) string {
	return fmt.Sprintf("%v %s %v", cmd.Env, cmd.Path, cmd.Args)
}
<commit_msg>command runner: print stdout to stderr for debug<commit_after>package command_runner

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
)

type CommandRunner interface {
	Run(*exec.Cmd) error
	Start(*exec.Cmd) error
	Wait(*exec.Cmd) error
	Kill(*exec.Cmd) error
	ServerRoot() string
}

type RealCommandRunner struct {
	debug bool
}

type CommandNotRunningError struct {
	cmd *exec.Cmd
}

func (e CommandNotRunningError) Error() string {
	return fmt.Sprintf("command is not running: %#v", e.cmd)
}

func New(debug bool) *RealCommandRunner {
	return &RealCommandRunner{debug}
}

func (r *RealCommandRunner) Run(cmd *exec.Cmd) error {
	if r.debug {
		log.Printf("\x1b[40;36mexecuting: %s\x1b[0m\n", prettyCommand(cmd))
		r.tee(cmd)
	}

	err := r.resolve(cmd).Run()

	if r.debug {
		if err != nil {
			log.Printf("\x1b[40;31mcommand failed: %s\x1b[0m\n", err)
		} else {
			log.Printf("\x1b[40;32mcommand succeeded\x1b[0m\n")
		}
	}

	return err
}

func (r *RealCommandRunner) Start(cmd *exec.Cmd) error {
	if r.debug {
		log.Printf("\x1b[40;36mspawning: %s\x1b[0m\n", prettyCommand(cmd))
		r.tee(cmd)
	}

	err := r.resolve(cmd).Start()

	if r.debug {
		if err != nil {
			log.Printf("\x1b[40;31mspawning failed: %s\x1b[0m\n", err)
		} else {
			log.Printf("\x1b[40;32mspawning succeeded\x1b[0m\n")
		}
	}

	return err
}

func (r *RealCommandRunner) Wait(cmd *exec.Cmd) error {
	return cmd.Wait()
}

func (r *RealCommandRunner) Kill(cmd *exec.Cmd) error {
	if cmd.Process == nil {
		return CommandNotRunningError{cmd}
	}

	return cmd.Process.Kill()
}

func (r *RealCommandRunner) ServerRoot() string {
	return "/"
}

func (r *RealCommandRunner) tee(cmd *exec.Cmd) {
	if cmd.Stderr == nil {
		cmd.Stderr = os.Stderr
	} else if cmd.Stderr != nil {
		cmd.Stderr = io.MultiWriter(cmd.Stderr, os.Stderr)
	}

	if cmd.Stdout == nil {
		cmd.Stdout = os.Stderr

	} else if cmd.Stdout != nil {
		cmd.Stdout = io.MultiWriter(cmd.Stdout, os.Stderr)
	}
}

func (r *RealCommandRunner) resolve(cmd *exec.Cmd) *exec.Cmd {
	originalPath := cmd.Path

	path, err := exec.LookPath(cmd.Path)
	if err != nil {
		path = cmd.Path
	}

	cmd.Path = path

	cmd.Args = append([]string{originalPath}, cmd.Args...)

	return cmd
}

func prettyCommand(cmd *exec.Cmd) string {
	return fmt.Sprintf("%v %s %v", cmd.Env, cmd.Path, cmd.Args)
}
<|endoftext|>


<commit_before>// Package iso8211 implements ISO 8211 parsing. It is targeted to NOAA
// IHO S-57 format vector chart files.
package iso8211

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

type RawHeader struct {
	Record_length                    [5]byte
	Interchange_level                byte
	Leader_id                        byte
	InLineCode                       byte
	Version                          byte
	Application_indicator            byte
	Field_control_length             [2]byte
	Base_address                     [5]byte
	Extended_character_set_indicator [3]byte
	Size_of_field_length             byte
	Size_of_field_position           byte
	Reserved                         byte
	Size_of_field_tag                byte
}

type DirEntry struct {
	Tag      []byte
	Length   int
	Position int
}

type Header struct {
	Record_length                        uint64
	Interchange_level                    byte
	Leader_id                            byte
	InLineCode                           byte
	Version                              byte
	Application_indicator                byte
	Field_control_length                 uint64
	Base_address                         uint64
	Extended_character_set_indicator     [3]byte
	Length_size, Position_size, Tag_size int8
	Entries                              []DirEntry
}

type LeadRecord struct {
	Header     Header
	FieldTypes map[string]FieldType
}

type Field struct {
	Tag       string
	Length    int
	Position  int
	FieldType FieldType
	SubFields []interface{}
}

type DataRecord struct {
	Header Header
	Lead   *LeadRecord
	Fields []Field
}

type RawRecordHeader struct {
	Data_structure     byte
	Data_type          byte
	Auxiliary_controls [2]byte
	Printable_ft       byte
	Printable_ut       byte
	Escape_seq         [3]byte
}

type SubFieldType struct {
	Kind reflect.Kind
	Size int
	Tag  string
}

type FieldType struct {
	Tag                string
	Length             int
	Position           int
	Data_structure     byte
	Data_type          byte
	Auxiliary_controls [2]byte
	Printable_ft       byte
	Printable_ut       byte
	Escape_seq         [3]byte
	Name               string
	Array_descriptor   string
	Format_controls    string
	SubFields          []SubFieldType
}

func (header *Header) Read(file io.Reader) error {
	var err error
	var ddr RawHeader
	ddrSize := uint64(binary.Size(ddr))
	// Read the header
	err = binary.Read(file, binary.LittleEndian, &ddr)
	if err != nil {
		return err
	}
	header.Record_length, _ = strconv.ParseUint(string(ddr.Record_length[:]), 10, 64)
	header.Interchange_level = ddr.Interchange_level
	header.Leader_id = ddr.Leader_id
	header.InLineCode = ddr.InLineCode
	header.Version = ddr.Version
	header.Application_indicator = ddr.Application_indicator
	header.Field_control_length, _ = strconv.ParseUint(string(ddr.Field_control_length[:]), 10, 64)
	header.Base_address, _ = strconv.ParseUint(string(ddr.Base_address[:]), 10, 64)
	header.Length_size = int8(ddr.Size_of_field_length - '0')
	header.Position_size = int8(ddr.Size_of_field_position - '0')
	header.Tag_size = int8(ddr.Size_of_field_tag - '0')
	// Read the directory
	entries := (header.Base_address - 1 - ddrSize) / uint64(header.Length_size+header.Position_size+header.Tag_size)
	header.Entries = make([]DirEntry, entries)
	dir := make([]byte, header.Base_address-ddrSize)
	file.Read(dir)
	buf := bytes.NewBuffer(dir)
	for idx := uint64(0); idx < entries; idx++ {
		header.Entries[idx].Tag = buf.Next(int(header.Tag_size))
		header.Entries[idx].Length, _ = strconv.Atoi(string(buf.Next(int(header.Length_size))[:]))
		header.Entries[idx].Position, _ = strconv.Atoi(string(buf.Next(int(header.Position_size))[:]))
	}
	return err
}

func (lead *LeadRecord) Read(file io.Reader) error {
	var err error
	err = lead.Header.Read(file)
	if lead.Header.Leader_id != 'L' {
		return errors.New("Record is not a Lead record")
	}
	err = lead.ReadFields(file)
	return err
}

func (lead *LeadRecord) ReadFields(file io.Reader) error {
	var err error
	lead.FieldTypes = make(map[string]FieldType, len(lead.Header.Entries))
	for _, d := range lead.Header.Entries {
		field := FieldType{Tag: string(d.Tag), Length: d.Length, Position: d.Position}
		field.Read(file)
		lead.FieldTypes[field.Tag] = field
	}
	return err
}

func (field *Field) Read(file io.Reader) error {
	var err error
	data := make([]byte, field.Length)
	file.Read(data)
	if field.FieldType.Tag != "" {
		field.SubFields = field.FieldType.Decode(data[:field.Length-1])
	}
	return err
}

func (data *DataRecord) Read(file io.Reader) error {
	var err error
	err = data.Header.Read(file)
	if err != nil {
		return err
	}
	if data.Header.Leader_id != 'D' {
		return errors.New("Record is not a Data record")
	}
	err = data.ReadFields(file)
	return err
}

func (data *DataRecord) ReadFields(file io.Reader) error {
	var err error
	data.Fields = make([]Field, len(data.Header.Entries))
	for i, d := range data.Header.Entries {
		field := Field{Tag: string(d.Tag), Length: d.Length, Position: d.Position}
		if data.Lead != nil {
			field.FieldType = data.Lead.FieldTypes[field.Tag]
		}
		field.Read(file)
		data.Fields[i] = field
	}
	return err
}

func (dir *FieldType) Read(file io.Reader) error {
	var field RawRecordHeader
	err := binary.Read(file, binary.LittleEndian, &field)
	dir.Data_structure = field.Data_structure
	dir.Data_type = field.Data_type
	copy(field.Auxiliary_controls[:], dir.Auxiliary_controls[:])
	dir.Printable_ft = field.Printable_ft
	dir.Printable_ut = field.Printable_ut
	copy(field.Escape_seq[:], dir.Escape_seq[:])
	fdata := make([]byte, dir.Length-9)
	file.Read(fdata)
	desc := strings.Split(string(fdata[:dir.Length-10]), "\x1f")
	dir.Name = desc[0]
	dir.Array_descriptor = desc[1]
	if len(desc) > 2 {
		dir.Format_controls = desc[2]
	}
	return err
}

/*
Format parses the ISO-8211 format controls and array descriptors.

Section 7.2.2.1 of the IHO S-57 Publication.
http://www.iho.int/iho_pubs/standard/S-57Ed3.1/31Main.pdf

Array Descriptor and Format Controls. The array descriptor is a ! separated
list of tags describing the data field. If it begins with a * the tag list
is repeated. The format controls decribe the format of the data for each tag.

eg: Descriptor AGEN!FIDN!FIDS , Format (b12,b14,b12) is three binary encoded
integers. AGEN is an int16, FIDN an int32 and FIDS an int16. The 'b' indicates
binary int, '1' indicates unsigned, the second digit indicates the number of
bytes.
Decriptor *YCOO!XCOO, Format (2b24) is two binary encoded integers. Both are
int32s, the '2' after the 'b' indicates signed. The * in the descriptor
indicates that pair is repeated to fill the data field.

*/
func (dir *FieldType) Format() []SubFieldType {
	if dir.SubFields != nil {
		return dir.SubFields
	}
	var re = regexp.MustCompile(`(\d*)(\w+)\(*(\d*)\)*`)

	if len(dir.Format_controls) > 2 {
		var types []SubFieldType
		Tags := strings.Split(dir.Array_descriptor, "!")
		Tagidx := 0
		for _, a := range re.FindAllStringSubmatch(dir.Format_controls, -1) {
			i := 1
			if len(a[1]) > 0 {
				i, _ = strconv.Atoi(a[1])
			}
			var size int
			if len(a[3]) > 0 {
				size, _ = strconv.Atoi(a[3])
			}
			for ; i > 0; i-- {
				switch a[2] {
				case "A":
					types = append(types, SubFieldType{reflect.String, size, Tags[Tagidx]})
				case "I":
				case "R":
					types = append(types, SubFieldType{reflect.String, size, Tags[Tagidx]})
				case "B":
					types = append(types, SubFieldType{reflect.Array, size / 8, Tags[Tagidx]})
				case "b21":
					types = append(types, SubFieldType{reflect.Int8, 1, Tags[Tagidx]})
				case "b22":
					types = append(types, SubFieldType{reflect.Int16, 2, Tags[Tagidx]})
				case "b24":
					types = append(types, SubFieldType{reflect.Int32, 4, Tags[Tagidx]})
				case "b11":
					types = append(types, SubFieldType{reflect.Uint8, 1, Tags[Tagidx]})
				case "b12":
					types = append(types, SubFieldType{reflect.Uint16, 2, Tags[Tagidx]})
				case "b14":
					types = append(types, SubFieldType{reflect.Uint32, 4, Tags[Tagidx]})
				}
				Tagidx++
			}
		}
		dir.SubFields = types
	}
	return dir.SubFields
}

func (dir FieldType) Decode(buffer []byte) []interface{} {
	buf := bytes.NewBuffer(buffer)
	var values []interface{}
	for buf.Len() > 0 {
		for _, ftype := range dir.Format() {
			switch ftype.Kind {
			case reflect.Uint8:
				{
					var v uint8
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Uint16:
				{
					var v uint16
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Uint32:
				{
					var v uint32
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int8:
				{
					var v int8
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int16:
				{
					var v int16
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int32:
				{
					var v int32
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			default:
				{
					if ftype.Size == 0 {
						i, _ := buf.ReadString('\x1f')
						if len(i) > 0 {
							values = append(values, i[:len(i)-1])
						} else {
							values = append(values, "")
						}
					} else {
						i := buf.Next(ftype.Size)
						values = append(values, string(i))
					}
				}
			}
		}
	}
	return values
}
<commit_msg>Return err<commit_after>// Package iso8211 implements ISO 8211 parsing. It is targeted to NOAA
// IHO S-57 format vector chart files.
package iso8211

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

type RawHeader struct {
	Record_length                    [5]byte
	Interchange_level                byte
	Leader_id                        byte
	InLineCode                       byte
	Version                          byte
	Application_indicator            byte
	Field_control_length             [2]byte
	Base_address                     [5]byte
	Extended_character_set_indicator [3]byte
	Size_of_field_length             byte
	Size_of_field_position           byte
	Reserved                         byte
	Size_of_field_tag                byte
}

type DirEntry struct {
	Tag      []byte
	Length   int
	Position int
}

type Header struct {
	Record_length                        uint64
	Interchange_level                    byte
	Leader_id                            byte
	InLineCode                           byte
	Version                              byte
	Application_indicator                byte
	Field_control_length                 uint64
	Base_address                         uint64
	Extended_character_set_indicator     [3]byte
	Length_size, Position_size, Tag_size int8
	Entries                              []DirEntry
}

type LeadRecord struct {
	Header     Header
	FieldTypes map[string]FieldType
}

type Field struct {
	Tag       string
	Length    int
	Position  int
	FieldType FieldType
	SubFields []interface{}
}

type DataRecord struct {
	Header Header
	Lead   *LeadRecord
	Fields []Field
}

type RawRecordHeader struct {
	Data_structure     byte
	Data_type          byte
	Auxiliary_controls [2]byte
	Printable_ft       byte
	Printable_ut       byte
	Escape_seq         [3]byte
}

type SubFieldType struct {
	Kind reflect.Kind
	Size int
	Tag  string
}

type FieldType struct {
	Tag                string
	Length             int
	Position           int
	Data_structure     byte
	Data_type          byte
	Auxiliary_controls [2]byte
	Printable_ft       byte
	Printable_ut       byte
	Escape_seq         [3]byte
	Name               string
	Array_descriptor   string
	Format_controls    string
	SubFields          []SubFieldType
}

func (header *Header) Read(file io.Reader) error {
	var err error
	var ddr RawHeader
	ddrSize := uint64(binary.Size(ddr))
	// Read the header
	err = binary.Read(file, binary.LittleEndian, &ddr)
	if err != nil {
		return err
	}
	header.Record_length, _ = strconv.ParseUint(string(ddr.Record_length[:]), 10, 64)
	header.Interchange_level = ddr.Interchange_level
	header.Leader_id = ddr.Leader_id
	header.InLineCode = ddr.InLineCode
	header.Version = ddr.Version
	header.Application_indicator = ddr.Application_indicator
	header.Field_control_length, _ = strconv.ParseUint(string(ddr.Field_control_length[:]), 10, 64)
	header.Base_address, _ = strconv.ParseUint(string(ddr.Base_address[:]), 10, 64)
	header.Length_size = int8(ddr.Size_of_field_length - '0')
	header.Position_size = int8(ddr.Size_of_field_position - '0')
	header.Tag_size = int8(ddr.Size_of_field_tag - '0')
	// Read the directory
	entries := (header.Base_address - 1 - ddrSize) / uint64(header.Length_size+header.Position_size+header.Tag_size)
	header.Entries = make([]DirEntry, entries)
	dir := make([]byte, header.Base_address-ddrSize)
	file.Read(dir)
	buf := bytes.NewBuffer(dir)
	for idx := uint64(0); idx < entries; idx++ {
		header.Entries[idx].Tag = buf.Next(int(header.Tag_size))
		header.Entries[idx].Length, _ = strconv.Atoi(string(buf.Next(int(header.Length_size))[:]))
		header.Entries[idx].Position, _ = strconv.Atoi(string(buf.Next(int(header.Position_size))[:]))
	}
	return err
}

func (lead *LeadRecord) Read(file io.Reader) error {
	var err error
	err = lead.Header.Read(file)
	if err != nil {
		return err
	}
	if lead.Header.Leader_id != 'L' {
		return errors.New("Record is not a Lead record")
	}
	err = lead.ReadFields(file)
	return err
}

func (lead *LeadRecord) ReadFields(file io.Reader) error {
	var err error
	lead.FieldTypes = make(map[string]FieldType, len(lead.Header.Entries))
	for _, d := range lead.Header.Entries {
		field := FieldType{Tag: string(d.Tag), Length: d.Length, Position: d.Position}
		field.Read(file)
		lead.FieldTypes[field.Tag] = field
	}
	return err
}

func (field *Field) Read(file io.Reader) error {
	var err error
	data := make([]byte, field.Length)
	file.Read(data)
	if field.FieldType.Tag != "" {
		field.SubFields = field.FieldType.Decode(data[:field.Length-1])
	}
	return err
}

func (data *DataRecord) Read(file io.Reader) error {
	var err error
	err = data.Header.Read(file)
	if err != nil {
		return err
	}
	if data.Header.Leader_id != 'D' {
		return errors.New("Record is not a Data record")
	}
	err = data.ReadFields(file)
	return err
}

func (data *DataRecord) ReadFields(file io.Reader) error {
	var err error
	data.Fields = make([]Field, len(data.Header.Entries))
	for i, d := range data.Header.Entries {
		field := Field{Tag: string(d.Tag), Length: d.Length, Position: d.Position}
		if data.Lead != nil {
			field.FieldType = data.Lead.FieldTypes[field.Tag]
		}
		field.Read(file)
		data.Fields[i] = field
	}
	return err
}

func (dir *FieldType) Read(file io.Reader) error {
	var field RawRecordHeader
	err := binary.Read(file, binary.LittleEndian, &field)
	dir.Data_structure = field.Data_structure
	dir.Data_type = field.Data_type
	copy(field.Auxiliary_controls[:], dir.Auxiliary_controls[:])
	dir.Printable_ft = field.Printable_ft
	dir.Printable_ut = field.Printable_ut
	copy(field.Escape_seq[:], dir.Escape_seq[:])
	fdata := make([]byte, dir.Length-9)
	file.Read(fdata)
	desc := strings.Split(string(fdata[:dir.Length-10]), "\x1f")
	dir.Name = desc[0]
	dir.Array_descriptor = desc[1]
	if len(desc) > 2 {
		dir.Format_controls = desc[2]
	}
	return err
}

/*
Format parses the ISO-8211 format controls and array descriptors.

Section 7.2.2.1 of the IHO S-57 Publication.
http://www.iho.int/iho_pubs/standard/S-57Ed3.1/31Main.pdf

Array Descriptor and Format Controls. The array descriptor is a ! separated
list of tags describing the data field. If it begins with a * the tag list
is repeated. The format controls decribe the format of the data for each tag.

eg: Descriptor AGEN!FIDN!FIDS , Format (b12,b14,b12) is three binary encoded
integers. AGEN is an int16, FIDN an int32 and FIDS an int16. The 'b' indicates
binary int, '1' indicates unsigned, the second digit indicates the number of
bytes.
Decriptor *YCOO!XCOO, Format (2b24) is two binary encoded integers. Both are
int32s, the '2' after the 'b' indicates signed. The * in the descriptor
indicates that pair is repeated to fill the data field.

*/
func (dir *FieldType) Format() []SubFieldType {
	if dir.SubFields != nil {
		return dir.SubFields
	}
	var re = regexp.MustCompile(`(\d*)(\w+)\(*(\d*)\)*`)

	if len(dir.Format_controls) > 2 {
		var types []SubFieldType
		Tags := strings.Split(dir.Array_descriptor, "!")
		Tagidx := 0
		for _, a := range re.FindAllStringSubmatch(dir.Format_controls, -1) {
			i := 1
			if len(a[1]) > 0 {
				i, _ = strconv.Atoi(a[1])
			}
			var size int
			if len(a[3]) > 0 {
				size, _ = strconv.Atoi(a[3])
			}
			for ; i > 0; i-- {
				switch a[2] {
				case "A":
					types = append(types, SubFieldType{reflect.String, size, Tags[Tagidx]})
				case "I":
				case "R":
					types = append(types, SubFieldType{reflect.String, size, Tags[Tagidx]})
				case "B":
					types = append(types, SubFieldType{reflect.Array, size / 8, Tags[Tagidx]})
				case "b21":
					types = append(types, SubFieldType{reflect.Int8, 1, Tags[Tagidx]})
				case "b22":
					types = append(types, SubFieldType{reflect.Int16, 2, Tags[Tagidx]})
				case "b24":
					types = append(types, SubFieldType{reflect.Int32, 4, Tags[Tagidx]})
				case "b11":
					types = append(types, SubFieldType{reflect.Uint8, 1, Tags[Tagidx]})
				case "b12":
					types = append(types, SubFieldType{reflect.Uint16, 2, Tags[Tagidx]})
				case "b14":
					types = append(types, SubFieldType{reflect.Uint32, 4, Tags[Tagidx]})
				}
				Tagidx++
			}
		}
		dir.SubFields = types
	}
	return dir.SubFields
}

func (dir FieldType) Decode(buffer []byte) []interface{} {
	buf := bytes.NewBuffer(buffer)
	var values []interface{}
	for buf.Len() > 0 {
		for _, ftype := range dir.Format() {
			switch ftype.Kind {
			case reflect.Uint8:
				{
					var v uint8
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Uint16:
				{
					var v uint16
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Uint32:
				{
					var v uint32
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int8:
				{
					var v int8
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int16:
				{
					var v int16
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			case reflect.Int32:
				{
					var v int32
					binary.Read(buf, binary.LittleEndian, &v)
					values = append(values, v)
				}
			default:
				{
					if ftype.Size == 0 {
						i, _ := buf.ReadString('\x1f')
						if len(i) > 0 {
							values = append(values, i[:len(i)-1])
						} else {
							values = append(values, "")
						}
					} else {
						i := buf.Next(ftype.Size)
						values = append(values, string(i))
					}
				}
			}
		}
	}
	return values
}
<|endoftext|>


<commit_before>/*
Copyright 2015-2016, RadiantBlue Technologies, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package job provides JobManager helper functions.
package job

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

// StatusType is a string describing the state of the job.
type StatusType int

// Enumerate valid StatusType values.
const (
	Submitted StatusType = iota
	Running
	Success
	Cancelled
	Error
	Fail
)

var statuses = [...]string{"submitted", "running", "success", "cancelled", "error", "fail"}

func (status StatusType) String() string {
	return statuses[status]
}

// S3Bucket defines the expected JSON structure for S3 buckets.
// An S3 bucket can be used for source (input) and destination (output) files.
type S3Bucket struct {
	Bucket string `json:"bucket,omitempty"`
	Key    string `json:"key,omitempty"`
}

// InputMsg defines the expected input JSON structure.
// We currently support S3 input (bucket/key), though provider-specific (e.g.,
// GRiD) may be legitimate.
type InputMsg struct {
	Source      S3Bucket         `json:"source,omitempty"`
	Function    *string          `json:"function,omitempty"`
	Options     *json.RawMessage `json:"options,omitempty"`
	Destination S3Bucket         `json:"destination,omitempty"`
}

// OutputMsg defines the expected output JSON structure.
type OutputMsg struct {
	Input      InputMsg                    `json:"input,omitempty"`
	StartedAt  time.Time                   `json:"started_at,omitempty"`
	FinishedAt time.Time                   `json:"finished_at,omitempty"`
	Code       int                         `json:"code,omitempty"`
	Message    string                      `json:"message,omitempty"`
	Response   map[string]*json.RawMessage `json:"response,omitempty"`
}

// ResourceMetadata defines the metadata required to register the service.
type ResourceMetadata struct {
	Name                string    `json:"name"`
	ServiceID           string    `json:"serviceID"`
	Description         string    `json:"description"`
	URL                 string    `json:"url"`
	Networks            string    `json:"networks"`
	QoS                 string    `json:"qos"`
	Availability        string    `json:"availability"`
	Tags                string    `json:"tags"`
	ClassType           string    `json:"classType"`
	TermDate            time.Time `json:"termDate"`
	ClientCertRequired  bool      `json:"clientCertRequired"`
	CredentialsRequired bool      `json:"credentialsRequired"`
	PreAuthRequired     bool      `json:"preAuthRequired"`
	Contracts           string    `json:"contracts"`
	Method              string    `json:"method"`
	MimeType            string    `json:"mimeType"`
	Params              string    `json:"params"`
	Reason              string    `json:"reason"`
}

// UpdateMsg defines the expected output JSON structure for updating the JobManager.
type UpdateMsg struct {
	Status string `json:"status"`
}

// Update handles PDAL status updates.
func Update(t StatusType, r *http.Request) {
	log.Println("Setting job status as \"", t.String(), "\"")
	// var res UpdateMsg
	// res.Status = t.String()
	// //	url := "http://192.168.99.100:8080/manager"
	// url := r.URL.Path + `/manager`
	//
	// jsonStr, err := json.Marshal(res)
	// req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	// req.Header.Set("Content-Type", "application/json")
	//
	// client := &http.Client{}
	// resp, err := client.Do(req)
	// if err != nil {
	// 	panic(err)
	// }
	// defer resp.Body.Close()
}

/*
BadRequest handles bad requests.

All bad requests result in a failure in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusBadRequest (400) as well as a message to the OutputMsg, which is returned as JSON.
*/
func BadRequest(w http.ResponseWriter, r *http.Request, res OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusBadRequest)
	res.Code = http.StatusBadRequest
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Fail, r)
}

/*
InternalError handles internal server errors.

All internal server errors result in an error in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusInternalServerError (500) as well as a message to the OutputMsg, which is returned as JSON.
*/
func InternalError(w http.ResponseWriter, r *http.Request, res *OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusInternalServerError)
	res.Code = http.StatusInternalServerError
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Error, r)
}

/*
Okay handles successful calls.

All successful calls result in sucess in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusOK (200) as well as a message to the OutputMsg, which is returned as JSON.
*/
func Okay(w http.ResponseWriter, r *http.Request, res OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	res.Code = http.StatusOK
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Success, r)
}

// GetInputMsg provides a common means of parsing the InputMsg JSON.
func GetInputMsg(w http.ResponseWriter, r *http.Request, res OutputMsg) InputMsg {
	var msg InputMsg

	// There should always be a body, else how are we to know what to do? Throw
	// 400 if missing.
	if r.Body == nil {
		BadRequest(w, r, res, "No JSON")
	}

	// Throw 500 if we cannot read the body.
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		InternalError(w, r, &res, err.Error())
	}

	// Throw 400 if we cannot unmarshal the body as a valid InputMsg.
	if err := json.Unmarshal(b, &msg); err != nil {
		BadRequest(w, r, res, err.Error())
	}

	return msg
}
<commit_msg>Mark optional fields with omitempty<commit_after>/*
Copyright 2015-2016, RadiantBlue Technologies, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package job provides JobManager helper functions.
package job

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

// StatusType is a string describing the state of the job.
type StatusType int

// Enumerate valid StatusType values.
const (
	Submitted StatusType = iota
	Running
	Success
	Cancelled
	Error
	Fail
)

var statuses = [...]string{"submitted", "running", "success", "cancelled", "error", "fail"}

func (status StatusType) String() string {
	return statuses[status]
}

// S3Bucket defines the expected JSON structure for S3 buckets.
// An S3 bucket can be used for source (input) and destination (output) files.
type S3Bucket struct {
	Bucket string `json:"bucket,omitempty"`
	Key    string `json:"key,omitempty"`
}

// InputMsg defines the expected input JSON structure.
// We currently support S3 input (bucket/key), though provider-specific (e.g.,
// GRiD) may be legitimate.
type InputMsg struct {
	Source      S3Bucket         `json:"source,omitempty"`
	Function    *string          `json:"function,omitempty"`
	Options     *json.RawMessage `json:"options,omitempty"`
	Destination S3Bucket         `json:"destination,omitempty"`
}

// OutputMsg defines the expected output JSON structure.
type OutputMsg struct {
	Input      InputMsg                    `json:"input,omitempty"`
	StartedAt  time.Time                   `json:"started_at,omitempty"`
	FinishedAt time.Time                   `json:"finished_at,omitempty"`
	Code       int                         `json:"code,omitempty"`
	Message    string                      `json:"message,omitempty"`
	Response   map[string]*json.RawMessage `json:"response,omitempty"`
}

// ResourceMetadata defines the metadata required to register the service.
type ResourceMetadata struct {
	Name                string    `json:"name"`
	ServiceID           string    `json:"serviceID"`
	Description         string    `json:"description"`
	URL                 string    `json:"url"`
	Networks            string    `json:"networks"`
	QoS                 string    `json:"qos"`
	Availability        string    `json:"availability"`
	Tags                string    `json:"tags"`
	ClassType           string    `json:"classType"`
	TermDate            time.Time `json:"termDate,omitempty"`
	ClientCertRequired  bool      `json:"clientCertRequired,omitempty"`
	CredentialsRequired bool      `json:"credentialsRequired,omitempty"`
	PreAuthRequired     bool      `json:"preAuthRequired,omitempty"`
	Contracts           string    `json:"contracts,omitempty"`
	Method              string    `json:"method,omitempty"`
	MimeType            string    `json:"mimeType,omitempty"`
	Params              string    `json:"params,omitempty"`
	Reason              string    `json:"reason,omitempty"`
}

// UpdateMsg defines the expected output JSON structure for updating the JobManager.
type UpdateMsg struct {
	Status string `json:"status"`
}

// Update handles PDAL status updates.
func Update(t StatusType, r *http.Request) {
	log.Println("Setting job status as \"", t.String(), "\"")
	// var res UpdateMsg
	// res.Status = t.String()
	// //	url := "http://192.168.99.100:8080/manager"
	// url := r.URL.Path + `/manager`
	//
	// jsonStr, err := json.Marshal(res)
	// req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	// req.Header.Set("Content-Type", "application/json")
	//
	// client := &http.Client{}
	// resp, err := client.Do(req)
	// if err != nil {
	// 	panic(err)
	// }
	// defer resp.Body.Close()
}

/*
BadRequest handles bad requests.

All bad requests result in a failure in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusBadRequest (400) as well as a message to the OutputMsg, which is returned as JSON.
*/
func BadRequest(w http.ResponseWriter, r *http.Request, res OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusBadRequest)
	res.Code = http.StatusBadRequest
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Fail, r)
}

/*
InternalError handles internal server errors.

All internal server errors result in an error in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusInternalServerError (500) as well as a message to the OutputMsg, which is returned as JSON.
*/
func InternalError(w http.ResponseWriter, r *http.Request, res *OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusInternalServerError)
	res.Code = http.StatusInternalServerError
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Error, r)
}

/*
Okay handles successful calls.

All successful calls result in sucess in the eyes of the JobManager. The ResponseWriter echos some key aspects of the Request (e.g., input, start time) and appends StatusOK (200) as well as a message to the OutputMsg, which is returned as JSON.
*/
func Okay(w http.ResponseWriter, r *http.Request, res OutputMsg, message string) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	res.Code = http.StatusOK
	res.Message = message
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.Fatal(err)
	}
	Update(Success, r)
}

// GetInputMsg provides a common means of parsing the InputMsg JSON.
func GetInputMsg(w http.ResponseWriter, r *http.Request, res OutputMsg) InputMsg {
	var msg InputMsg

	// There should always be a body, else how are we to know what to do? Throw
	// 400 if missing.
	if r.Body == nil {
		BadRequest(w, r, res, "No JSON")
	}

	// Throw 500 if we cannot read the body.
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		InternalError(w, r, &res, err.Error())
	}

	// Throw 400 if we cannot unmarshal the body as a valid InputMsg.
	if err := json.Unmarshal(b, &msg); err != nil {
		BadRequest(w, r, res, err.Error())
	}

	return msg
}
<|endoftext|>


<commit_before>package policy

import (
	"sync/atomic"

	"github.com/coredns/coredns/plugin/pkg/trace"
	"github.com/infobloxopen/themis/pdp"
	"github.com/infobloxopen/themis/pep"
)

type pepCacheHitHandler struct{}

func (ch *pepCacheHitHandler) Handle(req interface{}, resp interface{}, err error) {
	log.Infof("PEP responding to PDP request from cache %+v", req)
}

func newPepCacheHitHandler() *pepCacheHitHandler {
	return &pepCacheHitHandler{}
}

// connect establishes connection to PDP server.
func (p *policyPlugin) connect() error {
	log.Infof("Connecting %v", p)

	for _, addr := range p.conf.endpoints {
		p.connAttempts[addr] = new(uint32)
	}

	opts := []pep.Option{
		pep.WithConnectionTimeout(p.conf.connTimeout),
		pep.WithConnectionStateNotification(p.connStateCb),
	}

	if p.conf.cacheTTL > 0 {
		if p.conf.cacheLimit > 0 {
			opts = append(opts, pep.WithCacheTTLAndMaxSize(p.conf.cacheTTL, p.conf.cacheLimit))
		} else {
			opts = append(opts, pep.WithCacheTTL(p.conf.cacheTTL))
		}
	}

	if p.conf.streams <= 0 || !p.conf.hotSpot {
		opts = append(opts, pep.WithRoundRobinBalancer(p.conf.endpoints...))
	}

	if p.conf.streams > 0 {
		opts = append(opts, pep.WithStreams(p.conf.streams))
		if p.conf.hotSpot {
			opts = append(opts, pep.WithHotSpotBalancer(p.conf.endpoints...))
		}
	}

	opts = append(opts, pep.WithAutoRequestSize(p.conf.autoReqSize))
	if p.conf.maxReqSize > 0 {
		opts = append(opts, pep.WithMaxRequestSize(uint32(p.conf.maxReqSize)))
	}

	if p.trace != nil {
		if t, ok := p.trace.(trace.Trace); ok {
			opts = append(opts, pep.WithTracer(t.Tracer()))
		}
	}

	if p.conf.log {
		opts = append(opts, pep.WithOnCacheHitHandler(newPepCacheHitHandler()))
	}

	p.pdp = pep.NewClient(opts...)
	return p.pdp.Connect("")
}

// closeConn terminates previously established connection.
func (p *policyPlugin) closeConn() {
	if p.pdp != nil {
		go func() {
			p.wg.Wait()
			p.pdp.Close()
		}()
	}
}

func (p *policyPlugin) validate(buf []pdp.AttributeAssignment, ah *attrHolder, lt int, dbg *dbgMessenger) (bool, error) {
	req := ah.attrList(buf, lt)
	if len(req) <= 0 {
		return true, nil
	}

	if p.conf.log {
		log.Infof("PDP request: %+v", req)
	}

	var respBuffer []pdp.AttributeAssignment
	if !p.conf.autoResAttrs {
		respBuffer = p.attrPool.Get()
		defer p.attrPool.Put(respBuffer)
	}

	res := pdp.Response{Obligations: respBuffer}
	err := p.pdp.Validate(req, &res)

	ah.resetAttribute(attrIndexPolicyAction)
	ah.resetAttribute(attrIndexRedirectTo)
	ah.resetAttribute(attrIndexLog)

	if err != nil {
		log.Errorf("Policy validation failed due to error: %s", err)
		if dbg != nil {
			dbg.appendDefaultDecision(ah.attrList(buf, attrListTypeDefDecision))
		} else {
			ah.resetAttrList(attrListTypeDefDecision)
		}
		return false, err
	}

	if p.conf.log {
		log.Infof("PDP response: %+v", res)
	}
	if dbg != nil {
		dbg.appendResponse(&res)
	}

	if res.Effect != pdp.EffectPermit && res.Effect != pdp.EffectDeny {
		log.Errorf("Policy validation failed due to PDP error: %s", res.Status)
		if dbg != nil {
			dbg.appendDefaultDecision(ah.attrList(buf, attrListTypeDefDecision))
		} else {
			ah.resetAttrList(attrListTypeDefDecision)
		}
		return false, err
	}

	ah.addAttrList(res.Obligations)

	return res.Effect == pdp.EffectPermit, nil
}

func (p *policyPlugin) connStateCb(addr string, state int, err error) {
	switch state {
	default:
		if err != nil {
			log.Infof("Unknown connection notification %s (%s)", addr, err)
		} else {
			log.Infof("Unknown connection notification %s", addr)
		}

	case pep.StreamingConnectionEstablished:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		atomic.StoreUint32(ptr, 0)

		log.Infof("Connected to %s", addr)

	case pep.StreamingConnectionBroken:
		log.Errorf("Connection to %s has been broken", addr)

	case pep.StreamingConnectionConnecting:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		count := atomic.AddUint32(ptr, 1)

		if count <= 1 {
			log.Infof("Connecting to %s", addr)
		}

		if count > 100 {
			log.Errorf("Connecting to %s", addr)
			atomic.StoreUint32(ptr, 1)
		}

	case pep.StreamingConnectionFailure:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		if atomic.LoadUint32(ptr) <= 1 {
			log.Errorf("Failed to connect to %s (%s)", addr, err)
		}
	}
}
<commit_msg>policy plugin: fix default decision (#216)<commit_after>package policy

import (
	"sync/atomic"

	"github.com/coredns/coredns/plugin/pkg/trace"
	"github.com/infobloxopen/themis/pdp"
	"github.com/infobloxopen/themis/pep"
)

type pepCacheHitHandler struct{}

func (ch *pepCacheHitHandler) Handle(req interface{}, resp interface{}, err error) {
	log.Infof("PEP responding to PDP request from cache %+v", req)
}

func newPepCacheHitHandler() *pepCacheHitHandler {
	return &pepCacheHitHandler{}
}

// connect establishes connection to PDP server.
func (p *policyPlugin) connect() error {
	log.Infof("Connecting %v", p)

	for _, addr := range p.conf.endpoints {
		p.connAttempts[addr] = new(uint32)
	}

	opts := []pep.Option{
		pep.WithConnectionTimeout(p.conf.connTimeout),
		pep.WithConnectionStateNotification(p.connStateCb),
	}

	if p.conf.cacheTTL > 0 {
		if p.conf.cacheLimit > 0 {
			opts = append(opts, pep.WithCacheTTLAndMaxSize(p.conf.cacheTTL, p.conf.cacheLimit))
		} else {
			opts = append(opts, pep.WithCacheTTL(p.conf.cacheTTL))
		}
	}

	if p.conf.streams <= 0 || !p.conf.hotSpot {
		opts = append(opts, pep.WithRoundRobinBalancer(p.conf.endpoints...))
	}

	if p.conf.streams > 0 {
		opts = append(opts, pep.WithStreams(p.conf.streams))
		if p.conf.hotSpot {
			opts = append(opts, pep.WithHotSpotBalancer(p.conf.endpoints...))
		}
	}

	opts = append(opts, pep.WithAutoRequestSize(p.conf.autoReqSize))
	if p.conf.maxReqSize > 0 {
		opts = append(opts, pep.WithMaxRequestSize(uint32(p.conf.maxReqSize)))
	}

	if p.trace != nil {
		if t, ok := p.trace.(trace.Trace); ok {
			opts = append(opts, pep.WithTracer(t.Tracer()))
		}
	}

	if p.conf.log {
		opts = append(opts, pep.WithOnCacheHitHandler(newPepCacheHitHandler()))
	}

	p.pdp = pep.NewClient(opts...)
	return p.pdp.Connect("")
}

// closeConn terminates previously established connection.
func (p *policyPlugin) closeConn() {
	if p.pdp != nil {
		go func() {
			p.wg.Wait()
			p.pdp.Close()
		}()
	}
}

func (p *policyPlugin) validate(buf []pdp.AttributeAssignment, ah *attrHolder, lt int, dbg *dbgMessenger) (bool, error) {
	req := ah.attrList(buf, lt)
	if len(req) <= 0 {
		return true, nil
	}

	if p.conf.log {
		log.Infof("PDP request: %+v", req)
	}

	var respBuffer []pdp.AttributeAssignment
	if !p.conf.autoResAttrs {
		respBuffer = p.attrPool.Get()
		defer p.attrPool.Put(respBuffer)
	}

	res := pdp.Response{Obligations: respBuffer}
	err := p.pdp.Validate(req, &res)

	ah.resetAttribute(attrIndexPolicyAction)
	ah.resetAttribute(attrIndexRedirectTo)
	ah.resetAttribute(attrIndexLog)

	if err != nil {
		log.Errorf("Policy validation failed due to error: %s", err)
		if dbg != nil {
			dbg.appendDefaultDecision(ah.attrList(buf, attrListTypeDefDecision))
		} else {
			ah.resetAttrList(attrListTypeDefDecision)
		}
		if ah.actionValue() != actionInvalid {
			return true, nil
		}
		return false, err
	}

	if p.conf.log {
		log.Infof("PDP response: %+v", res)
	}
	if dbg != nil {
		dbg.appendResponse(&res)
	}

	if res.Effect != pdp.EffectPermit && res.Effect != pdp.EffectDeny {
		log.Errorf("Policy validation failed due to PDP error: %s", res.Status)
		if dbg != nil {
			dbg.appendDefaultDecision(ah.attrList(buf, attrListTypeDefDecision))
		} else {
			ah.resetAttrList(attrListTypeDefDecision)
		}
		if ah.actionValue() != actionInvalid {
			return true, nil
		}
		return false, err
	}

	ah.addAttrList(res.Obligations)

	return res.Effect == pdp.EffectPermit, nil
}

func (p *policyPlugin) connStateCb(addr string, state int, err error) {
	switch state {
	default:
		if err != nil {
			log.Infof("Unknown connection notification %s (%s)", addr, err)
		} else {
			log.Infof("Unknown connection notification %s", addr)
		}

	case pep.StreamingConnectionEstablished:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		atomic.StoreUint32(ptr, 0)

		log.Infof("Connected to %s", addr)

	case pep.StreamingConnectionBroken:
		log.Errorf("Connection to %s has been broken", addr)

	case pep.StreamingConnectionConnecting:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		count := atomic.AddUint32(ptr, 1)

		if count <= 1 {
			log.Infof("Connecting to %s", addr)
		}

		if count > 100 {
			log.Errorf("Connecting to %s", addr)
			atomic.StoreUint32(ptr, 1)
		}

	case pep.StreamingConnectionFailure:
		ptr, ok := p.connAttempts[addr]
		if !ok {
			ptr = p.unkConnAttempts
		}
		if atomic.LoadUint32(ptr) <= 1 {
			log.Errorf("Failed to connect to %s (%s)", addr, err)
		}
	}
}
<|endoftext|>


<commit_before>package templatehelpers

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"html/template"
	"math"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// FuncMap is a set of helper functions to make available in templates for the
// project.
var FuncMap template.FuncMap = template.FuncMap{
	"DistanceOfTimeInWords":        distanceOfTimeInWords,
	"DistanceOfTimeInWordsFromNow": distanceOfTimeInWordsFromNow,
	"FormatTime":                   formatTime,
	"FormatTimeWithMinute":         formatTimeWithMinute,
	"InKM":                         inKM,
	"MarshalJSON":                  marshalJSON,
	"MonthName":                    monthName,
	"NumberWithDelimiter":          numberWithDelimiter,
	"Pace":                         pace,
	"RenderTweetContent":           renderTweetContent,
	"RoundToString":                roundToString,
}

func distanceOfTimeInWords(to, from time.Time) string {
	d := from.Sub(to)
	min := int(round(d.Minutes()))

	if min == 0 {
		return "less than 1 minute"
	} else if min >= 1 && min <= 44 {
		return fmt.Sprintf("%d minutes", min)
	} else if min >= 45 && min <= 89 {
		return "about 1 hour"
	} else if min >= 90 && min <= 1439 {
		return fmt.Sprintf("about %d hours", int(round(d.Hours())))
	} else if min >= 1440 && min <= 2519 {
		return "about 1 day"
	} else if min >= 2520 && min <= 43199 {
		return fmt.Sprintf("%d days", int(round(d.Hours()/24.0)))
	} else if min >= 43200 && min <= 86399 {
		return "about 1 month"
	} else if min >= 86400 && min <= 525599 {
		return fmt.Sprintf("%d months", int(round(d.Hours()/24.0/30.0)))
	}

	return ""
}

func distanceOfTimeInWordsFromNow(to time.Time) string {
	return distanceOfTimeInWords(to, time.Now())
}

func formatTime(t *time.Time) string {
	return t.Format("January 2, 2006")
}

func formatTimeWithMinute(t *time.Time) string {
	return t.Format("January 2, 2006 15:04")
}

func inKM(m float64) float64 {
	return m / 1000.0
}

// Note that I thought I needed this to encode Javascript data in HTML
// templates, but I don't actually appear to need it so we can probably remove
// it.
func marshalJSON(o interface{}) string {
	bytes, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	return string(bytes)
}

func monthName(m time.Month) string {
	return m.String()
}

// Changes a number to a string and uses a separator for groups of three
// digits. For example, 1000 changes to "1,000".
func numberWithDelimiter(sep rune, n int) string {
	s := strconv.Itoa(n)

	startOffset := 0
	var buff bytes.Buffer

	if n < 0 {
		startOffset = 1
		buff.WriteByte('-')
	}

	l := len(s)

	commaIndex := 3 - ((l - startOffset) % 3)

	if commaIndex == 3 {
		commaIndex = 0
	}

	for i := startOffset; i < l; i++ {
		if commaIndex == 3 {
			buff.WriteRune(sep)
			commaIndex = 0
		}
		commaIndex++

		buff.WriteByte(s[i])
	}

	return buff.String()
}

// pace calculates the pace of a run in time per kilometer. This comes out as a
// "clock" time like 4:52 which translates to "4 minutes and 52 seconds" per
// kilometer.
func pace(distance float64, duration time.Duration) string {
	speed := float64(duration.Seconds()) / inKM(distance)
	min := int64(speed / 60.0)
	sec := int64(speed) % 60
	return fmt.Sprintf("%v:%02d", min, sec)
}

// Matches links in a tweet (like protocol://link).
var linkRE *regexp.Regexp = regexp.MustCompile(`(^|[\n ])([\w]+?:\/\/[\w]+[^ "\n\r\t< ]*)`)

// Matches tags in a tweet (like #mix11).
var tagRE *regexp.Regexp = regexp.MustCompile(`#(\w+)`)

// Matches users in a tweet (like #mix11).
var userRE *regexp.Regexp = regexp.MustCompile(`@(\w+)`)

// Renders the content of a tweet to HTML.
func renderTweetContent(content string) string {
	tagMap := make(map[string]string)

	// links like protocol://link
	content = linkRE.ReplaceAllStringFunc(content, func(link string) string {
		matches := linkRE.FindStringSubmatch(link)

		//fmt.Printf("matches = %+v (len %v)\n", matches, len(matches))

		whitespace := matches[1]
		href := matches[2]

		display := href
		if len(href) > 30 {
			display = fmt.Sprintf("%s…", href[0:30])
		}

		// replace with tags so links don't interfere with subsequent rules
		sum := sha1.Sum([]byte(href))
		tag := string(sum[:])
		tagMap[tag] = fmt.Sprintf(`<a href="%s" rel="nofollow">%s</a>`, href, display)

		// make sure to preserve whitespace before the inserted tag
		return whitespace + tag
	})

	// user links (like @brandur)
	content = userRE.ReplaceAllString(content,
		`<a href="https://www.twitter.com/$1" rel="nofollow">@$1</a>`)

	// hash tag search (like #mix11) -- note like anyone would never use one of
	// these, lol
	content = tagRE.ReplaceAllString(content,
		`<a href="https://search.twitter.com/search?q=$1" rel="nofollow">#$1</a>`)

	// replace the stand-in tags for links generated earlier
	for tag, link := range tagMap {
		content = strings.Replace(content, tag, link, -1)
	}

	return content
}

// There is no "round" function built into Go :/
func round(f float64) float64 {
	return math.Floor(f + .5)
}

func roundToString(f float64) string {
	return fmt.Sprintf("%.1f", f)
}
<commit_msg>Base64 sums to prevent weird encoding problems<commit_after>package templatehelpers

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"math"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// FuncMap is a set of helper functions to make available in templates for the
// project.
var FuncMap template.FuncMap = template.FuncMap{
	"DistanceOfTimeInWords":        distanceOfTimeInWords,
	"DistanceOfTimeInWordsFromNow": distanceOfTimeInWordsFromNow,
	"FormatTime":                   formatTime,
	"FormatTimeWithMinute":         formatTimeWithMinute,
	"InKM":                         inKM,
	"MarshalJSON":                  marshalJSON,
	"MonthName":                    monthName,
	"NumberWithDelimiter":          numberWithDelimiter,
	"Pace":                         pace,
	"RenderTweetContent":           renderTweetContent,
	"RoundToString":                roundToString,
}

func distanceOfTimeInWords(to, from time.Time) string {
	d := from.Sub(to)
	min := int(round(d.Minutes()))

	if min == 0 {
		return "less than 1 minute"
	} else if min >= 1 && min <= 44 {
		return fmt.Sprintf("%d minutes", min)
	} else if min >= 45 && min <= 89 {
		return "about 1 hour"
	} else if min >= 90 && min <= 1439 {
		return fmt.Sprintf("about %d hours", int(round(d.Hours())))
	} else if min >= 1440 && min <= 2519 {
		return "about 1 day"
	} else if min >= 2520 && min <= 43199 {
		return fmt.Sprintf("%d days", int(round(d.Hours()/24.0)))
	} else if min >= 43200 && min <= 86399 {
		return "about 1 month"
	} else if min >= 86400 && min <= 525599 {
		return fmt.Sprintf("%d months", int(round(d.Hours()/24.0/30.0)))
	}

	return ""
}

func distanceOfTimeInWordsFromNow(to time.Time) string {
	return distanceOfTimeInWords(to, time.Now())
}

func formatTime(t *time.Time) string {
	return t.Format("January 2, 2006")
}

func formatTimeWithMinute(t *time.Time) string {
	return t.Format("January 2, 2006 15:04")
}

func inKM(m float64) float64 {
	return m / 1000.0
}

// Note that I thought I needed this to encode Javascript data in HTML
// templates, but I don't actually appear to need it so we can probably remove
// it.
func marshalJSON(o interface{}) string {
	bytes, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	return string(bytes)
}

func monthName(m time.Month) string {
	return m.String()
}

// Changes a number to a string and uses a separator for groups of three
// digits. For example, 1000 changes to "1,000".
func numberWithDelimiter(sep rune, n int) string {
	s := strconv.Itoa(n)

	startOffset := 0
	var buff bytes.Buffer

	if n < 0 {
		startOffset = 1
		buff.WriteByte('-')
	}

	l := len(s)

	commaIndex := 3 - ((l - startOffset) % 3)

	if commaIndex == 3 {
		commaIndex = 0
	}

	for i := startOffset; i < l; i++ {
		if commaIndex == 3 {
			buff.WriteRune(sep)
			commaIndex = 0
		}
		commaIndex++

		buff.WriteByte(s[i])
	}

	return buff.String()
}

// pace calculates the pace of a run in time per kilometer. This comes out as a
// "clock" time like 4:52 which translates to "4 minutes and 52 seconds" per
// kilometer.
func pace(distance float64, duration time.Duration) string {
	speed := float64(duration.Seconds()) / inKM(distance)
	min := int64(speed / 60.0)
	sec := int64(speed) % 60
	return fmt.Sprintf("%v:%02d", min, sec)
}

// Matches links in a tweet (like protocol://link).
var linkRE *regexp.Regexp = regexp.MustCompile(`(^|[\n ])([\w]+?:\/\/[\w]+[^ "\n\r\t< ]*)`)

// Matches tags in a tweet (like #mix11).
var tagRE *regexp.Regexp = regexp.MustCompile(`#(\w+)`)

// Matches users in a tweet (like #mix11).
var userRE *regexp.Regexp = regexp.MustCompile(`@(\w+)`)

// Renders the content of a tweet to HTML.
func renderTweetContent(content string) string {
	tagMap := make(map[string]string)

	// links like protocol://link
	content = linkRE.ReplaceAllStringFunc(content, func(link string) string {
		matches := linkRE.FindStringSubmatch(link)

		//fmt.Printf("matches = %+v (len %v)\n", matches, len(matches))

		whitespace := matches[1]
		href := matches[2]

		display := href
		if len(href) > 30 {
			display = fmt.Sprintf("%s…", href[0:30])
		}

		// replace with tags so links don't interfere with subsequent rules
		sum := sha1.Sum([]byte(href))
		tag := base64.URLEncoding.EncodeToString(sum[:])
		tagMap[tag] = fmt.Sprintf(`<a href="%s" rel="nofollow">%s</a>`, href, display)

		// make sure to preserve whitespace before the inserted tag
		return whitespace + tag
	})

	// user links (like @brandur)
	content = userRE.ReplaceAllString(content,
		`<a href="https://www.twitter.com/$1" rel="nofollow">@$1</a>`)

	// hash tag search (like #mix11) -- note like anyone would never use one of
	// these, lol
	content = tagRE.ReplaceAllString(content,
		`<a href="https://search.twitter.com/search?q=$1" rel="nofollow">#$1</a>`)

	// replace the stand-in tags for links generated earlier
	for tag, link := range tagMap {
		content = strings.Replace(content, tag, link, -1)
	}

	return content
}

// There is no "round" function built into Go :/
func round(f float64) float64 {
	return math.Floor(f + .5)
}

func roundToString(f float64) string {
	return fmt.Sprintf("%.1f", f)
}
<|endoftext|>