<commit_before>package command

import (
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/mackerelio/mackerel-agent/agent"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/spec"
)

var logger = logging.GetLogger("command")

const idFileName = "id"

func IdFilePath(root string) string {
	return filepath.Join(root, idFileName)
}

func LoadHostId(root string) (string, error) {
	content, err := ioutil.ReadFile(IdFilePath(root))
	if err != nil {
		return "", err
	}
	return string(content), nil
}

func SaveHostId(root string, id string) error {
	err := os.MkdirAll(root, 0755)
	if err != nil {
		return err
	}

	file, err := os.Create(IdFilePath(root))
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.Write([]byte(id))
	if err != nil {
		return err
	}

	return nil
}

// prepareHost collects specs of the host and sends them to Mackerel server.
// A unique host-id is returned by the server if one is not specified.
func prepareHost(root string, api *mackerel.API, roleFullnames []string) (*mackerel.Host, error) {
	// XXX this configuration should be moved to under spec/linux
	os.Setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin:"+os.Getenv("PATH"))
	os.Setenv("LANG", "C") // prevent changing outputs of some command, e.g. ifconfig.

	hostname, meta, interfaces, err := collectHostSpecs()
	if err != nil {
		return nil, fmt.Errorf("error while collecting host specs: %s", err.Error())
	}

	var result *mackerel.Host
	if hostId, err := LoadHostId(root); err != nil { // create
		logger.Debugf("Registering new host on mackerel...")
		createdHostId, err := api.CreateHost(hostname, meta, interfaces, roleFullnames)
		if err != nil {
			return nil, fmt.Errorf("Failed to register this host: %s", err.Error())
		}

		result, err = api.FindHost(createdHostId)
		if err != nil {
			return nil, fmt.Errorf("Failed to find this host on mackerel: %s", err.Error())
		}
	} else { // update
		result, err = api.FindHost(hostId)
		if err != nil {
			return nil, fmt.Errorf("Failed to find this host on mackerel (You may want to delete file \"%s\" to register this host to an another organization): %s", IdFilePath(root), err.Error())
		}
		err := api.UpdateHost(hostId, hostname, meta, interfaces, roleFullnames)
		if err != nil {
			return nil, fmt.Errorf("Failed to update this host: %s", err.Error())
		}
	}

	err = SaveHostId(root, result.Id)
	if err != nil {
		return nil, fmt.Errorf("Failed to save host ID: %s", err.Error())
	}

	return result, nil
}

// Interval between each updating host specs.
var specsUpdateInterval = 1 * time.Hour

func delayByHost(host *mackerel.Host) int {
	s := sha1.Sum([]byte(host.Id))
	return int(s[len(s)-1]) % int(config.PostMetricsInterval.Seconds())
}

type postValue struct {
	values   []*mackerel.CreatingMetricsValue
	retryCnt int
}

type loopState uint8

const (
	loopStateFirst loopState = iota
	loopStateDefault
	loopStateQueued
	loopStateHadError
	loopStateTerminated
)

func loop(ag *agent.Agent, conf *config.Config, api *mackerel.API, host *mackerel.Host, termCh chan bool) int {
	quit := make(chan bool)

	// Periodically update host specs.
	go func() {
	updateHostLoop:
		for {
			select {
			case <-quit:
				break updateHostLoop
			case <-time.After(specsUpdateInterval):
				UpdateHostSpecs(conf, api, host)
			}
		}
	}()

	metricsResult := ag.Watch()
	postQueue := make(chan *postValue, conf.Connection.Post_Metrics_Buffer_Size)
	go func() {
	enqueueLoop:
		for {
			select {
			case <-quit:
				break enqueueLoop
			case result := <-metricsResult:
				created := float64(result.Created.Unix())
				creatingValues := [](*mackerel.CreatingMetricsValue){}
				for name, value := range (map[string]float64)(result.Values) {
					creatingValues = append(
						creatingValues,
						&mackerel.CreatingMetricsValue{host.Id, name, created, value},
					)
				}
				logger.Debugf("Enqueuing task to post metrics.")
				postQueue <- &postValue{creatingValues, 0}
			}
		}
	}()

	exitCh := make(chan int)
	go func() {
		postDelaySeconds := delayByHost(host)
		lState := loopStateFirst
		for {
			select {
			case <-termCh:
				if lState == loopStateTerminated {
					close(quit) // broadcast terminating
					exitCh <- 1
					return
				}
				lState = loopStateTerminated
				if len(postQueue) <= 0 {
					close(quit) // broadcast terminating
					exitCh <- 0
					return
				}
			case v := <-postQueue:
				origPostValues := [](*postValue){v}
				if len(postQueue) > 0 {
					// Bulk posting. However at most "two" metrics are to be posted, so postQueue isn't always empty yet.
					logger.Debugf("Merging datapoints with next queued ones")
					nextValues := <-postQueue
					origPostValues = append(origPostValues, nextValues)
				}

				delaySeconds := 0
				switch lState {
				case loopStateFirst: // request immediately to create graph defs of host
					// nop
				case loopStateQueued:
					delaySeconds = conf.Connection.Post_Metrics_Dequeue_Delay_Seconds
				case loopStateHadError:
					// TODO: better interval calculation. exponential backoff or so.
					delaySeconds = conf.Connection.Post_Metrics_Retry_Delay_Seconds
				case loopStateTerminated:
					// dequeue and post every one second when terminating.
					delaySeconds = 1
				default:
					// Sending data at every 0 second from all hosts causes request flooding.
					// To prevent flooding, this loop sleeps for some seconds
					// which is specific to the ID of the host running agent on.
					// The sleep second is up to 60s (to be exact up to `config.Postmetricsinterval.Seconds()`.
					elapsedSeconds := int(time.Now().Unix() % int64(config.PostMetricsInterval.Seconds()))
					if postDelaySeconds > elapsedSeconds {
						delaySeconds = postDelaySeconds - elapsedSeconds
					}
				}

				// determin next loopState before sleeping
				if lState != loopStateTerminated {
					if len(postQueue) > 0 {
						lState = loopStateQueued
					} else {
						lState = loopStateDefault
					}
				}

				logger.Debugf("Sleep %d seconds before posting.", delaySeconds)
				select {
				case <-time.After(time.Duration(delaySeconds) * time.Second):
					// nop
				case <-termCh:
					if lState == loopStateTerminated {
						close(quit) // broadcast terminating
						exitCh <- 1
						return
					}
					lState = loopStateTerminated
				}

				postValues := [](*mackerel.CreatingMetricsValue){}
				for _, v := range origPostValues {
					postValues = append(postValues, v.values...)
				}
				err := api.PostMetricsValues(postValues)
				if err != nil {
					logger.Errorf("Failed to post metrics value (will retry): %s", err.Error())
					if lState != loopStateTerminated {
						lState = loopStateHadError
					}
					go func() {
						for _, v := range origPostValues {
							v.retryCnt++
							// It is difficult to distinguish the error is server error or data error.
							// So, if retryCnt exceeded the configured limit, postValue is considered invalid and abandoned.
							if v.retryCnt > conf.Connection.Post_Metrics_Retry_Max {
								json, err := json.Marshal(v.values)
								if err != nil {
									logger.Errorf("Something wrong with post values. marshaling failed.")
								} else {
									logger.Errorf("Post values may be invalid and abandoned: %s", string(json))
								}
								continue
							}
							postQueue <- v
						}
					}()
					continue
				}
				logger.Debugf("Posting metrics succeeded.")

				if lState == loopStateTerminated && len(postQueue) <= 0 {
					close(quit) // broadcast terminating
					exitCh <- 0
					return
				}
			}
		}
	}()

	return <-exitCh
}

// collectHostSpecs collects host specs (correspond to "name", "meta" and "interfaces" fields in API v0)
func collectHostSpecs() (string, map[string]interface{}, []map[string]interface{}, error) {
	hostname, err := os.Hostname()
	if err != nil {
		return "", nil, nil, fmt.Errorf("failed to obtain hostname: %s", err.Error())
	}

	meta := spec.Collect(specGenerators())

	interfacesSpec, err := interfaceGenerator().Generate()
	if err != nil {
		return "", nil, nil, fmt.Errorf("failed to collect interfaces: %s", err.Error())
	}

	interfaces, _ := interfacesSpec.([]map[string]interface{})

	return hostname, meta, interfaces, nil
}

// UpdateHostSpecs updates the host information that is already registered on Mackerel.
func UpdateHostSpecs(conf *config.Config, api *mackerel.API, host *mackerel.Host) {
	logger.Debugf("Updating host specs...")

	hostname, meta, interfaces, err := collectHostSpecs()
	if err != nil {
		logger.Errorf("While collecting host specs: %s", err)
		return
	}

	err = api.UpdateHost(host.Id, hostname, meta, interfaces, conf.Roles)
	if err != nil {
		logger.Errorf("Error while updating host specs: %s", err)
	} else {
		logger.Debugf("Host specs sent.")
	}
}

// Prepare sets up API and registers the host data to the Mackerel server.
// Use returned values to call Run().
func Prepare(conf *config.Config) (*mackerel.API, *mackerel.Host, error) {
	api, err := mackerel.NewApi(conf.Apibase, conf.Apikey, conf.Verbose)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to prepare an api: %s", err.Error())
	}

	host, err := prepareHost(conf.Root, api, conf.Roles)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to preapre host: %s", err.Error())
	}

	return api, host, nil
}

// Run starts the main metric collecting logic and this function will never return.
func Run(conf *config.Config, api *mackerel.API, host *mackerel.Host, termCh chan bool) int {
	logger.Infof("Start: apibase = %s, hostName = %s, hostId = %s", conf.Apibase, host.Name, host.Id)

	ag := &agent.Agent{
		MetricsGenerators: metricsGenerators(conf),
		PluginGenerators:  pluginGenerators(conf),
	}
	ag.InitPluginGenerators(api)

	return loop(ag, conf, api, host, termCh)
}
<commit_msg>remove `exitCh <-` in main loop, use direct return<commit_after>package command

import (
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/mackerelio/mackerel-agent/agent"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/spec"
)

var logger = logging.GetLogger("command")

const idFileName = "id"

func IdFilePath(root string) string {
	return filepath.Join(root, idFileName)
}

func LoadHostId(root string) (string, error) {
	content, err := ioutil.ReadFile(IdFilePath(root))
	if err != nil {
		return "", err
	}
	return string(content), nil
}

func SaveHostId(root string, id string) error {
	err := os.MkdirAll(root, 0755)
	if err != nil {
		return err
	}

	file, err := os.Create(IdFilePath(root))
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.Write([]byte(id))
	if err != nil {
		return err
	}

	return nil
}

