<commit_before>//go:build linux && cgo && !agent
// +build linux,cgo,!agent

package db

import (
	"fmt"

	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/shared/api"
	"github.com/pkg/errors"
)

// Code generation directives.
//
//go:generate -command mapper lxd-generate db mapper -t profiles.mapper.go
//go:generate mapper reset
//
//go:generate mapper stmt -p db -e profile names
//go:generate mapper stmt -p db -e profile names-by-Project
//go:generate mapper stmt -p db -e profile names-by-Project-and-Name
//go:generate mapper stmt -p db -e profile objects
//go:generate mapper stmt -p db -e profile objects-by-Project
//go:generate mapper stmt -p db -e profile objects-by-Project-and-Name
//go:generate mapper stmt -p db -e profile config-ref
//go:generate mapper stmt -p db -e profile config-ref-by-Project
//go:generate mapper stmt -p db -e profile config-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile devices-ref
//go:generate mapper stmt -p db -e profile devices-ref-by-Project
//go:generate mapper stmt -p db -e profile devices-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile used-by-ref
//go:generate mapper stmt -p db -e profile used-by-ref-by-Project
//go:generate mapper stmt -p db -e profile used-by-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile id
//go:generate mapper stmt -p db -e profile create struct=Profile
//go:generate mapper stmt -p db -e profile create-config-ref
//go:generate mapper stmt -p db -e profile create-devices-ref
//go:generate mapper stmt -p db -e profile rename
//go:generate mapper stmt -p db -e profile delete
//go:generate mapper stmt -p db -e profile delete-config-ref
//go:generate mapper stmt -p db -e profile delete-devices-ref
//go:generate mapper stmt -p db -e profile update struct=Profile
//
//go:generate mapper method -p db -e profile URIs
//go:generate mapper method -p db -e profile List
//go:generate mapper method -p db -e profile Get
//go:generate mapper method -p db -e profile Exists struct=Profile
//go:generate mapper method -p db -e profile ID struct=Profile
//go:generate mapper method -p db -e profile ConfigRef
//go:generate mapper method -p db -e profile DevicesRef
//go:generate mapper method -p db -e profile UsedByRef
//go:generate mapper method -p db -e profile Create struct=Profile
//go:generate mapper method -p db -e profile Rename
//go:generate mapper method -p db -e profile Delete
//go:generate mapper method -p db -e profile Update struct=Profile

// Profile is a value object holding db-related details about a profile.
type Profile struct {
	ID          int
	Project     string `db:"primary=yes&join=projects.name"`
	Name        string `db:"primary=yes"`
	Description string `db:"coalesce=''"`
	Config      map[string]string
	Devices     map[string]map[string]string
	UsedBy      []string
}

// ProfileToAPI is a convenience to convert a Profile db struct into
// an API profile struct.
func ProfileToAPI(profile *Profile) *api.Profile {
	p := &api.Profile{
		Name:   profile.Name,
		UsedBy: profile.UsedBy,
	}
	p.Description = profile.Description
	p.Config = profile.Config
	p.Devices = profile.Devices

	return p
}

// ProfileFilter can be used to filter results yielded by ProfileList.
type ProfileFilter struct {
	Project string
	Name    string
}

// GetProfileNames returns the names of all profiles in the given project.
func (c *Cluster) GetProfileNames(project string) ([]string, error) {
	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check if project has profiles")
		}
		if !enabled {
			project = "default"
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	q := fmt.Sprintf(`
SELECT profiles.name
 FROM profiles
 JOIN projects ON projects.id = profiles.project_id
WHERE projects.name = ?
`)
	inargs := []interface{}{project}
	var name string
	outfmt := []interface{}{name}
	result, err := queryScan(c, q, inargs, outfmt)
	if err != nil {
		return []string{}, err
	}

	response := []string{}
	for _, r := range result {
		response = append(response, r[0].(string))
	}

	return response, nil
}

// GetProfile returns the profile with the given name.
func (c *Cluster) GetProfile(project, name string) (int64, *api.Profile, error) {
	var result *api.Profile
	var id int64

	err := c.Transaction(func(tx *ClusterTx) error {
		var err error
		id, result, err = tx.getProfile(project, name)
		return err
	})
	if err != nil {
		return -1, nil, err
	}

	return id, result, nil
}

// Returns the profile with the given name.
func (c *ClusterTx) getProfile(project, name string) (int64, *api.Profile, error) {
	var result *api.Profile
	var id int64

	enabled, err := c.ProjectHasProfiles(project)
	if err != nil {
		return -1, nil, errors.Wrap(err, "Check if project has profiles")
	}
	if !enabled {
		project = "default"
	}

	profile, err := c.GetProfile(project, name)
	if err != nil {
		return -1, nil, err
	}

	result = ProfileToAPI(profile)
	id = int64(profile.ID)

	return id, result, nil
}

// GetProfiles returns the profiles with the given names in the given project.
func (c *Cluster) GetProfiles(project string, names []string) ([]api.Profile, error) {
	profiles := make([]api.Profile, len(names))

	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check if project has profiles")
		}
		if !enabled {
			project = "default"
		}

		for i, name := range names {
			profile, err := tx.GetProfile(project, name)
			if err != nil {
				return errors.Wrapf(err, "Load profile %q", name)
			}
			profiles[i] = *ProfileToAPI(profile)
		}

		return nil
	})
	if err != nil {
		return nil, err
	}

	return profiles, nil
}

// GetInstancesWithProfile gets the names of the instance associated with the
// profile with the given name in the given project.
func (c *Cluster) GetInstancesWithProfile(project, profile string) (map[string][]string, error) {
	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check if project has profiles")
		}
		if !enabled {
			project = "default"
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	q := `SELECT instances.name, projects.name FROM instances
		JOIN instances_profiles ON instances.id == instances_profiles.instance_id
		JOIN projects ON projects.id == instances.project_id
		WHERE instances_profiles.profile_id ==
		  (SELECT profiles.id FROM profiles
		   JOIN projects ON projects.id == profiles.project_id
		   WHERE profiles.name=? AND projects.name=?)`

	results := map[string][]string{}
	inargs := []interface{}{profile, project}
	var name string
	outfmt := []interface{}{name, name}

	output, err := queryScan(c, q, inargs, outfmt)
	if err != nil {
		return nil, err
	}

	for _, r := range output {
		if results[r[1].(string)] == nil {
			results[r[1].(string)] = []string{}
		}

		results[r[1].(string)] = append(results[r[1].(string)], r[0].(string))
	}

	return results, nil
}

// RemoveUnreferencedProfiles removes unreferenced profiles.
func (c *Cluster) RemoveUnreferencedProfiles() error {
	stmt := `
DELETE FROM profiles_config WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices_config WHERE profile_device_id NOT IN (SELECT id FROM profiles_devices);
`
	err := exec(c, stmt)
	if err != nil {
		return err
	}

	return nil
}

// ExpandInstanceConfig expands the given instance config with the config
// values of the given profiles.
func ExpandInstanceConfig(config map[string]string, profiles []api.Profile) map[string]string {
	expandedConfig := map[string]string{}

	// Apply all the profiles
	profileConfigs := make([]map[string]string, len(profiles))
	for i, profile := range profiles {
		profileConfigs[i] = profile.Config
	}

	for i := range profileConfigs {
		for k, v := range profileConfigs[i] {
			expandedConfig[k] = v
		}
	}

	// Stick the given config on top
	for k, v := range config {
		expandedConfig[k] = v
	}

	return expandedConfig
}

// ExpandInstanceDevices expands the given instance devices with the devices
// defined in the given profiles.
func ExpandInstanceDevices(devices deviceConfig.Devices, profiles []api.Profile) deviceConfig.Devices {
	expandedDevices := deviceConfig.Devices{}

	// Apply all the profiles
	profileDevices := make([]deviceConfig.Devices, len(profiles))
	for i, profile := range profiles {
		profileDevices[i] = deviceConfig.NewDevices(profile.Devices)
	}
	for i := range profileDevices {
		for k, v := range profileDevices[i] {
			expandedDevices[k] = v
		}
	}

	// Stick the given devices on top
	for k, v := range devices {
		expandedDevices[k] = v
	}

	return expandedDevices
}
<commit_msg>lxd/db/profiles: Cleanup arg names and errors in GetProfiles<commit_after>//go:build linux && cgo && !agent
// +build linux,cgo,!agent

package db

import (
	"fmt"

	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/shared/api"
	"github.com/pkg/errors"
)

// Code generation directives.
//
//go:generate -command mapper lxd-generate db mapper -t profiles.mapper.go
//go:generate mapper reset
//
//go:generate mapper stmt -p db -e profile names
//go:generate mapper stmt -p db -e profile names-by-Project
//go:generate mapper stmt -p db -e profile names-by-Project-and-Name
//go:generate mapper stmt -p db -e profile objects
//go:generate mapper stmt -p db -e profile objects-by-Project
//go:generate mapper stmt -p db -e profile objects-by-Project-and-Name
//go:generate mapper stmt -p db -e profile config-ref
//go:generate mapper stmt -p db -e profile config-ref-by-Project
//go:generate mapper stmt -p db -e profile config-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile devices-ref
//go:generate mapper stmt -p db -e profile devices-ref-by-Project
//go:generate mapper stmt -p db -e profile devices-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile used-by-ref
//go:generate mapper stmt -p db -e profile used-by-ref-by-Project
//go:generate mapper stmt -p db -e profile used-by-ref-by-Project-and-Name
//go:generate mapper stmt -p db -e profile id
//go:generate mapper stmt -p db -e profile create struct=Profile
//go:generate mapper stmt -p db -e profile create-config-ref
//go:generate mapper stmt -p db -e profile create-devices-ref
//go:generate mapper stmt -p db -e profile rename
//go:generate mapper stmt -p db -e profile delete
//go:generate mapper stmt -p db -e profile delete-config-ref
//go:generate mapper stmt -p db -e profile delete-devices-ref
//go:generate mapper stmt -p db -e profile update struct=Profile
//
//go:generate mapper method -p db -e profile URIs
//go:generate mapper method -p db -e profile List
//go:generate mapper method -p db -e profile Get
//go:generate mapper method -p db -e profile Exists struct=Profile
//go:generate mapper method -p db -e profile ID struct=Profile
//go:generate mapper method -p db -e profile ConfigRef
//go:generate mapper method -p db -e profile DevicesRef
//go:generate mapper method -p db -e profile UsedByRef
//go:generate mapper method -p db -e profile Create struct=Profile
//go:generate mapper method -p db -e profile Rename
//go:generate mapper method -p db -e profile Delete
//go:generate mapper method -p db -e profile Update struct=Profile

// Profile is a value object holding db-related details about a profile.
type Profile struct {
	ID          int
	Project     string `db:"primary=yes&join=projects.name"`
	Name        string `db:"primary=yes"`
	Description string `db:"coalesce=''"`
	Config      map[string]string
	Devices     map[string]map[string]string
	UsedBy      []string
}

// ProfileToAPI is a convenience to convert a Profile db struct into
// an API profile struct.
func ProfileToAPI(profile *Profile) *api.Profile {
	p := &api.Profile{
		Name:   profile.Name,
		UsedBy: profile.UsedBy,
	}
	p.Description = profile.Description
	p.Config = profile.Config
	p.Devices = profile.Devices

	return p
}

// ProfileFilter can be used to filter results yielded by ProfileList.
type ProfileFilter struct {
	Project string
	Name    string
}

// GetProfileNames returns the names of all profiles in the given project.
func (c *Cluster) GetProfileNames(project string) ([]string, error) {
	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check if project has profiles")
		}
		if !enabled {
			project = "default"
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	q := fmt.Sprintf(`
SELECT profiles.name
 FROM profiles
 JOIN projects ON projects.id = profiles.project_id
WHERE projects.name = ?
`)
	inargs := []interface{}{project}
	var name string
	outfmt := []interface{}{name}
	result, err := queryScan(c, q, inargs, outfmt)
	if err != nil {
		return []string{}, err
	}

	response := []string{}
	for _, r := range result {
		response = append(response, r[0].(string))
	}

	return response, nil
}

// GetProfile returns the profile with the given name.
func (c *Cluster) GetProfile(project, name string) (int64, *api.Profile, error) {
	var result *api.Profile
	var id int64

	err := c.Transaction(func(tx *ClusterTx) error {
		var err error
		id, result, err = tx.getProfile(project, name)
		return err
	})
	if err != nil {
		return -1, nil, err
	}

	return id, result, nil
}

