<commit_before>package awqldb

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	awql "github.com/rvflash/awql-parser"
	"gopkg.in/yaml.v2"
)

// Environment.
const (
	dbDir   = "./src"
	rptFile = "%s/reports.yml"
	vwFile  = "views.yml"
)

// Error messages.
var (
	ErrVersion         = NewDatabaseError("version not supported")
	ErrNoTable         = NewDatabaseError("no table")
	ErrTableExists     = NewDatabaseError("table already exists")
	ErrLoadTables      = NewDatabaseError("tables")
	ErrLoadViews       = NewDatabaseError("views")
	ErrLoadColumns     = NewDatabaseError("columns")
	ErrMismatchColumns = NewDatabaseError("columns mismatch")
	ErrUnknownTable    = NewDatabaseError("unknown table")
	ErrUnknownColumn   = NewDatabaseError("unknown column")
)

// Database represents the database.
type Database struct {
	fd     map[string][]DataTable
	tb, vw []DataTable
	ready  bool
	Version,
	dir, vwFile string
}

// Open returns a new connexion to the Adwords database.
// @see https://github.com/rvflash/awql-db#data-source-name for how
// the DSN string is formatted
func Open(dsn string) (*Database, error) {
	// parseDsn extracts from the data source name, the database directory,
	// the API version and an optional boolean to disable the database loading.
	var parseDsn = func(s string) (dir, vwFile, version string, noOp bool) {
		dsn := strings.Split(s, "|")
		switch len(dsn) {
		case 3:
			vwFile = dsn[2]
			fallthrough
		case 2:
			dir = dsn[1]
			fallthrough
		case 1:
			opt := strings.Split(dsn[0], ":")
			if len(opt) == 2 {
				noOp, _ = strconv.ParseBool(opt[1])
			}
			version = opt[0]
		}
		return
	}
	dir, viewFile, version, noOp := parseDsn(dsn)

	db := &Database{}
	if err := db.setDir(dir); err != nil {
		return db, err
	}
	if viewFile == "" {
		db.vwFile = filepath.Join(db.dir, vwFile)
	} else {
		db.vwFile = viewFile
	}

	if err := db.setVersion(version); err != nil {
		return db, err
	}

	if !noOp {
		if err := db.Load(); err != nil {
			return db, err
		}
	}
	return db, nil
}

// IsSupported returns true if the version is supported.
func (d *Database) HasVersion(version string) bool {
	if version == "" {
		return false
	}
	for _, v := range d.SupportedVersions() {
		if v == version {
			return true
		}
	}
	return false
}

// SupportedVersions returns the list of Adwords API versions supported.
func (d *Database) SupportedVersions() (versions []string) {
	files, err := ioutil.ReadDir(d.dir)
	if err != nil {
		return
	}
	for _, f := range files {
		if f.IsDir() {
			versions = append(versions, f.Name())
		}
	}
	return
}

// AddView creates and adds a view in the database.
// Writes it to config file and adds it to current database.
// It return on error if the view can not be saved.
func (d *Database) AddView(stmt awql.CreateViewStmt) error {
	// Checks if the view already exists.
	v, err := d.newView(stmt)
	if err != nil {
		return err
	}

	// Updates the views configuration file.
	views := make([]DataTable, len(d.vw))
	copy(views, d.vw)
	var exists bool
	for i, ov := range d.vw {
		if exists := ov.SourceName() == v.SourceName(); exists {
			// View already exists, so replace it!
			d.vw[i] = v
			break
		}
	}
	if !exists {
		views = append(views, v)
	}

	// Stringify the views.
	s := "views:" + newline
	for _, v := range views {
		s += v.String()
	}
	if err := ioutil.WriteFile(d.vwFile, []byte(s), 0644); err != nil {
		return err
	}
	d.vw = views

	return nil
}

// ColumnNamesPrefixedBy returns a list of column names prefixed by its pattern.
// If the pattern is empty, all column names are returned.
func (d *Database) ColumnNamesPrefixedBy(pattern string) (columns []string) {
	for s := range d.fd {
		if strings.HasPrefix(s, pattern) {
			columns = append(columns, s)
		}
	}
	return
}

// Load loads all dependencies of the database.
func (d *Database) Load() error {
	if d.ready {
		return nil
	}
	if err := d.loadReports(); err != nil {
		return ErrLoadTables
	}
	if err := d.loadViews(); err != nil {
		return ErrLoadViews
	}
	if err := d.buildColumnsIndex(); err != nil {
		return ErrLoadColumns
	}
	d.ready = true

	return nil
}