// prepareHost collects specs of the host and sends them to Mackerel server.
// A unique host-id is returned by the server if one is not specified.
func prepareHost(root string, api *mackerel.API, roleFullnames []string) (*mackerel.Host, error) {
	// XXX this configuration should be moved to under spec/linux
	os.Setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin:"+os.Getenv("PATH"))
	os.Setenv("LANG", "C") // prevent changing outputs of some command, e.g. ifconfig.

	hostname, meta, interfaces, err := collectHostSpecs()
	if err != nil {
		return nil, fmt.Errorf("error while collecting host specs: %s", err.Error())
	}

	var result *mackerel.Host
	if hostId, err := LoadHostId(root); err != nil { // create
		logger.Debugf("Registering new host on mackerel...")
		createdHostId, err := api.CreateHost(hostname, meta, interfaces, roleFullnames)
		if err != nil {
			return nil, fmt.Errorf("Failed to register this host: %s", err.Error())
		}

		result, err = api.FindHost(createdHostId)
		if err != nil {
			return nil, fmt.Errorf("Failed to find this host on mackerel: %s", err.Error())
		}
	} else { // update
		result, err = api.FindHost(hostId)
		if err != nil {
			return nil, fmt.Errorf("Failed to find this host on mackerel (You may want to delete file \"%s\" to register this host to an another organization): %s", IdFilePath(root), err.Error())
		}
		err := api.UpdateHost(hostId, hostname, meta, interfaces, roleFullnames)
		if err != nil {
			return nil, fmt.Errorf("Failed to update this host: %s", err.Error())
		}
	}

	err = SaveHostId(root, result.Id)
	if err != nil {
		return nil, fmt.Errorf("Failed to save host ID: %s", err.Error())
	}

	return result, nil
}

// Interval between each updating host specs.
var specsUpdateInterval = 1 * time.Hour

func delayByHost(host *mackerel.Host) int {
	s := sha1.Sum([]byte(host.Id))
	return int(s[len(s)-1]) % int(config.PostMetricsInterval.Seconds())
}

type postValue struct {
	values   []*mackerel.CreatingMetricsValue
	retryCnt int
}

type loopState uint8

const (
	loopStateFirst loopState = iota
	loopStateDefault
	loopStateQueued
	loopStateHadError
	loopStateTerminated
)

func loop(ag *agent.Agent, conf *config.Config, api *mackerel.API, host *mackerel.Host, termCh chan bool) int {
	quit := make(chan bool)

	// Periodically update host specs.
	go func() {
	updateHostLoop:
		for {
			select {
			case <-quit:
				break updateHostLoop
			case <-time.After(specsUpdateInterval):
				UpdateHostSpecs(conf, api, host)
			}
		}
	}()

	metricsResult := ag.Watch()
	postQueue := make(chan *postValue, conf.Connection.Post_Metrics_Buffer_Size)
	go func() {
	enqueueLoop:
		for {
			select {
			case <-quit:
				break enqueueLoop
			case result := <-metricsResult:
				created := float64(result.Created.Unix())
				creatingValues := [](*mackerel.CreatingMetricsValue){}
				for name, value := range (map[string]float64)(result.Values) {
					creatingValues = append(
						creatingValues,
						&mackerel.CreatingMetricsValue{host.Id, name, created, value},
					)
				}
				logger.Debugf("Enqueuing task to post metrics.")
				postQueue <- &postValue{creatingValues, 0}
			}
		}
	}()

	return func() int {
		postDelaySeconds := delayByHost(host)
		lState := loopStateFirst
		for {
			select {
			case <-termCh:
				if lState == loopStateTerminated {
					close(quit) // broadcast terminating
					return 1
				}
				lState = loopStateTerminated
				if len(postQueue) <= 0 {
					close(quit) // broadcast terminating
					return 0
				}
			case v := <-postQueue:
				origPostValues := [](*postValue){v}
				if len(postQueue) > 0 {
					// Bulk posting. However at most "two" metrics are to be posted, so postQueue isn't always empty yet.
					logger.Debugf("Merging datapoints with next queued ones")
					nextValues := <-postQueue
					origPostValues = append(origPostValues, nextValues)
				}

				delaySeconds := 0
				switch lState {
				case loopStateFirst: // request immediately to create graph defs of host
					// nop
				case loopStateQueued:
					delaySeconds = conf.Connection.Post_Metrics_Dequeue_Delay_Seconds
				case loopStateHadError:
					// TODO: better interval calculation. exponential backoff or so.
					delaySeconds = conf.Connection.Post_Metrics_Retry_Delay_Seconds
				case loopStateTerminated:
					// dequeue and post every one second when terminating.
					delaySeconds = 1
				default:
					// Sending data at every 0 second from all hosts causes request flooding.
					// To prevent flooding, this loop sleeps for some seconds
					// which is specific to the ID of the host running agent on.
					// The sleep second is up to 60s (to be exact up to `config.Postmetricsinterval.Seconds()`.
					elapsedSeconds := int(time.Now().Unix() % int64(config.PostMetricsInterval.Seconds()))
					if postDelaySeconds > elapsedSeconds {
						delaySeconds = postDelaySeconds - elapsedSeconds
					}
				}

				// determin next loopState before sleeping
				if lState != loopStateTerminated {
					if len(postQueue) > 0 {
						lState = loopStateQueued
					} else {
						lState = loopStateDefault
					}
				}

				logger.Debugf("Sleep %d seconds before posting.", delaySeconds)
				select {
				case <-time.After(time.Duration(delaySeconds) * time.Second):
					// nop
				case <-termCh:
					if lState == loopStateTerminated {
						close(quit) // broadcast terminating
						return 1
					}
					lState = loopStateTerminated
				}

				postValues := [](*mackerel.CreatingMetricsValue){}
				for _, v := range origPostValues {
					postValues = append(postValues, v.values...)
				}
				err := api.PostMetricsValues(postValues)
				if err != nil {
					logger.Errorf("Failed to post metrics value (will retry): %s", err.Error())
					if lState != loopStateTerminated {
						lState = loopStateHadError
					}
					go func() {
						for _, v := range origPostValues {
							v.retryCnt++
							// It is difficult to distinguish the error is server error or data error.
							// So, if retryCnt exceeded the configured limit, postValue is considered invalid and abandoned.
							if v.retryCnt > conf.Connection.Post_Metrics_Retry_Max {
								json, err := json.Marshal(v.values)
								if err != nil {
									logger.Errorf("Something wrong with post values. marshaling failed.")
								} else {
									logger.Errorf("Post values may be invalid and abandoned: %s", string(json))
								}
								continue
							}
							postQueue <- v
						}
					}()
					continue
				}
				logger.Debugf("Posting metrics succeeded.")

				if lState == loopStateTerminated && len(postQueue) <= 0 {
					close(quit) // broadcast terminating
					return 0
				}
			}
		}
	}()
}

// collectHostSpecs collects host specs (correspond to "name", "meta" and "interfaces" fields in API v0)
func collectHostSpecs() (string, map[string]interface{}, []map[string]interface{}, error) {
	hostname, err := os.Hostname()
	if err != nil {
		return "", nil, nil, fmt.Errorf("failed to obtain hostname: %s", err.Error())
	}

	meta := spec.Collect(specGenerators())

	interfacesSpec, err := interfaceGenerator().Generate()
	if err != nil {
		return "", nil, nil, fmt.Errorf("failed to collect interfaces: %s", err.Error())
	}

	interfaces, _ := interfacesSpec.([]map[string]interface{})

	return hostname, meta, interfaces, nil
}

// UpdateHostSpecs updates the host information that is already registered on Mackerel.
func UpdateHostSpecs(conf *config.Config, api *mackerel.API, host *mackerel.Host) {
	logger.Debugf("Updating host specs...")

	hostname, meta, interfaces, err := collectHostSpecs()
	if err != nil {
		logger.Errorf("While collecting host specs: %s", err)
		return
	}

	err = api.UpdateHost(host.Id, hostname, meta, interfaces, conf.Roles)
	if err != nil {
		logger.Errorf("Error while updating host specs: %s", err)
	} else {
		logger.Debugf("Host specs sent.")
	}
}

// Prepare sets up API and registers the host data to the Mackerel server.
// Use returned values to call Run().
func Prepare(conf *config.Config) (*mackerel.API, *mackerel.Host, error) {
	api, err := mackerel.NewApi(conf.Apibase, conf.Apikey, conf.Verbose)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to prepare an api: %s", err.Error())
	}

	host, err := prepareHost(conf.Root, api, conf.Roles)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to preapre host: %s", err.Error())
	}

	return api, host, nil
}

// Run starts the main metric collecting logic and this function will never return.
func Run(conf *config.Config, api *mackerel.API, host *mackerel.Host, termCh chan bool) int {
	logger.Infof("Start: apibase = %s, hostName = %s, hostId = %s", conf.Apibase, host.Name, host.Id)

	ag := &agent.Agent{
		MetricsGenerators: metricsGenerators(conf),
		PluginGenerators:  pluginGenerators(conf),
	}
	ag.InitPluginGenerators(api)

	return loop(ag, conf, api, host, termCh)
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"github.com/hashicorp/serf/command/agent"
	"github.com/mitchellh/cli"
	"github.com/ryanuber/columnize"
	"net"
	"strings"
)

// MembersCommand is a Command implementation that queries a running
// Serf agent what members are part of the cluster currently.
type MembersCommand struct {
	Ui cli.Ui
}

// A container of member details. Maintaining a command-specific struct here
// makes sense so that the agent.Member struct can evolve without changing the
// keys in the output interface.
type Member struct {
	detail bool
	Name   string            `json:"name"`
	Addr   string            `json:"addr"`
	Port   uint16            `json:"port"`
	Tags   map[string]string `json:"tags"`
	Status string            `json:"status"`
	Proto  map[string]uint8  `json:"protocol"`
}

type MemberContainer struct {
	Members []Member `json:"members"`
}

func (c MemberContainer) String() string {
	var result []string
	for _, member := range c.Members {
		tags := strings.Join(agent.MarshalTags(member.Tags), ",")
		line := fmt.Sprintf("%s|%s|%s|%s",
			member.Name, member.Addr, member.Status, tags)
		if member.detail {
			line += fmt.Sprintf(
				"|Protocol Version: %d|Available Protocol Range: [%d, %d]",
				member.Proto["version"], member.Proto["min"], member.Proto["max"])
		}
		result = append(result, line)
	}
	output, _ := columnize.SimpleFormat(result)
	return output
}