// Returns the profile with the given name.
func (c *ClusterTx) getProfile(project, name string) (int64, *api.Profile, error) {
	var result *api.Profile
	var id int64

	enabled, err := c.ProjectHasProfiles(project)
	if err != nil {
		return -1, nil, errors.Wrap(err, "Check if project has profiles")
	}
	if !enabled {
		project = "default"
	}

	profile, err := c.GetProfile(project, name)
	if err != nil {
		return -1, nil, err
	}

	result = ProfileToAPI(profile)
	id = int64(profile.ID)

	return id, result, nil
}

// GetProfiles returns the profiles with the given names in the given project.
func (c *Cluster) GetProfiles(projectName string, profileNames []string) ([]api.Profile, error) {
	profiles := make([]api.Profile, len(profileNames))

	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(projectName)
		if err != nil {
			return errors.Wrapf(err, "Failed checking if project %q has profiles", projectName)
		}

		if !enabled {
			projectName = "default"
		}

		for i, profileName := range profileNames {
			profile, err := tx.GetProfile(projectName, profileName)
			if err != nil {
				return errors.Wrapf(err, "Failed loading profile %q", profileName)
			}

			profiles[i] = *ProfileToAPI(profile)
		}

		return nil
	})
	if err != nil {
		return nil, err
	}

	return profiles, nil
}

// GetInstancesWithProfile gets the names of the instance associated with the
// profile with the given name in the given project.
func (c *Cluster) GetInstancesWithProfile(project, profile string) (map[string][]string, error) {
	err := c.Transaction(func(tx *ClusterTx) error {
		enabled, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check if project has profiles")
		}
		if !enabled {
			project = "default"
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	q := `SELECT instances.name, projects.name FROM instances
		JOIN instances_profiles ON instances.id == instances_profiles.instance_id
		JOIN projects ON projects.id == instances.project_id
		WHERE instances_profiles.profile_id ==
		  (SELECT profiles.id FROM profiles
		   JOIN projects ON projects.id == profiles.project_id
		   WHERE profiles.name=? AND projects.name=?)`

	results := map[string][]string{}
	inargs := []interface{}{profile, project}
	var name string
	outfmt := []interface{}{name, name}

	output, err := queryScan(c, q, inargs, outfmt)
	if err != nil {
		return nil, err
	}

	for _, r := range output {
		if results[r[1].(string)] == nil {
			results[r[1].(string)] = []string{}
		}

		results[r[1].(string)] = append(results[r[1].(string)], r[0].(string))
	}

	return results, nil
}

// RemoveUnreferencedProfiles removes unreferenced profiles.
func (c *Cluster) RemoveUnreferencedProfiles() error {
	stmt := `
DELETE FROM profiles_config WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices WHERE profile_id NOT IN (SELECT id FROM profiles);
DELETE FROM profiles_devices_config WHERE profile_device_id NOT IN (SELECT id FROM profiles_devices);
`
	err := exec(c, stmt)
	if err != nil {
		return err
	}

	return nil
}

// ExpandInstanceConfig expands the given instance config with the config
// values of the given profiles.
func ExpandInstanceConfig(config map[string]string, profiles []api.Profile) map[string]string {
	expandedConfig := map[string]string{}

	// Apply all the profiles
	profileConfigs := make([]map[string]string, len(profiles))
	for i, profile := range profiles {
		profileConfigs[i] = profile.Config
	}

	for i := range profileConfigs {
		for k, v := range profileConfigs[i] {
			expandedConfig[k] = v
		}
	}

	// Stick the given config on top
	for k, v := range config {
		expandedConfig[k] = v
	}

	return expandedConfig
}

// ExpandInstanceDevices expands the given instance devices with the devices
// defined in the given profiles.
func ExpandInstanceDevices(devices deviceConfig.Devices, profiles []api.Profile) deviceConfig.Devices {
	expandedDevices := deviceConfig.Devices{}

	// Apply all the profiles
	profileDevices := make([]deviceConfig.Devices, len(profiles))
	for i, profile := range profiles {
		profileDevices[i] = deviceConfig.NewDevices(profile.Devices)
	}
	for i := range profileDevices {
		for k, v := range profileDevices[i] {
			expandedDevices[k] = v
		}
	}

	// Stick the given devices on top
	for k, v := range devices {
		expandedDevices[k] = v
	}

	return expandedDevices
}
<|endoftext|>


<commit_before>package node

import (
	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds node-local configuration values for a certain LXD instance.
type Config struct {
	tx *db.NodeTx // DB transaction the values in this config are bound to
	m  config.Map // Low-level map holding the config values.
}

// ConfigLoad loads a new Config object with the current node-local configuration
// values fetched from the database. An optional list of config value triggers
// can be passed, each config key must have at most one trigger.
func ConfigLoad(tx *db.NodeTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, errors.Wrap(err, "Cannot fetch node config from database")
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to load node config")
	}

	return &Config{tx: tx, m: m}, nil
}

// HTTPSAddress returns the address and port this LXD node should expose its
// API to, if any.
func (c *Config) HTTPSAddress() string {
	networkAddress := c.m.GetString("core.https_address")
	if networkAddress != "" {
		return util.CanonicalNetworkAddress(networkAddress, shared.HTTPSDefaultPort)
	}

	return networkAddress
}

// BGPAddress returns the address and port to setup the BGP listener on
func (c *Config) BGPAddress() string {
	return c.m.GetString("core.bgp_address")
}

// BGPRouterID returns the address to use as a router ID
func (c *Config) BGPRouterID() string {
	return c.m.GetString("core.bgp_routerid")
}

// ClusterAddress returns the address and port this LXD node should use for
// cluster communication.
func (c *Config) ClusterAddress() string {
	clusterAddress := c.m.GetString("cluster.https_address")
	if clusterAddress != "" {
		return util.CanonicalNetworkAddress(clusterAddress, shared.HTTPSDefaultPort)
	}

	return clusterAddress
}

// DebugAddress returns the address and port to setup the pprof listener on
func (c *Config) DebugAddress() string {
	debugAddress := c.m.GetString("core.debug_address")
	if debugAddress != "" {
		return util.CanonicalNetworkAddress(debugAddress, shared.HTTPDefaultPort)
	}

	return debugAddress
}

// MAASMachine returns the MAAS machine this instance is associated with, if
// any.
func (c *Config) MAASMachine() string {
	return c.m.GetString("maas.machine")
}

// StorageBackupsVolume returns the name of the pool/volume to use for storing backup tarballs
func (c *Config) StorageBackupsVolume() string {
	return c.m.GetString("storage.backups_volume")
}

// StorageImagesVolume returns the name of the pool/volume to use for storing image tarballs
func (c *Config) StorageImagesVolume() string {
	return c.m.GetString("storage.images_volume")
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]interface{} {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
func (c *Config) Replace(values map[string]interface{}) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
func (c *Config) Patch(patch map[string]interface{}) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}

	return c.update(values)
}

// HTTPSAddress is a convenience for loading the node configuration and
// returning the value of core.https_address.
func HTTPSAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.HTTPSAddress(), nil
}

// BGPAddress is a convenience for loading the node configuration and
// returning the value of core.bgp_address.
func BGPAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.BGPAddress(), nil
}

// BGPRouterID is a convenience for loading the node configuration and
// returning the value of core.bgp_routerid.
func BGPRouterID(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.BGPRouterID(), nil
}

// ClusterAddress is a convenience for loading the node configuration and
// returning the value of cluster.https_address.
func ClusterAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.ClusterAddress(), nil
}

// DebugAddress is a convenience for loading the node configuration and
// returning the value of core.debug_address.
func DebugAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.DebugAddress(), nil
}

func (c *Config) update(values map[string]interface{}) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateConfig(changed)
	if err != nil {
		return nil, errors.Wrap(err, "Cannot persist local configuration changes")
	}

	return changed, nil
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	// Network address for this LXD server
	"core.https_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// Network address for cluster communication
	"cluster.https_address": {Validator: validate.Optional(validate.IsListenAddress(true, false, false))},

	// Network address for the BGP server
	"core.bgp_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// Unique router ID for the BGP server
	"core.bgp_routerid": {Validator: validate.Optional(validate.IsNetworkAddressV4)},

	// Network address for the debug server
	"core.debug_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// MAAS machine this LXD instance is associated with
	"maas.machine": {},

	// Storage volumes to store backups/images on
	"storage.backups_volume": {},
	"storage.images_volume":  {},
}
<commit_msg>lxd/node: Add core.metrics_address config key<commit_after>package node

import (
	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds node-local configuration values for a certain LXD instance.
type Config struct {
	tx *db.NodeTx // DB transaction the values in this config are bound to
	m  config.Map // Low-level map holding the config values.
}

// ConfigLoad loads a new Config object with the current node-local configuration
// values fetched from the database. An optional list of config value triggers
// can be passed, each config key must have at most one trigger.
func ConfigLoad(tx *db.NodeTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, errors.Wrap(err, "Cannot fetch node config from database")
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to load node config")
	}

	return &Config{tx: tx, m: m}, nil
}

// HTTPSAddress returns the address and port this LXD node should expose its
// API to, if any.
func (c *Config) HTTPSAddress() string {
	networkAddress := c.m.GetString("core.https_address")
	if networkAddress != "" {
		return util.CanonicalNetworkAddress(networkAddress, shared.HTTPSDefaultPort)
	}

	return networkAddress
}

// BGPAddress returns the address and port to setup the BGP listener on
func (c *Config) BGPAddress() string {
	return c.m.GetString("core.bgp_address")
}

// BGPRouterID returns the address to use as a router ID
func (c *Config) BGPRouterID() string {
	return c.m.GetString("core.bgp_routerid")
}

// ClusterAddress returns the address and port this LXD node should use for
// cluster communication.
func (c *Config) ClusterAddress() string {
	clusterAddress := c.m.GetString("cluster.https_address")
	if clusterAddress != "" {
		return util.CanonicalNetworkAddress(clusterAddress, shared.HTTPSDefaultPort)
	}

	return clusterAddress
}

// DebugAddress returns the address and port to setup the pprof listener on
func (c *Config) DebugAddress() string {
	debugAddress := c.m.GetString("core.debug_address")
	if debugAddress != "" {
		return util.CanonicalNetworkAddress(debugAddress, shared.HTTPDefaultPort)
	}

	return debugAddress
}

// MetricsAddress returns the address and port to setup the metrics listener on
func (c *Config) MetricsAddress() string {
	metricsAddress := c.m.GetString("core.metrics_address")
	if metricsAddress != "" {
		return util.CanonicalNetworkAddress(metricsAddress, shared.HTTPSMetricsDefaultPort)
	}

	return metricsAddress
}

// MAASMachine returns the MAAS machine this instance is associated with, if
// any.
func (c *Config) MAASMachine() string {
	return c.m.GetString("maas.machine")
}

// StorageBackupsVolume returns the name of the pool/volume to use for storing backup tarballs
func (c *Config) StorageBackupsVolume() string {
	return c.m.GetString("storage.backups_volume")
}

// StorageImagesVolume returns the name of the pool/volume to use for storing image tarballs
func (c *Config) StorageImagesVolume() string {
	return c.m.GetString("storage.images_volume")
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]interface{} {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
func (c *Config) Replace(values map[string]interface{}) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
func (c *Config) Patch(patch map[string]interface{}) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}

	return c.update(values)
}

// HTTPSAddress is a convenience for loading the node configuration and
// returning the value of core.https_address.
func HTTPSAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.HTTPSAddress(), nil
}

// BGPAddress is a convenience for loading the node configuration and
// returning the value of core.bgp_address.
func BGPAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.BGPAddress(), nil
}

// BGPRouterID is a convenience for loading the node configuration and
// returning the value of core.bgp_routerid.
func BGPRouterID(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.BGPRouterID(), nil
}

// ClusterAddress is a convenience for loading the node configuration and
// returning the value of cluster.https_address.
func ClusterAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.ClusterAddress(), nil
}

// DebugAddress is a convenience for loading the node configuration and
// returning the value of core.debug_address.
func DebugAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.DebugAddress(), nil
}