// Table returns the table by its name or an error if it not exists.
func (d *Database) Table(table string) (DataTable, error) {
	for _, t := range d.tb {
		if t.SourceName() == table {
			return t, nil
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if v.SourceName() == table {
			return v, nil
		}
	}
	return nil, ErrUnknownTable
}

// Tables returns the list of all tables or a error if there is none.
func (d *Database) Tables() ([]DataTable, error) {
	if d.ready {
		if len(d.vw) > 0 {
			return append(d.tb, d.vw...), nil
		}
		return d.tb, nil
	}
	return nil, ErrNoTable
}

// TablesContains returns the list of tables prefixed by this pattern.
func (d *Database) TablesContains(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.Contains(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.Contains(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesPrefixedBy returns the list of tables prefixed by this pattern.
func (d *Database) TablesPrefixedBy(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.HasPrefix(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.HasPrefix(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesSuffixedBy returns the list of tables suffixed by this pattern.
func (d *Database) TablesSuffixedBy(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.HasSuffix(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.HasSuffix(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesWithColumn returns the list of tables using this column.
func (d *Database) TablesWithColumn(column string) []DataTable {
	return d.fd[column]
}

// buildColumnsIndex lists for each column the tables using it.
func (d *Database) buildColumnsIndex() error {
	if len(d.tb) == 0 {
		return ErrNoTable
	}

	// Create an index by column.
	d.fd = make(map[string][]DataTable)

	// Indexes the reports.
	var name string
	for _, t := range d.tb {
		for _, c := range t.Columns() {
			name = c.Name()
			d.fd[name] = append(d.fd[name], t)
		}
	}
	// Do the same with views.
	for _, v := range d.vw {
		for _, c := range v.Columns() {
			if c.Alias() != "" {
				name = c.Alias()
			} else {
				name = c.Name()
			}
			d.fd[name] = append(d.fd[name], v)
		}
	}

	return nil
}

// loadFile retrieves the content of the given file path.
func (d *Database) loadFile(path string) ([]byte, error) {
	// Gets path of reports reference.
	p, err := filepath.Abs(path)
	if err != nil {
		return []byte{}, err
	}
	// Gets reference in yaml format.
	ymlFile, err := ioutil.ReadFile(p)
	if err != nil {
		return []byte{}, err
	}
	return ymlFile, nil
}

// loadReports loads all report table and returns it as Database or error.
func (d *Database) loadReports() error {
	// Gets the content of the Yaml configuration file.
	file := filepath.Join(d.dir, fmt.Sprintf(rptFile, d.Version))
	ymlFile, err := d.loadFile(file)
	if err != nil {
		return err
	}

	// Reports represents all reports from the configuration file.
	type Reports struct {
		Reports []Table `yaml:"reports"`
	}
	var r Reports
	if err := yaml.Unmarshal(ymlFile, &r); err != nil {
		return err
	}

	// Converts slice of Table in slice of awql.CreateViewStmt.
	d.tb = make([]DataTable, len(r.Reports))
	for i := range r.Reports {
		d.tb[i] = r.Reports[i]
	}

	return nil
}

// loadReports loads all report table and returns it as Database or error.
func (d *Database) loadViews() error {
	// Gets the content of the Yaml configuration file.
	ymlFile, err := d.loadFile(d.vwFile)
	if err != nil {
		return err
	}

	// Views represents all views from the configuration file.
	type Views struct {
		Views []Table
	}
	var v Views
	if err := yaml.Unmarshal(ymlFile, &v); err != nil {
		return err
	}

	// Converts slice of Table in slice of awql.CreateViewStmt.
	d.vw = make([]DataTable, len(v.Views))
	for i, w := range v.Views {
		// Adds table properties on each view.
		t, err := d.Table(w.View.Name)
		if err != nil {
			return err
		}

		// Merges column properties of the view with these of the table.
		var fields []Column
		for _, c := range w.Cols {
			f, err := t.Field(c.Head)
			if err != nil {
				return nil
			}
			field := f.(Column)
			field.Label = c.Alias()
			fields = append(fields, field)
		}
		v.Views[i].Cols = fields

		// Finally, save it.
		d.vw[i] = v.Views[i]
	}

	return nil
}

// newView returns a new instance of Table for a view or an error.
func (d *Database) newView(stmt awql.CreateViewStmt) (DataTable, error) {
	// Checks if the new table already exists.
	if t, err := d.Table(stmt.SourceName()); err == nil {
		if !stmt.ReplaceMode() || !t.IsView() {
			return nil, ErrTableExists
		}
	}

	// Checks if table source exists. Gets its primary key.
	src, err := d.Table(stmt.SourceQuery().SourceName())
	if err != nil {
		return nil, err
	}
	t := src.(Table)

	// Prepares the view.
	view := Table{
		Name:       stmt.SourceName(),
		PrimaryKey: t.PrimaryKey,
	}

	// Prepares the data source.
	data := View{
		Name:       t.Name,
		PrimaryKey: view.PrimaryKey,
	}

	// Manages columns.
	cnames := stmt.Columns()
	cols := stmt.SourceQuery().Columns()
	csize := len(cols)
	if size := len(cnames); size > 0 && size != csize {
		return nil, ErrMismatchColumns
	}
	data.Cols = make([]Column, csize)
	for i := 0; i < csize; i++ {
		col, err := t.newColumn(cols[i], cnames[i])
		if err != nil {
			return nil, err
		}
		data.Cols[i] = col
	}

	// Manages where clause.
	where := stmt.SourceQuery().ConditionList()
	if size := len(where); size > 0 {
		data.Where = make([]Condition, size)
		for i := 0; i < size; i++ {
			data.Where[i] = newCondition(where[i])
		}
	}

	// Manages during clause.
	data.During = stmt.SourceQuery().DuringList()

	// Manages group by clause.
	group := stmt.SourceQuery().GroupList()
	if size := len(group); size > 0 {
		data.GroupBy = make([]GroupBy, size)
		for i := 0; i < size; i++ {
			data.GroupBy[i] = newGroupBy(group[i])
		}
	}

	// Manages order by clause.
	order := stmt.SourceQuery().OrderList()
	if size := len(order); size > 0 {
		data.OrderBy = make([]Order, size)
		for i := 0; i < size; i++ {
			data.OrderBy[i] = newOrderBy(order[i])
		}
	}

	// Manages limit clause.
	if row, ok := stmt.SourceQuery().PageSize(); ok {
		data.Limit.Offset = stmt.SourceQuery().StartIndex()
		data.Limit.RowCount = row
	}

	// Copy columns of the data source, merged with view's columns names, on the view.
	view.Cols = make([]Column, csize)
	copy(view.Cols, data.Cols)

	// Finally adds the table source.
	view.View = data

	return view, nil
}

// setDir defines the database root directory.
func (d *Database) setDir(dir string) (err error) {
	if dir == "" {
		// Set default directory if the path is empty.
		dir = dbDir
	}
	d.dir, err = filepath.Abs(dir)
	if err != nil {
		return NewXDatabaseError("connection failed", err.Error())
	}
	if _, err = os.Stat(d.dir); os.IsNotExist(err) {
		return NewXDatabaseError("connection failed", err.Error())
	}
	return nil
}

// setVersion defines the API version to use.
func (d *Database) setVersion(version string) error {
	if version == "" {
		// Set the latest API version if it is undefined.
		vs := d.SupportedVersions()
		sort.Strings(vs)
		version = vs[len(vs)-1]
	}
	d.Version = version

	// Checks if it's a valid API version.
	if !d.HasVersion(d.Version) {
		return ErrVersion
	}
	return nil
}
<commit_msg>Uses the last version of AWQL database to fix the bug with the non-replacement of the view<commit_after>package awqldb

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	awql "github.com/rvflash/awql-parser"
	"gopkg.in/yaml.v2"
)

// Environment.
const (
	dbDir   = "./src"
	rptFile = "%s/reports.yml"
	vwFile  = "views.yml"
)

// Error messages.
var (
	ErrVersion         = NewDatabaseError("version not supported")
	ErrNoTable         = NewDatabaseError("no table")
	ErrTableExists     = NewDatabaseError("table already exists")
	ErrLoadTables      = NewDatabaseError("tables")
	ErrLoadViews       = NewDatabaseError("views")
	ErrLoadColumns     = NewDatabaseError("columns")
	ErrMismatchColumns = NewDatabaseError("columns mismatch")
	ErrUnknownTable    = NewDatabaseError("unknown table")
	ErrUnknownColumn   = NewDatabaseError("unknown column")
)

// Database represents the database.
type Database struct {
	fd     map[string][]DataTable
	tb, vw []DataTable
	ready  bool
	Version,
	dir, vwFile string
}

// Open returns a new connexion to the Adwords database.
// @see https://github.com/rvflash/awql-db#data-source-name for how
// the DSN string is formatted
func Open(dsn string) (*Database, error) {
	// parseDsn extracts from the data source name, the database directory,
	// the API version and an optional boolean to disable the database loading.
	var parseDsn = func(s string) (dir, vwFile, version string, noOp bool) {
		dsn := strings.Split(s, "|")
		switch len(dsn) {
		case 3:
			vwFile = dsn[2]
			fallthrough
		case 2:
			dir = dsn[1]
			fallthrough
		case 1:
			opt := strings.Split(dsn[0], ":")
			if len(opt) == 2 {
				noOp, _ = strconv.ParseBool(opt[1])
			}
			version = opt[0]
		}
		return
	}
	dir, viewFile, version, noOp := parseDsn(dsn)

	db := &Database{}
	if err := db.setDir(dir); err != nil {
		return db, err
	}
	if viewFile == "" {
		db.vwFile = filepath.Join(db.dir, vwFile)
	} else {
		db.vwFile = viewFile
	}

	if err := db.setVersion(version); err != nil {
		return db, err
	}

	if !noOp {
		if err := db.Load(); err != nil {
			return db, err
		}
	}
	return db, nil
}

// IsSupported returns true if the version is supported.
func (d *Database) HasVersion(version string) bool {
	if version == "" {
		return false
	}
	for _, v := range d.SupportedVersions() {
		if v == version {
			return true
		}
	}
	return false
}

// SupportedVersions returns the list of Adwords API versions supported.
func (d *Database) SupportedVersions() (versions []string) {
	files, err := ioutil.ReadDir(d.dir)
	if err != nil {
		return
	}
	for _, f := range files {
		if f.IsDir() {
			versions = append(versions, f.Name())
		}
	}
	return
}

// AddView creates and adds a view in the database.
// Writes it to config file and adds it to current database.
// It return on error if the view can not be saved.
func (d *Database) AddView(stmt awql.CreateViewStmt) error {
	// Checks if the view already exists.
	v, err := d.newView(stmt)
	if err != nil {
		return err
	}

	// Updates the views configuration file.
	views := make([]DataTable, len(d.vw))
	copy(views, d.vw)
	var exists bool
	for i, ov := range d.vw {
		// View already exists, so replace it!
		if exists = ov.SourceName() == v.SourceName(); exists {
			d.vw[i] = v
			break
		}
	}
	if !exists {
		views = append(views, v)
	}

	// Stringify the views.
	s := "views:" + newline
	for _, v := range views {
		s += v.String()
	}
	if err := ioutil.WriteFile(d.vwFile, []byte(s), 0644); err != nil {
		return err
	}
	d.vw = views

	return nil
}

// ColumnNamesPrefixedBy returns a list of column names prefixed by its pattern.
// If the pattern is empty, all column names are returned.
func (d *Database) ColumnNamesPrefixedBy(pattern string) (columns []string) {
	for s := range d.fd {
		if strings.HasPrefix(s, pattern) {
			columns = append(columns, s)
		}
	}
	return
}

// Load loads all dependencies of the database.
func (d *Database) Load() error {
	if d.ready {
		return nil
	}
	if err := d.loadReports(); err != nil {
		return ErrLoadTables
	}
	if err := d.loadViews(); err != nil {
		return ErrLoadViews
	}
	if err := d.buildColumnsIndex(); err != nil {
		return ErrLoadColumns
	}
	d.ready = true

	return nil
}

// Table returns the table by its name or an error if it not exists.
func (d *Database) Table(table string) (DataTable, error) {
	for _, t := range d.tb {
		if t.SourceName() == table {
			return t, nil
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if v.SourceName() == table {
			return v, nil
		}
	}
	return nil, ErrUnknownTable
}

// Tables returns the list of all tables or a error if there is none.
func (d *Database) Tables() ([]DataTable, error) {
	if d.ready {
		if len(d.vw) > 0 {
			return append(d.tb, d.vw...), nil
		}
		return d.tb, nil
	}
	return nil, ErrNoTable
}

// TablesContains returns the list of tables prefixed by this pattern.
func (d *Database) TablesContains(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.Contains(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.Contains(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesPrefixedBy returns the list of tables prefixed by this pattern.
func (d *Database) TablesPrefixedBy(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.HasPrefix(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.HasPrefix(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesSuffixedBy returns the list of tables suffixed by this pattern.
func (d *Database) TablesSuffixedBy(pattern string) (tables []DataTable) {
	// Search in all reports.
	for _, t := range d.tb {
		if strings.HasSuffix(t.SourceName(), pattern) {
			tables = append(tables, t)
		}
	}
	// Search in Views
	for _, v := range d.vw {
		if strings.HasSuffix(v.SourceName(), pattern) {
			tables = append(tables, v)
		}
	}
	return tables
}

// TablesWithColumn returns the list of tables using this column.
func (d *Database) TablesWithColumn(column string) []DataTable {
	return d.fd[column]
}

// buildColumnsIndex lists for each column the tables using it.
func (d *Database) buildColumnsIndex() error {
	if len(d.tb) == 0 {
		return ErrNoTable
	}

	// Create an index by column.
	d.fd = make(map[string][]DataTable)

	// Indexes the reports.
	var name string
	for _, t := range d.tb {
		for _, c := range t.Columns() {
			name = c.Name()
			d.fd[name] = append(d.fd[name], t)
		}
	}
	// Do the same with views.
	for _, v := range d.vw {
		for _, c := range v.Columns() {
			if c.Alias() != "" {
				name = c.Alias()
			} else {
				name = c.Name()
			}
			d.fd[name] = append(d.fd[name], v)
		}
	}

	return nil
}

// loadFile retrieves the content of the given file path.
func (d *Database) loadFile(path string) ([]byte, error) {
	// Gets path of reports reference.
	p, err := filepath.Abs(path)
	if err != nil {
		return []byte{}, err
	}
	// Gets reference in yaml format.
	ymlFile, err := ioutil.ReadFile(p)
	if err != nil {
		return []byte{}, err
	}
	return ymlFile, nil
}

// loadReports loads all report table and returns it as Database or error.
func (d *Database) loadReports() error {
	// Gets the content of the Yaml configuration file.
	file := filepath.Join(d.dir, fmt.Sprintf(rptFile, d.Version))
	ymlFile, err := d.loadFile(file)
	if err != nil {
		return err
	}

	// Reports represents all reports from the configuration file.
	type Reports struct {
		Reports []Table `yaml:"reports"`
	}
	var r Reports
	if err := yaml.Unmarshal(ymlFile, &r); err != nil {
		return err
	}

	// Converts slice of Table in slice of awql.CreateViewStmt.
	d.tb = make([]DataTable, len(r.Reports))
	for i := range r.Reports {
		d.tb[i] = r.Reports[i]
	}

	return nil
}

// loadReports loads all report table and returns it as Database or error.
func (d *Database) loadViews() error {
	// Gets the content of the Yaml configuration file.
	ymlFile, err := d.loadFile(d.vwFile)
	if err != nil {
		return err
	}

	// Views represents all views from the configuration file.
	type Views struct {
		Views []Table
	}
	var v Views
	if err := yaml.Unmarshal(ymlFile, &v); err != nil {
		return err
	}

	// Converts slice of Table in slice of awql.CreateViewStmt.
	d.vw = make([]DataTable, len(v.Views))
	for i, w := range v.Views {
		// Adds table properties on each view.
		t, err := d.Table(w.View.Name)
		if err != nil {
			return err
		}

		// Merges column properties of the view with these of the table.
		var fields []Column
		for _, c := range w.Cols {
			f, err := t.Field(c.Head)
			if err != nil {
				return nil
			}
			field := f.(Column)
			field.Label = c.Alias()
			fields = append(fields, field)
		}
		v.Views[i].Cols = fields

		// Finally, save it.
		d.vw[i] = v.Views[i]
	}

	return nil
}

// newView returns a new instance of Table for a view or an error.
func (d *Database) newView(stmt awql.CreateViewStmt) (DataTable, error) {
	// Checks if the new table already exists.
	if t, err := d.Table(stmt.SourceName()); err == nil {
		if !stmt.ReplaceMode() || !t.IsView() {
			return nil, ErrTableExists
		}
	}

	// Checks if table source exists. Gets its primary key.
	src, err := d.Table(stmt.SourceQuery().SourceName())
	if err != nil {
		return nil, err
	}
	t := src.(Table)

	// Prepares the view.
	view := Table{
		Name:       stmt.SourceName(),
		PrimaryKey: t.PrimaryKey,
	}

	// Prepares the data source.
	data := View{
		Name:       t.Name,
		PrimaryKey: view.PrimaryKey,
	}

	// Manages columns.
	cnames := stmt.Columns()
	cols := stmt.SourceQuery().Columns()
	csize := len(cols)
	if size := len(cnames); size > 0 && size != csize {
		return nil, ErrMismatchColumns
	}
	data.Cols = make([]Column, csize)
	for i := 0; i < csize; i++ {
		col, err := t.newColumn(cols[i], cnames[i])
		if err != nil {
			return nil, err
		}
		data.Cols[i] = col
	}

	// Manages where clause.
	where := stmt.SourceQuery().ConditionList()
	if size := len(where); size > 0 {
		data.Where = make([]Condition, size)
		for i := 0; i < size; i++ {
			data.Where[i] = newCondition(where[i])
		}
	}

	// Manages during clause.
	data.During = stmt.SourceQuery().DuringList()

	// Manages group by clause.
	group := stmt.SourceQuery().GroupList()
	if size := len(group); size > 0 {
		data.GroupBy = make([]GroupBy, size)
		for i := 0; i < size; i++ {
			data.GroupBy[i] = newGroupBy(group[i])
		}
	}

	// Manages order by clause.
	order := stmt.SourceQuery().OrderList()
	if size := len(order); size > 0 {
		data.OrderBy = make([]Order, size)
		for i := 0; i < size; i++ {
			data.OrderBy[i] = newOrderBy(order[i])
		}
	}

	// Manages limit clause.
	if row, ok := stmt.SourceQuery().PageSize(); ok {
		data.Limit.Offset = stmt.SourceQuery().StartIndex()
		data.Limit.RowCount = row
	}

	// Copy columns of the data source, merged with view's columns names, on the view.
	view.Cols = make([]Column, csize)
	copy(view.Cols, data.Cols)

	// Finally adds the table source.
	view.View = data

	return view, nil
}

// setDir defines the database root directory.
func (d *Database) setDir(dir string) (err error) {
	if dir == "" {
		// Set default directory if the path is empty.
		dir = dbDir
	}
	d.dir, err = filepath.Abs(dir)
	if err != nil {
		return NewXDatabaseError("connection failed", err.Error())
	}
	if _, err = os.Stat(d.dir); os.IsNotExist(err) {
		return NewXDatabaseError("connection failed", err.Error())
	}
	return nil
}

// setVersion defines the API version to use.
func (d *Database) setVersion(version string) error {
	if version == "" {
		// Set the latest API version if it is undefined.
		vs := d.SupportedVersions()
		sort.Strings(vs)
		version = vs[len(vs)-1]
	}
	d.Version = version

	// Checks if it's a valid API version.
	if !d.HasVersion(d.Version) {
		return ErrVersion
	}
	return nil
}
<|endoftext|>


<commit_before>package vrp

import (
	"fmt"
	"log"
	"math/big"

	"honnef.co/go/ssa"
)

// s1 + s2
// s[:]
// len(s)
// "" -> len("")
// s1 == s2
// len(s1) <cmp> x

type StringInterval struct {
	Length IntInterval
}

func (s StringInterval) Union(other Range) Range {
	i, ok := other.(StringInterval)
	if !ok {
		i = StringInterval{EmptyIntInterval}
	}
	if s.Length.Empty() || !s.Length.IsKnown() {
		return i
	}
	if i.Length.Empty() || !i.Length.IsKnown() {
		return s
	}
	return StringInterval{
		Length: s.Length.Union(i.Length).(IntInterval),
	}
}

func (s StringInterval) String() string {
	return s.Length.String()
}

func (s StringInterval) IsKnown() bool {
	return s.Length.IsKnown()
}

type StringSliceConstraint struct {
	aConstraint
	X     ssa.Value
	Lower ssa.Value
	Upper ssa.Value
}

func (c *StringSliceConstraint) String() string {
	var lname, uname string
	if c.Lower != nil {
		lname = c.Lower.Name()
	}
	if c.Upper != nil {
		uname = c.Upper.Name()
	}
	return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}

func (c *StringSliceConstraint) Eval(g *Graph) Range {
	lr := NewIntInterval(NewZ(&big.Int{}), NewZ(&big.Int{}))
	if c.Lower != nil {
		lr = g.Range(c.Lower).(IntInterval)
	}
	ur := g.Range(c.X).(StringInterval).Length
	if c.Upper != nil {
		ur = g.Range(c.Upper).(IntInterval)
	}
	if !lr.IsKnown() || !ur.IsKnown() {
		return StringInterval{}
	}

	ls := []Z{
		ur.lower.Sub(lr.lower),
		ur.upper.Sub(lr.lower),
		ur.lower.Sub(lr.upper),
		ur.upper.Sub(lr.upper),
	}
	// TODO(dh): if we don't truncate lengths to 0 we might be able to
	// easily detect slices with high < low. we'd need to treat -∞
	// specially, though.
	for i, l := range ls {
		if l.Sign() == -1 {
			ls[i] = NewZ(&big.Int{})
		}
	}

	return StringInterval{
		Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
	}
}

func (c *StringSliceConstraint) Operands() []ssa.Value {
	vs := []ssa.Value{c.X}
	if c.Lower != nil {
		vs = append(vs, c.Lower)
	}
	if c.Upper != nil {
		vs = append(vs, c.Upper)
	}
	return vs
}

type StringIntersectionConstraint struct {
	aConstraint
	X ssa.Value
	I IntInterval
}

func (c *StringIntersectionConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.X}
}

func (c *StringIntersectionConstraint) Eval(g *Graph) Range {
	xi := g.Range(c.X).(StringInterval)
	if !xi.IsKnown() {
		return c.I
	}
	return StringInterval{
		Length: xi.Length.Intersection(c.I),
	}
}

func (c *StringIntersectionConstraint) String() string {
	return fmt.Sprintf("%s = %s.%t ⊓ %s", c.Y().Name(), c.X.Name(), c.Y().(*ssa.Sigma).Branch, c.I)
}

type StringConcatConstraint struct {
	aConstraint
	A ssa.Value
	B ssa.Value
}

func NewStringConcatConstraint(a, b, y ssa.Value) Constraint {
	return &StringConcatConstraint{
		aConstraint: aConstraint{
			y: y,
		},
		A: a,
		B: b,
	}
}

func (c StringConcatConstraint) String() string {
	return fmt.Sprintf("%s = %s + %s", c.Y().Name(), c.A.Name(), c.B.Name())
}

func (c StringConcatConstraint) Eval(g *Graph) Range {
	i1, i2 := g.Range(c.A).(StringInterval), g.Range(c.B).(StringInterval)
	if !i1.Length.IsKnown() || !i2.Length.IsKnown() {
		return StringInterval{}
	}
	return StringInterval{
		Length: i1.Length.Add(i2.Length),
	}
}

func (c StringConcatConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.A, c.B}
}

type StringLengthConstraint struct {
	aConstraint
	X ssa.Value
}

func NewStringLengthConstraint(x ssa.Value, y ssa.Value) Constraint {
	return &StringLengthConstraint{
		aConstraint: aConstraint{
			y: y,
		},
		X: x,
	}
}

func (c *StringLengthConstraint) String() string {
	return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}

func (c *StringLengthConstraint) Eval(g *Graph) Range {
	i := g.Range(c.X).(StringInterval).Length
	log.Println(c.Y().Name(), i)
	if !i.IsKnown() {
		return NewIntInterval(NewZ(&big.Int{}), PInfinity)
	}
	return i
}

func (c *StringLengthConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.X}
}

type StringIntervalConstraint struct {
	aConstraint
	I IntInterval
}

func (s *StringIntervalConstraint) Operands() []ssa.Value {
	return nil
}

func (c *StringIntervalConstraint) Eval(*Graph) Range {
	return c.I
}

func (c *StringIntervalConstraint) String() string {
	return fmt.Sprintf("%s = %s", c.Y().Name(), c.I)
}
<commit_msg>vrp: return correct Range type for string interval<commit_after>package vrp

import (
	"fmt"
	"log"
	"math/big"

	"honnef.co/go/ssa"
)

// s1 + s2
// s[:]
// len(s)
// "" -> len("")
// s1 == s2
// len(s1) <cmp> x

type StringInterval struct {
	Length IntInterval
}

func (s StringInterval) Union(other Range) Range {
	i, ok := other.(StringInterval)
	if !ok {
		i = StringInterval{EmptyIntInterval}
	}
	if s.Length.Empty() || !s.Length.IsKnown() {
		return i
	}
	if i.Length.Empty() || !i.Length.IsKnown() {
		return s
	}
	return StringInterval{
		Length: s.Length.Union(i.Length).(IntInterval),
	}
}

func (s StringInterval) String() string {
	return s.Length.String()
}

func (s StringInterval) IsKnown() bool {
	return s.Length.IsKnown()
}

type StringSliceConstraint struct {
	aConstraint
	X     ssa.Value
	Lower ssa.Value
	Upper ssa.Value
}

func (c *StringSliceConstraint) String() string {
	var lname, uname string
	if c.Lower != nil {
		lname = c.Lower.Name()
	}
	if c.Upper != nil {
		uname = c.Upper.Name()
	}
	return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}

func (c *StringSliceConstraint) Eval(g *Graph) Range {
	lr := NewIntInterval(NewZ(&big.Int{}), NewZ(&big.Int{}))
	if c.Lower != nil {
		lr = g.Range(c.Lower).(IntInterval)
	}
	ur := g.Range(c.X).(StringInterval).Length
	if c.Upper != nil {
		ur = g.Range(c.Upper).(IntInterval)
	}
	if !lr.IsKnown() || !ur.IsKnown() {
		return StringInterval{}
	}

	ls := []Z{
		ur.lower.Sub(lr.lower),
		ur.upper.Sub(lr.lower),
		ur.lower.Sub(lr.upper),
		ur.upper.Sub(lr.upper),
	}
	// TODO(dh): if we don't truncate lengths to 0 we might be able to
	// easily detect slices with high < low. we'd need to treat -∞
	// specially, though.
	for i, l := range ls {
		if l.Sign() == -1 {
			ls[i] = NewZ(&big.Int{})
		}
	}

	return StringInterval{
		Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
	}
}

func (c *StringSliceConstraint) Operands() []ssa.Value {
	vs := []ssa.Value{c.X}
	if c.Lower != nil {
		vs = append(vs, c.Lower)
	}
	if c.Upper != nil {
		vs = append(vs, c.Upper)
	}
	return vs
}

type StringIntersectionConstraint struct {
	aConstraint
	X ssa.Value
	I IntInterval
}

func (c *StringIntersectionConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.X}
}

func (c *StringIntersectionConstraint) Eval(g *Graph) Range {
	xi := g.Range(c.X).(StringInterval)
	if !xi.IsKnown() {
		return c.I
	}
	return StringInterval{
		Length: xi.Length.Intersection(c.I),
	}
}

func (c *StringIntersectionConstraint) String() string {
	return fmt.Sprintf("%s = %s.%t ⊓ %s", c.Y().Name(), c.X.Name(), c.Y().(*ssa.Sigma).Branch, c.I)
}

type StringConcatConstraint struct {
	aConstraint
	A ssa.Value
	B ssa.Value
}

func NewStringConcatConstraint(a, b, y ssa.Value) Constraint {
	return &StringConcatConstraint{
		aConstraint: aConstraint{
			y: y,
		},
		A: a,
		B: b,
	}
}

func (c StringConcatConstraint) String() string {
	return fmt.Sprintf("%s = %s + %s", c.Y().Name(), c.A.Name(), c.B.Name())
}

func (c StringConcatConstraint) Eval(g *Graph) Range {
	i1, i2 := g.Range(c.A).(StringInterval), g.Range(c.B).(StringInterval)
	if !i1.Length.IsKnown() || !i2.Length.IsKnown() {
		return StringInterval{}
	}
	return StringInterval{
		Length: i1.Length.Add(i2.Length),
	}
}

func (c StringConcatConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.A, c.B}
}

type StringLengthConstraint struct {
	aConstraint
	X ssa.Value
}

func NewStringLengthConstraint(x ssa.Value, y ssa.Value) Constraint {
	return &StringLengthConstraint{
		aConstraint: aConstraint{
			y: y,
		},
		X: x,
	}
}

func (c *StringLengthConstraint) String() string {
	return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}

func (c *StringLengthConstraint) Eval(g *Graph) Range {
	i := g.Range(c.X).(StringInterval).Length
	log.Println(c.Y().Name(), i)
	if !i.IsKnown() {
		return NewIntInterval(NewZ(&big.Int{}), PInfinity)
	}
	return i
}

func (c *StringLengthConstraint) Operands() []ssa.Value {
	return []ssa.Value{c.X}
}

type StringIntervalConstraint struct {
	aConstraint
	I IntInterval
}

func (s *StringIntervalConstraint) Operands() []ssa.Value {
	return nil
}

func (c *StringIntervalConstraint) Eval(*Graph) Range {
	return StringInterval{c.I}
}

func (c *StringIntervalConstraint) String() string {
	return fmt.Sprintf("%s = %s", c.Y().Name(), c.I)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/binary"
	"erlang/dist"
	"flag"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var listenPort string
var regLimit int
var unregTTL int

func init() {
	flag.StringVar(&listenPort, "port", "4369", "listen port")
	flag.IntVar(&regLimit, "nodes-limit", 1000, "limit size of registration table to prune unregistered nodes")
	flag.IntVar(&unregTTL, "unreg-ttl", 10, "prune unregistered nodes if unregistration older than this value in minutes")
}

type regAns struct {
	reply   []byte
	isClose bool
}

type regReq struct {
	buf     []byte
	replyTo chan regAns
	conn    net.Conn
}

func main() {
	flag.Parse()

	l, err := net.Listen("tcp", net.JoinHostPort("", listenPort))
	if err != nil {
		log.Fatal(err)
	}
	epm := make(chan regReq, 10)
	go epmReg(epm)
	for {
		conn, err := l.Accept()
		log.Printf("Accept new")
		if err != nil {
			log.Printf(err.Error())
		} else {
			go mLoop(conn, epm)
		}
	}
	log.Printf("Exit")
}

type nodeRec struct {
	dist.NodeInfo
	Time  time.Time
	Ready bool
	conn  net.Conn
}

func epmReg(in <-chan regReq) {

	var nReg = make(map[string]*nodeRec)

	for {
		select {
		case req := <-in:
			buf := req.buf
			if len(buf) == 0 {
				rs := len(nReg)
				log.Printf("REG %d records", rs)
				now := time.Now()

				for node, rec := range nReg {
					if rec.conn == req.conn {
						log.Printf("Connection for %s dropped", node)
						nReg[node].Ready = false
						nReg[node].Time = now
					} else if rs > regLimit && !rec.Ready && now.Sub(rec.Time).Minutes() > float64(unregTTL) {
						log.Printf("REG prune %s:%+v", node, rec)
						delete(nReg, node)
					}
				}
				continue
			}
			replyTo := req.replyTo
			log.Printf("IN: %v", buf)
			switch dist.MessageId(buf[0]) {
			case dist.ALIVE2_REQ:
				nConn := req.conn
				nPort := binary.BigEndian.Uint16(buf[1:3])
				nType := buf[3]
				nProto := buf[4]
				highVsn := binary.BigEndian.Uint16(buf[5:7])
				lowVsn := binary.BigEndian.Uint16(buf[7:9])
				nLen := binary.BigEndian.Uint16(buf[9:11])
				offset := (11 + nLen)
				nName := string(buf[11:offset])
				//nELen := binary.BigEndian.Uint16(buf[offset:(offset+2)])
				nExtra := buf[(offset + 2):]
				log.Printf("Alive: N:%s, P:%d, T:%d", nName, nPort, nType)

				reply := make([]byte, 4)
				reply[0] = byte(dist.ALIVE2_RESP)
				reply[1] = 0 // OK

				var data uint16 = 0
				if rec, ok := nReg[nName]; ok {
					log.Printf("Node %s found", nName)
					if rec.Ready {
						log.Printf("Node %s is running", nName)
						reply[1] = 1 // ERROR
						data = 99    // CANNOT REGISTER
					} else {
						log.Printf("Node %s is not running", nName)
						rec.conn = nConn
						rec.Port = nPort
						rec.Type = nType
						rec.Protocol = nProto
						rec.HighVsn = highVsn
						rec.LowVsn = lowVsn
						rec.Extra = nExtra
						rec.Creation = (rec.Creation % 3) + 1
						rec.Ready = true
						data = rec.Creation
					}
				} else {
					log.Printf("New node %s", nName)
					rec := &nodeRec{
						NodeInfo: dist.NodeInfo{
							Name:     nName,
							Port:     nPort,
							Type:     nType,
							Protocol: nProto,
							HighVsn:  highVsn,
							LowVsn:   lowVsn,
							Extra:    nExtra,
							Creation: 1,
						},
						conn:  nConn,
						Time:  time.Now(),
						Ready: true,
					}
					nReg[nName] = rec
					data = rec.Creation
				}

				binary.BigEndian.PutUint16(reply[2:4], data)
				replyTo <- regAns{reply: reply, isClose: false}
			case dist.PORT_PLEASE2_REQ:
				nName := buf[1:]
				var reply []byte
				if rec, ok := nReg[string(nName)]; ok && rec.Ready {
					reply = make([]byte, 14+len(nName)+len(rec.Extra))
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 0 // OK
					binary.BigEndian.PutUint16(reply[2:4], rec.Port)
					reply[4] = rec.Type
					reply[5] = rec.Protocol
					binary.BigEndian.PutUint16(reply[6:8], rec.HighVsn)
					binary.BigEndian.PutUint16(reply[8:10], rec.LowVsn)
					nLen := len(rec.Name)
					binary.BigEndian.PutUint16(reply[10:12], uint16(nLen))
					offset := (12 + nLen)
					copy(reply[12:offset], rec.Name)
					nELen := len(rec.Extra)
					binary.BigEndian.PutUint16(reply[offset:offset+2], uint16(nELen))
					copy(reply[offset+2:offset+2+nELen], rec.Extra)
				} else {
					reply = make([]byte, 2)
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 1 // ERROR
				}
				replyTo <- regAns{reply: reply, isClose: true}
			case dist.NAMES_REQ, dist.DUMP_REQ:
				log.Printf("NAMES_REQ")
				lp, err := strconv.Atoi(listenPort)
				if err != nil {
					log.Printf("Cannot convert %s to integer", listenPort)
					replyTo <- regAns{reply: nil, isClose: true}
				} else {
					log.Printf("Make a reply")

					var replyB bytes.Buffer
					reply := make([]byte, 4)
					binary.BigEndian.PutUint32(reply, uint32(lp))
					replyB.Write(reply)

					for node, rec := range nReg {
						if rec.Ready {
							if dist.MessageId(buf[0]) == dist.NAMES_REQ {
								replyB.Write([]byte(fmt.Sprintf("name %s at port %d\n", node, rec.Port)))
							} else {
								if rec.Ready {
									replyB.Write([]byte(fmt.Sprintf("active name     <%s> at port %d\n", node, rec.Port)))
								} else {
									replyB.Write([]byte(fmt.Sprintf("old/unused name <%s>, port = %d\n", node, rec.Port)))
								}
							}
						}
					}
					replyTo <- regAns{reply: replyB.Bytes(), isClose: true}
				}
			case dist.KILL_REQ:
				replyTo <- regAns{reply: []byte("OK"), isClose: true}
			default:
				replyTo <- regAns{reply: nil, isClose: true}
			}
		}
	}
}

func mLoop(c net.Conn, epm chan regReq) {
	buf := make([]byte, 1024)
	for {
		n, err := c.Read(buf)
		if err != nil {
			c.Close()
			log.Printf("Stop loop: %v", err)
			epm <- regReq{buf: []byte{}, conn: c}
			return
		}
		length := binary.BigEndian.Uint16(buf[0:2])
		if length != uint16(n-2) {
			log.Printf("Incomplete packet: %d from %d", n, length)
		}
		log.Printf("Read %d, %d: %v", n, length, buf[2:n])
		if isClose := handleMsg(c, buf[2:n], epm); isClose {
			break
		}
	}
	c.Close()
}

func handleMsg(c net.Conn, buf []byte, epm chan regReq) bool {
	myChan := make(chan regAns)
	epm <- regReq{buf: buf, replyTo: myChan, conn: c}
	select {
	case ans := <-myChan:
		log.Printf("Got reply: %+v", ans)
		if ans.reply != nil {
			c.Write(ans.reply)
		}
		return ans.isClose
	}
	return true
}
<commit_msg>Rename field Ready to Active<commit_after>package main

import (
	"bytes"
	"encoding/binary"
	"erlang/dist"
	"flag"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var listenPort string
var regLimit int
var unregTTL int

func init() {
	flag.StringVar(&listenPort, "port", "4369", "listen port")
	flag.IntVar(&regLimit, "nodes-limit", 1000, "limit size of registration table to prune unregistered nodes")
	flag.IntVar(&unregTTL, "unreg-ttl", 10, "prune unregistered nodes if unregistration older than this value in minutes")
}

type regAns struct {
	reply   []byte
	isClose bool
}

type regReq struct {
	buf     []byte
	replyTo chan regAns
	conn    net.Conn
}

func main() {
	flag.Parse()

	l, err := net.Listen("tcp", net.JoinHostPort("", listenPort))
	if err != nil {
		log.Fatal(err)
	}
	epm := make(chan regReq, 10)
	go epmReg(epm)
	for {
		conn, err := l.Accept()
		log.Printf("Accept new")
		if err != nil {
			log.Printf(err.Error())
		} else {
			go mLoop(conn, epm)
		}
	}
	log.Printf("Exit")
}

type nodeRec struct {
	dist.NodeInfo
	Time  time.Time
	Active bool
	conn  net.Conn
}

func epmReg(in <-chan regReq) {

	var nReg = make(map[string]*nodeRec)

	for {
		select {
		case req := <-in:
			buf := req.buf
			if len(buf) == 0 {
				rs := len(nReg)
				log.Printf("REG %d records", rs)
				now := time.Now()

				for node, rec := range nReg {
					if rec.conn == req.conn {
						log.Printf("Connection for %s dropped", node)
						nReg[node].Active = false
						nReg[node].Time = now
					} else if rs > regLimit && !rec.Active && now.Sub(rec.Time).Minutes() > float64(unregTTL) {
						log.Printf("REG prune %s:%+v", node, rec)
						delete(nReg, node)
					}
				}
				continue
			}
			replyTo := req.replyTo
			log.Printf("IN: %v", buf)
			switch dist.MessageId(buf[0]) {
			case dist.ALIVE2_REQ:
				nConn := req.conn
				nPort := binary.BigEndian.Uint16(buf[1:3])
				nType := buf[3]
				nProto := buf[4]
				highVsn := binary.BigEndian.Uint16(buf[5:7])
				lowVsn := binary.BigEndian.Uint16(buf[7:9])
				nLen := binary.BigEndian.Uint16(buf[9:11])
				offset := (11 + nLen)
				nName := string(buf[11:offset])
				//nELen := binary.BigEndian.Uint16(buf[offset:(offset+2)])
				nExtra := buf[(offset + 2):]
				log.Printf("Alive: N:%s, P:%d, T:%d", nName, nPort, nType)

				reply := make([]byte, 4)
				reply[0] = byte(dist.ALIVE2_RESP)
				reply[1] = 0 // OK

				var data uint16 = 0
				if rec, ok := nReg[nName]; ok {
					log.Printf("Node %s found", nName)
					if rec.Active {
						log.Printf("Node %s is running", nName)
						reply[1] = 1 // ERROR
						data = 99    // CANNOT REGISTER
					} else {
						log.Printf("Node %s is not running", nName)
						rec.conn = nConn
						rec.Port = nPort
						rec.Type = nType
						rec.Protocol = nProto
						rec.HighVsn = highVsn
						rec.LowVsn = lowVsn
						rec.Extra = nExtra
						rec.Creation = (rec.Creation % 3) + 1
						rec.Active = true
						data = rec.Creation
					}
				} else {
					log.Printf("New node %s", nName)
					rec := &nodeRec{
						NodeInfo: dist.NodeInfo{
							Name:     nName,
							Port:     nPort,
							Type:     nType,
							Protocol: nProto,
							HighVsn:  highVsn,
							LowVsn:   lowVsn,
							Extra:    nExtra,
							Creation: 1,
						},
						conn:  nConn,
						Time:  time.Now(),
						Active: true,
					}
					nReg[nName] = rec
					data = rec.Creation
				}

				binary.BigEndian.PutUint16(reply[2:4], data)
				replyTo <- regAns{reply: reply, isClose: false}
			case dist.PORT_PLEASE2_REQ:
				nName := buf[1:]
				var reply []byte
				if rec, ok := nReg[string(nName)]; ok && rec.Active {
					reply = make([]byte, 14+len(nName)+len(rec.Extra))
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 0 // OK
					binary.BigEndian.PutUint16(reply[2:4], rec.Port)
					reply[4] = rec.Type
					reply[5] = rec.Protocol
					binary.BigEndian.PutUint16(reply[6:8], rec.HighVsn)
					binary.BigEndian.PutUint16(reply[8:10], rec.LowVsn)
					nLen := len(rec.Name)
					binary.BigEndian.PutUint16(reply[10:12], uint16(nLen))
					offset := (12 + nLen)
					copy(reply[12:offset], rec.Name)
					nELen := len(rec.Extra)
					binary.BigEndian.PutUint16(reply[offset:offset+2], uint16(nELen))
					copy(reply[offset+2:offset+2+nELen], rec.Extra)
				} else {
					reply = make([]byte, 2)
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 1 // ERROR
				}
				replyTo <- regAns{reply: reply, isClose: true}
			case dist.NAMES_REQ, dist.DUMP_REQ:
				log.Printf("NAMES_REQ")
				lp, err := strconv.Atoi(listenPort)
				if err != nil {
					log.Printf("Cannot convert %s to integer", listenPort)
					replyTo <- regAns{reply: nil, isClose: true}
				} else {
					log.Printf("Make a reply")

					var replyB bytes.Buffer
					reply := make([]byte, 4)
					binary.BigEndian.PutUint32(reply, uint32(lp))
					replyB.Write(reply)

					for node, rec := range nReg {
						if rec.Active {
							if dist.MessageId(buf[0]) == dist.NAMES_REQ {
								replyB.Write([]byte(fmt.Sprintf("name %s at port %d\n", node, rec.Port)))
							} else {
								if rec.Active {
									replyB.Write([]byte(fmt.Sprintf("active name     <%s> at port %d\n", node, rec.Port)))
								} else {
									replyB.Write([]byte(fmt.Sprintf("old/unused name <%s>, port = %d\n", node, rec.Port)))
								}
							}
						}
					}
					replyTo <- regAns{reply: replyB.Bytes(), isClose: true}
				}
			case dist.KILL_REQ:
				replyTo <- regAns{reply: []byte("OK"), isClose: true}
			default:
				replyTo <- regAns{reply: nil, isClose: true}
			}
		}
	}
}

func mLoop(c net.Conn, epm chan regReq) {
	buf := make([]byte, 1024)
	for {
		n, err := c.Read(buf)
		if err != nil {
			c.Close()
			log.Printf("Stop loop: %v", err)
			epm <- regReq{buf: []byte{}, conn: c}
			return
		}
		length := binary.BigEndian.Uint16(buf[0:2])
		if length != uint16(n-2) {
			log.Printf("Incomplete packet: %d from %d", n, length)
		}
		log.Printf("Read %d, %d: %v", n, length, buf[2:n])
		if isClose := handleMsg(c, buf[2:n], epm); isClose {
			break
		}
	}
	c.Close()
}

func handleMsg(c net.Conn, buf []byte, epm chan regReq) bool {
	myChan := make(chan regAns)
	epm <- regReq{buf: buf, replyTo: myChan, conn: c}
	select {
	case ans := <-myChan:
		log.Printf("Got reply: %+v", ans)
		if ans.reply != nil {
			c.Write(ans.reply)
		}
		return ans.isClose
	}
	return true
}
<|endoftext|>


<commit_before>package polecalc

import (
	"os"
	"json"
	"bytes"
	"io/ioutil"
	"reflect"
)

// Holds all the necessary data for evaluating functions in the cuprate system
type Environment struct {
	// program parameters
	GridLength uint32 // points per side in Brillouin zone; typical value ~ 64
	NumProcs   uint16 // number of processes to use for mesh functions
	InitD1,    // initial values for self-consistent parameters
	InitMu,
	InitF0 float64

	// system constant physical parameters
	Alpha int8 // either -1 (d-wave) or +1 (s-wave)
	T0,   // Overall energy scale (default = 1.0)
	Tz, // z-direction hopping energy (|tz| < 0.3 or so)
	Thp, // Diagonal (next-nearest-neighbor) hopping energy (similar range as tz)
	X float64 // Doping / holon excess (0 < x < ~0.2)

	// self-consistently determined physical parameters
	D1, // diagonal hopping parameter generated by two-hole process
	Mu, // holon chemical potential
	F0 float64 // superconducting order parameter

	// cached value: must be reset with EpsilonMin() if D1 changes
	EpsilonMin float64
}

// The one-holon hopping energy Th is determined by Environment parameters
func (env *Environment) Th() float64 {
	return env.T0 * (1 - env.X)
}

// Set self-consistent parameters to the initial values as specified by the Environment
func (env *Environment) Initialize() {
	// hard-coded defaults
	if env.NumProcs <= 0 {
		env.NumProcs = 1
	}
	// specified defaults
	env.D1 = env.InitD1
	env.Mu = env.InitMu
	env.F0 = env.InitF0
	// must be determined after system is mostly initialized
	env.EpsilonMin = EpsilonMin(*env)
}

// Convert the Environment to string by returning a JSON representation
func (env *Environment) String() string {
	envBytes, err := json.Marshal(env)
	if err != nil {
		return ""
	}
	envStr := bytes.NewBuffer(envBytes).String()
	return envStr
}

// Construct an Environment from the JSON file with given path.
// Self-consistent parameters are not set to values given by Init fields.
func EnvironmentFromFile(filePath string) (*Environment, os.Error) {
	fileContents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil, err
	}
	return EnvironmentFromBytes(fileContents)
}

// Construct an Environment from the given JSON byte slice.
// Self-consistent parameters are not set to values given by Init fields.
func EnvironmentFromBytes(jsonData []byte) (*Environment, os.Error) {
	jsonObject := make(map[string]interface{})
	if err := json.Unmarshal(jsonData, &jsonObject); err != nil {
		return nil, err
	}
	env := new(Environment)
	envValue := reflect.Indirect(reflect.ValueOf(env))
	for key, value := range jsonObject {
		field := envValue.FieldByName(key)
		fieldType := field.Type().Name()
		// Hack to get around Unmarshal treating all numbers as float64's.
		// Will need to extend this for other types if they show up in Environment
		// (or come up with a more clever solution).
		if fieldType == "uint16" {
			field.Set(reflect.ValueOf(uint16(value.(float64))))
		} else if fieldType == "uint32" {
			field.Set(reflect.ValueOf(uint32(value.(float64))))
		} else if fieldType == "int8" {
			field.Set(reflect.ValueOf(int8(value.(float64))))
		} else {
			field.Set(reflect.ValueOf(value))
		}
	}
	return env, nil
}

// Write the Environment to a JSON file at the given path
func (env *Environment) WriteToFile(filePath string) os.Error {
	if err := WriteToJSONFile(env, filePath); err != nil {
		return err
	}
	return nil
}
<commit_msg>Added EnvironmentFromObject() to environment.go<commit_after>package polecalc

import (
	"os"
	"json"
	"bytes"
	"io/ioutil"
	"reflect"
)

// Holds all the necessary data for evaluating functions in the cuprate system
type Environment struct {
	// program parameters
	GridLength uint32 // points per side in Brillouin zone; typical value ~ 64
	NumProcs   uint16 // number of processes to use for mesh functions
	InitD1,    // initial values for self-consistent parameters
	InitMu,
	InitF0 float64

	// system constant physical parameters
	Alpha int8 // either -1 (d-wave) or +1 (s-wave)
	T0,   // Overall energy scale (default = 1.0)
	Tz, // z-direction hopping energy (|tz| < 0.3 or so)
	Thp, // Diagonal (next-nearest-neighbor) hopping energy (similar range as tz)
	X float64 // Doping / holon excess (0 < x < ~0.2)

	// self-consistently determined physical parameters
	D1, // diagonal hopping parameter generated by two-hole process
	Mu, // holon chemical potential
	F0 float64 // superconducting order parameter

	// cached value: must be reset with EpsilonMin() if D1 changes
	EpsilonMin float64
}

// The one-holon hopping energy Th is determined by Environment parameters
func (env *Environment) Th() float64 {
	return env.T0 * (1 - env.X)
}

// Set self-consistent parameters to the initial values as specified by the Environment
func (env *Environment) Initialize() {
	// hard-coded defaults
	if env.NumProcs <= 0 {
		env.NumProcs = 1
	}
	// specified defaults
	env.D1 = env.InitD1
	env.Mu = env.InitMu
	env.F0 = env.InitF0
	// must be determined after system is otherwise initialized
	env.EpsilonMin = EpsilonMin(*env)
}

// Convert the Environment to string by returning a JSON representation
func (env *Environment) String() string {
	envBytes, err := json.Marshal(env)
	if err != nil {
		return ""
	}
	envStr := bytes.NewBuffer(envBytes).String()
	return envStr
}

// Construct an Environment from the JSON file with given path.
// Self-consistent parameters are not set to values given by Init fields.
func EnvironmentFromFile(filePath string) (*Environment, os.Error) {
	fileContents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil, err
	}
	return EnvironmentFromBytes(fileContents)
}

// Construct an Environment from the given JSON byte slice.
// Self-consistent parameters are not set to values given by Init fields.
func EnvironmentFromBytes(jsonData []byte) (*Environment, os.Error) {
	jsonObject := make(map[string]interface{})
	if err := json.Unmarshal(jsonData, &jsonObject); err != nil {
		return nil, err
	}
	return EnvironmentFromObject(jsonObject)
}

// Construct an Environment from the given JSON object.
// Self-consistent parameters are not set to values given by Init fields.
func EnvironmentFromObject(jsonObject map[string]interface{}) (*Environment, os.Error) {
	env := new(Environment)
	envValue := reflect.Indirect(reflect.ValueOf(env))
	for key, value := range jsonObject {
		field := envValue.FieldByName(key)
		fieldType := field.Type().Name()
		// Hack to get around Unmarshal treating all numbers as
		// float64's. Will need to extend this for other types if they
		// show up in Environment (or come up with a more clever
		// solution).
		if fieldType == "uint16" {
			field.Set(reflect.ValueOf(uint16(value.(float64))))
		} else if fieldType == "uint32" {
			field.Set(reflect.ValueOf(uint32(value.(float64))))
		} else if fieldType == "int8" {
			field.Set(reflect.ValueOf(int8(value.(float64))))
		} else {
			field.Set(reflect.ValueOf(value))
		}
	}
	return env, nil
}

// Write the Environment to a JSON file at the given path
func (env *Environment) WriteToFile(filePath string) os.Error {
	if err := WriteToJSONFile(env, filePath); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package chacha20poly1305

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"testing"
)

// stolen from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-00#section-7
var testVectors = [][]string{
	[]string{
		"e3c37ba4984da482b4f978f314b149857f4f3027470bced382ad92889ed4fcb6",
		"1400000cbe2f24b0b1bf5276fc91a9ad",
		"0000000000000000",
		"00000000000000001603030010",
		"46d4b8cfb0323dcad49cafe58ad009602fe190ebb314ddab20e541fdb7b7541c",
	},
}

func TestSealing(t *testing.T) {
	for i, vector := range testVectors {
		t.Logf("Running test vector %d", i)

		key, err := hex.DecodeString(vector[0])
		if err != nil {
			t.Error(err)
		}

		plaintext, err := hex.DecodeString(vector[1])
		if err != nil {
			t.Error(err)
		}

		nonce, err := hex.DecodeString(vector[2])
		if err != nil {
			t.Error(err)
		}

		data, err := hex.DecodeString(vector[3])
		if err != nil {
			t.Error(err)
		}

		expected, err := hex.DecodeString(vector[4])
		if err != nil {
			t.Error(err)
		}

		c, err := NewChaCha20Poly1305(key)
		if err != nil {
			t.Error(err)
		}

		actual := c.Seal(nil, nonce, plaintext, data)

		if !bytes.Equal(expected, actual) {
			t.Errorf("Bad seal: expected %x, was %x", expected, actual)

			for i, v := range expected {
				if actual[i] != v {
					t.Logf("Mismatch at offset %d: %x vs %x", i, v, actual[i])
					break
				}
			}
		}
	}
}

func TestRoundtrip(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	actual, err := c.Open(nil, nonce, ciphertext, data)
	if err != nil {
		t.Error(err)
	}

	if !bytes.Equal(plaintext, actual) {
		t.Errorf("Bad seal: expected %x, was %x", plaintext, actual)
	}
}

func TestModifiedData(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	data[0] ^= 1

	_, err = c.Open(nil, nonce, ciphertext, data)
	if err != ErrAuthFailed {
		t.Error("Should have failed, but didn't")
	}
}

func TestModifiedCiphertext(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	ciphertext[0] ^= 1

	_, err = c.Open(nil, nonce, ciphertext, data)
	if err != ErrAuthFailed {
		t.Error("Should have failed, but didn't")
	}
}

func ExampleNewChaCha20Poly1305() {
	key := make([]byte, 32)  // this should be secret
	nonce := make([]byte, 8) // this should be randomly generated by crypto/rand

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		panic(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	fmt.Printf("%x\n", ciphertext)
	// Output:
	// e6669e9e333e4a5af5df32dd1c232712994c7c7796126e91a2ad
}
<commit_msg>Add a benchmark.<commit_after>package chacha20poly1305

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"testing"
)

// stolen from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-00#section-7
var testVectors = [][]string{
	[]string{
		"e3c37ba4984da482b4f978f314b149857f4f3027470bced382ad92889ed4fcb6",
		"1400000cbe2f24b0b1bf5276fc91a9ad",
		"0000000000000000",
		"00000000000000001603030010",
		"46d4b8cfb0323dcad49cafe58ad009602fe190ebb314ddab20e541fdb7b7541c",
	},
}

func TestSealing(t *testing.T) {
	for i, vector := range testVectors {
		t.Logf("Running test vector %d", i)

		key, err := hex.DecodeString(vector[0])
		if err != nil {
			t.Error(err)
		}

		plaintext, err := hex.DecodeString(vector[1])
		if err != nil {
			t.Error(err)
		}

		nonce, err := hex.DecodeString(vector[2])
		if err != nil {
			t.Error(err)
		}

		data, err := hex.DecodeString(vector[3])
		if err != nil {
			t.Error(err)
		}

		expected, err := hex.DecodeString(vector[4])
		if err != nil {
			t.Error(err)
		}

		c, err := NewChaCha20Poly1305(key)
		if err != nil {
			t.Error(err)
		}

		actual := c.Seal(nil, nonce, plaintext, data)

		if !bytes.Equal(expected, actual) {
			t.Errorf("Bad seal: expected %x, was %x", expected, actual)

			for i, v := range expected {
				if actual[i] != v {
					t.Logf("Mismatch at offset %d: %x vs %x", i, v, actual[i])
					break
				}
			}
		}
	}
}

func TestRoundtrip(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	actual, err := c.Open(nil, nonce, ciphertext, data)
	if err != nil {
		t.Error(err)
	}

	if !bytes.Equal(plaintext, actual) {
		t.Errorf("Bad seal: expected %x, was %x", plaintext, actual)
	}
}

func TestModifiedData(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	data[0] ^= 1

	_, err = c.Open(nil, nonce, ciphertext, data)
	if err != ErrAuthFailed {
		t.Error("Should have failed, but didn't")
	}
}

func TestModifiedCiphertext(t *testing.T) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		t.Error(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	ciphertext[0] ^= 1

	_, err = c.Open(nil, nonce, ciphertext, data)
	if err != ErrAuthFailed {
		t.Error("Should have failed, but didn't")
	}
}

func BenchmarkChaCha20Poly1305(b *testing.B) {
	key := make([]byte, 32)
	nonce := make([]byte, 8)
	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		panic(err)
	}

	input := make([]byte, 1024*1024)

	b.SetBytes(int64(len(input)))
	for i := 0; i < b.N; i++ {
		c.Seal(nil, nonce, input, nil)
	}
}

func ExampleNewChaCha20Poly1305() {
	key := make([]byte, 32)  // this should be secret
	nonce := make([]byte, 8) // this should be randomly generated by crypto/rand

	c, err := NewChaCha20Poly1305(key)
	if err != nil {
		panic(err)
	}

	plaintext := []byte("yay for me")
	data := []byte("whoah yeah")
	ciphertext := c.Seal(nil, nonce, plaintext, data)

	fmt.Printf("%x\n", ciphertext)
	// Output:
	// e6669e9e333e4a5af5df32dd1c232712994c7c7796126e91a2ad
}
<|endoftext|>


<commit_before>package client // import "kego.io/editor/client"

import (
	"net/url"

	"encoding/json"

	"fmt"

	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
	"kego.io/editor/client/items"
	"kego.io/editor/client/tree"
	"kego.io/editor/shared"
	"kego.io/editor/shared/connection"
	"kego.io/js/console"
	"kego.io/ke"
	"kego.io/kerr"
	"kego.io/system/node"
)

type appData struct {
	path    string
	aliases map[string]string
	fail    chan error
	conn    *connection.Conn
	spinner *dom.HTMLDivElement
}

var app appData
var window dom.Window
var doc dom.Document
var body dom.Element

func Start(path string) error {

	window = dom.GetWindow()
	doc = window.Document()
	body = doc.GetElementByID("body")

	// We parse the json info attribute from the body tag
	info, err := getInfo(body)
	if err != nil {
		return kerr.New("MGLVIQIDDY", err, "getInfo")
	}

	app.path = path
	app.aliases = info.Aliases
	app.fail = make(chan error)

	packageNode := &node.Node{}
	if err := ke.UnmarshalNode([]byte(info.Package), packageNode, app.path, app.aliases); err != nil {
		return kerr.New("KXIKEWOKJI", err, "UnmarshalNode")
	}

	// We dial the websocket connection to the server
	ws, err := websocket.Dial(fmt.Sprintf("ws://%s:%s/_socket", window.Location().Hostname, window.Location().Port))
	if err != nil {
		return kerr.New("XBMAKPJICG", err, "websocket.Dial")
	}

	// socket allows us to specify the message type - binary or string.
	s := &socket{ws, connection.MESSAGE_TYPE}

	app.conn = connection.New(s, app.fail, false, app.path, app.aliases)
	go handle(app.conn.Receive)

	app.spinner = body.GetElementsByClassName("mdl-spinner")[0].(*dom.HTMLDivElement)

	nav := body.GetElementsByClassName("mdl-navigation")[0].(*dom.BasicHTMLElement)
	content := body.GetElementsByClassName("page-content")[0].(*dom.HTMLDivElement)
	// We create a new root tree element
	t := tree.New(nav, content, app.conn, items.Root(), app.fail, app.path, app.aliases)
	items.AddPackage(packageNode, t.Root, info.Data, info.Types)

	window.AddEventListener("keydown", true, func(e dom.Event) {
		t.KeyboardEvent(e.(*dom.KeyboardEvent))
	})

	go func() {
		err, open := <-app.fail
		if !open {
			// Channel has been closed, so app should gracefully exit.
			body.SetInnerHTML("
Server disconnected.
")
		} else {
			// Error received, so app should display error.
			console.Error(err.Error())
			body.SetInnerHTML("
Error:" + err.Error() + "
")
		}
		app.conn.Close()
	}()

	app.spinner.Style().Set("display", "none")

	return nil
}

func handle(f func() error) {
	if err := f(); err != nil {
		app.fail <- err
	}
}

func getInfo(body dom.Element) (info shared.Info, err error) {
	infoJson, err := url.QueryUnescape(body.GetAttribute("info"))
	if err != nil {
		return shared.Info{}, kerr.New("CENGCYKHHP", err, "url.QueryUnescape (info)")
	}
	err = json.Unmarshal([]byte(infoJson), &info)
	if err != nil {
		return shared.Info{}, kerr.New("AAFXLQRUEW", err, "json.Unmarshal (info)")
	}
	return info, nil
}
<commit_msg>Improved keyboard event handling<commit_after>package client // import "kego.io/editor/client"

import (
	"net/url"

	"encoding/json"

	"fmt"

	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
	"kego.io/editor/client/items"
	"kego.io/editor/client/tree"
	"kego.io/editor/shared"
	"kego.io/editor/shared/connection"
	"kego.io/js/console"
	"kego.io/ke"
	"kego.io/kerr"
	"kego.io/system/node"
)

type appData struct {
	path    string
	aliases map[string]string
	fail    chan error
	conn    *connection.Conn
	spinner *dom.HTMLDivElement
}

var app appData
var window dom.Window
var doc dom.HTMLDocument
var body *dom.HTMLBodyElement

func Start(path string) error {

	window = dom.GetWindow()
	doc = window.Document().(dom.HTMLDocument)
	body = doc.GetElementByID("body").(*dom.HTMLBodyElement)

	// We parse the json info attribute from the body tag
	info, err := getInfo(body)
	if err != nil {
		return kerr.New("MGLVIQIDDY", err, "getInfo")
	}

	app.path = path
	app.aliases = info.Aliases
	app.fail = make(chan error)

	packageNode := &node.Node{}
	if err := ke.UnmarshalNode([]byte(info.Package), packageNode, app.path, app.aliases); err != nil {
		return kerr.New("KXIKEWOKJI", err, "UnmarshalNode")
	}

	// We dial the websocket connection to the server
	ws, err := websocket.Dial(fmt.Sprintf("ws://%s:%s/_socket", window.Location().Hostname, window.Location().Port))
	if err != nil {
		return kerr.New("XBMAKPJICG", err, "websocket.Dial")
	}

	// socket allows us to specify the message type - binary or string.
	s := &socket{ws, connection.MESSAGE_TYPE}

	app.conn = connection.New(s, app.fail, false, app.path, app.aliases)
	go handle(app.conn.Receive)

	app.spinner = body.GetElementsByClassName("mdl-spinner")[0].(*dom.HTMLDivElement)

	nav := body.GetElementsByClassName("mdl-navigation")[0].(*dom.BasicHTMLElement)
	content := body.GetElementsByClassName("page-content")[0].(*dom.HTMLDivElement)
	// We create a new root tree element
	t := tree.New(nav, content, app.conn, items.Root(), app.fail, app.path, app.aliases)
	items.AddPackage(packageNode, t.Root, info.Data, info.Types)

	window.AddEventListener("keydown", true, func(e dom.Event) {
		k := e.(*dom.KeyboardEvent)
		switch doc.ActiveElement().TagName() {
		case "INPUT", "TEXTAREA":
			if k.KeyCode == 27 {
				doc.ActiveElement().Blur()
			}
			return
		default:
			t.KeyboardEvent(k)
		}
	})

	go func() {
		err, open := <-app.fail
		if !open {
			// Channel has been closed, so app should gracefully exit.
			body.SetInnerHTML("
Server disconnected.
")
		} else {
			// Error received, so app should display error.
			console.Error(err.Error())
			body.SetInnerHTML("
Error:" + err.Error() + "
")
		}
		app.conn.Close()
	}()

	app.spinner.Style().Set("display", "none")

	return nil
}

func handle(f func() error) {
	if err := f(); err != nil {
		app.fail <- err
	}
}

func getInfo(body dom.Element) (info shared.Info, err error) {
	infoJson, err := url.QueryUnescape(body.GetAttribute("info"))
	if err != nil {
		return shared.Info{}, kerr.New("CENGCYKHHP", err, "url.QueryUnescape (info)")
	}
	err = json.Unmarshal([]byte(infoJson), &info)
	if err != nil {
		return shared.Info{}, kerr.New("AAFXLQRUEW", err, "json.Unmarshal (info)")
	}
	return info, nil
}
<|endoftext|>


<commit_before>package server // import "kego.io/editor/server"

import (
	"bytes"
	"net"
	"net/http"

	"golang.org/x/net/websocket"

	"github.com/davelondon/gopackages"
	"github.com/davelondon/kerr"

	"fmt"

	"strings"

	"net/rpc"

	"encoding/base64"
	"encoding/gob"

	"time"

	"io"

	"context"

	"github.com/pkg/browser"
	"kego.io/context/cmdctx"
	"kego.io/context/envctx"
	"kego.io/context/sysctx"
	"kego.io/context/wgctx"
	"kego.io/editor/server/auther"
	"kego.io/editor/server/static"
	"kego.io/editor/shared"
	"kego.io/process"
	"kego.io/process/generate"
	"kego.io/process/parser"
	"kego.io/system"
)

const writeTimeout = time.Second * 2

func Start(ctx context.Context, cancel context.CancelFunc) error {

	wgctx.Add(ctx, "Start")
	defer wgctx.Done(ctx, "Start")

	auth := auther.New()

	cmd := cmdctx.FromContext(ctx)

	fail := make(chan error)

	cmd.Println("Starting editor server... ")

	// This contains the source map that will be persisted between requests
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		if strings.HasSuffix(req.URL.Path, "/favicon.ico") {
			w.WriteHeader(404)
			return
		}
		if strings.HasSuffix(req.URL.Path, "/script.js") {
			if err := script(ctx, w, req, false); err != nil {
				fail <- kerr.Wrap("XPVTVKDWHJ", err)
				return
			}
			return
		}
		if strings.HasSuffix(req.URL.Path, "/script.js.map") {
			if err := script(ctx, w, req, true); err != nil {
				fail <- kerr.Wrap("JAIBRHULSI", err)
				return
			}
			return
		}
		if err := root(ctx, w, req, auth); err != nil {
			fail <- kerr.Wrap("QOMJGNOCQF", err)
			return
		}
	})

	if err := rpc.Register(&Server{ctx: ctx, auth: auth}); err != nil {
		return kerr.Wrap("RESBVVGRMH", err)
	}

	http.Handle("/_rpc", websocket.Handler(func(ws *websocket.Conn) {
		ws.PayloadType = websocket.BinaryFrame
		rpc.ServeConn(ws)
	}))

	go func() {
		if err := serve(ctx); err != nil {
			fail <- err
		}
	}()

	done := ctx.Done()
	for {
		select {
		case <-done:
			fmt.Println("Exiting editor server (interupted)... ")
			return nil
		case err, open := <-fail:
			if !open {
				// Channel has been closed, so app should gracefully exit.
				cancel()
				cmd.Println("Exiting editor server (finished)... ")
			} else {
				// Error received, so app should display error.
				// return kerr.New("WKHPTVJBIL", err, "Fail channel receive")
				fmt.Println(err)
			}
			if !cmd.Debug {
				return nil
			}
		}
	}
}

func script(ctx context.Context, w http.ResponseWriter, req *http.Request, sourceMap bool) error {

	wgctx.Add(ctx, "script")
	defer wgctx.Done(ctx, "script")

	path := ""
	if strings.HasSuffix(req.URL.Path, ".map") {
		path = req.URL.Path[1 : len(req.URL.Path)-14]
	} else {
		path = req.URL.Path[1 : len(req.URL.Path)-10]
	}

	env, err := parser.ScanForEnv(ctx, path)

	// This is the client code for the editor which we will compile to
	// Javascript using GopherJs below.
	source, err := generate.Editor(ctx, env)
	if err != nil {
		return kerr.Wrap("UWPDBQXURR", err)
	}

	var out []byte
	withCancel(ctx, func() {
		out, err = Compile(ctx, source, sourceMap)
	})
	if err != nil {
		return kerr.Wrap("LSUXHJMSSX", err)
	}

	withCancel(ctx, func() {
		err = writeWithTimeout(w, out)
	})
	if err != nil {
		return kerr.Wrap("GAOWWBAIAL", err)
	}

	return nil
}

func withCancel(ctx context.Context, op func()) {
	c := make(chan struct{}, 1)
	go func() {
		op()
		close(c)
	}()
	select {
	case <-c:
	case <-ctx.Done():
	}
}

func writeWithTimeout(w io.Writer, b []byte) error {
	c := make(chan error, 1)
	go func() {
		_, err := w.Write(b)
		c <- err
	}()
	select {
	case err := <-c:
		if err != nil {
			return kerr.Wrap("MJDFBJLCIT", err)
		}
		return nil
	case <-time.After(writeTimeout):
		return kerr.New("RTEATWMHDT", "Timeout")
	}
}

func root(ctx context.Context, w http.ResponseWriter, req *http.Request, auth auther.Auther) error {

	wgctx.Add(ctx, "root")
	defer wgctx.Done(ctx, "root")

	if b, err := static.Asset(req.URL.Path[1:]); err == nil {
		if strings.HasSuffix(req.URL.Path, ".css") {
			w.Header().Set("Content-Type", "text/css")
		}
		if err := writeWithTimeout(w, b); err != nil {
			return kerr.Wrap("PVPCXBIUJT", err)
		}
		return nil
	}

	path := req.URL.Path[1:]
	if strings.HasSuffix(path, "/") {
		path = path[0 : len(path)-1]
	}

	// use a new context with a blank sysctx for the duration of this function
	// to prevent caching
	ctx = sysctx.NewContext(ctx)

	scache := sysctx.FromContext(ctx)

	env, err := parser.ScanForEnv(ctx, path)
	if err != nil {
		if _, ok := kerr.Source(err).(gopackages.NotFoundError); ok {
			w.WriteHeader(404)
			return nil
		}
		return kerr.Wrap("ALINBMKDRP", err)
	}

	pcache, err := parser.Parse(ctx, path)
	if err != nil {
		return kerr.Wrap("HIHWJRPUKE", err)
	}

	if err := process.GenerateAll(ctx, env.Path, map[string]bool{}); err != nil {
		return kerr.Wrap("LVGHABDYNQ", err)
	}

	data := map[string]string{}
	for _, name := range pcache.Globals.Keys() {
		if g, ok := pcache.Globals.Get(name); ok {
			data[name] = g.File
		}
	}

	pkgBytes := pcache.PackageBytes
	pkgFilename := pcache.PackageFilename
	if pkgBytes == nil {
		b, err := system.Marshal(ctx, system.EmptyPackage())
		if err != nil {
			return kerr.Wrap("OUBOTYGPKU", err)
		}
		pkgBytes = b
		pkgFilename = "package.ke.json"
	}

	imports := map[string]shared.ImportInfo{}

	var scan func(string) error
	scan = func(path string) error {
		if _, ok := imports[path]; ok {
			return nil
		}
		syspi, ok := scache.Get(path)
		if !ok {
			return kerr.New("VIGKIUPNCF", "%s not found in sys ctx", path)
		}
		info := shared.ImportInfo{
			Path:    path,
			Aliases: syspi.Aliases,
			Types:   map[string]shared.TypeInfo{},
		}
		for _, name := range syspi.Files.Keys() {
			if b, ok := syspi.Files.Get(name); ok {
				info.Types[name] = shared.TypeInfo{
					File:  b.File,
					Bytes: b.Bytes,
				}
			}
		}
		imports[path] = info

		for _, child := range syspi.Aliases {
			if err := scan(child); err != nil {
				return kerr.Wrap("NCULMUUUOT", err)
			}
		}
		return nil
	}
	// First we always import system
	if err := scan("kego.io/system"); err != nil {
		return kerr.Wrap("KRXSLOJKWV", err)
	}
	if err := scan(env.Path); err != nil {
		return kerr.Wrap("EELKQDCJGN", err)
	}

	info := shared.Info{
		Path:            env.Path,
		Aliases:         env.Aliases,
		Data:            data,
		Package:         pkgBytes,
		PackageFilename: pkgFilename,
		Imports:         imports,
		Hash:            auth.Sign([]byte(env.Path)),
	}
	buf := bytes.NewBuffer([]byte{})
	err = gob.NewEncoder(buf).Encode(info)
	if err != nil {
		return kerr.Wrap("OHBYTULHUQ", err)
	}
	base64EncodedString := base64.StdEncoding.EncodeToString(buf.Bytes())
	attrib := base64EncodedString

	source := []byte(`

				<meta charset="utf-8">
				<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
				<link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.min.css">
				<link rel="stylesheet" href="/split.css">
				<link rel="stylesheet" href="/editors.css">
				<link rel="stylesheet" href="/tree.css">

				<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">

			<body id="body" info="` + attrib + `">

		`)

	if err := writeWithTimeout(w, source); err != nil {
		return kerr.Wrap("ICJSAIMDRF", err)
	}

	return nil
}

func serve(ctx context.Context) error {

	wgctx.Add(ctx, "serve")
	defer wgctx.Done(ctx, "serve")

	env := envctx.FromContext(ctx)
	cmd := cmdctx.FromContext(ctx)

	// Starting with port zero chooses a random open port
	listner, err := net.Listen("tcp", fmt.Sprintf(":%d", cmd.Port))
	if err != nil {
		return kerr.Wrap("QGLXHWPWQW", err)
	}
	defer listner.Close()

	// Here we get the address we're serving on
	address, ok := listner.Addr().(*net.TCPAddr)
	if !ok {
		return kerr.New("CBLPYVGGUR", "Can't find address (l.Addr() is not *net.TCPAddr)")
	}

	url := fmt.Sprintf("http://localhost:%d/%s/", address.Port, env.Path)

	cmd.Printf("Server now running on %s\n", url)

	// We open the default browser and navigate to the address we're serving
	// from.
	if err := browser.OpenURL(url); err != nil {
		return kerr.Wrap("AEJLAXGVVA", err)
	}

	withCancel(ctx, func() {
		err = http.Serve(listner, nil)
	})
	if err != nil {
		return kerr.Wrap("TUCBTWMRNN", err)
	}

	if cmd.Debug {
		return kerr.New("ATUTBOICGJ", "Connection closed")
	}
	return nil

}
<commit_msg>Ignore error from browser.Open<commit_after>package server // import "kego.io/editor/server"

import (
	"bytes"
	"net"
	"net/http"

	"golang.org/x/net/websocket"

	"github.com/davelondon/gopackages"
	"github.com/davelondon/kerr"

	"fmt"

	"strings"

	"net/rpc"

	"encoding/base64"
	"encoding/gob"

	"time"

	"io"

	"context"

	"github.com/pkg/browser"
	"kego.io/context/cmdctx"
	"kego.io/context/envctx"
	"kego.io/context/sysctx"
	"kego.io/context/wgctx"
	"kego.io/editor/server/auther"
	"kego.io/editor/server/static"
	"kego.io/editor/shared"
	"kego.io/process"
	"kego.io/process/generate"
	"kego.io/process/parser"
	"kego.io/system"
)

const writeTimeout = time.Second * 2

func Start(ctx context.Context, cancel context.CancelFunc) error {

	wgctx.Add(ctx, "Start")
	defer wgctx.Done(ctx, "Start")

	auth := auther.New()

	cmd := cmdctx.FromContext(ctx)

	fail := make(chan error)

	cmd.Println("Starting editor server... ")

	// This contains the source map that will be persisted between requests
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		if strings.HasSuffix(req.URL.Path, "/favicon.ico") {
			w.WriteHeader(404)
			return
		}
		if strings.HasSuffix(req.URL.Path, "/script.js") {
			if err := script(ctx, w, req, false); err != nil {
				fail <- kerr.Wrap("XPVTVKDWHJ", err)
				return
			}
			return
		}
		if strings.HasSuffix(req.URL.Path, "/script.js.map") {
			if err := script(ctx, w, req, true); err != nil {
				fail <- kerr.Wrap("JAIBRHULSI", err)
				return
			}
			return
		}
		if err := root(ctx, w, req, auth); err != nil {
			fail <- kerr.Wrap("QOMJGNOCQF", err)
			return
		}
	})

	if err := rpc.Register(&Server{ctx: ctx, auth: auth}); err != nil {
		return kerr.Wrap("RESBVVGRMH", err)
	}

	http.Handle("/_rpc", websocket.Handler(func(ws *websocket.Conn) {
		ws.PayloadType = websocket.BinaryFrame
		rpc.ServeConn(ws)
	}))

	go func() {
		if err := serve(ctx); err != nil {
			fail <- err
		}
	}()

	done := ctx.Done()
	for {
		select {
		case <-done:
			fmt.Println("Exiting editor server (interupted)... ")
			return nil
		case err, open := <-fail:
			if !open {
				// Channel has been closed, so app should gracefully exit.
				cancel()
				cmd.Println("Exiting editor server (finished)... ")
			} else {
				// Error received, so app should display error.
				// return kerr.New("WKHPTVJBIL", err, "Fail channel receive")
				fmt.Println(err)
			}
			if !cmd.Debug {
				return nil
			}
		}
	}
}

func script(ctx context.Context, w http.ResponseWriter, req *http.Request, sourceMap bool) error {

	wgctx.Add(ctx, "script")
	defer wgctx.Done(ctx, "script")

	path := ""
	if strings.HasSuffix(req.URL.Path, ".map") {
		path = req.URL.Path[1 : len(req.URL.Path)-14]
	} else {
		path = req.URL.Path[1 : len(req.URL.Path)-10]
	}

	env, err := parser.ScanForEnv(ctx, path)

	// This is the client code for the editor which we will compile to
	// Javascript using GopherJs below.
	source, err := generate.Editor(ctx, env)
	if err != nil {
		return kerr.Wrap("UWPDBQXURR", err)
	}

	var out []byte
	withCancel(ctx, func() {
		out, err = Compile(ctx, source, sourceMap)
	})
	if err != nil {
		return kerr.Wrap("LSUXHJMSSX", err)
	}

	withCancel(ctx, func() {
		err = writeWithTimeout(w, out)
	})
	if err != nil {
		return kerr.Wrap("GAOWWBAIAL", err)
	}

	return nil
}

func withCancel(ctx context.Context, op func()) {
	c := make(chan struct{}, 1)
	go func() {
		op()
		close(c)
	}()
	select {
	case <-c:
	case <-ctx.Done():
	}
}

func writeWithTimeout(w io.Writer, b []byte) error {
	c := make(chan error, 1)
	go func() {
		_, err := w.Write(b)
		c <- err
	}()
	select {
	case err := <-c:
		if err != nil {
			return kerr.Wrap("MJDFBJLCIT", err)
		}
		return nil
	case <-time.After(writeTimeout):
		return kerr.New("RTEATWMHDT", "Timeout")
	}
}

func root(ctx context.Context, w http.ResponseWriter, req *http.Request, auth auther.Auther) error {

	wgctx.Add(ctx, "root")
	defer wgctx.Done(ctx, "root")

	if b, err := static.Asset(req.URL.Path[1:]); err == nil {
		if strings.HasSuffix(req.URL.Path, ".css") {
			w.Header().Set("Content-Type", "text/css")
		}
		if err := writeWithTimeout(w, b); err != nil {
			return kerr.Wrap("PVPCXBIUJT", err)
		}
		return nil
	}

	path := req.URL.Path[1:]
	if strings.HasSuffix(path, "/") {
		path = path[0 : len(path)-1]
	}

	// use a new context with a blank sysctx for the duration of this function
	// to prevent caching
	ctx = sysctx.NewContext(ctx)

	scache := sysctx.FromContext(ctx)

	env, err := parser.ScanForEnv(ctx, path)
	if err != nil {
		if _, ok := kerr.Source(err).(gopackages.NotFoundError); ok {
			w.WriteHeader(404)
			return nil
		}
		return kerr.Wrap("ALINBMKDRP", err)
	}

	pcache, err := parser.Parse(ctx, path)
	if err != nil {
		return kerr.Wrap("HIHWJRPUKE", err)
	}

	if err := process.GenerateAll(ctx, env.Path, map[string]bool{}); err != nil {
		return kerr.Wrap("LVGHABDYNQ", err)
	}

	data := map[string]string{}
	for _, name := range pcache.Globals.Keys() {
		if g, ok := pcache.Globals.Get(name); ok {
			data[name] = g.File
		}
	}

	pkgBytes := pcache.PackageBytes
	pkgFilename := pcache.PackageFilename
	if pkgBytes == nil {
		b, err := system.Marshal(ctx, system.EmptyPackage())
		if err != nil {
			return kerr.Wrap("OUBOTYGPKU", err)
		}
		pkgBytes = b
		pkgFilename = "package.ke.json"
	}

	imports := map[string]shared.ImportInfo{}

	var scan func(string) error
	scan = func(path string) error {
		if _, ok := imports[path]; ok {
			return nil
		}
		syspi, ok := scache.Get(path)
		if !ok {
			return kerr.New("VIGKIUPNCF", "%s not found in sys ctx", path)
		}
		info := shared.ImportInfo{
			Path:    path,
			Aliases: syspi.Aliases,
			Types:   map[string]shared.TypeInfo{},
		}
		for _, name := range syspi.Files.Keys() {
			if b, ok := syspi.Files.Get(name); ok {
				info.Types[name] = shared.TypeInfo{
					File:  b.File,
					Bytes: b.Bytes,
				}
			}
		}
		imports[path] = info

		for _, child := range syspi.Aliases {
			if err := scan(child); err != nil {
				return kerr.Wrap("NCULMUUUOT", err)
			}
		}
		return nil
	}
	// First we always import system
	if err := scan("kego.io/system"); err != nil {
		return kerr.Wrap("KRXSLOJKWV", err)
	}
	if err := scan(env.Path); err != nil {
		return kerr.Wrap("EELKQDCJGN", err)
	}

	info := shared.Info{
		Path:            env.Path,
		Aliases:         env.Aliases,
		Data:            data,
		Package:         pkgBytes,
		PackageFilename: pkgFilename,
		Imports:         imports,
		Hash:            auth.Sign([]byte(env.Path)),
	}
	buf := bytes.NewBuffer([]byte{})
	err = gob.NewEncoder(buf).Encode(info)
	if err != nil {
		return kerr.Wrap("OHBYTULHUQ", err)
	}
	base64EncodedString := base64.StdEncoding.EncodeToString(buf.Bytes())
	attrib := base64EncodedString

	source := []byte(`

				<meta charset="utf-8">
				<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
				<link rel="stylesheet" href="/bootstrap/css/bootstrap-theme.min.css">
				<link rel="stylesheet" href="/split.css">
				<link rel="stylesheet" href="/editors.css">
				<link rel="stylesheet" href="/tree.css">

				<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">

			<body id="body" info="` + attrib + `">

		`)

	if err := writeWithTimeout(w, source); err != nil {
		return kerr.Wrap("ICJSAIMDRF", err)
	}

	return nil
}

func serve(ctx context.Context) error {

	wgctx.Add(ctx, "serve")
	defer wgctx.Done(ctx, "serve")

	env := envctx.FromContext(ctx)
	cmd := cmdctx.FromContext(ctx)

	// Starting with port zero chooses a random open port
	listner, err := net.Listen("tcp", fmt.Sprintf(":%d", cmd.Port))
	if err != nil {
		return kerr.Wrap("QGLXHWPWQW", err)
	}
	defer listner.Close()

	// Here we get the address we're serving on
	address, ok := listner.Addr().(*net.TCPAddr)
	if !ok {
		return kerr.New("CBLPYVGGUR", "Can't find address (l.Addr() is not *net.TCPAddr)")
	}

	url := fmt.Sprintf("http://localhost:%d/%s/", address.Port, env.Path)

	cmd.Printf("Server now running on %s\n", url)

	// We open the default browser and navigate to the address we're serving
	// from. This will error if the system doesn't have a browser, so we can
	// ignore the error.
	browser.OpenURL(url)

	withCancel(ctx, func() {
		err = http.Serve(listner, nil)
	})
	if err != nil {
		return kerr.Wrap("TUCBTWMRNN", err)
	}

	if cmd.Debug {
		return kerr.New("ATUTBOICGJ", "Connection closed")
	}
	return nil

}
<|endoftext|>


<commit_before>//Package wmenu creates menus for cli programs.
//It uses wlog for it's interface with the command line.
//It uses os.Stdin, os.Stdout, and os.Stderr with concurrency by default.
//wmenu allows you to change the color of the different parts of the menu.
//This package also creates it's own error structure so you can type assert if you need to.
package wmenu

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//Menu is used to display options to a user.
//A user can then select options and Menu will validate the response and perform the correct action.
type Menu struct {
	question        string
	defaultFunction func(Opt)
	options         []Opt
	ui              wlog.UI
	multiSeparator  string
	multiFunction   func([]Opt)
	loopOnInvalid   bool
	clear           bool
	tries           int
}

//NewMenu creates a menu with a wlog.UI as the writer.
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeparator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
		tries:           3,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, responseColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(questionColor, errorColor, wlog.None, wlog.None, optionColor, responseColor, wlog.None, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
//This is checked when LoopOnInvalid is activated.
//Meaning if an error occurred then it will clear the screen before asking again.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeparator sets the separator to use when multiple options are valid responses.
//Default value is a space.
func (m *Menu) SetSeparator(sep string) {
	m.multiSeparator = sep
}

//SetTries sets the number of tries on the loop before failing out.
//Default is 3.
//Negative values act like 0.
func (m *Menu) SetTries(i int) {
	m.tries = i
}

//LoopOnInvalid is used if an invalid option was given then it will prompt the user again.
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds an option to the menu for the user to select from.
//title is the string the user will select
//isDefault is whether this option is a default option (IE when no options are selected).
//function is what is called when only this option is selected.
//If function is nil then it will default to the menu's Action.
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action to use in certain scenarios.
//If the selected option (by default or user selected) does not have a function applied to it this will be called.
//If there are no default options and no option was selected this will be called with an option that has an ID of -1.
func (m *Menu) Action(function func(Opt)) {
	m.defaultFunction = function
}

//MultipleAction is called when multiple options are selected (by default or user selected).
//If this is set then it uses the separator string specified by SetSeparator (Default is a space) to separate the responses.
//If this is not set then it is implied that the menu only allows for one option to be selected.
func (m *Menu) MultipleAction(function func([]Opt)) {
	m.multiFunction = function
}

//ChangeReaderWriter changes where the menu listens and writes to.
//reader is where user input is collected.
//writer and errorWriter is where the menu should write to.
func (m *Menu) ChangeReaderWriter(reader io.Reader, writer, errorWriter io.Writer) {
	var ui wlog.UI
	ui = wlog.New(reader, writer, errorWriter)
	m.ui = ui
}

//Run is used to execute the menu.
//It will print to options and question to the screen.
//It will only clear the screen if ClearOnMenuRun is activated.
//This will validate all responses.
//Errors are of type MenuError.
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Opt
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print options to screen
		m.print()
		//step 2 ask question, get and validate response
		opt, err := m.ask()
		if err != nil {
			if !IsMenuErr(err) {
				err = newMenuError(err, "", m.triesLeft())
			}
			if m.loopOnInvalid && m.tries > 0 {
				if m.clear {
					Clear()
				}
				m.tries = m.tries - 1
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	m.callAppropriate(options)
	return nil
}

func (m *Menu) callAppropriate(options []Opt) {
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Opt{ID: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.ID, opt.Text))
	}
}

func (m *Menu) ask() ([]Opt, error) {
	res, err := m.ui.Ask(m.question)
	if err != nil {
		return nil, err
	}
	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		if m.checkOptAndFunc(opt) {
			return nil, newMenuError(ErrNoResponse, "", m.triesLeft())
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeparator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "", m.triesLeft())
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response, m.triesLeft())
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response), m.triesLeft())
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response), m.triesLeft())
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Opt
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

//gets a list of default options
func (m *Menu) getDefault() []Opt {
	var opt []Opt
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}

//make sure that there is an action available to be called in certain cases
func (m *Menu) checkOptAndFunc(opt []Opt) bool {
	return ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 1 && m.multiFunction == nil))
}

func (m *Menu) triesLeft() int {
	if m.loopOnInvalid && m.tries > 0 {
		return m.tries
	}
	return 0
}
<commit_msg>Added todo task to issue<commit_after>//Package wmenu creates menus for cli programs.
//It uses wlog for it's interface with the command line.
//It uses os.Stdin, os.Stdout, and os.Stderr with concurrency by default.
//wmenu allows you to change the color of the different parts of the menu.
//This package also creates it's own error structure so you can type assert if you need to.
package wmenu

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//TODO:0 Make error required on return issue:2

//Menu is used to display options to a user.
//A user can then select options and Menu will validate the response and perform the correct action.
type Menu struct {
	question        string
	defaultFunction func(Opt)
	options         []Opt
	ui              wlog.UI
	multiSeparator  string
	multiFunction   func([]Opt)
	loopOnInvalid   bool
	clear           bool
	tries           int
}

//NewMenu creates a menu with a wlog.UI as the writer.
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeparator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
		tries:           3,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, responseColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(questionColor, errorColor, wlog.None, wlog.None, optionColor, responseColor, wlog.None, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
//This is checked when LoopOnInvalid is activated.
//Meaning if an error occurred then it will clear the screen before asking again.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeparator sets the separator to use when multiple options are valid responses.
//Default value is a space.
func (m *Menu) SetSeparator(sep string) {
	m.multiSeparator = sep
}

//SetTries sets the number of tries on the loop before failing out.
//Default is 3.
//Negative values act like 0.
func (m *Menu) SetTries(i int) {
	m.tries = i
}

//LoopOnInvalid is used if an invalid option was given then it will prompt the user again.
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds an option to the menu for the user to select from.
//title is the string the user will select
//isDefault is whether this option is a default option (IE when no options are selected).
//function is what is called when only this option is selected.
//If function is nil then it will default to the menu's Action.
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action to use in certain scenarios.
//If the selected option (by default or user selected) does not have a function applied to it this will be called.
//If there are no default options and no option was selected this will be called with an option that has an ID of -1.
func (m *Menu) Action(function func(Opt)) {
	m.defaultFunction = function
}

//MultipleAction is called when multiple options are selected (by default or user selected).
//If this is set then it uses the separator string specified by SetSeparator (Default is a space) to separate the responses.
//If this is not set then it is implied that the menu only allows for one option to be selected.
func (m *Menu) MultipleAction(function func([]Opt)) {
	m.multiFunction = function
}

//ChangeReaderWriter changes where the menu listens and writes to.
//reader is where user input is collected.
//writer and errorWriter is where the menu should write to.
func (m *Menu) ChangeReaderWriter(reader io.Reader, writer, errorWriter io.Writer) {
	var ui wlog.UI
	ui = wlog.New(reader, writer, errorWriter)
	m.ui = ui
}

//Run is used to execute the menu.
//It will print to options and question to the screen.
//It will only clear the screen if ClearOnMenuRun is activated.
//This will validate all responses.
//Errors are of type MenuError.
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Opt
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print options to screen
		m.print()
		//step 2 ask question, get and validate response
		opt, err := m.ask()
		if err != nil {
			if !IsMenuErr(err) {
				err = newMenuError(err, "", m.triesLeft())
			}
			if m.loopOnInvalid && m.tries > 0 {
				if m.clear {
					Clear()
				}
				m.tries = m.tries - 1
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	m.callAppropriate(options)
	return nil
}

func (m *Menu) callAppropriate(options []Opt) {
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Opt{ID: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.ID, opt.Text))
	}
}

func (m *Menu) ask() ([]Opt, error) {
	res, err := m.ui.Ask(m.question)
	if err != nil {
		return nil, err
	}
	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		if m.checkOptAndFunc(opt) {
			return nil, newMenuError(ErrNoResponse, "", m.triesLeft())
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeparator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "", m.triesLeft())
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response, m.triesLeft())
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response), m.triesLeft())
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response), m.triesLeft())
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Opt
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

//gets a list of default options
func (m *Menu) getDefault() []Opt {
	var opt []Opt
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}

//make sure that there is an action available to be called in certain cases
func (m *Menu) checkOptAndFunc(opt []Opt) bool {
	return ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 1 && m.multiFunction == nil))
}

func (m *Menu) triesLeft() int {
	if m.loopOnInvalid && m.tries > 0 {
		return m.tries
	}
	return 0
}
<|endoftext|>