func (c *MembersCommand) Help() string {
	helpText := `
Usage: serf members [options]

  Outputs the members of a running Serf agent.

Options:

  -detailed                 Additional information such as protocol verions
                            will be shown (only affects text output format).

  -format                   If provided, output is returned in the specified
                            format. Valid formats are 'json', and 'text' (default)

  -name=<regexp>            If provided, only members matching the regexp are
                            returned.

  -role=<regexp>            If provided, output is filtered to only nodes matching
                            the regular expression for role
                            '-role' is deprecated in favor of '-tag role=foo'.

  -status=<regexp>          If provided, output is filtered to only nodes matching
                            the regular expression for status

  -tag <key>=<regexp>       If provided, output is filtered to only nodes with the
                            tag <key> with value matching the regular expression.
                            tag can be specified multiple times to filter on
                            multiple keys.

  -rpc-addr=127.0.0.1:7373  RPC address of the Serf agent.

  -rpc-auth=""              RPC auth token of the Serf agent.
`
	return strings.TrimSpace(helpText)
}

func (c *MembersCommand) Run(args []string) int {
	var detailed bool
	var roleFilter, statusFilter, nameFilter, format string
	var tags []string
	cmdFlags := flag.NewFlagSet("members", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.BoolVar(&detailed, "detailed", false, "detailed output")
	cmdFlags.StringVar(&roleFilter, "role", "", "role filter")
	cmdFlags.StringVar(&statusFilter, "status", "", "status filter")
	cmdFlags.StringVar(&format, "format", "text", "output format")
	cmdFlags.Var((*agent.AppendSliceValue)(&tags), "tag", "tag filter")
	cmdFlags.StringVar(&nameFilter, "name", "", "name filter")
	rpcAddr := RPCAddrFlag(cmdFlags)
	rpcAuth := RPCAuthFlag(cmdFlags)
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	// Deprecation warning for role
	if roleFilter != "" {
		c.Ui.Output("Deprecation warning: 'Role' has been replaced with 'Tags'")
		tags = append(tags, fmt.Sprintf("role=%s", roleFilter))
	}

	reqtags, err := agent.UnmarshalTags(tags)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error: %s", err))
		return 1
	}

	client, err := RPCClient(*rpcAddr, *rpcAuth)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
		return 1
	}
	defer client.Close()

	members, err := client.MembersFiltered(reqtags, statusFilter, nameFilter)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error retrieving members: %s", err))
		return 1
	}

	result := MemberContainer{}

	for _, member := range members {
		addr := net.TCPAddr{IP: member.Addr, Port: int(member.Port)}

		result.Members = append(result.Members, Member{
			detail: detailed,
			Name:   member.Name,
			Addr:   addr.String(),
			Port:   member.Port,
			Tags:   member.Tags,
			Status: member.Status,
			Proto: map[string]uint8{
				"min":     member.DelegateMin,
				"max":     member.DelegateMax,
				"version": member.DelegateCur,
			},
		})
	}

	output, err := formatOutput(result, format)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Encoding error: %s", err))
		return 1
	}

	c.Ui.Output(string(output))
	return 0
}

func (c *MembersCommand) Synopsis() string {
	return "Lists the members of a Serf cluster"
}
<commit_msg>command: update members command for new columnize API adjustments<commit_after>package command

import (
	"flag"
	"fmt"
	"github.com/hashicorp/serf/command/agent"
	"github.com/mitchellh/cli"
	"github.com/ryanuber/columnize"
	"net"
	"strings"
)

// MembersCommand is a Command implementation that queries a running
// Serf agent what members are part of the cluster currently.
type MembersCommand struct {
	Ui cli.Ui
}

// A container of member details. Maintaining a command-specific struct here
// makes sense so that the agent.Member struct can evolve without changing the
// keys in the output interface.
type Member struct {
	detail bool
	Name   string            `json:"name"`
	Addr   string            `json:"addr"`
	Port   uint16            `json:"port"`
	Tags   map[string]string `json:"tags"`
	Status string            `json:"status"`
	Proto  map[string]uint8  `json:"protocol"`
}

type MemberContainer struct {
	Members []Member `json:"members"`
}

func (c MemberContainer) String() string {
	var result []string
	for _, member := range c.Members {
		tags := strings.Join(agent.MarshalTags(member.Tags), ",")
		line := fmt.Sprintf("%s|%s|%s|%s",
			member.Name, member.Addr, member.Status, tags)
		if member.detail {
			line += fmt.Sprintf(
				"|Protocol Version: %d|Available Protocol Range: [%d, %d]",
				member.Proto["version"], member.Proto["min"], member.Proto["max"])
		}
		result = append(result, line)
	}
	return columnize.SimpleFormat(result)
}

func (c *MembersCommand) Help() string {
	helpText := `
Usage: serf members [options]

  Outputs the members of a running Serf agent.

Options:

  -detailed                 Additional information such as protocol verions
                            will be shown (only affects text output format).

  -format                   If provided, output is returned in the specified
                            format. Valid formats are 'json', and 'text' (default)

  -name=<regexp>            If provided, only members matching the regexp are
                            returned.

  -role=<regexp>            If provided, output is filtered to only nodes matching
                            the regular expression for role
                            '-role' is deprecated in favor of '-tag role=foo'.

  -status=<regexp>          If provided, output is filtered to only nodes matching
                            the regular expression for status

  -tag <key>=<regexp>       If provided, output is filtered to only nodes with the
                            tag <key> with value matching the regular expression.
                            tag can be specified multiple times to filter on
                            multiple keys.

  -rpc-addr=127.0.0.1:7373  RPC address of the Serf agent.

  -rpc-auth=""              RPC auth token of the Serf agent.
`
	return strings.TrimSpace(helpText)
}

func (c *MembersCommand) Run(args []string) int {
	var detailed bool
	var roleFilter, statusFilter, nameFilter, format string
	var tags []string
	cmdFlags := flag.NewFlagSet("members", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.BoolVar(&detailed, "detailed", false, "detailed output")
	cmdFlags.StringVar(&roleFilter, "role", "", "role filter")
	cmdFlags.StringVar(&statusFilter, "status", "", "status filter")
	cmdFlags.StringVar(&format, "format", "text", "output format")
	cmdFlags.Var((*agent.AppendSliceValue)(&tags), "tag", "tag filter")
	cmdFlags.StringVar(&nameFilter, "name", "", "name filter")
	rpcAddr := RPCAddrFlag(cmdFlags)
	rpcAuth := RPCAuthFlag(cmdFlags)
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	// Deprecation warning for role
	if roleFilter != "" {
		c.Ui.Output("Deprecation warning: 'Role' has been replaced with 'Tags'")
		tags = append(tags, fmt.Sprintf("role=%s", roleFilter))
	}

	reqtags, err := agent.UnmarshalTags(tags)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error: %s", err))
		return 1
	}

	client, err := RPCClient(*rpcAddr, *rpcAuth)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
		return 1
	}
	defer client.Close()

	members, err := client.MembersFiltered(reqtags, statusFilter, nameFilter)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error retrieving members: %s", err))
		return 1
	}

	result := MemberContainer{}

	for _, member := range members {
		addr := net.TCPAddr{IP: member.Addr, Port: int(member.Port)}

		result.Members = append(result.Members, Member{
			detail: detailed,
			Name:   member.Name,
			Addr:   addr.String(),
			Port:   member.Port,
			Tags:   member.Tags,
			Status: member.Status,
			Proto: map[string]uint8{
				"min":     member.DelegateMin,
				"max":     member.DelegateMax,
				"version": member.DelegateCur,
			},
		})
	}

	output, err := formatOutput(result, format)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Encoding error: %s", err))
		return 1
	}

	c.Ui.Output(string(output))
	return 0
}

func (c *MembersCommand) Synopsis() string {
	return "Lists the members of a Serf cluster"
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
)

// RefreshCommand is a cli.Command implementation that refreshes the state
// file.
type RefreshCommand struct {
	ContextOpts *terraform.ContextOpts
	Ui          cli.Ui
}

func (c *RefreshCommand) Run(args []string) int {
	var statePath, stateOutPath string

	cmdFlags := flag.NewFlagSet("refresh", flag.ContinueOnError)
	cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
	cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var configPath string
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error("The apply command expacts at most one argument.")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		configPath = args[0]
	} else {
		var err error
		configPath, err = os.Getwd()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		}
	}

	// If we don't specify an output path, default to out normal state
	// path.
	if stateOutPath == "" {
		stateOutPath = statePath
	}

	// Verify that the state path exists
	if _, err := os.Stat(statePath); err != nil {
		if os.IsNotExist(err) {
			c.Ui.Error(fmt.Sprintf(
				"The Terraform state file for your infrastructure does not\n"+
					"exist. The 'refresh' command only works and only makes sense\n"+
					"when there is existing state that Terraform is managing. Please\n"+
					"double-check the value given below and try again. If you\n"+
					"haven't created infrastructure with Terraform yet, use the\n"+
					"'terraform apply' command.\n\n"+
					"Path: %s",
				statePath))
			return 1
		}

		c.Ui.Error(fmt.Sprintf(
			"There was an error reading the Terraform state that is needed\n"+
				"for refreshing. The path and error are shown below.\n\n"+
				"Path: %s\n\nError: %s",
			statePath,
			err))
		return 1
	}

	// Build the context based on the arguments given
	c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
	ctx, err := ContextArg(configPath, statePath, c.ContextOpts)
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if !validateContext(ctx, c.Ui) {
		return 1
	}

	state, err := ctx.Refresh()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
		return 1
	}

	log.Printf("[INFO] Writing state output to: %s", stateOutPath)
	f, err := os.Create(stateOutPath)
	if err == nil {
		defer f.Close()
		err = terraform.WriteState(state, f)
	}
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
		return 1
	}

	return 0
}

func (c *RefreshCommand) Help() string {
	helpText := `
Usage: terraform refresh [options] [dir]

  Update the state file of your infrastructure with metadata that matches
  the physical resources they are tracking.

  This will not modify your infrastructure, but it can modify your
  state file to update metadata. This metadata might cause new changes
  to occur when you generate a plan or call apply next.

Options:

  -state=path         Path to read and save state (unless state-out
                      is specified). Defaults to "terraform.tfstate".

  -state-out=path     Path to write updated state file. By default, the
                      "-state" path will be used.

`
	return strings.TrimSpace(helpText)
}

func (c *RefreshCommand) Synopsis() string {
	return "Refresh the local state of your infrastructure"
}
<commit_msg>command/refresh: better comment<commit_after>package command

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
)

// RefreshCommand is a cli.Command implementation that refreshes the state
// file.
type RefreshCommand struct {
	ContextOpts *terraform.ContextOpts
	Ui          cli.Ui
}