// MetricsAddress is a convenience for loading the node configuration and
// returning the value of core.metrics_address.
func MetricsAddress(node *db.Node) (string, error) {
	var config *Config
	err := node.Transaction(func(tx *db.NodeTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	if err != nil {
		return "", err
	}

	return config.MetricsAddress(), nil
}

func (c *Config) update(values map[string]interface{}) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateConfig(changed)
	if err != nil {
		return nil, errors.Wrap(err, "Cannot persist local configuration changes")
	}

	return changed, nil
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	// Network address for this LXD server
	"core.https_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// Network address for cluster communication
	"cluster.https_address": {Validator: validate.Optional(validate.IsListenAddress(true, false, false))},

	// Network address for the BGP server
	"core.bgp_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// Unique router ID for the BGP server
	"core.bgp_routerid": {Validator: validate.Optional(validate.IsNetworkAddressV4)},

	// Network address for the debug server
	"core.debug_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// Network address for the debug server
	"core.metrics_address": {Validator: validate.Optional(validate.IsListenAddress(true, true, false))},

	// MAAS machine this LXD instance is associated with
	"maas.machine": {},

	// Storage volumes to store backups/images on
	"storage.backups_volume": {},
	"storage.images_volume":  {},
}
<|endoftext|>


<commit_before>package lzma

// Constants used by the distance codec.
const (
	// number of the supported len states
	lenStates = 4
	// start for the position models
	startPosModel = 4
	// first index with align bits support
	endPosModel = 14
	// bits for the position slots
	posSlotBits = 6
	// number of align bits
	alignBits = 4
	// maximum positon slot
	maxPosSlot = 63
)

// distCodec provides encoding and decoding of distance values. It support
// values for dist from 0 to 2^32-1. Note the real match distance is one
// higher.
type distCodec struct {
	posSlotCodecs [lenStates]treeCodec
	posModel      [endPosModel - startPosModel]treeReverseCodec
	alignCodec    treeReverseCodec
}

// newDistCodec creates a new distance codec.
func newDistCodec() *distCodec {
	dc := new(distCodec)
	for i := range dc.posSlotCodecs {
		dc.posSlotCodecs[i] = makeTreeCodec(posSlotBits)
	}
	for i := range dc.posModel {
		posSlot := startPosModel + i
		bits := (posSlot >> 1) - 1
		dc.posModel[i] = makeTreeReverseCodec(bits)
	}
	dc.alignCodec = makeTreeReverseCodec(alignBits)
	return dc
}

// Converts the value l to a supported lenState value.
func lenState(l uint32) uint32 {
	s := l
	if s >= lenStates {
		s = lenStates - 1
	}
	return s
}

// Encode encodes the distance using the parameter l. Dist can have values from
// the full range of uint32 values.
func (dc *distCodec) Encode(dist uint32, l uint32, e *rangeEncoder,
) (err error) {
	// Compute the posSlot using nlz32
	var posSlot uint32
	var bits uint32
	if dist < startPosModel {
		posSlot = dist
	} else {
		bits = uint32(30 - nlz32(dist))
		posSlot = startPosModel - 2 + (bits << 1)
		posSlot += (dist >> uint(bits)) & 1
	}

	if err = dc.posSlotCodecs[lenState(l)].Encode(posSlot, e); err != nil {
		return
	}

	switch {
	case posSlot < startPosModel:
		return nil
	case posSlot < endPosModel:
		tc := &dc.posModel[posSlot-startPosModel]
		return tc.Encode(dist, e)
	}
	dic := directCodec(bits - alignBits)
	if err = dic.Encode(dist>>alignBits, e); err != nil {
		return
	}
	return dc.alignCodec.Encode(dist, e)
}

// Decode decodes the distance using the parameter l.
func (dc *distCodec) Decode(l uint32, d *rangeDecoder,
) (dist uint32, err error) {
	posSlot, err := dc.posSlotCodecs[lenState(l)].Decode(d)
	if err != nil {
		return
	}

	// posSlot equals distance
	if posSlot < startPosModel {
		return posSlot, nil
	}

	// posSlot are using the individial models
	bits := (posSlot >> 1) - 1
	dist = (2 | (posSlot & 1)) << bits
	var u uint32
	if posSlot < endPosModel {
		tc := &dc.posModel[posSlot-startPosModel]
		if u, err = tc.Decode(d); err != nil {
			return 0, err
		}
		dist += u
		return dist, nil
	}

	// posSlots use direct encoding and a single model for the four align
	// bits.
	dic := directCodec(bits - alignBits)
	if u, err = dic.Decode(d); err != nil {
		return 0, err
	}
	dist += u << alignBits
	if u, err = dc.alignCodec.Decode(d); err != nil {
		return 0, err
	}
	dist += u
	return dist, nil
}
<commit_msg>lzma: simplified lenState function used by the distance codec<commit_after>package lzma

// Constants used by the distance codec.
const (
	// number of the supported len states
	lenStates = 4
	// start for the position models
	startPosModel = 4
	// first index with align bits support
	endPosModel = 14
	// bits for the position slots
	posSlotBits = 6
	// number of align bits
	alignBits = 4
	// maximum positon slot
	maxPosSlot = 63
)

// distCodec provides encoding and decoding of distance values. It support
// values for dist from 0 to 2^32-1. Note the real match distance is one
// higher.
type distCodec struct {
	posSlotCodecs [lenStates]treeCodec
	posModel      [endPosModel - startPosModel]treeReverseCodec
	alignCodec    treeReverseCodec
}

// newDistCodec creates a new distance codec.
func newDistCodec() *distCodec {
	dc := new(distCodec)
	for i := range dc.posSlotCodecs {
		dc.posSlotCodecs[i] = makeTreeCodec(posSlotBits)
	}
	for i := range dc.posModel {
		posSlot := startPosModel + i
		bits := (posSlot >> 1) - 1
		dc.posModel[i] = makeTreeReverseCodec(bits)
	}
	dc.alignCodec = makeTreeReverseCodec(alignBits)
	return dc
}

// Converts the value l to a supported lenState value.
func lenState(l uint32) uint32 {
	if l >= lenStates {
		l = lenStates - 1
	}
	return l
}

// Encode encodes the distance using the parameter l. Dist can have values from
// the full range of uint32 values.
func (dc *distCodec) Encode(dist uint32, l uint32, e *rangeEncoder,
) (err error) {
	// Compute the posSlot using nlz32
	var posSlot uint32
	var bits uint32
	if dist < startPosModel {
		posSlot = dist
	} else {
		bits = uint32(30 - nlz32(dist))
		posSlot = startPosModel - 2 + (bits << 1)
		posSlot += (dist >> uint(bits)) & 1
	}

	if err = dc.posSlotCodecs[lenState(l)].Encode(posSlot, e); err != nil {
		return
	}

	switch {
	case posSlot < startPosModel:
		return nil
	case posSlot < endPosModel:
		tc := &dc.posModel[posSlot-startPosModel]
		return tc.Encode(dist, e)
	}
	dic := directCodec(bits - alignBits)
	if err = dic.Encode(dist>>alignBits, e); err != nil {
		return
	}
	return dc.alignCodec.Encode(dist, e)
}

// Decode decodes the distance using the parameter l.
func (dc *distCodec) Decode(l uint32, d *rangeDecoder,
) (dist uint32, err error) {
	posSlot, err := dc.posSlotCodecs[lenState(l)].Decode(d)
	if err != nil {
		return
	}

	// posSlot equals distance
	if posSlot < startPosModel {
		return posSlot, nil
	}

	// posSlot are using the individial models
	bits := (posSlot >> 1) - 1
	dist = (2 | (posSlot & 1)) << bits
	var u uint32
	if posSlot < endPosModel {
		tc := &dc.posModel[posSlot-startPosModel]
		if u, err = tc.Decode(d); err != nil {
			return 0, err
		}
		dist += u
		return dist, nil
	}

	// posSlots use direct encoding and a single model for the four align
	// bits.
	dic := directCodec(bits - alignBits)
	if u, err = dic.Decode(d); err != nil {
		return 0, err
	}
	dist += u << alignBits
	if u, err = dc.alignCodec.Decode(d); err != nil {
		return 0, err
	}
	dist += u
	return dist, nil
}
<|endoftext|>


<commit_before>package editor

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

	"github.com/nsf/termbox-go"
	"github.com/wx13/sith/file"
	"github.com/wx13/sith/syntaxcolor"
	"github.com/wx13/sith/terminal"
)

// Editor is the main editor object.  It orchestrates the terminal,
// the buffer, etc.
type Editor struct {
	screen     *terminal.Screen
	file       *file.File
	files      []*file.File
	fileIdx    int
	fileIdxPrv int
	keyboard   *terminal.Keyboard
	flushChan  chan struct{}
	keymap     KeyMap
	xKeymap    KeyMap

	searchHist  []string
	replaceHist []string

	copyBuffer []string
	copyContig int
	copyHist   [][]string
}

// NewEditor creates a new Editor object.
func NewEditor() *Editor {
	return &Editor{
		flushChan:  make(chan struct{}, 1),
		screen:     terminal.NewScreen(),
		copyBuffer: []string{},
		copyContig: 0,
		copyHist:   [][]string{},
	}
}

// OpenNewFile offers a file selection menu to choose a new file to open.
func (editor *Editor) OpenNewFile() {
	dir, _ := os.Getwd()
	dir += "/"
	names := []string{}
	idx := 0
	files := []os.FileInfo{}
	filename := ""
	for {
		files, _ = ioutil.ReadDir(dir)
		dotdot, err := os.Stat("../")
		if err == nil {
			files = append([]os.FileInfo{dotdot}, files...)
		}
		names = []string{}
		for _, file := range files {
			if file.IsDir() {
				names = append(names, file.Name()+"/")
			} else {
				names = append(names, file.Name())
			}
		}
		menu := terminal.NewMenu(editor.screen)
		key := ""
		idx, key = menu.Choose(names, 0, "ctrlO")
		editor.Flush()
		if idx < 0 || key == "cancel" {
			return
		}
		if key == "ctrlO" {
			var err error
			p := terminal.MakePrompt(editor.screen)
			filename, err = p.Ask(dir, nil)
			if err != nil {
				editor.screen.Notify("Unknown answer")
				return
			}
			break
		}
		chosenFile := files[idx]
		if chosenFile.IsDir() {
			dir = filepath.Clean(dir+chosenFile.Name()) + "/"
		} else {
			filename = names[idx]
			break
		}
	}
	cwd, _ := os.Getwd()
	chosenFile, _ := filepath.Rel(cwd, dir+filename)
	editor.OpenFile(chosenFile)
	editor.fileIdxPrv = editor.fileIdx
	editor.fileIdx = len(editor.files) - 1
	editor.file = editor.files[editor.fileIdx]
}

// OpenFile opens a specified file.
func (editor *Editor) OpenFile(name string) {
	file := file.NewFile(name, editor.flushChan, editor.screen)
	file.SyntaxRules = syntaxcolor.NewSyntaxRules(name)
	editor.files = append(editor.files, file)
}

// OpenFiles opens a set of specified files.
func (editor *Editor) OpenFiles(fileNames []string) {
	for _, name := range fileNames {
		editor.OpenFile(name)
	}
	if len(editor.files) == 0 {
		editor.files = append(editor.files, file.NewFile("", editor.flushChan, editor.screen))
	}
	editor.fileIdx = 0
	editor.fileIdxPrv = 0
	editor.file = editor.files[0]
}

func (editor *Editor) ReloadAll() {
	for _, file := range editor.files {
		file.Reload()
	}
}

// Quit closes all the files and exits the editor.
func (editor *Editor) Quit() {
	for range editor.files {
		if !editor.CloseFile() {
			editor.NextFile()
		}
	}
}

// CloseFile closes the current file.
func (editor *Editor) CloseFile() bool {
	editor.Flush()
	idx := editor.fileIdx
	if !editor.files[idx].Close() {
		return false
	}
	editor.files = append(editor.files[:idx], editor.files[idx+1:]...)
	if len(editor.files) == 0 {
		editor.screen.Close()
		return true
	}
	editor.NextFile()
	return true
}

// Listen is the main editor loop.
func (editor *Editor) Listen() {

	editor.keyboard = terminal.NewKeyboard()
	editor.keymap = editor.MakeKeyMap()
	editor.xKeymap = editor.MakeExtraKeyMap()
	for {
		cmd, r := editor.keyboard.GetKey()
		editor.handleCmd(cmd, r)
		editor.copyContig--
		editor.RequestFlush()
	}

}

func (editor *Editor) handleCmd(cmd string, r rune) {
	ans := editor.keymap.Run(cmd)
	if ans == "" {
		return
	}
	if ans == "char" {
		editor.file.InsertChar(r)
	} else {
		editor.screen.Notify("Unknown keypress")
	}
}

// ExtraMode allows for additional keypresses.
func (editor *Editor) ExtraMode() {
	p := terminal.MakePrompt(editor.screen)
	r := p.GetRune("key:")
	ans := editor.xKeymap.Run(string(r))
	if len(ans) > 0 {
		editor.screen.Notify("Unknown command")
	}
}

// NextFile cycles to the next open file.
func (editor *Editor) NextFile() {
	editor.SwitchFile(editor.fileIdx + 1)
}

// PrevFile cycles to the previous open file.
func (editor *Editor) PrevFile() {
	editor.SwitchFile(editor.fileIdx - 1)
}

// LastFile toggles between the two most recent files.
func (editor *Editor) LastFile() {
	editor.SwitchFile(editor.fileIdxPrv)
}

// SelectFile offers a menu to select from open files.
func (editor *Editor) SelectFile() {
	names := []string{}
	for _, file := range editor.files {
		status := ""
		if file.IsModified() {
			status = "*"
		}
		if file.FileChanged() {
			status += "+"
		}
		names = append(names, status+file.Name)
	}
	menu := terminal.NewMenu(editor.screen)
	idx, cmd := menu.Choose(names, editor.fileIdx)
	if idx >= 0 && cmd == "" {
		editor.SwitchFile(idx)
	}
}

// SetCharMode offers a menu for selecting the character
// display mode.
func (editor *Editor) SetCharMode() {
	modes := editor.screen.ListCharModes()
	menu := terminal.NewMenu(editor.screen)
	idx, cmd := menu.Choose(modes, 0)
	if idx >= 0 && cmd == "" {
		editor.screen.SetCharMode(idx)
	}
}

// CmdMenu offers a menu of available commands.
func (editor *Editor) CmdMenu() {

	keys := editor.keymap.Keys()
	sort.Strings(keys)
	names := editor.keymap.DisplayNames(keys, "")

	xkeys := editor.xKeymap.Keys()
	sort.Strings(xkeys)
	xnames := editor.xKeymap.DisplayNames(xkeys, "Alt-6 ")

	names = append(names, xnames...)

	menu := terminal.NewMenu(editor.screen)
	idx, cancel := menu.Choose(names, 0)
	if idx < 0 || cancel != "" {
		return
	}

	if idx < len(keys) {
		key := keys[idx]
		editor.keymap.Run(key)
	} else {
		key := xkeys[idx-len(keys)]
		editor.xKeymap.Run(key)
	}

}

// Save saves the buffer to the file.
func (editor *Editor) Save() {
	filetype := editor.file.SyntaxRules.GetFileType(editor.file.Name)
	if filetype == "go" {
		editor.GoFmt()
	}
	editor.file.RequestSave()
}

// SaveAll saves all the open buffers.
func (editor *Editor) SaveAll() {
	for _, file := range editor.files {
		filetype := file.SyntaxRules.GetFileType(file.Name)
		if filetype == "go" {
			editor.GoFmt()
		}
		file.RequestSave()
	}
}

// SaveAs prompts for a file to save to.
func (editor *Editor) SaveAs() {
	p := terminal.MakePrompt(editor.screen)
	filename, err := p.Ask("Save to:", nil)
	if err != nil {
		editor.screen.Notify("Cancelled")
		return
	}
	editor.file.Name = filename
	editor.Save()
}

// GoFmt runs the Go formatter on the buffer text.
func (editor *Editor) GoFmt() {
	err := editor.file.GoFmt()
	if err == nil {
		editor.RequestFlush()
		editor.file.NotifyUser("GoFmt done")
	} else {
		editor.file.NotifyUser(err.Error())
	}
}

func intMod(a, n int) int {
	if a < 0 {
		return a - n*((a-n+1)/n)
	}
	return a - n*(a/n)
}

// SwitchFile changes to a new file buffer.
func (editor *Editor) SwitchFile(n int) {
	n = intMod(n, len(editor.files))
	editor.fileIdxPrv = editor.fileIdx
	editor.fileIdx = n
	editor.file = editor.files[n]
}

// HighlightCursors highlights all the multi-cursors.
func (editor *Editor) HighlightCursors() {
	cells := termbox.CellBuffer()
	cols, _ := termbox.Size()
	for k := range editor.file.MultiCursor.Cursors()[1:] {
		r, c := editor.file.GetCursor(k + 1)
		j := r*cols + c
		if j < 0 || j >= len(cells) {
			continue
		}
		cells[j].Bg |= termbox.AttrReverse
		cells[j].Fg |= termbox.AttrReverse
	}
}

// Flush writes the current buffer to the screen.
func (editor *Editor) Flush() {
	editor.file.Flush()
	editor.HighlightCursors()
	editor.UpdateStatus()
	editor.screen.Flush()
}

// KeepFlushed waits for flush requests, and then flushes
// to the screen.
func (editor *Editor) KeepFlushed() {
	go func() {
		for {
			<-editor.flushChan
			editor.Flush()
		}
	}()
}

// RequestFlush requests a flush event (async).
func (editor *Editor) RequestFlush() {
	select {
	case editor.flushChan <- struct{}{}:
	default:
	}
}

func (editor *Editor) getFilename(maxNameLen int) string {
	name := editor.file.Name
	nameLen := len(name)
	if nameLen > maxNameLen {
		name = name[0:maxNameLen/2] + "..." + name[nameLen-maxNameLen/2:nameLen]
	}
	return name
}

func (editor *Editor) writeModStatus(row, col int) int {
	if editor.file.IsModified() {
		editor.screen.WriteStringColor(row, col-3, "M  ", termbox.ColorRed, termbox.ColorDefault)
		return 3
	}
	for _, file := range editor.files {
		if file.IsModified() {
			editor.screen.WriteStringColor(row, col-3, "M  ", termbox.ColorYellow, termbox.ColorDefault)
			return 3
		}
	}
	return 0
}

func (editor *Editor) writeSyncStatus(row, col int) int {
	if editor.file.FileChanged() {
		editor.screen.WriteStringColor(row, col-3, "S  ", termbox.ColorRed, termbox.ColorDefault)
		return 3
	}
	for _, file := range editor.files {
		if file.FileChanged() {
			editor.screen.WriteStringColor(row, col-3, "S  ", termbox.ColorYellow, termbox.ColorDefault)
			return 3
		}
	}
	return 0
}

// UpdateStatus updates the status line.
func (editor *Editor) UpdateStatus() {
	cols, rows := termbox.Size()

	name := editor.getFilename(cols / 3)
	message := fmt.Sprintf("%s (%d/%d)   %d/%d,%d",
		name,
		editor.fileIdx,
		len(editor.files),
		editor.file.MultiCursor.GetRow(0),
		editor.file.Length()-1,
		editor.file.MultiCursor.GetCol(0),
	)
	col := cols - len(message)
	editor.screen.WriteString(rows-1, col, message)
	editor.screen.WriteString(rows-1, 0, "[ Sith 0.4.4 ]")
	editor.screen.DecorateStatusLine()
	col -= editor.writeModStatus(rows-1, col)
	col -= editor.writeSyncStatus(rows-1, col)
	editor.file.WriteStatus(rows-1, col)
	editor.screen.SetCursor(editor.file.GetCursor(0))
}
<commit_msg>make overlapping cursor more visible<commit_after>package editor

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

	"github.com/nsf/termbox-go"
	"github.com/wx13/sith/file"
	"github.com/wx13/sith/syntaxcolor"
	"github.com/wx13/sith/terminal"
)

// Editor is the main editor object.  It orchestrates the terminal,
// the buffer, etc.
type Editor struct {
	screen     *terminal.Screen
	file       *file.File
	files      []*file.File
	fileIdx    int
	fileIdxPrv int
	keyboard   *terminal.Keyboard
	flushChan  chan struct{}
	keymap     KeyMap
	xKeymap    KeyMap

	searchHist  []string
	replaceHist []string

	copyBuffer []string
	copyContig int
	copyHist   [][]string
}

// NewEditor creates a new Editor object.
func NewEditor() *Editor {
	return &Editor{
		flushChan:  make(chan struct{}, 1),
		screen:     terminal.NewScreen(),
		copyBuffer: []string{},
		copyContig: 0,
		copyHist:   [][]string{},
	}
}

// OpenNewFile offers a file selection menu to choose a new file to open.
func (editor *Editor) OpenNewFile() {
	dir, _ := os.Getwd()
	dir += "/"
	names := []string{}
	idx := 0
	files := []os.FileInfo{}
	filename := ""
	for {
		files, _ = ioutil.ReadDir(dir)
		dotdot, err := os.Stat("../")
		if err == nil {
			files = append([]os.FileInfo{dotdot}, files...)
		}
		names = []string{}
		for _, file := range files {
			if file.IsDir() {
				names = append(names, file.Name()+"/")
			} else {
				names = append(names, file.Name())
			}
		}
		menu := terminal.NewMenu(editor.screen)
		key := ""
		idx, key = menu.Choose(names, 0, "ctrlO")
		editor.Flush()
		if idx < 0 || key == "cancel" {
			return
		}
		if key == "ctrlO" {
			var err error
			p := terminal.MakePrompt(editor.screen)
			filename, err = p.Ask(dir, nil)
			if err != nil {
				editor.screen.Notify("Unknown answer")
				return
			}
			break
		}
		chosenFile := files[idx]
		if chosenFile.IsDir() {
			dir = filepath.Clean(dir+chosenFile.Name()) + "/"
		} else {
			filename = names[idx]
			break
		}
	}
	cwd, _ := os.Getwd()
	chosenFile, _ := filepath.Rel(cwd, dir+filename)
	editor.OpenFile(chosenFile)
	editor.fileIdxPrv = editor.fileIdx
	editor.fileIdx = len(editor.files) - 1
	editor.file = editor.files[editor.fileIdx]
}

// OpenFile opens a specified file.
func (editor *Editor) OpenFile(name string) {
	file := file.NewFile(name, editor.flushChan, editor.screen)
	file.SyntaxRules = syntaxcolor.NewSyntaxRules(name)
	editor.files = append(editor.files, file)
}

// OpenFiles opens a set of specified files.
func (editor *Editor) OpenFiles(fileNames []string) {
	for _, name := range fileNames {
		editor.OpenFile(name)
	}
	if len(editor.files) == 0 {
		editor.files = append(editor.files, file.NewFile("", editor.flushChan, editor.screen))
	}
	editor.fileIdx = 0
	editor.fileIdxPrv = 0
	editor.file = editor.files[0]
}

func (editor *Editor) ReloadAll() {
	for _, file := range editor.files {
		file.Reload()
	}
}

// Quit closes all the files and exits the editor.
func (editor *Editor) Quit() {
	for range editor.files {
		if !editor.CloseFile() {
			editor.NextFile()
		}
	}
}

// CloseFile closes the current file.
func (editor *Editor) CloseFile() bool {
	editor.Flush()
	idx := editor.fileIdx
	if !editor.files[idx].Close() {
		return false
	}
	editor.files = append(editor.files[:idx], editor.files[idx+1:]...)
	if len(editor.files) == 0 {
		editor.screen.Close()
		return true
	}
	editor.NextFile()
	return true
}

// Listen is the main editor loop.
func (editor *Editor) Listen() {

	editor.keyboard = terminal.NewKeyboard()
	editor.keymap = editor.MakeKeyMap()
	editor.xKeymap = editor.MakeExtraKeyMap()
	for {
		cmd, r := editor.keyboard.GetKey()
		editor.handleCmd(cmd, r)
		editor.copyContig--
		editor.RequestFlush()
	}

}

func (editor *Editor) handleCmd(cmd string, r rune) {
	ans := editor.keymap.Run(cmd)
	if ans == "" {
		return
	}
	if ans == "char" {
		editor.file.InsertChar(r)
	} else {
		editor.screen.Notify("Unknown keypress")
	}
}

// ExtraMode allows for additional keypresses.
func (editor *Editor) ExtraMode() {
	p := terminal.MakePrompt(editor.screen)
	r := p.GetRune("key:")
	ans := editor.xKeymap.Run(string(r))
	if len(ans) > 0 {
		editor.screen.Notify("Unknown command")
	}
}

// NextFile cycles to the next open file.
func (editor *Editor) NextFile() {
	editor.SwitchFile(editor.fileIdx + 1)
}

// PrevFile cycles to the previous open file.
func (editor *Editor) PrevFile() {
	editor.SwitchFile(editor.fileIdx - 1)
}

// LastFile toggles between the two most recent files.
func (editor *Editor) LastFile() {
	editor.SwitchFile(editor.fileIdxPrv)
}

// SelectFile offers a menu to select from open files.
func (editor *Editor) SelectFile() {
	names := []string{}
	for _, file := range editor.files {
		status := ""
		if file.IsModified() {
			status = "*"
		}
		if file.FileChanged() {
			status += "+"
		}
		names = append(names, status+file.Name)
	}
	menu := terminal.NewMenu(editor.screen)
	idx, cmd := menu.Choose(names, editor.fileIdx)
	if idx >= 0 && cmd == "" {
		editor.SwitchFile(idx)
	}
}

// SetCharMode offers a menu for selecting the character
// display mode.
func (editor *Editor) SetCharMode() {
	modes := editor.screen.ListCharModes()
	menu := terminal.NewMenu(editor.screen)
	idx, cmd := menu.Choose(modes, 0)
	if idx >= 0 && cmd == "" {
		editor.screen.SetCharMode(idx)
	}
}