func (c *RefreshCommand) Run(args []string) int {
	var statePath, stateOutPath string

	cmdFlags := flag.NewFlagSet("refresh", flag.ContinueOnError)
	cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
	cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var configPath string
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error("The apply command expacts at most one argument.")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		configPath = args[0]
	} else {
		var err error
		configPath, err = os.Getwd()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		}
	}

	// If we don't specify an output path, default to out normal state
	// path.
	if stateOutPath == "" {
		stateOutPath = statePath
	}

	// Verify that the state path exists. The "ContextArg" function below
	// will actually do this, but we want to provide a richer error message
	// if possible.
	if _, err := os.Stat(statePath); err != nil {
		if os.IsNotExist(err) {
			c.Ui.Error(fmt.Sprintf(
				"The Terraform state file for your infrastructure does not\n"+
					"exist. The 'refresh' command only works and only makes sense\n"+
					"when there is existing state that Terraform is managing. Please\n"+
					"double-check the value given below and try again. If you\n"+
					"haven't created infrastructure with Terraform yet, use the\n"+
					"'terraform apply' command.\n\n"+
					"Path: %s",
				statePath))
			return 1
		}

		c.Ui.Error(fmt.Sprintf(
			"There was an error reading the Terraform state that is needed\n"+
				"for refreshing. The path and error are shown below.\n\n"+
				"Path: %s\n\nError: %s",
			statePath,
			err))
		return 1
	}

	// Build the context based on the arguments given
	c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
	ctx, err := ContextArg(configPath, statePath, c.ContextOpts)
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if !validateContext(ctx, c.Ui) {
		return 1
	}

	state, err := ctx.Refresh()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
		return 1
	}

	log.Printf("[INFO] Writing state output to: %s", stateOutPath)
	f, err := os.Create(stateOutPath)
	if err == nil {
		defer f.Close()
		err = terraform.WriteState(state, f)
	}
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
		return 1
	}

	return 0
}

func (c *RefreshCommand) Help() string {
	helpText := `
Usage: terraform refresh [options] [dir]

  Update the state file of your infrastructure with metadata that matches
  the physical resources they are tracking.

  This will not modify your infrastructure, but it can modify your
  state file to update metadata. This metadata might cause new changes
  to occur when you generate a plan or call apply next.

Options:

  -state=path         Path to read and save state (unless state-out
                      is specified). Defaults to "terraform.tfstate".

  -state-out=path     Path to write updated state file. By default, the
                      "-state" path will be used.

`
	return strings.TrimSpace(helpText)
}

func (c *RefreshCommand) Synopsis() string {
	return "Refresh the local state of your infrastructure"
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/github/hub/git"
	"github.com/github/hub/github"
	"github.com/github/hub/ui"
	"github.com/github/hub/utils"
)

var cmdCreate = &Command{
	Run:   create,
	Usage: "create [-poc] [-d <DESCRIPTION>] [-h <HOMEPAGE>] [[<ORGANIZATION>/]<NAME>]",
	Long: `Create a new repository on GitHub and add a git remote for it.

## Options:
	-p
		Create a private repository.

	-d <DESCRIPTION>
		Use this text as the description of the GitHub repository.

	-h <HOMEPAGE>
		Use this text as the URL of the GitHub repository.

	-o, --browse
		Open the new repository in a web browser.

	-c, --copy
		Put the URL of the new repository to clipboard instead of printing it.

	[<ORGANIZATION>/]<NAME>
		The name for the repository on GitHub (default: name of the current working
		directory).

		Optionally, create the repository within <ORGANIZATION>.

## Examples:
		$ hub create
		[ repo created on GitHub ]
		> git remote add -f origin git@github.com:USER/REPO.git

		$ hub create sinatra/recipes
		[ repo created in GitHub organization ]
		> git remote add -f origin git@github.com:sinatra/recipes.git

## See also:

hub-init(1), hub(1)
`,
}

var (
	flagCreatePrivate,
	flagCreateBrowse,
	flagCreateCopy bool

	flagCreateDescription,
	flagCreateHomepage string
)

func init() {
	cmdCreate.Flag.BoolVarP(&flagCreatePrivate, "private", "p", false, "PRIVATE")
	cmdCreate.Flag.BoolVarP(&flagCreateBrowse, "browse", "o", false, "BROWSE")
	cmdCreate.Flag.BoolVarP(&flagCreateCopy, "copy", "c", false, "COPY")
	cmdCreate.Flag.StringVarP(&flagCreateDescription, "description", "d", "", "DESCRIPTION")
	cmdCreate.Flag.StringVarP(&flagCreateHomepage, "homepage", "h", "", "HOMEPAGE")

	CmdRunner.Use(cmdCreate)
}

func create(command *Command, args *Args) {
	_, err := git.Dir()
	if err != nil {
		err = fmt.Errorf("'create' must be run from inside a git repository")
		utils.Check(err)
	}

	var newRepoName string
	if args.IsParamsEmpty() {
		dirName, err := git.WorkdirName()
		utils.Check(err)
		newRepoName = github.SanitizeProjectName(dirName)
	} else {
		reg := regexp.MustCompile("^[^-]")
		if !reg.MatchString(args.FirstParam()) {
			err = fmt.Errorf("invalid argument: %s", args.FirstParam())
			utils.Check(err)
		}
		newRepoName = args.FirstParam()
	}

	config := github.CurrentConfig()
	host, err := config.DefaultHost()
	if err != nil {
		utils.Check(github.FormatError("creating repository", err))
	}

	owner := host.User
	if strings.Contains(newRepoName, "/") {
		split := strings.SplitN(newRepoName, "/", 2)
		owner = split[0]
		newRepoName = split[1]
	}

	project := github.NewProject(owner, newRepoName, host.Host)
	gh := github.NewClient(project.Host)

	if gh.IsRepositoryExist(project) {

		repo, err := gh.Repository(project)
		if err != nil {
			utils.Check(err)
		}

		if !repo.Private && flagCreatePrivate {
			err = fmt.Errorf("Existing public repository detected. git remote not updated")
			utils.Check(err)
		} else {
			ui.Errorln("Existing repository detected. Updating git remote")
		}
	} else {
		if !args.Noop {
			repo, err := gh.CreateRepository(project, flagCreateDescription, flagCreateHomepage, flagCreatePrivate)
			utils.Check(err)
			project = github.NewProject(repo.FullName, "", project.Host)
		}
	}

	localRepo, err := github.LocalRepo()
	utils.Check(err)

	remote, _ := localRepo.OriginRemote()
	if remote == nil || remote.Name != "origin" {
		url := project.GitURL("", "", true)
		args.Before("git", "remote", "add", "-f", "origin", url)
	}

	webUrl := project.WebURL("", "", "")
	args.NoForward()
	printBrowseOrCopy(args, webUrl, flagCreateBrowse, flagCreateCopy)
}
<commit_msg>Updates based on PR<commit_after>package commands

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/github/hub/git"
	"github.com/github/hub/github"
	"github.com/github/hub/ui"
	"github.com/github/hub/utils"
)

var cmdCreate = &Command{
	Run:   create,
	Usage: "create [-poc] [-d <DESCRIPTION>] [-h <HOMEPAGE>] [[<ORGANIZATION>/]<NAME>]",
	Long: `Create a new repository on GitHub and add a git remote for it.

## Options:
	-p
		Create a private repository.

	-d <DESCRIPTION>
		Use this text as the description of the GitHub repository.

	-h <HOMEPAGE>
		Use this text as the URL of the GitHub repository.

	-o, --browse
		Open the new repository in a web browser.

	-c, --copy
		Put the URL of the new repository to clipboard instead of printing it.

	[<ORGANIZATION>/]<NAME>
		The name for the repository on GitHub (default: name of the current working
		directory).

		Optionally, create the repository within <ORGANIZATION>.

## Examples:
		$ hub create
		[ repo created on GitHub ]
		> git remote add -f origin git@github.com:USER/REPO.git

		$ hub create sinatra/recipes
		[ repo created in GitHub organization ]
		> git remote add -f origin git@github.com:sinatra/recipes.git

## See also:

hub-init(1), hub(1)
`,
}

var (
	flagCreatePrivate,
	flagCreateBrowse,
	flagCreateCopy bool

	flagCreateDescription,
	flagCreateHomepage string
)

func init() {
	cmdCreate.Flag.BoolVarP(&flagCreatePrivate, "private", "p", false, "PRIVATE")
	cmdCreate.Flag.BoolVarP(&flagCreateBrowse, "browse", "o", false, "BROWSE")
	cmdCreate.Flag.BoolVarP(&flagCreateCopy, "copy", "c", false, "COPY")
	cmdCreate.Flag.StringVarP(&flagCreateDescription, "description", "d", "", "DESCRIPTION")
	cmdCreate.Flag.StringVarP(&flagCreateHomepage, "homepage", "h", "", "HOMEPAGE")

	CmdRunner.Use(cmdCreate)
}

func create(command *Command, args *Args) {
	_, err := git.Dir()
	if err != nil {
		err = fmt.Errorf("'create' must be run from inside a git repository")
		utils.Check(err)
	}

	var newRepoName string
	if args.IsParamsEmpty() {
		dirName, err := git.WorkdirName()
		utils.Check(err)
		newRepoName = github.SanitizeProjectName(dirName)
	} else {
		reg := regexp.MustCompile("^[^-]")
		if !reg.MatchString(args.FirstParam()) {
			err = fmt.Errorf("invalid argument: %s", args.FirstParam())
			utils.Check(err)
		}
		newRepoName = args.FirstParam()
	}

	config := github.CurrentConfig()
	host, err := config.DefaultHost()
	if err != nil {
		utils.Check(github.FormatError("creating repository", err))
	}

	owner := host.User
	if strings.Contains(newRepoName, "/") {
		split := strings.SplitN(newRepoName, "/", 2)
		owner = split[0]
		newRepoName = split[1]
	}

	project := github.NewProject(owner, newRepoName, host.Host)
	gh := github.NewClient(project.Host)

	repo, err := gh.Repository(project)
	if err != nil {
		utils.Check(err)
	}

	if repo != nil {
		if !repo.Private && flagCreatePrivate {
			err = fmt.Errorf("Repository `%s' already exists and is public", repo.FullName)
			utils.Check(err)
		} else {
			ui.Errorln("Existing repository detected. Updating git remote")
		}
	} else {
		if !args.Noop {
			repo, err := gh.CreateRepository(project, flagCreateDescription, flagCreateHomepage, flagCreatePrivate)
			utils.Check(err)
			project = github.NewProject(repo.FullName, "", project.Host)
		}
	}

	localRepo, err := github.LocalRepo()
	utils.Check(err)

	remote, _ := localRepo.OriginRemote()
	if remote == nil || remote.Name != "origin" {
		url := project.GitURL("", "", true)
		args.Before("git", "remote", "add", "-f", "origin", url)
	}

	webUrl := project.WebURL("", "", "")
	args.NoForward()
	printBrowseOrCopy(args, webUrl, flagCreateBrowse, flagCreateCopy)
}
<|endoftext|>