// CmdMenu offers a menu of available commands.
func (editor *Editor) CmdMenu() {

	keys := editor.keymap.Keys()
	sort.Strings(keys)
	names := editor.keymap.DisplayNames(keys, "")

	xkeys := editor.xKeymap.Keys()
	sort.Strings(xkeys)
	xnames := editor.xKeymap.DisplayNames(xkeys, "Alt-6 ")

	names = append(names, xnames...)

	menu := terminal.NewMenu(editor.screen)
	idx, cancel := menu.Choose(names, 0)
	if idx < 0 || cancel != "" {
		return
	}

	if idx < len(keys) {
		key := keys[idx]
		editor.keymap.Run(key)
	} else {
		key := xkeys[idx-len(keys)]
		editor.xKeymap.Run(key)
	}

}

// Save saves the buffer to the file.
func (editor *Editor) Save() {
	filetype := editor.file.SyntaxRules.GetFileType(editor.file.Name)
	if filetype == "go" {
		editor.GoFmt()
	}
	editor.file.RequestSave()
}

// SaveAll saves all the open buffers.
func (editor *Editor) SaveAll() {
	for _, file := range editor.files {
		filetype := file.SyntaxRules.GetFileType(file.Name)
		if filetype == "go" {
			editor.GoFmt()
		}
		file.RequestSave()
	}
}

// SaveAs prompts for a file to save to.
func (editor *Editor) SaveAs() {
	p := terminal.MakePrompt(editor.screen)
	filename, err := p.Ask("Save to:", nil)
	if err != nil {
		editor.screen.Notify("Cancelled")
		return
	}
	editor.file.Name = filename
	editor.Save()
}

// GoFmt runs the Go formatter on the buffer text.
func (editor *Editor) GoFmt() {
	err := editor.file.GoFmt()
	if err == nil {
		editor.RequestFlush()
		editor.file.NotifyUser("GoFmt done")
	} else {
		editor.file.NotifyUser(err.Error())
	}
}

func intMod(a, n int) int {
	if a < 0 {
		return a - n*((a-n+1)/n)
	}
	return a - n*(a/n)
}

// SwitchFile changes to a new file buffer.
func (editor *Editor) SwitchFile(n int) {
	n = intMod(n, len(editor.files))
	editor.fileIdxPrv = editor.fileIdx
	editor.fileIdx = n
	editor.file = editor.files[n]
}

// HighlightCursors highlights all the multi-cursors.
func (editor *Editor) HighlightCursors() {
	cells := termbox.CellBuffer()
	cols, _ := termbox.Size()
	r0, c0 := editor.file.GetCursor(0)
	for k := range editor.file.MultiCursor.Cursors()[1:] {
		r, c := editor.file.GetCursor(k + 1)
		j := r*cols + c
		if j < 0 || j >= len(cells) {
			continue
		}
		if r == r0 && c == c0 {
			cells[j].Bg |= termbox.AttrBold
			cells[j].Fg |= termbox.AttrBold | termbox.ColorYellow
		} else {
			cells[j].Bg |= termbox.AttrReverse
			cells[j].Fg |= termbox.AttrReverse
		}
	}
}

// Flush writes the current buffer to the screen.
func (editor *Editor) Flush() {
	editor.file.Flush()
	editor.HighlightCursors()
	editor.UpdateStatus()
	editor.screen.Flush()
}

// KeepFlushed waits for flush requests, and then flushes
// to the screen.
func (editor *Editor) KeepFlushed() {
	go func() {
		for {
			<-editor.flushChan
			editor.Flush()
		}
	}()
}

// RequestFlush requests a flush event (async).
func (editor *Editor) RequestFlush() {
	select {
	case editor.flushChan <- struct{}{}:
	default:
	}
}

func (editor *Editor) getFilename(maxNameLen int) string {
	name := editor.file.Name
	nameLen := len(name)
	if nameLen > maxNameLen {
		name = name[0:maxNameLen/2] + "..." + name[nameLen-maxNameLen/2:nameLen]
	}
	return name
}

func (editor *Editor) writeModStatus(row, col int) int {
	if editor.file.IsModified() {
		editor.screen.WriteStringColor(row, col-3, "M  ", termbox.ColorRed, termbox.ColorDefault)
		return 3
	}
	for _, file := range editor.files {
		if file.IsModified() {
			editor.screen.WriteStringColor(row, col-3, "M  ", termbox.ColorYellow, termbox.ColorDefault)
			return 3
		}
	}
	return 0
}

func (editor *Editor) writeSyncStatus(row, col int) int {
	if editor.file.FileChanged() {
		editor.screen.WriteStringColor(row, col-3, "S  ", termbox.ColorRed, termbox.ColorDefault)
		return 3
	}
	for _, file := range editor.files {
		if file.FileChanged() {
			editor.screen.WriteStringColor(row, col-3, "S  ", termbox.ColorYellow, termbox.ColorDefault)
			return 3
		}
	}
	return 0
}

// UpdateStatus updates the status line.
func (editor *Editor) UpdateStatus() {
	cols, rows := termbox.Size()

	name := editor.getFilename(cols / 3)
	message := fmt.Sprintf("%s (%d/%d)   %d/%d,%d",
		name,
		editor.fileIdx,
		len(editor.files),
		editor.file.MultiCursor.GetRow(0),
		editor.file.Length()-1,
		editor.file.MultiCursor.GetCol(0),
	)
	col := cols - len(message)
	editor.screen.WriteString(rows-1, col, message)
	editor.screen.WriteString(rows-1, 0, "[ Sith 0.4.4 ]")
	editor.screen.DecorateStatusLine()
	col -= editor.writeModStatus(rows-1, col)
	col -= editor.writeSyncStatus(rows-1, col)
	editor.file.WriteStatus(rows-1, col)
	editor.screen.SetCursor(editor.file.GetCursor(0))
}
<|endoftext|>


<commit_before>package parse

// STATUS borked

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

const (
	arjHeaderSize    = 0x22 // XXX what is size?1
	arjBlockSizeMin  = 30
	arjBlockSizeMax  = 2600
	arjMaxSFX        = 500000 // size of self-extracting prefix
	arjHeaderIDHi    = 0xea
	arjHeaderIDLo    = 0x60
	arjFirstHdrSize  = 0x1e
	arjCommentMax    = 2048
	arjFileNameMax   = 512
	arjHeaderSizeMax = (arjFirstHdrSize + 10 + arjFileNameMax + arjCommentMax)
	arjCrcMask       = 0xffffffff
)

func ARJ(file *os.File) (*ParsedLayout, error) {

	if !isARJ(file) {
		return nil, nil
	}

	mainHeader, err := parseARJMainHeader(file)

	// XXX rest of arj

	return &ParsedLayout{
		FileKind: Archive,
		Layout:   mainHeader}, err
}

func parseARJMainHeader(f *os.File) ([]Layout, error) {

	var err error
	offset, err := findARJHeader(f)
	if err != nil {
		return nil, err
	}

	mainHeaderLen := int64(35) // XXX hdr len?!

	f.Seek(mainHeaderLen, os.SEEK_SET)

	archiveName := ""
	comment := ""

	if archiveName, _, err = zeroTerminatedASCII(f); err != nil {
		return nil, err
	}
	archiveNameLen := int64(len(archiveName)) + 1 // including terminating zero

	if comment, _, err = zeroTerminatedASCII(f); err != nil {
		return nil, err
	}
	commentLen := int64(len(comment)) + 1

	chunk := Layout{
		Offset: offset,
		Length: mainHeaderLen + int64(len(archiveName)+len(comment)) + 8,
		Type:   Group,
		Info:   "main header",
		Childs: []Layout{
			// XXX convert arjMainHeader into []Layout and add to Childs in return
			{Offset: offset, Length: 2, Type: Uint16le, Info: "magic"},
			{Offset: offset + 2, Length: 2, Type: Uint16le, Info: "basic header size"}, // excl. Magic+HdrSize
			{Offset: offset + 4, Length: 1, Type: Uint8, Info: "size up to and including 'extra data'"},
			{Offset: offset + 5, Length: 1, Type: Uint8, Info: "archiver version number"},
			{Offset: offset + 6, Length: 1, Type: Uint8, Info: "minimum archiver version to extract"},
			{Offset: offset + 7, Length: 1, Type: Uint8, Info: "host OS"},   // XXX map hostOSes
			{Offset: offset + 8, Length: 1, Type: Uint8, Info: "arj flags"}, // XXX show bitfield
			{Offset: offset + 9, Length: 1, Type: Uint8, Info: "security version"},
			{Offset: offset + 10, Length: 1, Type: Uint8, Info: "file type"},        // XXX map fileTypes
			{Offset: offset + 11, Length: 4, Type: Uint32le, Info: "created time"},  // XXX time in "msdos-format"
			{Offset: offset + 15, Length: 4, Type: Uint32le, Info: "modified time"}, // XXX time in "msdos-format"
			{Offset: offset + 19, Length: 4, Type: Uint32le, Info: "archive size for secured archive"},
			{Offset: offset + 23, Length: 4, Type: Uint32le, Info: "security envelope file position"},
			{Offset: offset + 27, Length: 4, Type: Uint32le, Info: "filespec position in filename"},
			{Offset: offset + 31, Length: 2, Type: Uint16le, Info: "length in bytes of security envelope data"},
			{Offset: offset + 33, Length: 1, Type: Uint8, Info: "encryption version"},
			{Offset: offset + 34, Length: 1, Type: Uint8, Info: "last chapter"}, // XXX
		},
	}
	offset += mainHeaderLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: archiveNameLen, Type: ASCIIZ, Info: "archive name"},
	}...)
	offset += archiveNameLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: commentLen, Type: ASCIIZ, Info: "comment"},
	}...)
	offset += commentLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: 4, Type: Uint32le, Info: "crc32"},
		{Offset: offset + 4, Length: 4, Type: Uint32le, Info: "ext header size"},
	}...)
	offset += 8

	return []Layout{chunk}, nil

	/*
	   XXX dont understand to parse 0x22, is 0 in both my samples
	   ?   extra data
	     1   arj protection factor
	     1   arj flags (second series)
	               (0x01 = ALTVOLNAME_FLAG) indicates special volume naming
	                                        option
	               (0x02 = reserved bit)
	     2   spare bytes
	*/
}

func isARJ(file *os.File) bool {

	file.Seek(0, os.SEEK_SET)
	r := io.Reader(file)
	var b [2]byte
	if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
		return false
	}
	return b[0] == 0x60 && b[1] == 0xea
}

/**
 * finds arj header and leaves file position at it
 */
func findARJHeader(file *os.File) (int64, error) {

	reader := io.Reader(file)

	pos, _ := file.Seek(0, os.SEEK_CUR)
	lastpos, _ := file.Seek(0, os.SEEK_END)
	lastpos -= 2

	if lastpos > arjMaxSFX {
		lastpos = arjMaxSFX
	}
	for ; pos < lastpos; pos++ {
		fmt.Printf("setting pos to %04x\n", pos)
		pos2, _ := file.Seek(pos, os.SEEK_SET)
		if pos != pos2 {
			fmt.Printf("expected %d, got %d\n", pos, pos2)
		}

		var c byte
		if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
			return 0, err
		}

		for pos < lastpos {
			if c != arjHeaderIDLo { // low order first
				if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
					return 0, err
				}
			} else {
				if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
					return 0, err
				}
				if c == arjHeaderIDHi {
					// fmt.Println("yes 1")
					break
				}
			}
			pos++
		}
		if pos >= lastpos {
			// fmt.Println("yes 2")
			break
		}

		var headerSize uint16
		if err := binary.Read(reader, binary.LittleEndian, &headerSize); err != nil {
			return 0, err
		}

		// fmt.Printf("header size %02x\n", headerSize)

		if headerSize <= arjHeaderSizeMax {
			// fmt.Printf("arcpos %04x\n", arcpos)

			// XXX implement crc check?
			//crc = crcMask
			//fread_crc(header, headersize, fd)
			//if (crc ^ crcMask) == fget_crc(fd) {
			file.Seek(pos, os.SEEK_SET)
			return pos, nil
			//}
		}
	}

	return 0, fmt.Errorf("could not find arj header")
}
<commit_msg>remove unused stuff<commit_after>package parse

// STATUS borked

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

const (
	arjHeaderSize    = 0x22 // XXX what is size?1
	arjBlockSizeMin  = 30
	arjBlockSizeMax  = 2600
	arjMaxSFX        = 500000 // size of self-extracting prefix
	arjHeaderIDHi    = 0xea
	arjHeaderIDLo    = 0x60
	arjFirstHdrSize  = 0x1e
	arjCommentMax    = 2048
	arjFileNameMax   = 512
	arjHeaderSizeMax = (arjFirstHdrSize + 10 + arjFileNameMax + arjCommentMax)
	arjCrcMask       = 0xffffffff
)

func ARJ(file *os.File) (*ParsedLayout, error) {

	if !isARJ(file) {
		return nil, nil
	}

	mainHeader, err := parseARJMainHeader(file)

	// XXX rest of arj

	return &ParsedLayout{
		FileKind: Archive,
		Layout:   mainHeader}, err
}