<commit_before>package commands

import (
	"archive/zip"
	"fmt"
	updater "github.com/inconshreveable/go-update"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var cmdUpdate = &Command{
	Run:   update,
	Usage: "update",
	Short: "Update gh",
	Long: `Update gh to the latest version.

Examples:
  git update
`,
}

func update(cmd *Command, args *Args) {
	err := doUpdate()
	utils.Check(err)
	os.Exit(0)
}

func doUpdate() (err error) {
	client := github.NewClient(github.GitHubHost)
	releases, err := client.Releases(github.NewProject("jingweno", "gh", github.GitHubHost))
	if err != nil {
		err = fmt.Errorf("Error fetching releases: %s", err)
		return
	}

	latestRelease := releases[0]
	tagName := latestRelease.TagName
	version := strings.TrimPrefix(tagName, "v")

	fmt.Printf("Updating gh to release %s...\n", tagName)
	downloadURL := fmt.Sprintf("https://github.com/jingweno/gh/releases/download/%s/gh_%s-snapshot_%s_%s.zip", tagName, version, runtime.GOOS, runtime.GOARCH)
	path, err := downloadFile(downloadURL)
	if err != nil {
		err = fmt.Errorf("Can't download update from %s to %s", downloadURL, path)
		return
	}

	exec, err := unzipExecutable(path)
	if err != nil {
		err = fmt.Errorf("Can't unzip gh executable: %s", err)
		return
	}

	err, _ = updater.FromFile(exec)
	if err == nil {
		fmt.Println("Done!")
	}

	return
}

func unzipExecutable(path string) (exec string, err error) {
	rc, err := zip.OpenReader(path)
	if err != nil {
		err = fmt.Errorf("Can't open zip file %s: %s", path, err)
		return
	}
	defer rc.Close()

	dir := filepath.Dir(path)
	for _, file := range rc.File {
		frc, e := file.Open()
		if e != nil {
			err = fmt.Errorf("Can't open zip entry %s when reading: %s", file.Name, err)
			return
		}
		defer frc.Close()

		if !strings.HasPrefix(file.Name, "gh") {
			continue
		}

		dest := filepath.Join(dir, filepath.Base(file.Name))
		f, e := os.Create(dest)
		if e != nil {
			err = e
			return
		}
		defer f.Close()

		copied, e := io.Copy(f, frc)
		if e != nil {
			err = e
			return
		}

		if uint32(copied) != file.UncompressedSize {
			err = fmt.Errorf("Zip entry %s is corrupted", file.Name)
			return
		}

		exec = f.Name()

		break
	}

	if exec == "" {
		err = fmt.Errorf("No gh executable is found")
	}

	return
}

func downloadFile(url string) (path string, err error) {
	dir, err := ioutil.TempDir("", "gh-update")
	if err != nil {
		return
	}

	file, err := os.Create(filepath.Join(dir, filepath.Base(url)))
	if err != nil {
		return
	}
	defer file.Close()

	resp, err := http.Get(url)
	defer resp.Body.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return
	}

	path = file.Name()

	return
}
<commit_msg>Don't update if current version is the latest<commit_after>package commands

import (
	"archive/zip"
	"fmt"
	updater "github.com/inconshreveable/go-update"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var cmdUpdate = &Command{
	Run:   update,
	Usage: "update",
	Short: "Update gh",
	Long: `Update gh to the latest version.

Examples:
  git update
`,
}

func update(cmd *Command, args *Args) {
	err := doUpdate()
	utils.Check(err)
	os.Exit(0)
}

func doUpdate() (err error) {
	client := github.NewClient(github.GitHubHost)
	releases, err := client.Releases(github.NewProject("jingweno", "gh", github.GitHubHost))
	if err != nil {
		err = fmt.Errorf("Error fetching releases: %s", err)
		return
	}

	latestRelease := releases[0]
	tagName := latestRelease.TagName
	version := strings.TrimPrefix(tagName, "v")
	if version == Version {
		err = fmt.Errorf("You're already on the latest version: %s", Version)
		return
	}

	fmt.Printf("Updating gh to release %s...\n", version)
	downloadURL := fmt.Sprintf("https://github.com/jingweno/gh/releases/download/%s/gh_%s-snapshot_%s_%s.zip", tagName, version, runtime.GOOS, runtime.GOARCH)
	path, err := downloadFile(downloadURL)
	if err != nil {
		err = fmt.Errorf("Can't download update from %s to %s", downloadURL, path)
		return
	}

	exec, err := unzipExecutable(path)
	if err != nil {
		err = fmt.Errorf("Can't unzip gh executable: %s", err)
		return
	}

	err, _ = updater.FromFile(exec)
	if err == nil {
		fmt.Println("Done!")
	}

	return
}

func unzipExecutable(path string) (exec string, err error) {
	rc, err := zip.OpenReader(path)
	if err != nil {
		err = fmt.Errorf("Can't open zip file %s: %s", path, err)
		return
	}
	defer rc.Close()

	dir := filepath.Dir(path)
	for _, file := range rc.File {
		frc, e := file.Open()
		if e != nil {
			err = fmt.Errorf("Can't open zip entry %s when reading: %s", file.Name, err)
			return
		}
		defer frc.Close()

		if !strings.HasPrefix(file.Name, "gh") {
			continue
		}

		dest := filepath.Join(dir, filepath.Base(file.Name))
		f, e := os.Create(dest)
		if e != nil {
			err = e
			return
		}
		defer f.Close()

		copied, e := io.Copy(f, frc)
		if e != nil {
			err = e
			return
		}

		if uint32(copied) != file.UncompressedSize {
			err = fmt.Errorf("Zip entry %s is corrupted", file.Name)
			return
		}

		exec = f.Name()

		break
	}

	if exec == "" {
		err = fmt.Errorf("No gh executable is found")
	}

	return
}

func downloadFile(url string) (path string, err error) {
	dir, err := ioutil.TempDir("", "gh-update")
	if err != nil {
		return
	}

	file, err := os.Create(filepath.Join(dir, filepath.Base(url)))
	if err != nil {
		return
	}
	defer file.Close()

	resp, err := http.Get(url)
	defer resp.Body.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return
	}

	path = file.Name()

	return
}
<|endoftext|>


<commit_before>//+build skip

package main

import (
	"encoding/json"
	"fmt"
	bleed "github.com/FiloSottile/Heartbleed/bleed"
	"log"
	"net/http"
	"strings"
)

var PAYLOAD = []byte("heartbleed.filippo.io")

func defaultHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "http://filippo.io/Heartbleed", http.StatusFound)
}

func testHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hi there.")
}

type result struct {
	Code  int    `json:"code"`
	Data  string `json:"data"`
	Error string `json:"error"`
}

func bleedHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	host := r.URL.Path[len("/bleed/"):]
	if strings.Index(host, ":") == -1 {
		host = host + ":443"
	}
	data, err := bleed.Heartbleed(string(host), PAYLOAD)
	var rc int
	var errS string
	if err == bleed.Safe {
		rc = 1
		data = []byte("")
		log.Printf("%v - SAFE", host)
	} else if err != nil {
		rc = 2
		data = []byte("")
		errS = err.Error()
		log.Printf("%v - ERROR", host)
	} else {
		rc = 0
		log.Printf("%v - VULNERABLE", host)
	}
	res := result{rc, string(data), errS}
	j, err := json.Marshal(res)
	if err != nil {
		log.Println("ERROR", err)
	} else {
		w.Write(j)
	}
}

func main() {
	http.HandleFunc("/", defaultHandler)
	http.HandleFunc("/test", testHandler)
	http.HandleFunc("/bleed/", bleedHandler)
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Patched _serve for starttls changes<commit_after>//+build skip

package main

import (
	"encoding/json"
	"fmt"
	bleed "github.com/FiloSottile/Heartbleed/bleed"
	"log"
	"net/http"
	"strings"
)

var PAYLOAD = []byte("heartbleed.filippo.io")

func defaultHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "http://filippo.io/Heartbleed", http.StatusFound)
}

func testHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hi there.")
}

type result struct {
	Code  int    `json:"code"`
	Data  string `json:"data"`
	Error string `json:"error"`
}

func bleedHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	host := r.URL.Path[len("/bleed/"):]
	if strings.Index(host, ":") == -1 {
		host = host + ":443"
	}

	tgt := bleed.Target{
		HostIp: string(host),
	}
	data, err := bleed.Heartbleed(&tgt, PAYLOAD)
	var rc int
	var errS string
	if err == bleed.Safe {
		rc = 1
		data = []byte("")
		log.Printf("%v - SAFE", host)
	} else if err != nil {
		rc = 2
		data = []byte("")
		errS = err.Error()
		log.Printf("%v - ERROR", host)
	} else {
		rc = 0
		log.Printf("%v - VULNERABLE", host)
	}
	res := result{rc, string(data), errS}
	j, err := json.Marshal(res)
	if err != nil {
		log.Println("ERROR", err)
	} else {
		w.Write(j)
	}
}

func main() {
	http.HandleFunc("/", defaultHandler)
	http.HandleFunc("/test", testHandler)
	http.HandleFunc("/bleed/", bleedHandler)
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before><commit_msg>helper methods for multcachers<commit_after><|endoftext|>


<commit_before>package s3vfs

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	pathpkg "path"
	"path/filepath"
	"time"

	"golang.org/x/tools/godoc/vfs"

	"strings"

	"github.com/sqs/s3"
	"github.com/sqs/s3/s3util"
	"sourcegraph.com/sourcegraph/rwvfs"
)

var DefaultS3Config = s3util.Config{
	Keys: &s3.Keys{
		AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
		SecretKey: os.Getenv("AWS_SECRET_KEY"),
	},
	Service: s3.DefaultService,
}

// S3 returns an implementation of FileSystem using the specified S3 bucket and
// config. If config is nil, DefaultS3Config is used.
//
// The bucket URL is the full URL to the bucket on Amazon S3, including the
// bucket name and AWS region (e.g.,
// https://s3-us-west-2.amazonaws.com/mybucket).
func S3(bucket *url.URL, config *s3util.Config) rwvfs.FileSystem {
	if config == nil {
		config = &DefaultS3Config
	}
	return &S3FS{bucket, config}
}

type S3FS struct {
	bucket *url.URL
	config *s3util.Config
}

func (fs *S3FS) String() string {
	return fmt.Sprintf("S3 filesystem at %s", fs.bucket)
}