func parseARJMainHeader(f *os.File) ([]Layout, error) {

	var err error
	offset, err := findARJHeader(f)
	if err != nil {
		return nil, err
	}

	mainHeaderLen := int64(35) // XXX hdr len?!

	f.Seek(mainHeaderLen, os.SEEK_SET)

	archiveName := ""
	comment := ""

	if archiveName, _, err = zeroTerminatedASCII(f); err != nil {
		return nil, err
	}
	archiveNameLen := int64(len(archiveName)) + 1 // including terminating zero

	if comment, _, err = zeroTerminatedASCII(f); err != nil {
		return nil, err
	}
	commentLen := int64(len(comment)) + 1

	chunk := Layout{
		Offset: offset,
		Length: mainHeaderLen + int64(len(archiveName)+len(comment)) + 8,
		Type:   Group,
		Info:   "main header",
		Childs: []Layout{
			// XXX convert arjMainHeader into []Layout and add to Childs in return
			{Offset: offset, Length: 2, Type: Uint16le, Info: "magic"},
			{Offset: offset + 2, Length: 2, Type: Uint16le, Info: "basic header size"}, // excl. Magic+HdrSize
			{Offset: offset + 4, Length: 1, Type: Uint8, Info: "size up to and including 'extra data'"},
			{Offset: offset + 5, Length: 1, Type: Uint8, Info: "archiver version number"},
			{Offset: offset + 6, Length: 1, Type: Uint8, Info: "minimum archiver version to extract"},
			{Offset: offset + 7, Length: 1, Type: Uint8, Info: "host OS"},   // XXX map hostOSes
			{Offset: offset + 8, Length: 1, Type: Uint8, Info: "arj flags"}, // XXX show bitfield
			{Offset: offset + 9, Length: 1, Type: Uint8, Info: "security version"},
			{Offset: offset + 10, Length: 1, Type: Uint8, Info: "file type"},        // XXX map fileTypes
			{Offset: offset + 11, Length: 4, Type: Uint32le, Info: "created time"},  // XXX time in "msdos-format"
			{Offset: offset + 15, Length: 4, Type: Uint32le, Info: "modified time"}, // XXX time in "msdos-format"
			{Offset: offset + 19, Length: 4, Type: Uint32le, Info: "archive size for secured archive"},
			{Offset: offset + 23, Length: 4, Type: Uint32le, Info: "security envelope file position"},
			{Offset: offset + 27, Length: 4, Type: Uint32le, Info: "filespec position in filename"},
			{Offset: offset + 31, Length: 2, Type: Uint16le, Info: "length in bytes of security envelope data"},
			{Offset: offset + 33, Length: 1, Type: Uint8, Info: "encryption version"},
			{Offset: offset + 34, Length: 1, Type: Uint8, Info: "last chapter"}, // XXX
		},
	}
	offset += mainHeaderLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: archiveNameLen, Type: ASCIIZ, Info: "archive name"},
	}...)
	offset += archiveNameLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: commentLen, Type: ASCIIZ, Info: "comment"},
	}...)
	offset += commentLen

	chunk.Childs = append(chunk.Childs, []Layout{
		{Offset: offset, Length: 4, Type: Uint32le, Info: "crc32"},
		{Offset: offset + 4, Length: 4, Type: Uint32le, Info: "ext header size"},
	}...)
	offset += 8

	return []Layout{chunk}, nil

	/*
	   XXX dont understand to parse 0x22, is 0 in both my samples
	   ?   extra data
	     1   arj protection factor
	     1   arj flags (second series)
	               (0x01 = ALTVOLNAME_FLAG) indicates special volume naming
	                                        option
	               (0x02 = reserved bit)
	     2   spare bytes
	*/
}

func isARJ(file *os.File) bool {

	file.Seek(0, os.SEEK_SET)
	r := io.Reader(file)
	var b [2]byte
	if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
		return false
	}
	return b[0] == 0x60 && b[1] == 0xea
}

/**
 * finds arj header and leaves file position at it
 */
func findARJHeader(file *os.File) (int64, error) {

	reader := io.Reader(file)

	pos, _ := file.Seek(0, os.SEEK_CUR)
	lastpos, _ := file.Seek(0, os.SEEK_END)
	lastpos -= 2

	if lastpos > arjMaxSFX {
		lastpos = arjMaxSFX
	}
	for ; pos < lastpos; pos++ {
		fmt.Printf("setting pos to %04x\n", pos)
		pos2, _ := file.Seek(pos, os.SEEK_SET)
		if pos != pos2 {
			fmt.Printf("expected %d, got %d\n", pos, pos2)
		}

		var c byte
		if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
			return 0, err
		}

		for pos < lastpos {
			if c != arjHeaderIDLo { // low order first
				if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
					return 0, err
				}
			} else {
				if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
					return 0, err
				}
				if c == arjHeaderIDHi {
					// fmt.Println("yes 1")
					break
				}
			}
			pos++
		}
		if pos >= lastpos {
			// fmt.Println("yes 2")
			break
		}

		var headerSize uint16
		if err := binary.Read(reader, binary.LittleEndian, &headerSize); err != nil {
			return 0, err
		}

		// fmt.Printf("header size %02x\n", headerSize)

		if headerSize <= arjHeaderSizeMax {
			return pos, nil
		}
	}

	return 0, fmt.Errorf("could not find arj header")
}
<|endoftext|>


<commit_before>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"exec"
	"flag"
	"fmt"
	"go/build"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

func usage() {
	fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n")
	fmt.Fprintf(os.Stderr, "\tgoinstall -a\n")
	flag.PrintDefaults()
	os.Exit(2)
}

const logfile = "goinstall.log"

var (
	fset          = token.NewFileSet()
	argv0         = os.Args[0]
	errors        = false
	parents       = make(map[string]string)
	visit         = make(map[string]status)
	installedPkgs = make(map[string]map[string]bool)

	allpkg            = flag.Bool("a", false, "install all previously installed packages")
	reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL)
	update            = flag.Bool("u", false, "update already-downloaded packages")
	doInstall         = flag.Bool("install", true, "build and install")
	clean             = flag.Bool("clean", false, "clean the package directory before installing")
	nuke              = flag.Bool("nuke", false, "clean the package directory and target before installing")
	useMake           = flag.Bool("make", true, "use make to build and install")
	verbose           = flag.Bool("v", false, "verbose")
)

type status int // status for visited map
const (
	unvisited status = iota
	visiting
	done
)

func logf(format string, args ...interface{}) {
	format = "%s: " + format
	args = append([]interface{}{argv0}, args...)
	fmt.Fprintf(os.Stderr, format, args...)
}

func printf(format string, args ...interface{}) {
	if *verbose {
		logf(format, args...)
	}
}

func errorf(format string, args ...interface{}) {
	errors = true
	logf(format, args...)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	if runtime.GOROOT() == "" {
		fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
		os.Exit(1)
	}
	readPackageList()

	// special case - "unsafe" is already installed
	visit["unsafe"] = done

	args := flag.Args()
	if *allpkg {
		if len(args) != 0 {
			usage() // -a and package list both provided
		}
		// install all packages that were ever installed
		n := 0
		for _, pkgs := range installedPkgs {
			for pkg := range pkgs {
				args = append(args, pkg)
				n++
			}
		}
		if n == 0 {
			logf("no installed packages\n")
			os.Exit(1)
		}
	}
	if len(args) == 0 {
		usage()
	}
	for _, path := range args {
		if strings.HasPrefix(path, "http://") {
			errorf("'http://' used in remote path, try '%s'\n", path[7:])
			continue
		}

		install(path, "")
	}
	if errors {
		os.Exit(1)
	}
}

// printDeps prints the dependency path that leads to pkg.
func printDeps(pkg string) {
	if pkg == "" {
		return
	}
	if visit[pkg] != done {
		printDeps(parents[pkg])
	}
	fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg)
}

// readPackageList reads the list of installed packages from the
// goinstall.log files in GOROOT and the GOPATHs and initalizes
// the installedPkgs variable.
func readPackageList() {
	for _, t := range build.Path {
		installedPkgs[t.Path] = make(map[string]bool)
		name := filepath.Join(t.Path, logfile)
		pkglistdata, err := ioutil.ReadFile(name)
		if err != nil {
			printf("%s\n", err)
			continue
		}
		pkglist := strings.Fields(string(pkglistdata))
		for _, pkg := range pkglist {
			installedPkgs[t.Path][pkg] = true
		}
	}
}

// logPackage logs the named package as installed in the goinstall.log file
// in the given tree if the package is not already in that file.
func logPackage(pkg string, tree *build.Tree) (logged bool) {
	if installedPkgs[tree.Path][pkg] {
		return false
	}
	name := filepath.Join(tree.Path, logfile)
	fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		logf("%s\n", err)
		return false
	}
	fmt.Fprintf(fout, "%s\n", pkg)
	fout.Close()
	return true
}

// install installs the package named by path, which is needed by parent.
func install(pkg, parent string) {
	// Make sure we're not already trying to install pkg.
	switch visit[pkg] {
	case done:
		return
	case visiting:
		fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0)
		printDeps(parent)
		fmt.Fprintf(os.Stderr, "\t%s\n", pkg)
		os.Exit(2)
	}
	parents[pkg] = parent
	visit[pkg] = visiting
	defer func() {
		visit[pkg] = done
	}()

	// Check whether package is local or remote.
	// If remote, download or update it.
	tree, pkg, err := build.FindTree(pkg)
	// Don't build the standard library.
	if err == nil && tree.Goroot && isStandardPath(pkg) {
		if parent == "" {
			errorf("%s: can not goinstall the standard library\n", pkg)
		} else {
			printf("%s: skipping standard library\n", pkg)
		}
		return
	}
	// Download remote packages if not found or forced with -u flag.
	remote, public := isRemote(pkg), false
	if remote && (err == build.ErrNotFound || (err == nil && *update)) {
		printf("%s: download\n", pkg)
		public, err = download(pkg, tree.SrcDir())
	}
	if err != nil {
		errorf("%s: %v\n", pkg, err)
		return
	}
	dir := filepath.Join(tree.SrcDir(), pkg)

	// Install prerequisites.
	dirInfo, err := build.ScanDir(dir, parent == "")
	if err != nil {
		errorf("%s: %v\n", pkg, err)
		return
	}
	if len(dirInfo.GoFiles)+len(dirInfo.CgoFiles) == 0 {
		errorf("%s: package has no files\n", pkg)
		return
	}
	for _, p := range dirInfo.Imports {
		if p != "C" {
			install(p, pkg)
		}
	}
	if errors {
		return
	}

	// Install this package.
	if *useMake {
		err := domake(dir, pkg, tree, dirInfo.IsCommand())
		if err != nil {
			errorf("%s: install: %v\n", pkg, err)
			return
		}
	} else {
		script, err := build.Build(tree, pkg, dirInfo)
		if err != nil {
			errorf("%s: install: %v\n", pkg, err)
			return
		}
		if *nuke {
			printf("%s: nuke\n", pkg)
			script.Nuke()
		} else if *clean {
			printf("%s: clean\n", pkg)
			script.Clean()
		}
		if *doInstall {
			if script.Stale() {
				printf("%s: install\n", pkg)
				if err := script.Run(); err != nil {
					errorf("%s: install: %v\n", pkg, err)
					return
				}
			} else {
				printf("%s: up-to-date\n", pkg)
			}
		}
	}

	if remote {
		// mark package as installed in goinstall.log
		logged := logPackage(pkg, tree)

		// report installation to the dashboard if this is the first
		// install from a public repository.
		if logged && public {
			maybeReportToDashboard(pkg)
		}
	}
}

// Is this a standard package path?  strings container/vector etc.
// Assume that if the first element has a dot, it's a domain name
// and is not the standard package path.
func isStandardPath(s string) bool {
	dot := strings.Index(s, ".")
	slash := strings.Index(s, "/")
	return dot < 0 || 0 < slash && slash < dot
}

// run runs the command cmd in directory dir with standard input stdin.
// If the command fails, run prints the command and output on standard error
// in addition to returning a non-nil os.Error.
func run(dir string, stdin []byte, cmd ...string) os.Error {
	return genRun(dir, stdin, cmd, false)
}

// quietRun is like run but prints nothing on failure unless -v is used.
func quietRun(dir string, stdin []byte, cmd ...string) os.Error {
	return genRun(dir, stdin, cmd, true)
}