func (fs *S3FS) url(path string) string {
	path = pathpkg.Join(fs.bucket.Path, path)
	return fs.bucket.ResolveReference(&url.URL{Path: path}).String()
}

func (fs *S3FS) Open(name string) (vfs.ReadSeekCloser, error) {
	rdr, err := s3util.Open(fs.url(name), fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "open", Path: fs.url(name), Err: err}
	}

	b, err := ioutil.ReadAll(rdr)
	if err != nil {
		return nil, err
	}

	return nopCloser{bytes.NewReader(b)}, nil
}

func (fs *S3FS) ReadDir(path string) ([]os.FileInfo, error) {
	dir, err := s3util.NewFile(fs.url(path), fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "readdir", Path: fs.url(path), Err: err}
	}

	fis, err := dir.Readdir(0)
	if err != nil {
		return nil, err
	}
	for i, fi := range fis {
		fis[i] = &fileInfo{
			name:    pathpkg.Base(fi.Name()),
			size:    fi.Size(),
			mode:    fi.Mode(),
			modTime: fi.ModTime(),
			sys:     fi.Sys(),
		}
	}
	return fis, nil
}

func (fs *S3FS) Lstat(name string) (os.FileInfo, error) {
	fi, err := fs.lstat(name)
	if err != nil {
		return nil, &os.PathError{Op: "lstat", Path: fs.url(name), Err: err}
	}
	return fi, nil
}

func (fs *S3FS) lstat(name string) (os.FileInfo, error) {
	name = strings.TrimPrefix(filepath.Clean(name), "/")

	if name == "." {
		return &fileInfo{
			name:    ".",
			size:    0,
			mode:    os.ModeDir,
			modTime: time.Time{},
		}, nil
	}

	q := make(url.Values)
	q.Set("prefix", name+"/")
	q.Set("max-keys", "1")
	u := fs.bucket.ResolveReference(&url.URL{RawQuery: q.Encode()})

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	fs.config.Sign(req, *fs.config.Keys)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}

	result := struct{ Contents []struct{ Key string } }{}
	if err := xml.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}

	// If Contents is non-empty, then this is a dir.
	if len(result.Contents) == 1 {
		return &fileInfo{
			name: name,
			size: 0,
			mode: os.ModeDir,
		}, nil
	}

	// Otherwise, see if a key exists here.
	req, err = http.NewRequest("HEAD", fs.url(name), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	fs.config.Sign(req, *fs.config.Keys)
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusNotFound {
		return nil, os.ErrNotExist
	} else if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	t, _ := time.Parse(http.TimeFormat, resp.Header.Get("last-modified"))
	return &fileInfo{
		name:    name,
		size:    resp.ContentLength,
		mode:    0, // file
		modTime: t,
	}, nil
}

func (fs *S3FS) Stat(name string) (os.FileInfo, error) {
	return fs.Lstat(name)
}

// Create opens the file at path for writing, creating the file if it doesn't
// exist and truncating it otherwise.
func (fs *S3FS) Create(path string) (io.WriteCloser, error) {
	wc, err := s3util.Create(fs.url(path), nil, fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "create", Path: fs.url(path), Err: err}
	}
	return wc, nil
}

func (fs *S3FS) Mkdir(name string) error {
	// S3 doesn't have directories.
	return nil
}

// MkdirAll implements rwvfs.MkdirAllOverrider.
func (fs *S3FS) MkdirAll(name string) error {
	// S3 doesn't have directories.
	return nil
}

func (fs *S3FS) Remove(name string) (err error) {
	var rdr io.ReadCloser
	rdr, err = s3util.Delete(fs.url(name), fs.config)
	defer func() {
		if rdr != nil {
			err2 := rdr.Close()
			if err == nil {
				err = err2
			}
		}
	}()
	return err
}

type nopCloser struct {
	io.ReadSeeker
}

func (nc nopCloser) Close() error { return nil }

type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (f *fileInfo) Name() string       { return f.name }
func (f *fileInfo) Size() int64        { return f.size }
func (f *fileInfo) Mode() os.FileMode  { return f.mode }
func (f *fileInfo) ModTime() time.Time { return f.modTime }
func (f *fileInfo) IsDir() bool        { return f.mode&os.ModeDir != 0 }
func (f *fileInfo) Sys() interface{}   { return f.sys }

type respError struct {
	r *http.Response
	b bytes.Buffer
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.r = r
	io.Copy(&e.b, r.Body)
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"unwanted http status %d: %q",
		e.r.StatusCode,
		e.b.String(),
	)
}
<commit_msg>close resp bodies and use configured HTTP client<commit_after>package s3vfs

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	pathpkg "path"
	"path/filepath"
	"time"

	"golang.org/x/tools/godoc/vfs"

	"strings"

	"github.com/sqs/s3"
	"github.com/sqs/s3/s3util"
	"sourcegraph.com/sourcegraph/rwvfs"
)

var DefaultS3Config = s3util.Config{
	Keys: &s3.Keys{
		AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
		SecretKey: os.Getenv("AWS_SECRET_KEY"),
	},
	Service: s3.DefaultService,
}

// S3 returns an implementation of FileSystem using the specified S3 bucket and
// config. If config is nil, DefaultS3Config is used.
//
// The bucket URL is the full URL to the bucket on Amazon S3, including the
// bucket name and AWS region (e.g.,
// https://s3-us-west-2.amazonaws.com/mybucket).
func S3(bucket *url.URL, config *s3util.Config) rwvfs.FileSystem {
	if config == nil {
		config = &DefaultS3Config
	}
	return &S3FS{bucket, config}
}

type S3FS struct {
	bucket *url.URL
	config *s3util.Config
}

func (fs *S3FS) String() string {
	return fmt.Sprintf("S3 filesystem at %s", fs.bucket)
}

func (fs *S3FS) url(path string) string {
	path = pathpkg.Join(fs.bucket.Path, path)
	return fs.bucket.ResolveReference(&url.URL{Path: path}).String()
}

func (fs *S3FS) Open(name string) (vfs.ReadSeekCloser, error) {
	rdr, err := s3util.Open(fs.url(name), fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "open", Path: fs.url(name), Err: err}
	}

	b, err := ioutil.ReadAll(rdr)
	if err != nil {
		return nil, err
	}
	defer rdr.Close()

	return nopCloser{bytes.NewReader(b)}, nil
}

func (fs *S3FS) ReadDir(path string) ([]os.FileInfo, error) {
	dir, err := s3util.NewFile(fs.url(path), fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "readdir", Path: fs.url(path), Err: err}
	}

	fis, err := dir.Readdir(0)
	if err != nil {
		return nil, err
	}
	for i, fi := range fis {
		fis[i] = &fileInfo{
			name:    pathpkg.Base(fi.Name()),
			size:    fi.Size(),
			mode:    fi.Mode(),
			modTime: fi.ModTime(),
			sys:     fi.Sys(),
		}
	}
	return fis, nil
}

func (fs *S3FS) Lstat(name string) (os.FileInfo, error) {
	fi, err := fs.lstat(name)
	if err != nil {
		return nil, &os.PathError{Op: "lstat", Path: fs.url(name), Err: err}
	}
	return fi, nil
}

func (fs *S3FS) lstat(name string) (os.FileInfo, error) {
	name = strings.TrimPrefix(filepath.Clean(name), "/")

	if name == "." {
		return &fileInfo{
			name:    ".",
			size:    0,
			mode:    os.ModeDir,
			modTime: time.Time{},
		}, nil
	}

	client := fs.config.Client
	if client == nil {
		client = http.DefaultClient
	}

	q := make(url.Values)
	q.Set("prefix", name+"/")
	q.Set("max-keys", "1")
	u := fs.bucket.ResolveReference(&url.URL{RawQuery: q.Encode()})

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	fs.config.Sign(req, *fs.config.Keys)
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		resp.Body.Close()
		return nil, newRespError(resp)
	}

	result := struct{ Contents []struct{ Key string } }{}
	if err := xml.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}

	if err := resp.Body.Close(); err != nil {
		return nil, err
	}

	// If Contents is non-empty, then this is a dir.
	if len(result.Contents) == 1 {
		return &fileInfo{
			name: name,
			size: 0,
			mode: os.ModeDir,
		}, nil
	}

	// Otherwise, see if a key exists here.
	req, err = http.NewRequest("HEAD", fs.url(name), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	fs.config.Sign(req, *fs.config.Keys)
	resp, err = client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return nil, os.ErrNotExist
	} else if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	t, _ := time.Parse(http.TimeFormat, resp.Header.Get("last-modified"))
	return &fileInfo{
		name:    name,
		size:    resp.ContentLength,
		mode:    0, // file
		modTime: t,
	}, nil
}

func (fs *S3FS) Stat(name string) (os.FileInfo, error) {
	return fs.Lstat(name)
}

// Create opens the file at path for writing, creating the file if it doesn't
// exist and truncating it otherwise.
func (fs *S3FS) Create(path string) (io.WriteCloser, error) {
	wc, err := s3util.Create(fs.url(path), nil, fs.config)
	if err != nil {
		return nil, &os.PathError{Op: "create", Path: fs.url(path), Err: err}
	}
	return wc, nil
}

func (fs *S3FS) Mkdir(name string) error {
	// S3 doesn't have directories.
	return nil
}

// MkdirAll implements rwvfs.MkdirAllOverrider.
func (fs *S3FS) MkdirAll(name string) error {
	// S3 doesn't have directories.
	return nil
}

func (fs *S3FS) Remove(name string) (err error) {
	var rdr io.ReadCloser
	rdr, err = s3util.Delete(fs.url(name), fs.config)
	defer func() {
		if rdr != nil {
			err2 := rdr.Close()
			if err == nil {
				err = err2
			}
		}
	}()
	return err
}

type nopCloser struct {
	io.ReadSeeker
}

func (nc nopCloser) Close() error { return nil }

type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (f *fileInfo) Name() string       { return f.name }
func (f *fileInfo) Size() int64        { return f.size }
func (f *fileInfo) Mode() os.FileMode  { return f.mode }
func (f *fileInfo) ModTime() time.Time { return f.modTime }
func (f *fileInfo) IsDir() bool        { return f.mode&os.ModeDir != 0 }
func (f *fileInfo) Sys() interface{}   { return f.sys }

type respError struct {
	r *http.Response
	b bytes.Buffer
}

func newRespError(r *http.Response) *respError {
	e := new(respError)
	e.r = r
	io.Copy(&e.b, r.Body)
	r.Body.Close()
	return e
}

func (e *respError) Error() string {
	return fmt.Sprintf(
		"unwanted http status %d: %q",
		e.r.StatusCode,
		e.b.String(),
	)
}
<|endoftext|>