// genRun implements run and quietRun.
func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error {
	cmd := exec.Command(arg[0], arg[1:]...)
	cmd.Stdin = bytes.NewBuffer(stdin)
	cmd.Dir = dir
	printf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
	out, err := cmd.CombinedOutput()
	if err != nil {
		if !quiet || *verbose {
			if dir != "" {
				dir = "cd " + dir + "; "
			}
			fmt.Fprintf(os.Stderr, "%s: === %s%s\n", cmd.Path, dir, strings.Join(cmd.Args, " "))
			os.Stderr.Write(out)
			fmt.Fprintf(os.Stderr, "--- %s\n", err)
		}
		return os.NewError("running " + arg[0] + ": " + err.String())
	}
	return nil
}
<commit_msg>goinstall: abort and warn when using any url scheme, not just 'http://'<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"exec"
	"flag"
	"fmt"
	"go/build"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
)

func usage() {
	fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n")
	fmt.Fprintf(os.Stderr, "\tgoinstall -a\n")
	flag.PrintDefaults()
	os.Exit(2)
}

const logfile = "goinstall.log"

var (
	fset          = token.NewFileSet()
	argv0         = os.Args[0]
	errors        = false
	parents       = make(map[string]string)
	visit         = make(map[string]status)
	installedPkgs = make(map[string]map[string]bool)
	schemeRe      = regexp.MustCompile(`^[a-z]+://`)

	allpkg            = flag.Bool("a", false, "install all previously installed packages")
	reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL)
	update            = flag.Bool("u", false, "update already-downloaded packages")
	doInstall         = flag.Bool("install", true, "build and install")
	clean             = flag.Bool("clean", false, "clean the package directory before installing")
	nuke              = flag.Bool("nuke", false, "clean the package directory and target before installing")
	useMake           = flag.Bool("make", true, "use make to build and install")
	verbose           = flag.Bool("v", false, "verbose")
)

type status int // status for visited map
const (
	unvisited status = iota
	visiting
	done
)

func logf(format string, args ...interface{}) {
	format = "%s: " + format
	args = append([]interface{}{argv0}, args...)
	fmt.Fprintf(os.Stderr, format, args...)
}

func printf(format string, args ...interface{}) {
	if *verbose {
		logf(format, args...)
	}
}

func errorf(format string, args ...interface{}) {
	errors = true
	logf(format, args...)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	if runtime.GOROOT() == "" {
		fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
		os.Exit(1)
	}
	readPackageList()

	// special case - "unsafe" is already installed
	visit["unsafe"] = done

	args := flag.Args()
	if *allpkg {
		if len(args) != 0 {
			usage() // -a and package list both provided
		}
		// install all packages that were ever installed
		n := 0
		for _, pkgs := range installedPkgs {
			for pkg := range pkgs {
				args = append(args, pkg)
				n++
			}
		}
		if n == 0 {
			logf("no installed packages\n")
			os.Exit(1)
		}
	}
	if len(args) == 0 {
		usage()
	}
	for _, path := range args {
		if s := schemeRe.FindString(path); s != "" {
			errorf("%q used in import path, try %q\n", s, path[len(s):])
			continue
		}

		install(path, "")
	}
	if errors {
		os.Exit(1)
	}
}

// printDeps prints the dependency path that leads to pkg.
func printDeps(pkg string) {
	if pkg == "" {
		return
	}
	if visit[pkg] != done {
		printDeps(parents[pkg])
	}
	fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg)
}

// readPackageList reads the list of installed packages from the
// goinstall.log files in GOROOT and the GOPATHs and initalizes
// the installedPkgs variable.
func readPackageList() {
	for _, t := range build.Path {
		installedPkgs[t.Path] = make(map[string]bool)
		name := filepath.Join(t.Path, logfile)
		pkglistdata, err := ioutil.ReadFile(name)
		if err != nil {
			printf("%s\n", err)
			continue
		}
		pkglist := strings.Fields(string(pkglistdata))
		for _, pkg := range pkglist {
			installedPkgs[t.Path][pkg] = true
		}
	}
}

// logPackage logs the named package as installed in the goinstall.log file
// in the given tree if the package is not already in that file.
func logPackage(pkg string, tree *build.Tree) (logged bool) {
	if installedPkgs[tree.Path][pkg] {
		return false
	}
	name := filepath.Join(tree.Path, logfile)
	fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		logf("%s\n", err)
		return false
	}
	fmt.Fprintf(fout, "%s\n", pkg)
	fout.Close()
	return true
}

// install installs the package named by path, which is needed by parent.
func install(pkg, parent string) {
	// Make sure we're not already trying to install pkg.
	switch visit[pkg] {
	case done:
		return
	case visiting:
		fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0)
		printDeps(parent)
		fmt.Fprintf(os.Stderr, "\t%s\n", pkg)
		os.Exit(2)
	}
	parents[pkg] = parent
	visit[pkg] = visiting
	defer func() {
		visit[pkg] = done
	}()

	// Check whether package is local or remote.
	// If remote, download or update it.
	tree, pkg, err := build.FindTree(pkg)
	// Don't build the standard library.
	if err == nil && tree.Goroot && isStandardPath(pkg) {
		if parent == "" {
			errorf("%s: can not goinstall the standard library\n", pkg)
		} else {
			printf("%s: skipping standard library\n", pkg)
		}
		return
	}
	// Download remote packages if not found or forced with -u flag.
	remote, public := isRemote(pkg), false
	if remote && (err == build.ErrNotFound || (err == nil && *update)) {
		printf("%s: download\n", pkg)
		public, err = download(pkg, tree.SrcDir())
	}
	if err != nil {
		errorf("%s: %v\n", pkg, err)
		return
	}
	dir := filepath.Join(tree.SrcDir(), pkg)

	// Install prerequisites.
	dirInfo, err := build.ScanDir(dir, parent == "")
	if err != nil {
		errorf("%s: %v\n", pkg, err)
		return
	}
	if len(dirInfo.GoFiles)+len(dirInfo.CgoFiles) == 0 {
		errorf("%s: package has no files\n", pkg)
		return
	}
	for _, p := range dirInfo.Imports {
		if p != "C" {
			install(p, pkg)
		}
	}
	if errors {
		return
	}

	// Install this package.
	if *useMake {
		err := domake(dir, pkg, tree, dirInfo.IsCommand())
		if err != nil {
			errorf("%s: install: %v\n", pkg, err)
			return
		}
	} else {
		script, err := build.Build(tree, pkg, dirInfo)
		if err != nil {
			errorf("%s: install: %v\n", pkg, err)
			return
		}
		if *nuke {
			printf("%s: nuke\n", pkg)
			script.Nuke()
		} else if *clean {
			printf("%s: clean\n", pkg)
			script.Clean()
		}
		if *doInstall {
			if script.Stale() {
				printf("%s: install\n", pkg)
				if err := script.Run(); err != nil {
					errorf("%s: install: %v\n", pkg, err)
					return
				}
			} else {
				printf("%s: up-to-date\n", pkg)
			}
		}
	}

	if remote {
		// mark package as installed in goinstall.log
		logged := logPackage(pkg, tree)

		// report installation to the dashboard if this is the first
		// install from a public repository.
		if logged && public {
			maybeReportToDashboard(pkg)
		}
	}
}

// Is this a standard package path?  strings container/vector etc.
// Assume that if the first element has a dot, it's a domain name
// and is not the standard package path.
func isStandardPath(s string) bool {
	dot := strings.Index(s, ".")
	slash := strings.Index(s, "/")
	return dot < 0 || 0 < slash && slash < dot
}

// run runs the command cmd in directory dir with standard input stdin.
// If the command fails, run prints the command and output on standard error
// in addition to returning a non-nil os.Error.
func run(dir string, stdin []byte, cmd ...string) os.Error {
	return genRun(dir, stdin, cmd, false)
}

// quietRun is like run but prints nothing on failure unless -v is used.
func quietRun(dir string, stdin []byte, cmd ...string) os.Error {
	return genRun(dir, stdin, cmd, true)
}

// genRun implements run and quietRun.
func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error {
	cmd := exec.Command(arg[0], arg[1:]...)
	cmd.Stdin = bytes.NewBuffer(stdin)
	cmd.Dir = dir
	printf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
	out, err := cmd.CombinedOutput()
	if err != nil {
		if !quiet || *verbose {
			if dir != "" {
				dir = "cd " + dir + "; "
			}
			fmt.Fprintf(os.Stderr, "%s: === %s%s\n", cmd.Path, dir, strings.Join(cmd.Args, " "))
			os.Stderr.Write(out)
			fmt.Fprintf(os.Stderr, "--- %s\n", err)
		}
		return os.NewError("running " + arg[0] + ": " + err.String())
	}
	return nil
}
<|endoftext|>


<commit_before>package machine

import (
	"fmt"

	"github.com/jsok/vending/coins"
)

type Machine struct {
	picker      Picker
	changeMaker coins.ChangeMaker
}

func NewMachine(picker Picker, changeMaker coins.ChangeMaker) *Machine {
	return &Machine{picker, changeMaker}
}

// Purchase the item in the specific slot and accept the given coins as payment
// Return success of the purchase, and associated change or a full refund in the
// event of a failure.
func (m *Machine) Purchase(slot int, payment coins.Change) (coins.Change, error) {
	item, err := m.picker.Pick(slot)

	if err != nil {
		return payment, fmt.Errorf("Failed for reason: %v. Issuing full refund", err)
	}

	paid := payment.Value()
	if paid == item.Price {
		return coins.Change{}, nil
	} else if paid < item.Price {
		return payment, fmt.Errorf("Item in slot %d costs %dc, you only paid %dc. Issuing full refund.",
			slot, item.Price, paid)
	}

	return m.changeMaker.MakeChange(paid - item.Price)
}

type Picker interface {
	Pick(index int) (*Item, error)
}

type itemPicker struct {
	slots []Slot
}

func (p *itemPicker) Pick(index int) (*Item, error) {
	if index < 0 || index > len(p.slots) {
		return nil, fmt.Errorf("There are no items in slot %d", index)
	}
	slot := p.slots[index]
	if slot.inventory <= 0 {
		return nil, fmt.Errorf("The item in slot %d is out of stock", index)
	}
	slot.inventory -= 1
	return slot.item, nil
}
<commit_msg>Use slot pointers so inventory is properly persisted<commit_after>package machine

import (
	"fmt"

	"github.com/jsok/vending/coins"
)

type Machine struct {
	picker      Picker
	changeMaker coins.ChangeMaker
}

func NewMachine(picker Picker, changeMaker coins.ChangeMaker) *Machine {
	return &Machine{picker, changeMaker}
}

// Purchase the item in the specific slot and accept the given coins as payment
// Return success of the purchase, and associated change or a full refund in the
// event of a failure.
func (m *Machine) Purchase(slot int, payment coins.Change) (coins.Change, error) {
	item, err := m.picker.Pick(slot)

	if err != nil {
		return payment, fmt.Errorf("Failed for reason: %v. Issuing full refund", err)
	}

	paid := payment.Value()
	if paid == item.Price {
		return coins.Change{}, nil
	} else if paid < item.Price {
		return payment, fmt.Errorf("Item in slot %d costs %dc, you only paid %dc. Issuing full refund.",
			slot, item.Price, paid)
	}

	return m.changeMaker.MakeChange(paid - item.Price)
}

type Picker interface {
	Pick(index int) (*Item, error)
}

type itemPicker struct {
	slots []*Slot
}

func (p *itemPicker) Pick(index int) (*Item, error) {
	if index < 0 || index > len(p.slots) {
		return nil, fmt.Errorf("There are no items in slot %d", index)
	}
	slot := p.slots[index]
	if slot.inventory <= 0 {
		return nil, fmt.Errorf("The item in slot %d is out of stock", index)
	}
	slot.inventory -= 1
	return slot.item, nil
}
<|endoftext|>


<commit_before>package parser

import (
	"io"

	"github.com/orktes/orlang/ast"

	"github.com/orktes/orlang/scanner"
)

type Parser struct {
	s                *scanner.Scanner
	tokenBuffer      []scanner.Token
	lastTokens       []scanner.Token
	parserError      string
	errorToken       scanner.Token
	Error            func(tokenIndx int, pos ast.Position, msg string)
	ContinueOnErrors bool
	snapshots        [][]scanner.Token
	readTokens       int
	// comments attaching
	nodeComments          map[ast.Node][]ast.Comment
	comments              []ast.Comment
	commentAfterNodeCheck ast.Node
	// macros
	macros map[string]*ast.Macro
}

// NewParser return new Parser for a given scanner
func NewParser(s *scanner.Scanner) *Parser {
	return &Parser{
		s:            s,
		nodeComments: map[ast.Node][]ast.Comment{},
		macros:       map[string]*ast.Macro{},
	}
}

// Parse source code from io.Reader
func Parse(reader io.Reader) (file *ast.File, err error) {
	return NewParser(scanner.NewScanner(reader)).Parse()
}

// Parse source code but consuming io.Parser
func (p *Parser) Parse() (file *ast.File, err error) {
	file = &ast.File{}
	p.s.Error = p.error

loop:
	for {
		var node ast.Node
		var check = func(n ast.Node, ok bool) bool {
			if ok {
				node = n
			}
			return ok
		}
		switch {
		case check(p.parseFuncDecl()):
			ident := node.(*ast.FunctionDeclaration).Identifier
			if ident == nil || ident.Text == "" {
				p.error("Root level functions can't be anonymous")
			}
		case check(p.parseVarDecl()):
		case check(p.parseImportDecl()):
		case check(p.parseExportDecl()):
		case p.eof():
			break loop
		case check(p.parseMacro()):
			if node != nil {
				macro, isMacro := node.(*ast.Macro)
				if isMacro && macro != nil {
					p.macros[macro.Name.Text] = macro
				}
			}
		default:
			token := p.read()
			p.error(unexpectedToken(token))
		}

		if node != nil {
			file.AppendNode(node)
		}

		if p.parserError != "" {
			token := p.errorToken
			posError := &PosError{Position: ast.StartPositionFromToken(token), Message: p.parserError}
			p.parserError = ""
			if !p.ContinueOnErrors {
				err = posError
				break loop
			}
		}
	}

	file.Comments = p.comments
	file.NodeComments = p.nodeComments
	file.Macros = p.macros

	return
}

func (p *Parser) eof() (ok bool) {
	if _, ok = p.expectToken(scanner.TokenTypeEOF); !ok {
		p.unread()
	}
	return
}

func (p *Parser) parseStatementOrExpression(block bool) (node ast.Node, ok bool) {
	if node, ok = p.parseStatement(block); !ok {
		node, ok = p.parseExpression()
	}
	return
}

func (p *Parser) parseImportDecl() (node ast.Node, ok bool) {
	return
}

func (p *Parser) parseExportDecl() (node ast.Node, ok bool) {
	return
}

func (p *Parser) expectPattern(tokenTypes ...scanner.TokenType) (tokens []scanner.Token, ok bool) {
	ok = true
	for _, tokenType := range tokenTypes {
		token := p.read()
		tokens = append(tokens, token)
		if token.Type != tokenType {
			ok = false
			break
		}
	}

	return
}

func (p *Parser) expectToken(tokenTypes ...scanner.TokenType) (token scanner.Token, ok bool) {
	token = p.read()
	for _, tokenType := range tokenTypes {
		if token.Type == tokenType {
			ok = true
		}
	}
	return
}

func (p *Parser) readToken(expandMacros bool) (token scanner.Token) {
readToken:
	if len(p.tokenBuffer) > 0 {
		token = p.tokenBuffer[0]
		p.tokenBuffer = p.tokenBuffer[1:]
	} else {
		for {
			tok := p.s.Scan()
			// TODO convert NEWLINES to semicolons on some scenarios
			if tok.Type == scanner.TokenTypeComment {
				p.processComment(tok)
			} else if tok.Type != scanner.TokenTypeWhitespace {
				token = tok
				p.readTokens++
				break
			}
		}
	}

	p.lastTokens = []scanner.Token{token}

	if len(p.snapshots) > 0 {
		p.snapshots[len(p.snapshots)-1] = append(p.snapshots[len(p.snapshots)-1], token)
	}

	if expandMacros && token.Type == scanner.TokenTypeMacroCallIdent {
		if p.parseMacroCall(token) {
			goto readToken
		} else {
			// TODO throw error or something here
		}
	}

	return
}

func (p *Parser) read() (token scanner.Token) {
	return p.readToken(true)
}

func (p *Parser) unread() {
	if len(p.snapshots) > 0 {
		snapshot := p.snapshots[len(p.snapshots)-1]
		p.snapshots[len(p.snapshots)-1] = snapshot[:len(snapshot)-1]
	}
	p.returnToBuffer(p.lastTokens)
}

func (p *Parser) returnToBuffer(tokens []scanner.Token) {
	buffer := make([]scanner.Token, 0, len(tokens)+len(p.tokenBuffer))
	buffer = append(buffer, tokens...)
	buffer = append(buffer, p.tokenBuffer...)
	p.tokenBuffer = buffer
	p.lastTokens = []scanner.Token{}
}

func (p *Parser) lastToken() (token scanner.Token) {
	if len(p.lastTokens) > 0 {
		return p.lastTokens[len(p.lastTokens)-1]
	}

	// This should not happen
	// TODO figure out why we sometimes endup here
	return p.peek()
}

func (p *Parser) skip() {
	p.skipMultiple(1)
}

func (p *Parser) skipMultiple(amount int) {
	for i := 0; i < amount; i++ {
		p.read()
	}
	p.lastTokens = []scanner.Token{}
}

func (p *Parser) peek() scanner.Token {
	return p.peekMultiple(1)[0]
}

func (p *Parser) peekMultiple(amount int) (tokens []scanner.Token) {
	tokens = make([]scanner.Token, amount)
	for i := 0; i < amount; i++ {
		tokens[i] = p.read()
	}

	p.tokenBuffer = append(p.tokenBuffer, tokens...)
	p.lastTokens = []scanner.Token{}
	return
}

func (p *Parser) snapshot() {
	p.snapshots = append(p.snapshots, []scanner.Token{})
}

func (p *Parser) restore() {
	if len(p.snapshots) > 0 {
		p.returnToBuffer(p.snapshots[len(p.snapshots)-1])
		p.commit()
	}
}

func (p *Parser) commit() {
	if len(p.snapshots) > 0 {
		p.snapshots = p.snapshots[:len(p.snapshots)-1]
	}
}

func (p *Parser) error(err string) {
	if p.parserError == "" {
		p.parserError = err
		p.errorToken = p.lastToken()
	}
	if p.Error != nil {
		p.Error(p.readTokens-len(p.tokenBuffer), ast.StartPositionFromToken(p.lastToken()), err)
	}
}
<commit_msg>Fix comment<commit_after>package parser

import (
	"io"

	"github.com/orktes/orlang/ast"

	"github.com/orktes/orlang/scanner"
)

type Parser struct {
	s                *scanner.Scanner
	tokenBuffer      []scanner.Token
	lastTokens       []scanner.Token
	parserError      string
	errorToken       scanner.Token
	Error            func(tokenIndx int, pos ast.Position, msg string)
	ContinueOnErrors bool
	snapshots        [][]scanner.Token
	readTokens       int
	// comments attaching
	nodeComments          map[ast.Node][]ast.Comment
	comments              []ast.Comment
	commentAfterNodeCheck ast.Node
	// macros
	macros map[string]*ast.Macro
}

// NewParser return new Parser for a given scanner
func NewParser(s *scanner.Scanner) *Parser {
	return &Parser{
		s:            s,
		nodeComments: map[ast.Node][]ast.Comment{},
		macros:       map[string]*ast.Macro{},
	}
}

// Parse source code from io.Reader
func Parse(reader io.Reader) (file *ast.File, err error) {
	return NewParser(scanner.NewScanner(reader)).Parse()
}

// Parse source code
func (p *Parser) Parse() (file *ast.File, err error) {
	file = &ast.File{}
	p.s.Error = p.error

loop:
	for {
		var node ast.Node
		var check = func(n ast.Node, ok bool) bool {
			if ok {
				node = n
			}
			return ok
		}
		switch {
		case check(p.parseFuncDecl()):
			ident := node.(*ast.FunctionDeclaration).Identifier
			if ident == nil || ident.Text == "" {
				p.error("Root level functions can't be anonymous")
			}
		case check(p.parseVarDecl()):
		case check(p.parseImportDecl()):
		case check(p.parseExportDecl()):
		case p.eof():
			break loop
		case check(p.parseMacro()):
			if node != nil {
				macro, isMacro := node.(*ast.Macro)
				if isMacro && macro != nil {
					p.macros[macro.Name.Text] = macro
				}
			}
		default:
			token := p.read()
			p.error(unexpectedToken(token))
		}

		if node != nil {
			file.AppendNode(node)
		}

		if p.parserError != "" {
			token := p.errorToken
			posError := &PosError{Position: ast.StartPositionFromToken(token), Message: p.parserError}
			p.parserError = ""
			if !p.ContinueOnErrors {
				err = posError
				break loop
			}
		}
	}

	file.Comments = p.comments
	file.NodeComments = p.nodeComments
	file.Macros = p.macros

	return
}

func (p *Parser) eof() (ok bool) {
	if _, ok = p.expectToken(scanner.TokenTypeEOF); !ok {
		p.unread()
	}
	return
}

func (p *Parser) parseStatementOrExpression(block bool) (node ast.Node, ok bool) {
	if node, ok = p.parseStatement(block); !ok {
		node, ok = p.parseExpression()
	}
	return
}

func (p *Parser) parseImportDecl() (node ast.Node, ok bool) {
	return
}

func (p *Parser) parseExportDecl() (node ast.Node, ok bool) {
	return
}

func (p *Parser) expectPattern(tokenTypes ...scanner.TokenType) (tokens []scanner.Token, ok bool) {
	ok = true
	for _, tokenType := range tokenTypes {
		token := p.read()
		tokens = append(tokens, token)
		if token.Type != tokenType {
			ok = false
			break
		}
	}

	return
}

func (p *Parser) expectToken(tokenTypes ...scanner.TokenType) (token scanner.Token, ok bool) {
	token = p.read()
	for _, tokenType := range tokenTypes {
		if token.Type == tokenType {
			ok = true
		}
	}
	return
}

func (p *Parser) readToken(expandMacros bool) (token scanner.Token) {
readToken:
	if len(p.tokenBuffer) > 0 {
		token = p.tokenBuffer[0]
		p.tokenBuffer = p.tokenBuffer[1:]
	} else {
		for {
			tok := p.s.Scan()
			// TODO convert NEWLINES to semicolons on some scenarios
			if tok.Type == scanner.TokenTypeComment {
				p.processComment(tok)
			} else if tok.Type != scanner.TokenTypeWhitespace {
				token = tok
				p.readTokens++
				break
			}
		}
	}

	p.lastTokens = []scanner.Token{token}

	if len(p.snapshots) > 0 {
		p.snapshots[len(p.snapshots)-1] = append(p.snapshots[len(p.snapshots)-1], token)
	}

	if expandMacros && token.Type == scanner.TokenTypeMacroCallIdent {
		if p.parseMacroCall(token) {
			goto readToken
		} else {
			// TODO throw error or something here
		}
	}

	return
}

func (p *Parser) read() (token scanner.Token) {
	return p.readToken(true)
}

func (p *Parser) unread() {
	if len(p.snapshots) > 0 {
		snapshot := p.snapshots[len(p.snapshots)-1]
		p.snapshots[len(p.snapshots)-1] = snapshot[:len(snapshot)-1]
	}
	p.returnToBuffer(p.lastTokens)
}

func (p *Parser) returnToBuffer(tokens []scanner.Token) {
	buffer := make([]scanner.Token, 0, len(tokens)+len(p.tokenBuffer))
	buffer = append(buffer, tokens...)
	buffer = append(buffer, p.tokenBuffer...)
	p.tokenBuffer = buffer
	p.lastTokens = []scanner.Token{}
}

func (p *Parser) lastToken() (token scanner.Token) {
	if len(p.lastTokens) > 0 {
		return p.lastTokens[len(p.lastTokens)-1]
	}

	// This should not happen
	// TODO figure out why we sometimes endup here
	return p.peek()
}

func (p *Parser) skip() {
	p.skipMultiple(1)
}

func (p *Parser) skipMultiple(amount int) {
	for i := 0; i < amount; i++ {
		p.read()
	}
	p.lastTokens = []scanner.Token{}
}

func (p *Parser) peek() scanner.Token {
	return p.peekMultiple(1)[0]
}

func (p *Parser) peekMultiple(amount int) (tokens []scanner.Token) {
	tokens = make([]scanner.Token, amount)
	for i := 0; i < amount; i++ {
		tokens[i] = p.read()
	}

	p.tokenBuffer = append(p.tokenBuffer, tokens...)
	p.lastTokens = []scanner.Token{}
	return
}

func (p *Parser) snapshot() {
	p.snapshots = append(p.snapshots, []scanner.Token{})
}

func (p *Parser) restore() {
	if len(p.snapshots) > 0 {
		p.returnToBuffer(p.snapshots[len(p.snapshots)-1])
		p.commit()
	}
}

func (p *Parser) commit() {
	if len(p.snapshots) > 0 {
		p.snapshots = p.snapshots[:len(p.snapshots)-1]
	}
}

func (p *Parser) error(err string) {
	if p.parserError == "" {
		p.parserError = err
		p.errorToken = p.lastToken()
	}
	if p.Error != nil {
		p.Error(p.readTokens-len(p.tokenBuffer), ast.StartPositionFromToken(p.lastToken()), err)
	}
}
<|endoftext|>