<commit_before>// Copyright 2009 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 expvar provides a standardized interface to public variables, such
// as operation counters in servers. It exposes these variables via HTTP at
// /debug/vars in JSON format.
//
// Operations to set or modify these public variables are atomic.
//
// In addition to adding the HTTP handler, this package registers the
// following variables:
//
//	cmdline   os.Args
//	memstats  runtime.Memstats
//
// The package is sometimes only imported for the side effect of
// registering its HTTP handler and the above variables. To use it
// this way, link this package into your program:
//	import _ "expvar"
//
package expvar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net/http"
	"os"
	"runtime"
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
)

// Var is an abstract type for all exported variables.
type Var interface {
	// String returns a valid JSON value for the variable.
	String() string
}

// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
	i int64
}

func (v *Int) String() string {
	return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
}

func (v *Int) Add(delta int64) {
	atomic.AddInt64(&v.i, delta)
}

func (v *Int) Set(value int64) {
	atomic.StoreInt64(&v.i, value)
}

// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
	f uint64
}

func (v *Float) String() string {
	return strconv.FormatFloat(
		math.Float64frombits(atomic.LoadUint64(&v.f)), 'g', -1, 64)
}

// Add adds delta to v.
func (v *Float) Add(delta float64) {
	for {
		cur := atomic.LoadUint64(&v.f)
		curVal := math.Float64frombits(cur)
		nxtVal := curVal + delta
		nxt := math.Float64bits(nxtVal)
		if atomic.CompareAndSwapUint64(&v.f, cur, nxt) {
			return
		}
	}
}

// Set sets v to value.
func (v *Float) Set(value float64) {
	atomic.StoreUint64(&v.f, math.Float64bits(value))
}

// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
	mu   sync.RWMutex
	m    map[string]Var
	keys []string // sorted
}

// KeyValue represents a single entry in a Map.
type KeyValue struct {
	Key   string
	Value Var
}

func (v *Map) String() string {
	v.mu.RLock()
	defer v.mu.RUnlock()
	var b bytes.Buffer
	fmt.Fprintf(&b, "{")
	first := true
	v.doLocked(func(kv KeyValue) {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "%q: %v", kv.Key, kv.Value)
		first = false
	})
	fmt.Fprintf(&b, "}")
	return b.String()
}

func (v *Map) Init() *Map {
	v.m = make(map[string]Var)
	return v
}

// updateKeys updates the sorted list of keys in v.keys.
// must be called with v.mu held.
func (v *Map) updateKeys() {
	if len(v.m) == len(v.keys) {
		// No new key.
		return
	}
	v.keys = v.keys[:0]
	for k := range v.m {
		v.keys = append(v.keys, k)
	}
	sort.Strings(v.keys)
}

func (v *Map) Get(key string) Var {
	v.mu.RLock()
	defer v.mu.RUnlock()
	return v.m[key]
}

func (v *Map) Set(key string, av Var) {
	v.mu.Lock()
	defer v.mu.Unlock()
	v.m[key] = av
	v.updateKeys()
}

func (v *Map) Add(key string, delta int64) {
	v.mu.RLock()
	av, ok := v.m[key]
	v.mu.RUnlock()
	if !ok {
		// check again under the write lock
		v.mu.Lock()
		av, ok = v.m[key]
		if !ok {
			av = new(Int)
			v.m[key] = av
			v.updateKeys()
		}
		v.mu.Unlock()
	}

	// Add to Int; ignore otherwise.
	if iv, ok := av.(*Int); ok {
		iv.Add(delta)
	}
}

// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string, delta float64) {
	v.mu.RLock()
	av, ok := v.m[key]
	v.mu.RUnlock()
	if !ok {
		// check again under the write lock
		v.mu.Lock()
		av, ok = v.m[key]
		if !ok {
			av = new(Float)
			v.m[key] = av
			v.updateKeys()
		}
		v.mu.Unlock()
	}

	// Add to Float; ignore otherwise.
	if iv, ok := av.(*Float); ok {
		iv.Add(delta)
	}
}

// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
	v.mu.RLock()
	defer v.mu.RUnlock()
	v.doLocked(f)
}

// doLocked calls f for each entry in the map.
// v.mu must be held for reads.
func (v *Map) doLocked(f func(KeyValue)) {
	for _, k := range v.keys {
		f(KeyValue{k, v.m[k]})
	}
}

// String is a string variable, and satisfies the Var interface.
type String struct {
	mu sync.RWMutex
	s  string
}

func (v *String) String() string {
	v.mu.RLock()
	s := v.s
	v.mu.RUnlock()
	b, _ := json.Marshal(s)
	return string(b)
}

func (v *String) Set(value string) {
	v.mu.Lock()
	defer v.mu.Unlock()
	v.s = value
}

// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}

func (f Func) String() string {
	v, _ := json.Marshal(f())
	return string(v)
}

// All published variables.
var (
	mutex   sync.RWMutex
	vars    = make(map[string]Var)
	varKeys []string // sorted
)

// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
	mutex.Lock()
	defer mutex.Unlock()
	if _, existing := vars[name]; existing {
		log.Panicln("Reuse of exported var name:", name)
	}
	vars[name] = v
	varKeys = append(varKeys, name)
	sort.Strings(varKeys)
}

// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
	mutex.RLock()
	defer mutex.RUnlock()
	return vars[name]
}

// Convenience functions for creating new exported variables.

func NewInt(name string) *Int {
	v := new(Int)
	Publish(name, v)
	return v
}

func NewFloat(name string) *Float {
	v := new(Float)
	Publish(name, v)
	return v
}

func NewMap(name string) *Map {
	v := new(Map).Init()
	Publish(name, v)
	return v
}

func NewString(name string) *String {
	v := new(String)
	Publish(name, v)
	return v
}

// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
	mutex.RLock()
	defer mutex.RUnlock()
	for _, k := range varKeys {
		f(KeyValue{k, vars[k]})
	}
}

func expvarHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "{\n")
	first := true
	Do(func(kv KeyValue) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

func cmdline() interface{} {
	return os.Args
}

func memstats() interface{} {
	stats := new(runtime.MemStats)
	runtime.ReadMemStats(stats)
	return *stats
}

func init() {
	http.HandleFunc("/debug/vars", expvarHandler)
	Publish("cmdline", Func(cmdline))
	Publish("memstats", Func(memstats))
}
<commit_msg>expvar: slightly expand documentation for Var's String method<commit_after>// Copyright 2009 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 expvar provides a standardized interface to public variables, such
// as operation counters in servers. It exposes these variables via HTTP at
// /debug/vars in JSON format.
//
// Operations to set or modify these public variables are atomic.
//
// In addition to adding the HTTP handler, this package registers the
// following variables:
//
//	cmdline   os.Args
//	memstats  runtime.Memstats
//
// The package is sometimes only imported for the side effect of
// registering its HTTP handler and the above variables. To use it
// this way, link this package into your program:
//	import _ "expvar"
//
package expvar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net/http"
	"os"
	"runtime"
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
)

// Var is an abstract type for all exported variables.
type Var interface {
	// String returns a valid JSON value for the variable.
	// Types with String methods that do not return valid JSON
	// (such as time.Time) must not be used as a Var.
	String() string
}

// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
	i int64
}

func (v *Int) String() string {
	return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
}

func (v *Int) Add(delta int64) {
	atomic.AddInt64(&v.i, delta)
}

func (v *Int) Set(value int64) {
	atomic.StoreInt64(&v.i, value)
}

// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
	f uint64
}

func (v *Float) String() string {
	return strconv.FormatFloat(
		math.Float64frombits(atomic.LoadUint64(&v.f)), 'g', -1, 64)
}

// Add adds delta to v.
func (v *Float) Add(delta float64) {
	for {
		cur := atomic.LoadUint64(&v.f)
		curVal := math.Float64frombits(cur)
		nxtVal := curVal + delta
		nxt := math.Float64bits(nxtVal)
		if atomic.CompareAndSwapUint64(&v.f, cur, nxt) {
			return
		}
	}
}

// Set sets v to value.
func (v *Float) Set(value float64) {
	atomic.StoreUint64(&v.f, math.Float64bits(value))
}

// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
	mu   sync.RWMutex
	m    map[string]Var
	keys []string // sorted
}

// KeyValue represents a single entry in a Map.
type KeyValue struct {
	Key   string
	Value Var
}

func (v *Map) String() string {
	v.mu.RLock()
	defer v.mu.RUnlock()
	var b bytes.Buffer
	fmt.Fprintf(&b, "{")
	first := true
	v.doLocked(func(kv KeyValue) {
		if !first {
			fmt.Fprintf(&b, ", ")
		}
		fmt.Fprintf(&b, "%q: %v", kv.Key, kv.Value)
		first = false
	})
	fmt.Fprintf(&b, "}")
	return b.String()
}

func (v *Map) Init() *Map {
	v.m = make(map[string]Var)
	return v
}

// updateKeys updates the sorted list of keys in v.keys.
// must be called with v.mu held.
func (v *Map) updateKeys() {
	if len(v.m) == len(v.keys) {
		// No new key.
		return
	}
	v.keys = v.keys[:0]
	for k := range v.m {
		v.keys = append(v.keys, k)
	}
	sort.Strings(v.keys)
}

func (v *Map) Get(key string) Var {
	v.mu.RLock()
	defer v.mu.RUnlock()
	return v.m[key]
}

func (v *Map) Set(key string, av Var) {
	v.mu.Lock()
	defer v.mu.Unlock()
	v.m[key] = av
	v.updateKeys()
}

func (v *Map) Add(key string, delta int64) {
	v.mu.RLock()
	av, ok := v.m[key]
	v.mu.RUnlock()
	if !ok {
		// check again under the write lock
		v.mu.Lock()
		av, ok = v.m[key]
		if !ok {
			av = new(Int)
			v.m[key] = av
			v.updateKeys()
		}
		v.mu.Unlock()
	}

	// Add to Int; ignore otherwise.
	if iv, ok := av.(*Int); ok {
		iv.Add(delta)
	}
}

// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string, delta float64) {
	v.mu.RLock()
	av, ok := v.m[key]
	v.mu.RUnlock()
	if !ok {
		// check again under the write lock
		v.mu.Lock()
		av, ok = v.m[key]
		if !ok {
			av = new(Float)
			v.m[key] = av
			v.updateKeys()
		}
		v.mu.Unlock()
	}

	// Add to Float; ignore otherwise.
	if iv, ok := av.(*Float); ok {
		iv.Add(delta)
	}
}

// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
	v.mu.RLock()
	defer v.mu.RUnlock()
	v.doLocked(f)
}

// doLocked calls f for each entry in the map.
// v.mu must be held for reads.
func (v *Map) doLocked(f func(KeyValue)) {
	for _, k := range v.keys {
		f(KeyValue{k, v.m[k]})
	}
}

// String is a string variable, and satisfies the Var interface.
type String struct {
	mu sync.RWMutex
	s  string
}

func (v *String) String() string {
	v.mu.RLock()
	s := v.s
	v.mu.RUnlock()
	b, _ := json.Marshal(s)
	return string(b)
}

func (v *String) Set(value string) {
	v.mu.Lock()
	defer v.mu.Unlock()
	v.s = value
}

// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}

func (f Func) String() string {
	v, _ := json.Marshal(f())
	return string(v)
}

// All published variables.
var (
	mutex   sync.RWMutex
	vars    = make(map[string]Var)
	varKeys []string // sorted
)

// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
	mutex.Lock()
	defer mutex.Unlock()
	if _, existing := vars[name]; existing {
		log.Panicln("Reuse of exported var name:", name)
	}
	vars[name] = v
	varKeys = append(varKeys, name)
	sort.Strings(varKeys)
}

// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
	mutex.RLock()
	defer mutex.RUnlock()
	return vars[name]
}

// Convenience functions for creating new exported variables.

func NewInt(name string) *Int {
	v := new(Int)
	Publish(name, v)
	return v
}

func NewFloat(name string) *Float {
	v := new(Float)
	Publish(name, v)
	return v
}

func NewMap(name string) *Map {
	v := new(Map).Init()
	Publish(name, v)
	return v
}

func NewString(name string) *String {
	v := new(String)
	Publish(name, v)
	return v
}

// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
	mutex.RLock()
	defer mutex.RUnlock()
	for _, k := range varKeys {
		f(KeyValue{k, vars[k]})
	}
}

func expvarHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "{\n")
	first := true
	Do(func(kv KeyValue) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

func cmdline() interface{} {
	return os.Args
}

func memstats() interface{} {
	stats := new(runtime.MemStats)
	runtime.ReadMemStats(stats)
	return *stats
}

func init() {
	http.HandleFunc("/debug/vars", expvarHandler)
	Publish("cmdline", Func(cmdline))
	Publish("memstats", Func(memstats))
}
<|endoftext|>


<commit_before><commit_msg>Closing the channel is not a good idea if we want to reconnect<commit_after><|endoftext|>


<commit_before>package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

var sections []*Section

//--- Linear pattern matcher

type Pattern struct {
  Text []rune
  Length, Pos int
}

func newPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true if Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

type TemplateEntry struct {
  SitePath, HardPath string
  FileInfo os.FileInfo
  InsertionLine int
}

func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

func makeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := makeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

func makeHardPath(siteRoot, startDir, sitePath string) string {
  // A hard path names a location in the physical file system rather than
  //  in the website's directory structure. It is either an absolute path
  //  or a relative path with respect to the directory containing the
  //  top-level template that is being parsed.
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  // Note that filepath.Join automatically performs filepath.Clean, thus
  //  returning a lexically unique form of the path. However, the path
  //  does not uniquely identify a file if it includes a symbolic link.
  //  Therefore, we cannot rely on string comparison to prevent cycles.
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

func parse(templatePath string) error {

  // We resolve absolute paths with the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)

  // Make an insertion stack with a top-level entry.
  entry, error := makeTemplateEntry(siteRoot, startDir, templatePath, 0)
  if error != nil {
    return error
  }
  stack := []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir, stack)
}

func doParse(siteRoot, startDir string, stack []*TemplateEntry) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {
        lines = append(lines, stack[j].String())
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  var error error
  var file *os.File

  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }

  reader := bufio.NewReader(file)
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  codePattern := newPattern("<?code")
  insertPattern := newPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := newPattern("?>")

  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0
  lineIndex := 1
  prefix := true

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {
      content := string(buffer)
      if topLevel {
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])
          if prefix {
            if topLevel {
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]
        if open == &codePattern {
          emitCode(string(content))
        } else if open == &insertPattern {
          childPath := strings.TrimSpace(string(content))
          entry, error := makeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)
          if error != nil {
            return error
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir, stack)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse the top-level template. The resulting source code goes into
    //  the global variable sections.
    templatePath := os.Args[argIx]

    // We parse the template to obtain code sections and static sections.
    error := parse(templatePath)
    if error != nil {
      writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
      continue
    }

    // Concatenate only the code sections.
    output := bytes.Buffer{}
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      }
    }
    fileSet := token.NewFileSet()
    fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing code sections: %s\n", error))
      continue
    }

    //  Has the package fmt been imported? Is the name "fmt" available?
    fmtIsImported := false
    var fmtImportName string       // use this if fmt has already been imported
    seenName := map[string]bool{}  // consult this if we have to import fmt

    for _, importSpec := range fileNode.Imports {
      importPath, _ := strconv.Unquote(importSpec.Path.Value)
      var importName string
      if importSpec.Name == nil {
        importName = path.Base(importPath)
      } else {
        importName = importSpec.Name.Name
      }
      seenName[importName] = true
      if !fmtIsImported && importPath == "fmt" && importName != "_" {
        fmtIsImported = true
        fmtImportName = importName
      }
    }

    var importAs, printPrefix string  // NB: these are "" by default
    if fmtIsImported {
      if fmtImportName != "." {  // no prefix is needed with a dot import
        printPrefix = fmtImportName+"."
      }
    } else {
      if !seenName["fmt"] {
        importAs = "fmt"
      } else {
        for i := 0; ; i++ {
          importAs = fmt.Sprintf("fmt_%d", i)
          _, found := seenName[importAs]
          if !found {
            break
          }
        }
      }
      printPrefix = importAs+"."
    }

    // Concatenate the code sections and static sections.
    output.Reset()
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      } else {
        s := fmt.Sprintf(";%sPrint(%s);", printPrefix, section.Text)
        fmt.Fprintf(&output, s)
      }
    }
    // Have Go parse the entire template output.
    fileSet = token.NewFileSet()
    fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing entire template output: %s\n", error))
      continue
    }
    // Finally, inject an import statement if necessary.
    if !fmtIsImported {
      if importAs == "fmt" {
        astutil.AddImport(fileSet, fileNode, "fmt")
      } else {
        astutil.AddNamedImport(fileSet, fileNode, importAs, "fmt")
      }
    }

    // Print with a custom configuration: soft tabs of two spaces each.
    config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
    (&config).Fprint(writer, fileSet, fileNode)
  }
}
<commit_msg>Factor out the package (fmt) and function (Print)<commit_after>package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

var sections []*Section

//--- Linear pattern matcher

type Pattern struct {
  Text []rune
  Length, Pos int
}

func newPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true if Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

type TemplateEntry struct {
  SitePath, HardPath string
  FileInfo os.FileInfo
  InsertionLine int
}

func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

func makeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := makeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

func makeHardPath(siteRoot, startDir, sitePath string) string {
  // A hard path names a location in the physical file system rather than
  //  in the website's directory structure. It is either an absolute path
  //  or a relative path with respect to the directory containing the
  //  top-level template that is being parsed.
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  // Note that filepath.Join automatically performs filepath.Clean, thus
  //  returning a lexically unique form of the path. However, the path
  //  does not uniquely identify a file if it includes a symbolic link.
  //  Therefore, we cannot rely on string comparison to prevent cycles.
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

func parse(templatePath string) error {

  // We resolve absolute paths with the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)

  // Make an insertion stack with a top-level entry.
  entry, error := makeTemplateEntry(siteRoot, startDir, templatePath, 0)
  if error != nil {
    return error
  }
  stack := []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir, stack)
}

func doParse(siteRoot, startDir string, stack []*TemplateEntry) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {
        lines = append(lines, stack[j].String())
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  var error error
  var file *os.File

  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }

  reader := bufio.NewReader(file)
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  codePattern := newPattern("<?code")
  insertPattern := newPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := newPattern("?>")

  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0
  lineIndex := 1
  prefix := true

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {
      content := string(buffer)
      if topLevel {
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])
          if prefix {
            if topLevel {
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]
        if open == &codePattern {
          emitCode(string(content))
        } else if open == &insertPattern {
          childPath := strings.TrimSpace(string(content))
          entry, error := makeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)
          if error != nil {
            return error
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir, stack)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse the top-level template. The resulting source code goes into
    //  the global variable sections.
    templatePath := os.Args[argIx]

    // We parse the template to obtain code sections and static sections.
    error := parse(templatePath)
    if error != nil {
      writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
      continue
    }

    // Concatenate only the code sections.
    output := bytes.Buffer{}
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      }
    }
    fileSet := token.NewFileSet()
    fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing code sections: %s\n", error))
      continue
    }

    seekPath := "fmt"
    seekName := path.Base(seekPath)
    printCall := "Print"

    //  Has the package been imported? Is the name available?
    isImported := false
    var importedAs string          // use this if the path has been imported
    seenName := map[string]bool{}  // consult this if we have to import

    for _, importSpec := range fileNode.Imports {
      importPath, _ := strconv.Unquote(importSpec.Path.Value)
      var importName string
      if importSpec.Name == nil {
        importName = path.Base(importPath)
      } else {
        importName = importSpec.Name.Name
      }
      seenName[importName] = true
      if !isImported && importPath == seekPath && importName != "_" {
        isImported = true
        importedAs = importName
      }
    }

    var importAs, printPrefix string  // NB: these are "" by default
    if isImported {
      if importedAs != "." {  // no prefix is needed with a dot import
        printPrefix = importedAs+"."
      }
    } else {
      if !seenName[seekName] {
        importAs = seekName
      } else {
        for i := 0; ; i++ {
          importAs = fmt.Sprintf("%s_%d", seekName, i)
          _, found := seenName[importAs]
          if !found {
            break
          }
        }
      }
      printPrefix = importAs+"."
    }

    // Concatenate the code sections and static sections.
    output.Reset()
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      } else {
        s := fmt.Sprintf(";%s%s(%s);", printPrefix, printCall, section.Text)
        fmt.Fprintf(&output, s)
      }
    }
    // Have Go parse the entire template output.
    fileSet = token.NewFileSet()
    fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing entire template output: %s\n", error))
      continue
    }
    // Finally, inject an import statement if necessary.
    if !isImported {
      if importAs == seekName {
        astutil.AddImport(fileSet, fileNode, seekPath)
      } else {
        astutil.AddNamedImport(fileSet, fileNode, importAs, seekPath)
      }
    }

    // Print with a custom configuration: soft tabs of two spaces each.
    config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
    (&config).Fprint(writer, fileSet, fileNode)
  }
}
<|endoftext|>