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

package subscription

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/pkg/errors"

	"github.com/diegobernardes/flare"
	"github.com/diegobernardes/flare/infra/task"
)

// Trigger is used to process the signals on documents change.
type Trigger struct {
	document   flare.DocumentRepositorier
	repository flare.SubscriptionRepositorier
	httpClient *http.Client
	pusher     task.Pusher
}

func (t *Trigger) marshal(document *flare.Document, action string) ([]byte, error) {
	resource := map[string]interface{}{
		"id": document.Resource.ID,
	}

	if document.Resource.Change.Format != "" {
		resource["format"] = document.Resource.Change.Format
	}

	rawContent := map[string]interface{}{
		"action": action,
		"document": map[string]interface{}{
			"id":       document.ID,
			"revision": document.Revision,
		},
		"resource":  resource,
		"updatedAt": time.Now().Format(time.RFC3339),
	}

	content, err := json.Marshal(rawContent)
	if err != nil {
		return nil, errors.Wrap(err, "error during message marshal")
	}
	return content, nil
}

func (t *Trigger) unmarshal(rawContent []byte) (*flare.Document, string, error) {
	type content struct {
		Action   string `json:"action"`
		Document struct {
			ID       string `json:"id"`
			Revision int64  `json:"revision"`
		} `json:"document"`
		Resource struct {
			ID     string `json:"id"`
			Format string `json:"format"`
		}
		UpdateAt time.Time `json:"updatedAt"`
	}

	var value content
	if err := json.Unmarshal(rawContent, &value); err != nil {
		panic(err)
	}

	resource := flare.Resource{
		ID: value.Resource.ID,
		Change: flare.ResourceChange{
			Format: value.Resource.Format,
		},
	}

	return &flare.Document{
		ID:        value.Document.ID,
		Revision:  value.Document.Revision,
		UpdatedAt: value.UpdateAt,
		Resource:  resource,
	}, value.Action, nil
}

// Update the document change signal.
func (t *Trigger) Update(ctx context.Context, document *flare.Document) error {
	content, err := t.marshal(document, flare.SubscriptionTriggerUpdate)
	if err != nil {
		return errors.Wrap(err, "error during trigger")
	}

	if err = t.pusher.Push(ctx, content); err != nil {
		return errors.Wrap(err, "error during message delivery")
	}
	return nil
}

// Delete the document change signal.
func (t *Trigger) Delete(ctx context.Context, document *flare.Document) error {
	content, err := t.marshal(document, flare.SubscriptionTriggerDelete)
	if err != nil {
		return errors.Wrap(err, "error during trigger")
	}

	if err = t.pusher.Push(ctx, content); err != nil {
		return errors.Wrap(err, "error during message delivery")
	}
	return nil
}

// Process is used to consume the tasks.
func (t *Trigger) Process(ctx context.Context, rawContent []byte) error {
	rawDocument, action, err := t.unmarshal(rawContent)
	if err != nil {
		return errors.Wrap(err, "could not unmarshal the message")
	}

	var document *flare.Document
	switch action {
	case flare.SubscriptionTriggerUpdate:
		document, err = t.document.FindOneWithRevision(ctx, rawDocument.ID, rawDocument.Revision)
		if err != nil {
			return errors.Wrap(err, "error during document find")
		}
	case flare.SubscriptionTriggerDelete:
		document, err = t.document.FindOne(ctx, rawDocument.ID)
		if err != nil {
			return errors.Wrap(err, "error during document find")
		}
	}

	if err = t.repository.Trigger(ctx, action, document, t.exec(document)); err != nil {
		return errors.Wrap(err, "error during message process")
	}
	return nil
}

func (t *Trigger) exec(
	document *flare.Document,
) func(context.Context, flare.Subscription, string) error {
	return func(ctx context.Context, sub flare.Subscription, kind string) error {
		content, err := t.buildContent(document, sub, kind)
		if err != nil {
			return errors.Wrap(err, "error during content build")
		}

		buf := bytes.NewBuffer(content)
		req, err := http.NewRequest(sub.Endpoint.Method, sub.Endpoint.URL.String(), buf)
		if err != nil {
			return errors.Wrap(err, "error during http request create")
		}
		req = req.WithContext(ctx)

		for key, values := range sub.Endpoint.Headers {
			for _, value := range values {
				req.Header.Add(key, value)
			}
		}
		req.Header.Add("Content-Type", "application/json")

		resp, err := t.httpClient.Do(req)
		if err != nil {
			return errors.Wrap(err, "error during http request")
		}

		for _, status := range sub.Delivery.Success {
			if status == resp.StatusCode {
				return nil
			}
		}

		for _, status := range sub.Delivery.Discard {
			if status == resp.StatusCode {
				return nil
			}
		}

		return errors.Errorf(
			"success and discard status don't match with the response value '%d'", resp.StatusCode,
		)
	}
}

func (t *Trigger) buildContent(
	document *flare.Document, sub flare.Subscription, kind string,
) ([]byte, error) {
	rawContent := map[string]interface{}{
		"id":        document.ID,
		"action":    kind,
		"updatedAt": document.UpdatedAt.String(),
	}
	if sub.Data != nil {
		replacer, err := wildcardReplace(&sub.Resource, document)
		if err != nil {
			return nil, errors.Wrap(err, "failed to extract the wildcards from document id")
		}

		for key, rawValue := range sub.Data {
			if value, ok := rawValue.(string); ok {
				sub.Data[key] = replacer(value)
			}
		}

		rawContent["data"] = sub.Data
	}

	if sub.SendDocument {
		rawContent["document"] = document.Content
	}

	content, err := json.Marshal(rawContent)
	if err != nil {
		return nil, errors.Wrap(err, "error during response generate")
	}
	return content, nil
}

// Init initialize the Trigger.
func (t *Trigger) Init(options ...func(*Trigger)) error {
	for _, option := range options {
		option(t)
	}

	if t.document == nil {
		return errors.New("document repository not found")
	}

	if t.pusher == nil {
		return errors.New("pusher not found")
	}

	if t.repository == nil {
		return errors.New("repository not found")
	}

	if t.httpClient == nil {
		return errors.New("httpClient not found")
	}

	return nil
}

// TriggerRepository set the repository on Trigger.
func TriggerRepository(repository flare.SubscriptionRepositorier) func(*Trigger) {
	return func(t *Trigger) {
		t.repository = repository
	}
}

// TriggerHTTPClient set the httpClient on Trigger.
func TriggerHTTPClient(httpClient *http.Client) func(*Trigger) {
	return func(t *Trigger) {
		t.httpClient = httpClient
	}
}

// TriggerPusher set the pusher that gonna receive the trigger notifications.
func TriggerPusher(pusher task.Pusher) func(*Trigger) {
	return func(t *Trigger) {
		t.pusher = pusher
	}
}

// TriggerDocumentRepository set the document repository.
func TriggerDocumentRepository(repo flare.DocumentRepositorier) func(*Trigger) {
	return func(t *Trigger) {
		t.document = repo
	}
}
<commit_msg>subscription: only send the data on trigger if it exists<commit_after>// Copyright 2017 Diego Bernardes. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package subscription

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/pkg/errors"

	"github.com/diegobernardes/flare"
	"github.com/diegobernardes/flare/infra/task"
)

// Trigger is used to process the signals on documents change.
type Trigger struct {
	document   flare.DocumentRepositorier
	repository flare.SubscriptionRepositorier
	httpClient *http.Client
	pusher     task.Pusher
}

func (t *Trigger) marshal(document *flare.Document, action string) ([]byte, error) {
	resource := map[string]interface{}{
		"id": document.Resource.ID,
	}

	if document.Resource.Change.Format != "" {
		resource["format"] = document.Resource.Change.Format
	}

	rawContent := map[string]interface{}{
		"action": action,
		"document": map[string]interface{}{
			"id":       document.ID,
			"revision": document.Revision,
		},
		"resource":  resource,
		"updatedAt": time.Now().Format(time.RFC3339),
	}

	content, err := json.Marshal(rawContent)
	if err != nil {
		return nil, errors.Wrap(err, "error during message marshal")
	}
	return content, nil
}

func (t *Trigger) unmarshal(rawContent []byte) (*flare.Document, string, error) {
	type content struct {
		Action   string `json:"action"`
		Document struct {
			ID       string `json:"id"`
			Revision int64  `json:"revision"`
		} `json:"document"`
		Resource struct {
			ID     string `json:"id"`
			Format string `json:"format"`
		}
		UpdateAt time.Time `json:"updatedAt"`
	}

	var value content
	if err := json.Unmarshal(rawContent, &value); err != nil {
		panic(err)
	}

	resource := flare.Resource{
		ID: value.Resource.ID,
		Change: flare.ResourceChange{
			Format: value.Resource.Format,
		},
	}

	return &flare.Document{
		ID:        value.Document.ID,
		Revision:  value.Document.Revision,
		UpdatedAt: value.UpdateAt,
		Resource:  resource,
	}, value.Action, nil
}

// Update the document change signal.
func (t *Trigger) Update(ctx context.Context, document *flare.Document) error {
	content, err := t.marshal(document, flare.SubscriptionTriggerUpdate)
	if err != nil {
		return errors.Wrap(err, "error during trigger")
	}

	if err = t.pusher.Push(ctx, content); err != nil {
		return errors.Wrap(err, "error during message delivery")
	}
	return nil
}

// Delete the document change signal.
func (t *Trigger) Delete(ctx context.Context, document *flare.Document) error {
	content, err := t.marshal(document, flare.SubscriptionTriggerDelete)
	if err != nil {
		return errors.Wrap(err, "error during trigger")
	}

	if err = t.pusher.Push(ctx, content); err != nil {
		return errors.Wrap(err, "error during message delivery")
	}
	return nil
}

// Process is used to consume the tasks.
func (t *Trigger) Process(ctx context.Context, rawContent []byte) error {
	rawDocument, action, err := t.unmarshal(rawContent)
	if err != nil {
		return errors.Wrap(err, "could not unmarshal the message")
	}

	var document *flare.Document
	switch action {
	case flare.SubscriptionTriggerUpdate:
		document, err = t.document.FindOneWithRevision(ctx, rawDocument.ID, rawDocument.Revision)
		if err != nil {
			return errors.Wrap(err, "error during document find")
		}
	case flare.SubscriptionTriggerDelete:
		document, err = t.document.FindOne(ctx, rawDocument.ID)
		if err != nil {
			return errors.Wrap(err, "error during document find")
		}
	}

	if err = t.repository.Trigger(ctx, action, document, t.exec(document)); err != nil {
		return errors.Wrap(err, "error during message process")
	}
	return nil
}

func (t *Trigger) exec(
	document *flare.Document,
) func(context.Context, flare.Subscription, string) error {
	return func(ctx context.Context, sub flare.Subscription, kind string) error {
		content, err := t.buildContent(document, sub, kind)
		if err != nil {
			return errors.Wrap(err, "error during content build")
		}

		buf := bytes.NewBuffer(content)
		req, err := http.NewRequest(sub.Endpoint.Method, sub.Endpoint.URL.String(), buf)
		if err != nil {
			return errors.Wrap(err, "error during http request create")
		}
		req = req.WithContext(ctx)

		for key, values := range sub.Endpoint.Headers {
			for _, value := range values {
				req.Header.Add(key, value)
			}
		}
		req.Header.Add("Content-Type", "application/json")

		resp, err := t.httpClient.Do(req)
		if err != nil {
			return errors.Wrap(err, "error during http request")
		}

		for _, status := range sub.Delivery.Success {
			if status == resp.StatusCode {
				return nil
			}
		}

		for _, status := range sub.Delivery.Discard {
			if status == resp.StatusCode {
				return nil
			}
		}

		return errors.Errorf(
			"success and discard status don't match with the response value '%d'", resp.StatusCode,
		)
	}
}

func (t *Trigger) buildContent(
	document *flare.Document, sub flare.Subscription, kind string,
) ([]byte, error) {
	rawContent := map[string]interface{}{
		"id":        document.ID,
		"action":    kind,
		"updatedAt": document.UpdatedAt.String(),
	}
	if len(sub.Data) > 0 {
		replacer, err := wildcardReplace(&sub.Resource, document)
		if err != nil {
			return nil, errors.Wrap(err, "failed to extract the wildcards from document id")
		}

		for key, rawValue := range sub.Data {
			if value, ok := rawValue.(string); ok {
				sub.Data[key] = replacer(value)
			}
		}

		rawContent["data"] = sub.Data
	}

	if sub.SendDocument {
		rawContent["document"] = document.Content
	}

	content, err := json.Marshal(rawContent)
	if err != nil {
		return nil, errors.Wrap(err, "error during response generate")
	}
	return content, nil
}

// Init initialize the Trigger.
func (t *Trigger) Init(options ...func(*Trigger)) error {
	for _, option := range options {
		option(t)
	}

	if t.document == nil {
		return errors.New("document repository not found")
	}

	if t.pusher == nil {
		return errors.New("pusher not found")
	}

	if t.repository == nil {
		return errors.New("repository not found")
	}

	if t.httpClient == nil {
		return errors.New("httpClient not found")
	}

	return nil
}

// TriggerRepository set the repository on Trigger.
func TriggerRepository(repository flare.SubscriptionRepositorier) func(*Trigger) {
	return func(t *Trigger) {
		t.repository = repository
	}
}

// TriggerHTTPClient set the httpClient on Trigger.
func TriggerHTTPClient(httpClient *http.Client) func(*Trigger) {
	return func(t *Trigger) {
		t.httpClient = httpClient
	}
}

// TriggerPusher set the pusher that gonna receive the trigger notifications.
func TriggerPusher(pusher task.Pusher) func(*Trigger) {
	return func(t *Trigger) {
		t.pusher = pusher
	}
}

// TriggerDocumentRepository set the document repository.
func TriggerDocumentRepository(repo flare.DocumentRepositorier) func(*Trigger) {
	return func(t *Trigger) {
		t.document = repo
	}
}
<|endoftext|>


<commit_before>package deis

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"github.com/goware/urlx"
)

// Client oversees the interaction between the deis and controller
type Client struct {
	// HTTP deis used to communicate with the API.
	HTTPClient *http.Client

	// VerifySSL determines whether or not to verify SSL connections.
	VerifySSL bool

	// URL used to communicate with the controller.
	ControllerURL *url.URL

	//UserAgent is the user agent used when making requests
	UserAgent string

	//API Version used by the controller, set after a http request.
	ControllerAPIVersion string

	// Token is used to authenticate the request against the API.
	Token string
}

// APIVersion is the api version the sdk is compatible with.
const APIVersion = "2.0"

var (
	// ErrAPIMismatch occurs when the sdk is using a different api version than the deis.
	ErrAPIMismatch = errors.New("API Version Mismatch between server and deis")

	// DefaultUserAgent is used as the default user agent when making requests.
	DefaultUserAgent = fmt.Sprintf("Deis Go SDK V%s", APIVersion)
)

// New creates a new deis to communicate with the api.
func New(verifySSL bool, controllerURL string, token string) (*Client, error) {
	// urlx, unlike the native url library, uses sane defaults when URL parsing,
	// preventing issues like missing schemes.
	u, err := urlx.Parse(controllerURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		HTTPClient:    createHTTPClient(verifySSL),
		VerifySSL:     verifySSL,
		ControllerURL: u,
		Token:         token,
		UserAgent:     DefaultUserAgent,
	}, nil
}
<commit_msg>chore(deis): bump api version for latest release (#23)<commit_after>package deis

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"github.com/goware/urlx"
)

// Client oversees the interaction between the deis and controller
type Client struct {
	// HTTP deis used to communicate with the API.
	HTTPClient *http.Client

	// VerifySSL determines whether or not to verify SSL connections.
	VerifySSL bool

	// URL used to communicate with the controller.
	ControllerURL *url.URL

	//UserAgent is the user agent used when making requests
	UserAgent string

	//API Version used by the controller, set after a http request.
	ControllerAPIVersion string

	// Token is used to authenticate the request against the API.
	Token string
}

// APIVersion is the api version the sdk is compatible with.
const APIVersion = "2.1"

var (
	// ErrAPIMismatch occurs when the sdk is using a different api version than the deis.
	ErrAPIMismatch = errors.New("API Version Mismatch between server and deis")

	// DefaultUserAgent is used as the default user agent when making requests.
	DefaultUserAgent = fmt.Sprintf("Deis Go SDK V%s", APIVersion)
)

// New creates a new deis to communicate with the api.
func New(verifySSL bool, controllerURL string, token string) (*Client, error) {
	// urlx, unlike the native url library, uses sane defaults when URL parsing,
	// preventing issues like missing schemes.
	u, err := urlx.Parse(controllerURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		HTTPClient:    createHTTPClient(verifySSL),
		VerifySSL:     verifySSL,
		ControllerURL: u,
		Token:         token,
		UserAgent:     DefaultUserAgent,
	}, nil
}
<|endoftext|>


<commit_before>package simlog

// from beego

import (
	"fmt"
	"log"
)

//--------------------
// LOG LEVEL
//--------------------

// Log levels to control the logging output.
type Level int

const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarning
	LevelError
	LevelCritical
)

var (
	levelName    = []string{"[T]", "[D]", "[I]", "[W]", "[E]", "[C]"}
	level        = LevelTrace //日志等级，传参设定
	SimLogger    *log.Logger
	logSplitFunc func()                 = func() {}
	Trace        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelTrace, v...) }
	Debug        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelDebug, v...) }
	Info         func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelInfo, v...) }
	Warn         func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelWarning, v...) }
	Error        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelError, v...) }
	Critical     func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelCritical, v...) }
)

func (this Level) String() string {
	return levelName[this]
}

// LogLevel returns the global log level and can be used in
// own implementations of the logger interface.
func LogLevel() Level {
	return level
}

// SetLogLevel sets the global log level used by the simple
// logger.
func SetLogLevel(l Level) {
	level = l
}

// logger references the used application logger.
//var NettaoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)

// SetLogger sets a new logger.
func SetLogger(l *log.Logger) {
	SimLogger = l
}

func SetSplit(maxsize, maxindex int) {
	setLogSplit(maxsize, maxindex)
	logSplitFunc = logSplit
}

func Init(path string, maxday int, loglevel Level) {
	logInit(path, maxday, loglevel)
	Trace = TraceFunc
	Debug = DebugFunc
	Info = InfoFunc
	Warn = WarnFunc
	Error = ErrorFunc
	Critical = CriticalFunc
}

func Close() {
	logClose()
}

func PrintFunc(loglevel Level, v ...interface{}) {
	if level <= loglevel {
		log.Println(loglevel.String() + fmt.Sprint(v...))
	}
}

func TraceFunc(v ...interface{}) {
	if level <= LevelTrace {
		logSplitFunc()
		SimLogger.Output(2, LevelTrace.String()+fmt.Sprint(v...))
	}
}

func DebugFunc(v ...interface{}) {
	if level <= LevelDebug {
		logSplitFunc()
		SimLogger.Output(2, LevelDebug.String()+fmt.Sprint(v...))
	}
}

func InfoFunc(v ...interface{}) {
	if level <= LevelInfo {
		logSplitFunc()
		SimLogger.Output(2, LevelInfo.String()+fmt.Sprint(v...))
	}
}

func WarnFunc(v ...interface{}) {
	if level <= LevelWarning {
		logSplitFunc()
		SimLogger.Output(2, LevelWarning.String()+fmt.Sprint(v...))
	}
}

func ErrorFunc(v ...interface{}) {
	if level <= LevelError {
		logSplitFunc()
		SimLogger.Output(2, LevelError.String()+fmt.Sprint(v...))
	}
}

func CriticalFunc(v ...interface{}) {
	if level <= LevelCritical {
		logSplitFunc()
		SimLogger.Output(2, LevelCritical.String()+fmt.Sprint(v...))
	}
}

// Trace logs a message at trace level.
func LogTrace(format string) {
	if level <= LevelTrace {
		SimLogger.Printf(LevelTrace.String() + format)
	}
}

// Debug logs a message at debug level.
func LogDebug(format string, skips ...int) {
	if level <= LevelDebug {
		logPrintf(LevelDebug.String()+format, skips...)
	}
}

// Info logs a message at info level.
func LogInfo(format string, skips ...int) {
	if level <= LevelInfo {
		logPrintf(LevelInfo.String()+format, skips...)
	}
}

// Warning logs a message at warning level.
func LogWarn(format string, skips ...int) {
	if level <= LevelWarning {
		logPrintf(LevelWarning.String()+format, skips...)
	}
}

// Error logs a message at error level.
func LogError(format string, skips ...int) {
	if level <= LevelError {
		logPrintf(LevelError.String()+format, skips...)
	}
}

// Critical logs a message at critical level.
func LogCritical(format string, skips ...int) {
	if level <= LevelCritical {
		logPrintf(LevelCritical.String()+format, skips...)
	}
}

func logPrintf(format string, v ...int) {
	logSplitFunc()
	skip := 3
	if len(v) > 0 {
		skip = v[0]
	}
	SimLogger.Output(skip, format)
}

func logSplit() {
	logfilelock.Lock()
	defer logfilelock.Unlock()
	logcnt++
	if logcnt > MAXLOGCNT {
		changelogindex(1)
		logcnt = 0
	}
}
<commit_msg>mod LevelName<commit_after>package simlog

// from beego

import (
	"fmt"
	"log"
)

//--------------------
// LOG LEVEL
//--------------------

// Log levels to control the logging output.
type Level int

const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarning
	LevelError
	LevelCritical
)

var (
	levelName    = []string{"[TRACE]", "[DEBUG]", "[INFO]", "[WARN]", "[ERROR]", "[CRITICAL]"}
	level        = LevelTrace //日志等级，传参设定
	SimLogger    *log.Logger
	logSplitFunc func()                 = func() {}
	Trace        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelTrace, v...) }
	Debug        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelDebug, v...) }
	Info         func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelInfo, v...) }
	Warn         func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelWarning, v...) }
	Error        func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelError, v...) }
	Critical     func(v ...interface{}) = func(v ...interface{}) { PrintFunc(LevelCritical, v...) }
)

func (this Level) String() string {
	return levelName[this]
}

// LogLevel returns the global log level and can be used in
// own implementations of the logger interface.
func LogLevel() Level {
	return level
}

// SetLogLevel sets the global log level used by the simple
// logger.
func SetLogLevel(l Level) {
	level = l
}

// logger references the used application logger.
//var NettaoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)

// SetLogger sets a new logger.
func SetLogger(l *log.Logger) {
	SimLogger = l
}

func SetSplit(maxsize, maxindex int) {
	setLogSplit(maxsize, maxindex)
	logSplitFunc = logSplit
}

func Init(path string, maxday int, loglevel Level) {
	logInit(path, maxday, loglevel)
	Trace = TraceFunc
	Debug = DebugFunc
	Info = InfoFunc
	Warn = WarnFunc
	Error = ErrorFunc
	Critical = CriticalFunc
}

func Close() {
	logClose()
}

func PrintFunc(loglevel Level, v ...interface{}) {
	if level <= loglevel {
		log.Println(loglevel.String() + fmt.Sprint(v...))
	}
}

func TraceFunc(v ...interface{}) {
	if level <= LevelTrace {
		logSplitFunc()
		SimLogger.Output(2, LevelTrace.String()+fmt.Sprint(v...))
	}
}

func DebugFunc(v ...interface{}) {
	if level <= LevelDebug {
		logSplitFunc()
		SimLogger.Output(2, LevelDebug.String()+fmt.Sprint(v...))
	}
}

func InfoFunc(v ...interface{}) {
	if level <= LevelInfo {
		logSplitFunc()
		SimLogger.Output(2, LevelInfo.String()+fmt.Sprint(v...))
	}
}

func WarnFunc(v ...interface{}) {
	if level <= LevelWarning {
		logSplitFunc()
		SimLogger.Output(2, LevelWarning.String()+fmt.Sprint(v...))
	}
}

func ErrorFunc(v ...interface{}) {
	if level <= LevelError {
		logSplitFunc()
		SimLogger.Output(2, LevelError.String()+fmt.Sprint(v...))
	}
}

func CriticalFunc(v ...interface{}) {
	if level <= LevelCritical {
		logSplitFunc()
		SimLogger.Output(2, LevelCritical.String()+fmt.Sprint(v...))
	}
}

// Trace logs a message at trace level.
func LogTrace(format string) {
	if level <= LevelTrace {
		SimLogger.Printf(LevelTrace.String() + format)
	}
}

// Debug logs a message at debug level.
func LogDebug(format string, skips ...int) {
	if level <= LevelDebug {
		logPrintf(LevelDebug.String()+format, skips...)
	}
}

// Info logs a message at info level.
func LogInfo(format string, skips ...int) {
	if level <= LevelInfo {
		logPrintf(LevelInfo.String()+format, skips...)
	}
}

// Warning logs a message at warning level.
func LogWarn(format string, skips ...int) {
	if level <= LevelWarning {
		logPrintf(LevelWarning.String()+format, skips...)
	}
}

// Error logs a message at error level.
func LogError(format string, skips ...int) {
	if level <= LevelError {
		logPrintf(LevelError.String()+format, skips...)
	}
}

// Critical logs a message at critical level.
func LogCritical(format string, skips ...int) {
	if level <= LevelCritical {
		logPrintf(LevelCritical.String()+format, skips...)
	}
}

func logPrintf(format string, v ...int) {
	logSplitFunc()
	skip := 3
	if len(v) > 0 {
		skip = v[0]
	}
	SimLogger.Output(skip, format)
}

func logSplit() {
	logfilelock.Lock()
	defer logfilelock.Unlock()
	logcnt++
	if logcnt > MAXLOGCNT {
		changelogindex(1)
		logcnt = 0
	}
}
<|endoftext|>


<commit_before>package dnsr

import (
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{},
		cache:  cache,
	}
	r.cacheRoot()
	return r
}

func (r *Resolver) Resolve(qname string, qtype uint16) <-chan dns.RR {
	c := make(chan dns.RR, 20)
	go func() {
		qname = toLowerFQDN(qname)
		defer close(c)
		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}
		pname, ok := qname, true
		if qtype == dns.TypeNS {
			pname, ok = parent(qname)
		}
	outer:
		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.Resolve(pname, dns.TypeNS) {
				ns, ok := nrr.(*dns.NS)
				if !ok {
					continue
				}
				for arr := range r.Resolve(ns.Ns, dns.TypeA) {
					a, ok := arr.(*dns.A)
					if !ok {
						continue
					}
					addr := a.A.String() + ":53"
					qmsg := &dns.Msg{}
					qmsg.SetQuestion(qname, qtype)
					qmsg.MsgHdr.RecursionDesired = false
					// fmt.Printf(";; dig +norecurse @%s %s %s\n", a.A.String(), qname, dns.TypeToString[qtype])
					rmsg, _, err := r.client.Exchange(qmsg, addr)
					if err != nil {
						continue // FIXME: handle errors better from flaky/failing NS servers
					}
					r.cacheSave(rmsg.Answer...)
					r.cacheSave(rmsg.Ns...)
					r.cacheSave(rmsg.Extra...)
					if rmsg.Rcode == dns.RcodeNameError {
						r.cacheAdd(qname, qtype) // FIXME: cache NXDOMAIN responses responsibly
						return
					}
					break outer
				}
			}
		}

		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}

		for _, crr := range r.cacheGet(qname, dns.TypeCNAME) {
			cn, ok := crr.(*dns.CNAME)
			if !ok {
				continue
			}
			r.cacheAdd(qname, qtype, crr)
			for rr := range r.Resolve(cn.Target, qtype) {
				r.cacheAdd(qname, qtype, rr)
				c <- rr
			}
		}
	}()
	return c
}

func inject(c chan<- dns.RR, rrs ...dns.RR) {
	for _, rr := range rrs {
		c <- rr
	}
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

type key struct {
	qname string
	qtype uint16
}

type entry struct {
	m   sync.RWMutex
	exp time.Time
	rrs map[string]dns.RR
}

func (r *Resolver) cacheRoot() {
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			r.cacheSave(t.RR)
		}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype uint16) []dns.RR {
	e := r.getEntry(qname, qtype)
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	rrs := make([]dns.RR, 0, len(e.rrs))
	for _, rr := range e.rrs {
		rrs = append(rrs, rr)
	}
	return rrs
}

// cacheSave saves 1 or more DNS records to the resolver cache.
func (r *Resolver) cacheSave(rrs ...dns.RR) {
	for _, rr := range rrs {
		h := rr.Header()
		r.cacheAdd(h.Name, h.Rrtype, rr)
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, qtype uint16, rrs ...dns.RR) {
	qname = toLowerFQDN(qname)
	now := time.Now()
	e := r.getEntry(qname, qtype)
	if e == nil {
		e = &entry{
			exp: now.Add(24 * time.Hour),
			rrs: make(map[string]dns.RR, 0),
		}
		r.cache.Add(key{qname, qtype}, e)
	}
	e.m.Lock()
	defer e.m.Unlock()
	for _, rr := range rrs {
		h := rr.Header()
		if h.Rrtype != qtype {
			continue
		}
		e.rrs[rr.String()] = rr
		exp := now.Add(time.Duration(h.Ttl) * time.Second)
		if exp.Before(e.exp) {
			e.exp = exp
		}
	}
}

func (r *Resolver) getEntry(qname string, qtype uint16) *entry {
	c, ok := r.cache.Get(key{qname, qtype})
	if !ok {
		return nil
	}
	e := c.(*entry)
	if time.Now().After(e.exp) {
		return nil
	}
	return e
}
<commit_msg>always ask for an A record; the server will respond with NS records<commit_after>package dnsr

import (
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{},
		cache:  cache,
	}
	r.cacheRoot()
	return r
}

func (r *Resolver) Resolve(qname string, qtype uint16) <-chan dns.RR {
	c := make(chan dns.RR, 20)
	go func() {
		qname = toLowerFQDN(qname)
		defer close(c)
		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}
		pname, ok := qname, true
		if qtype == dns.TypeNS {
			pname, ok = parent(qname)
		}
	outer:
		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.Resolve(pname, dns.TypeNS) {
				ns, ok := nrr.(*dns.NS)
				if !ok {
					continue
				}
				for arr := range r.Resolve(ns.Ns, dns.TypeA) {
					a, ok := arr.(*dns.A)
					if !ok {
						continue
					}
					addr := a.A.String() + ":53"
					qmsg := &dns.Msg{}
					qmsg.SetQuestion(qname, dns.TypeA)
					qmsg.MsgHdr.RecursionDesired = false
					// fmt.Printf(";; dig +norecurse @%s %s %s\n", a.A.String(), qname, dns.TypeToString[qtype])
					rmsg, _, err := r.client.Exchange(qmsg, addr)
					if err != nil {
						continue // FIXME: handle errors better from flaky/failing NS servers
					}
					r.cacheSave(rmsg.Answer...)
					r.cacheSave(rmsg.Ns...)
					r.cacheSave(rmsg.Extra...)
					if rmsg.Rcode == dns.RcodeNameError {
						r.cacheAdd(qname, qtype) // FIXME: cache NXDOMAIN responses responsibly
						return
					}
					break outer
				}
			}
		}

		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}

		for _, crr := range r.cacheGet(qname, dns.TypeCNAME) {
			cn, ok := crr.(*dns.CNAME)
			if !ok {
				continue
			}
			r.cacheAdd(qname, qtype, crr)
			for rr := range r.Resolve(cn.Target, qtype) {
				r.cacheAdd(qname, qtype, rr)
				c <- rr
			}
		}
	}()
	return c
}

func inject(c chan<- dns.RR, rrs ...dns.RR) {
	for _, rr := range rrs {
		c <- rr
	}
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

type key struct {
	qname string
	qtype uint16
}

type entry struct {
	m   sync.RWMutex
	exp time.Time
	rrs map[string]dns.RR
}

func (r *Resolver) cacheRoot() {
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			r.cacheSave(t.RR)
		}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype uint16) []dns.RR {
	e := r.getEntry(qname, qtype)
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	rrs := make([]dns.RR, 0, len(e.rrs))
	for _, rr := range e.rrs {
		rrs = append(rrs, rr)
	}
	return rrs
}

// cacheSave saves 1 or more DNS records to the resolver cache.
func (r *Resolver) cacheSave(rrs ...dns.RR) {
	for _, rr := range rrs {
		h := rr.Header()
		r.cacheAdd(h.Name, h.Rrtype, rr)
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, qtype uint16, rrs ...dns.RR) {
	qname = toLowerFQDN(qname)
	now := time.Now()
	e := r.getEntry(qname, qtype)
	if e == nil {
		e = &entry{
			exp: now.Add(24 * time.Hour),
			rrs: make(map[string]dns.RR, 0),
		}
		r.cache.Add(key{qname, qtype}, e)
	}
	e.m.Lock()
	defer e.m.Unlock()
	for _, rr := range rrs {
		h := rr.Header()
		if h.Rrtype != qtype {
			continue
		}
		e.rrs[rr.String()] = rr
		exp := now.Add(time.Duration(h.Ttl) * time.Second)
		if exp.Before(e.exp) {
			e.exp = exp
		}
	}
}

func (r *Resolver) getEntry(qname string, qtype uint16) *entry {
	c, ok := r.cache.Get(key{qname, qtype})
	if !ok {
		return nil
	}
	e := c.(*entry)
	if time.Now().After(e.exp) {
		return nil
	}
	return e
}
<|endoftext|>


<commit_before>// Package appsettings provides simple key/value store functionality designed
// to be used for easily storing and recalling application runtime settings
package appsettings

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strconv"
	"sync"
)

// DataTree is the host of key and branches of values
type DataTree interface {
	GetString(key string) (string, error)
	SetString(key string, val string)
	GetInt(key string) (int, error)
	SetInt(key string, val int)
	GetInt64(key string) (int64, error)
	SetInt64(key string, val int64)
	Delete(key string)
	DeleteTree(key string)
	GetTree(key string) DataTree

	HasTree(key string) bool
	HasLeaf(key string) bool

	GetTrees() map[string]*tree
	GetLeaves() map[string]string
}

type tree struct {
	Branches map[string]*tree
	Leaves   map[string]string

	sync.Mutex
}

// AppSettings is the root most DataTree
type AppSettings struct {
	filename string
	pretty   bool

	*tree
}

// Option sets an option of the passed JqMux
type Option func(*AppSettings)

// OptionPrettyPrint configures AppSettings to pretty print the saved json
func OptionPrettyPrint(app *AppSettings) {
	app.pretty = true
}

// NewAppSettings gets a new AppSettings struct
func NewAppSettings(dbFilename string, options ...Option) (*AppSettings, error) {
	a := &AppSettings{
		filename: dbFilename,
		pretty:   false,

		tree: &tree{
			Branches: make(map[string]*tree),
			Leaves:   make(map[string]string),
		},
	}

	for _, option := range options {
		option(a)
	}

	if _, err := os.Stat(dbFilename); os.IsNotExist(err) {
		d1, _ := json.Marshal(a)
		err := ioutil.WriteFile(dbFilename, d1, 0644)
		if err != nil {
			return nil, err
		}
	} else {
		d1, err := ioutil.ReadFile(dbFilename)
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(d1, a)
		if err != nil {
			return nil, err
		}
	}

	return a, nil
}

// ErrUndefinedKey is returned when the key requested from get is undefined.
var ErrUndefinedKey = errors.New("undefined key")

// GetString gets the give keys leaf as a string.
//
// Returns an ErrUndefinedKey if the key is not defined
func (a *tree) GetString(key string) (string, error) {
	a.Lock()
	defer a.Unlock()

	if _, ok := a.Leaves[key]; !ok {
		return "", ErrUndefinedKey
	}

	return a.Leaves[key], nil
}

// SetString sets the give key leaf as a string.
func (a *tree) SetString(key string, val string) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = val
}

// GetInt gets the give keys leaf as an int.
//
// Returns an ErrUndefinedKey if the key is not defined.
//
// Will return an error if ParseInt of the value of the leaf fails.
func (a *tree) GetInt(key string) (int, error) {
	str, err := a.GetString(key)
	if err != nil {
		return 0, err
	}

	i, err := strconv.Atoi(str)
	if err != nil {
		return 0, err
	}
	return i, nil
}

// SetInt sets the give key leaf as a int.
func (a *tree) SetInt(key string, val int) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = strconv.Itoa(val)
}

// GetInt64 gets the give keys leaf as an int64.
//
// Returns an ErrUndefinedKey if the key is not defined.
//
// Will return an error if ParseInt(...,10, 64) of the value of the leaf fails.
func (a *tree) GetInt64(key string) (int64, error) {
	str, err := a.GetString(key)
	if err != nil {
		return 0, err
	}

	i, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return 0, err
	}
	return i, nil
}

// SetInt64 sets the give key leaf as a int64.
func (a *tree) SetInt64(key string, val int64) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = strconv.FormatInt(val, 10)
}

// Delete removes the given leaf from the branch.
func (a *tree) Delete(key string) {
	a.Lock()
	defer a.Unlock()

	delete(a.Leaves, key)
}

// Delete removes the given tree from the branch.
func (a *tree) DeleteTree(key string) {
	a.Lock()
	defer a.Unlock()

	delete(a.Branches, key)
}

func (a *tree) GetLeaves() map[string]string {
	return a.Leaves
}

func (a *tree) GetTrees() map[string]*tree {
	return a.Branches
}

// GetTree fetches a tree for app setting storage
func (a *tree) GetTree(key string) DataTree {
	a.Lock()
	defer a.Unlock()

	if _, ok := a.Branches[key]; !ok {
		a.Branches[key] = &tree{
			Branches: make(map[string]*tree),
			Leaves:   make(map[string]string),
		}
	}

	return a.Branches[key]
}

// HasTree checks if the given key is defined as a tree
func (a *tree) HasTree(key string) bool {
	a.Lock()
	defer a.Unlock()

	_, ok := a.Branches[key]
	return ok
}

// HasTree checks if the given key is defined as a leaf value
func (a *tree) HasLeaf(key string) bool {
	a.Lock()
	defer a.Unlock()

	_, ok := a.Leaves[key]
	return ok
}

// Persist causes the current state of the app settings to be persisted.
func (a *AppSettings) Persist() error {
	a.Lock()
	defer a.Unlock()

	var err error
	var d1 []byte

	if a.pretty {
		d1, err = json.MarshalIndent(a, "", "\t")
	} else {
		d1, err = json.Marshal(a)
	}
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(a.filename, d1, 0644)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Corrects typo<commit_after>// Package appsettings provides simple key/value store functionality designed
// to be used for easily storing and recalling application runtime settings
package appsettings

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strconv"
	"sync"
)

// DataTree is the host of key and branches of values
type DataTree interface {
	GetString(key string) (string, error)
	SetString(key string, val string)
	GetInt(key string) (int, error)
	SetInt(key string, val int)
	GetInt64(key string) (int64, error)
	SetInt64(key string, val int64)
	Delete(key string)
	DeleteTree(key string)
	GetTree(key string) DataTree

	HasTree(key string) bool
	HasLeaf(key string) bool

	GetTrees() map[string]*tree
	GetLeaves() map[string]string
}

type tree struct {
	Branches map[string]*tree
	Leaves   map[string]string

	sync.Mutex
}

// AppSettings is the root most DataTree
type AppSettings struct {
	filename string
	pretty   bool

	*tree
}

// Option sets an option of the passed AppSettings
type Option func(*AppSettings)

// OptionPrettyPrint configures AppSettings to pretty print the saved json
func OptionPrettyPrint(app *AppSettings) {
	app.pretty = true
}

// NewAppSettings gets a new AppSettings struct
func NewAppSettings(dbFilename string, options ...Option) (*AppSettings, error) {
	a := &AppSettings{
		filename: dbFilename,
		pretty:   false,

		tree: &tree{
			Branches: make(map[string]*tree),
			Leaves:   make(map[string]string),
		},
	}

	for _, option := range options {
		option(a)
	}

	if _, err := os.Stat(dbFilename); os.IsNotExist(err) {
		d1, _ := json.Marshal(a)
		err := ioutil.WriteFile(dbFilename, d1, 0644)
		if err != nil {
			return nil, err
		}
	} else {
		d1, err := ioutil.ReadFile(dbFilename)
		if err != nil {
			return nil, err
		}

		err = json.Unmarshal(d1, a)
		if err != nil {
			return nil, err
		}
	}

	return a, nil
}

// ErrUndefinedKey is returned when the key requested from get is undefined.
var ErrUndefinedKey = errors.New("undefined key")

// GetString gets the give keys leaf as a string.
//
// Returns an ErrUndefinedKey if the key is not defined
func (a *tree) GetString(key string) (string, error) {
	a.Lock()
	defer a.Unlock()

	if _, ok := a.Leaves[key]; !ok {
		return "", ErrUndefinedKey
	}

	return a.Leaves[key], nil
}

// SetString sets the give key leaf as a string.
func (a *tree) SetString(key string, val string) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = val
}

// GetInt gets the give keys leaf as an int.
//
// Returns an ErrUndefinedKey if the key is not defined.
//
// Will return an error if ParseInt of the value of the leaf fails.
func (a *tree) GetInt(key string) (int, error) {
	str, err := a.GetString(key)
	if err != nil {
		return 0, err
	}

	i, err := strconv.Atoi(str)
	if err != nil {
		return 0, err
	}
	return i, nil
}

// SetInt sets the give key leaf as a int.
func (a *tree) SetInt(key string, val int) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = strconv.Itoa(val)
}

// GetInt64 gets the give keys leaf as an int64.
//
// Returns an ErrUndefinedKey if the key is not defined.
//
// Will return an error if ParseInt(...,10, 64) of the value of the leaf fails.
func (a *tree) GetInt64(key string) (int64, error) {
	str, err := a.GetString(key)
	if err != nil {
		return 0, err
	}

	i, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return 0, err
	}
	return i, nil
}

// SetInt64 sets the give key leaf as a int64.
func (a *tree) SetInt64(key string, val int64) {
	a.Lock()
	defer a.Unlock()

	a.Leaves[key] = strconv.FormatInt(val, 10)
}

// Delete removes the given leaf from the branch.
func (a *tree) Delete(key string) {
	a.Lock()
	defer a.Unlock()

	delete(a.Leaves, key)
}

// Delete removes the given tree from the branch.
func (a *tree) DeleteTree(key string) {
	a.Lock()
	defer a.Unlock()

	delete(a.Branches, key)
}

func (a *tree) GetLeaves() map[string]string {
	return a.Leaves
}

func (a *tree) GetTrees() map[string]*tree {
	return a.Branches
}

// GetTree fetches a tree for app setting storage
func (a *tree) GetTree(key string) DataTree {
	a.Lock()
	defer a.Unlock()

	if _, ok := a.Branches[key]; !ok {
		a.Branches[key] = &tree{
			Branches: make(map[string]*tree),
			Leaves:   make(map[string]string),
		}
	}

	return a.Branches[key]
}

// HasTree checks if the given key is defined as a tree
func (a *tree) HasTree(key string) bool {
	a.Lock()
	defer a.Unlock()

	_, ok := a.Branches[key]
	return ok
}

// HasTree checks if the given key is defined as a leaf value
func (a *tree) HasLeaf(key string) bool {
	a.Lock()
	defer a.Unlock()

	_, ok := a.Leaves[key]
	return ok
}

// Persist causes the current state of the app settings to be persisted.
func (a *AppSettings) Persist() error {
	a.Lock()
	defer a.Unlock()

	var err error
	var d1 []byte

	if a.pretty {
		d1, err = json.MarshalIndent(a, "", "\t")
	} else {
		d1, err = json.Marshal(a)
	}
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(a.filename, d1, 0644)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "http://174.129.255.150:4873"
	node.NodeVersion = "1.6.4"
	node.NpmVersion = "2.7.5"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		lock := getUpdateLock()
		defer lock.Unlock()
		Log("setting up plugins... ")
		if err := node.Setup(); err != nil {
			panic(err)
		}
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errln("WARNING: command %s has already been defined", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		if err := node.InstallPackage(name); err != nil {
			panic(err)
		}
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			if err := (node.RemovePackage(name)); err != nil {
				panic(err)
			}
		}
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		if err := node.RemovePackage(name); err != nil {
			panic(err)
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		var topic = '%s';
		var command = '%s';
		if (command === '') { command = null }
		require('%s')
		.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0]
		.run(%s)`, topic, command, module, ctxJSON)

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			Exit(getExitCode(err))
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	if err != nil {
		panic(err)
	}
	if err := cmd.Start(); err != nil {
		panic(err)
	}
	buf := new(bytes.Buffer)
	buf.ReadFrom(output)
	s := buf.String()
	var plugin Plugin
	err = json.Unmarshal(buf.Bytes(), &plugin)
	if err != nil {
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", s)
		return nil
	}
	if err := cmd.Wait(); err != nil {
		panic(err)
	}
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<commit_msg>fix warning output for duplicate commands<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "http://174.129.255.150:4873"
	node.NodeVersion = "1.6.4"
	node.NpmVersion = "2.7.5"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		lock := getUpdateLock()
		defer lock.Unlock()
		Log("setting up plugins... ")
		if err := node.Setup(); err != nil {
			panic(err)
		}
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		if err := node.InstallPackage(name); err != nil {
			panic(err)
		}
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			if err := (node.RemovePackage(name)); err != nil {
				panic(err)
			}
		}
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		if err := node.RemovePackage(name); err != nil {
			panic(err)
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		var topic = '%s';
		var command = '%s';
		if (command === '') { command = null }
		require('%s')
		.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0]
		.run(%s)`, topic, command, module, ctxJSON)

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			Exit(getExitCode(err))
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	if err != nil {
		panic(err)
	}
	if err := cmd.Start(); err != nil {
		panic(err)
	}
	buf := new(bytes.Buffer)
	buf.ReadFrom(output)
	s := buf.String()
	var plugin Plugin
	err = json.Unmarshal(buf.Bytes(), &plugin)
	if err != nil {
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", s)
		return nil
	}
	if err := cmd.Wait(); err != nil {
		panic(err)
	}
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run `npm install`?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if len(plugin.Commands) > 0 {
				Println(plugin.Name, plugin.Version)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for _, plugin := range plugins {
		for _, command := range plugin.Commands {
			command.Run = runFn(plugin, command.Topic, command.Command)
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		names = append(names, plugin.Name)
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	plugins := make([]*Plugin, 0, len(pluginNames))
	for _, name := range pluginNames {
		plugins = append(plugins, getPlugin(name, false))
	}
	AddPluginsToCache(plugins...)
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(plugins ...string) error {
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(plugins...)
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}
<commit_msg>simplify plugins:link output<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run `npm install`?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if len(plugin.Commands) > 0 {
				Println(plugin.Name, plugin.Version)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for _, plugin := range plugins {
		for _, command := range plugin.Commands {
			command.Run = runFn(plugin, command.Topic, command.Command)
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		names = append(names, plugin.Name)
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	plugins := make([]*Plugin, 0, len(pluginNames))
	for _, name := range pluginNames {
		plugins = append(plugins, getPlugin(name, false))
	}
	AddPluginsToCache(plugins...)
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(plugins ...string) error {
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(plugins...)
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/dickeyxxx/golock"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "https://d3nfsbmspisrno.cloudfront.net"
	node.NodeVersion = "2.0.1"
	node.NpmVersion = "2.9.0"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		golock.Lock(updateLockPath)
		defer golock.Unlock(updateLockPath)
		Debugln("setting up iojs", node.NodeVersion)
		ExitIfError(node.Setup())
		clearOldNodeInstalls()
	}
}

func clearOldNodeInstalls() {
	files, _ := ioutil.ReadDir(AppDir)
	for _, f := range files {
		name := f.Name()
		if name != node.NodeBase() && strings.HasPrefix(name, "iojs-v") {
			os.RemoveAll(filepath.Join(AppDir, name))
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := node.InstallPackage(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(node.RemovePackage(name))
		}
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := node.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var module = require(moduleName);
		var topic = '%s';
		var command = '%s';
		var ctx = %s;
		var logPath = %s;
		process.chdir(ctx.cwd);
		process.on('uncaughtException', function (err) {
			console.error(' !   Error in ' + moduleName + ':')
			if (err.message) {
				console.error(' !   ' + err.message);
			} else {
				console.error(' !   ' + err);
			}
			if (err.stack) {
				var fs = require('fs');
				var log = function (line) {
					var d = new Date().toISOString()
					.replace(/T/, ' ')
					.replace(/-/g, '/')
					.replace(/\..+/, '');
					fs.appendFileSync(logPath, d + ' ' + line + '\n');
				}
				log('Error during ' + topic + ':' + command);
				log(err.stack);
				console.error(' !   See ' + logPath + ' for more info.');
			}
			process.exit(1);
		});
		if (command === '') { command = null }
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, module, topic, command, ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = node.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	plugin.name = require('` + name + `/package.json').name;
	console.log(JSON.stringify(plugin))`
	cmd := node.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := node.InstallPackage(name); err != nil {
				panic(errors.New(string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	cache := FetchPluginCache()
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := cache[name]
		if plugin == nil {
			plugin = getPlugin(name, true)
		}
		if plugin != nil {
			for _, command := range plugin.Commands {
				command.Plugin = name
				command.Run = runFn(name, command.Topic, command.Command)
			}
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		if !ignorePlugin(f.Name()) {
			names = append(names, f.Name())
		}
	}
	return names
}

func ignorePlugin(plugin string) bool {
	ignored := []string{".bin", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}
<commit_msg>append plugin info to user agent<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/dickeyxxx/golock"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "https://d3nfsbmspisrno.cloudfront.net"
	node.NodeVersion = "2.0.1"
	node.NpmVersion = "2.9.0"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		golock.Lock(updateLockPath)
		defer golock.Unlock(updateLockPath)
		Debugln("setting up iojs", node.NodeVersion)
		ExitIfError(node.Setup())
		clearOldNodeInstalls()
	}
}

func clearOldNodeInstalls() {
	files, _ := ioutil.ReadDir(AppDir)
	for _, f := range files {
		name := f.Name()
		if name != node.NodeBase() && strings.HasPrefix(name, "iojs-v") {
			os.RemoveAll(filepath.Join(AppDir, name))
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := node.InstallPackage(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(node.RemovePackage(name))
		}
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := node.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctx.Version = ctx.Version + " " + module + " iojs-v" + node.NodeVersion
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var module = require(moduleName);
		var topic = '%s';
		var command = '%s';
		var ctx = %s;
		var logPath = %s;
		process.chdir(ctx.cwd);
		process.on('uncaughtException', function (err) {
			console.error(' !   Error in ' + moduleName + ':')
			if (err.message) {
				console.error(' !   ' + err.message);
			} else {
				console.error(' !   ' + err);
			}
			if (err.stack) {
				var fs = require('fs');
				var log = function (line) {
					var d = new Date().toISOString()
					.replace(/T/, ' ')
					.replace(/-/g, '/')
					.replace(/\..+/, '');
					fs.appendFileSync(logPath, d + ' ' + line + '\n');
				}
				log('Error during ' + topic + ':' + command);
				log(err.stack);
				console.error(' !   See ' + logPath + ' for more info.');
			}
			process.exit(1);
		});
		if (command === '') { command = null }
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, module, topic, command, ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = node.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	plugin.name = require('` + name + `/package.json').name;
	console.log(JSON.stringify(plugin))`
	cmd := node.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := node.InstallPackage(name); err != nil {
				panic(errors.New(string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	cache := FetchPluginCache()
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := cache[name]
		if plugin == nil {
			plugin = getPlugin(name, true)
		}
		if plugin != nil {
			for _, command := range plugin.Commands {
				command.Plugin = name
				command.Run = runFn(name, command.Topic, command.Command)
			}
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		if !ignorePlugin(f.Name()) {
			names = append(names, f.Name())
		}
	}
	return names
}

func ignorePlugin(plugin string) bool {
	ignored := []string{".bin", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

/*
// go preamble
#cgo pkg-config: mxnet
#include <mxnet/c_predict_api.h>
#include <stdlib.h>
*/
import "C"
import (
	"fmt"
	"unsafe"
)

type InputNode struct {
	Key   string
	Shape []uint32
}

func MXPredCreate(symbol []byte,
	params []byte,
	devType int, devId int,
	nodes []InputNode,
) {

	var (
		pc *C.char
		shapeIdx  = []uint32{0}
		shapeData = []uint32{}
	)

	// malloc for **char which like [][]string to store node keys
	keys := C.malloc(C.size_t(len(nodes)) * C.size_t(unsafe.Sizeof(pc)))
	// free it by ourself, go gc won't do that for us
	defer C.free(unsafe.Pointer(keys))
	for i := 0; i < len(nodes); i++ {
		p := (**C.char)(unsafe.Pointer(uintptr(keys) + uintptr(i)*unsafe.Sizeof(pc)))
		*p = C.CString(nodes[i].Key)

		shapeIdx = append(shapeIdx, uint32(len(nodes[i].Shape)))
		shapeData = append(shapeData, nodes[i].Shape...)
	}
	var handle C.PredictorHandle

	success, err := C.MXPredCreate((*C.char)(unsafe.Pointer(&symbol[0])),
		(*C.char)(unsafe.Pointer(&params[0])),
		C.int(len(params)),
		C.int(devType),
		C.int(devId),
		C.mx_uint(len(nodes)),
		(**C.char)(keys),
		(*C.mx_uint)(&shapeIdx[0]),
		(*C.mx_uint)(&shapeData[0]),
		handle,
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if success < 0 {
		return
	}
	fmt.Println(handle)
}

func main() {
	fmt.Println("fsaf")
}
<commit_msg>free mem we created<commit_after>package main

/*
// go preamble
#cgo pkg-config: mxnet
#include <mxnet/c_predict_api.h>
#include <stdlib.h>
*/
import "C"
import (
	"fmt"
	"unsafe"
)

type InputNode struct {
	Key   string
	Shape []uint32
}

func MXPredCreate(symbol []byte,
	params []byte,
	devType int, devId int,
	nodes []InputNode,
) {

	var (
		pc *C.char
		shapeIdx  = []uint32{0}
		shapeData = []uint32{}
	)

	// malloc for **char which like [][]string to store node keys
	keys := C.malloc(C.size_t(len(nodes)) * C.size_t(unsafe.Sizeof(pc)))
	for i := 0; i < len(nodes); i++ {
		p := (**C.char)(unsafe.Pointer(uintptr(keys) + uintptr(i)*unsafe.Sizeof(pc)))
		*p = C.CString(nodes[i].Key)

		shapeIdx = append(shapeIdx, uint32(len(nodes[i].Shape)))
		shapeData = append(shapeData, nodes[i].Shape...)
	}
	var handle C.PredictorHandle

	success, err := C.MXPredCreate((*C.char)(unsafe.Pointer(&symbol[0])),
		(*C.char)(unsafe.Pointer(&params[0])),
		C.int(len(params)),
		C.int(devType),
		C.int(devId),
		C.mx_uint(len(nodes)),
		(**C.char)(keys),
		(*C.mx_uint)(&shapeIdx[0]),
		(*C.mx_uint)(&shapeData[0]),
		handle,
	)

	// free mem we created, go gc won't do that for us
	defer C.free(unsafe.Pointer(keys))
	for i := 0; i < len(nodes);i++ {
		p := (**C.char)(unsafe.Pointer(uintptr(keys) + uintptr(i)*unsafe.Sizeof(pc)))
		C.free(unsafe.Pointer(*p))
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	if success < 0 {
		return
	}
	fmt.Println(handle)
}

func main() {
	fmt.Println("fsaf")
}
<|endoftext|>


<commit_before>package errhtml

import (
	. "github.com/101loops/bdd"
	"path/filepath"
)

var _ = Describe("Source", func() {

	var cwd string
	var absFile = func(path string) string { return filepath.Join(cwd, path) }

	BeforeSuite(func() {
		cwd = "/Users/gopher/workspace/src/host/vendor/library"
	})

	It("return file name", func() {
		src := source{File: absFile("file.go")}
		Check(src.FileName(), Equals, "file.go")
	})

	Context("return file location when", func() {
		It("in GOROOT", func() {
			loc := fileLocationFromPath(absFile("/src/pkg/runtime/panic.c"), envConf{GoRoot: cwd})

			Check(loc, Equals, &fileLocation{"GOROOT", "/runtime", "panic.c"})
			Check(loc.String(), Equals, "<GOROOT>/runtime/panic.c")
		})

		It("in GOPATH", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{GoPaths: []string{"nonsense", "/Users/gopher/workspace"}})

			Check(loc, Equals, &fileLocation{"GOPATH", "/host/vendor/library", "file.go"})
			Check(loc.String(), Equals, "<GOPATH>/host/vendor/library/file.go")
		})

		It("in working directory", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{CurrentDir: cwd})

			Check(loc, Equals, &fileLocation{"CWD", "/", "file.go"})
			Check(loc.String(), Equals, "<CWD>/file.go")
		})

		It("standalone", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{})

			Check(loc, Equals, &fileLocation{"", cwd, "file.go"})
			Check(loc.String(), Equals, cwd+"/file.go")
		})
	})

})
<commit_msg>added more tests<commit_after>package errhtml

import (
	. "github.com/101loops/bdd"
	"path/filepath"
)

var _ = Describe("Source", func() {

	var cwd string
	var absFile = func(path string) string { return filepath.Join(cwd, path) }

	BeforeSuite(func() {
		cwd = "/Users/gopher/workspace/src/host/vendor/library"
	})

	Context("create", func() {
		It("from file", func() {
			src := NewFileSource("directory/file.go", 42).source

			Check(src.Line, Equals, 42)
			Check(src.File, Equals, "directory/file.go")

			Check(src.FileName(), Equals, "file.go")
			Check(src.AbbreviatedFilePath(), Equals, "directory/file.go")
			Check(src.AbbreviatedFilePathDirectories(), Equals, []string{"directory"})
		})

		It("from text", func() {
			src := NewTextSource("err").source

			Check(src.Text, Equals, "err")
		})
	})

	Context("return file location when", func() {
		It("in GOROOT", func() {
			loc := fileLocationFromPath(absFile("/src/pkg/runtime/panic.c"), envConf{GoRoot: cwd})

			Check(loc, Equals, &fileLocation{"GOROOT", "/runtime", "panic.c"})
			Check(loc.String(), Equals, "<GOROOT>/runtime/panic.c")
		})

		It("in GOPATH", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{GoPaths: []string{"nonsense", "/Users/gopher/workspace"}})

			Check(loc, Equals, &fileLocation{"GOPATH", "/host/vendor/library", "file.go"})
			Check(loc.String(), Equals, "<GOPATH>/host/vendor/library/file.go")
		})

		It("in working directory", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{CurrentDir: cwd})

			Check(loc, Equals, &fileLocation{"CWD", "/", "file.go"})
			Check(loc.String(), Equals, "<CWD>/file.go")
		})

		It("standalone", func() {
			loc := fileLocationFromPath(absFile("file.go"), envConf{})

			Check(loc, Equals, &fileLocation{"", cwd, "file.go"})
			Check(loc.String(), Equals, cwd+"/file.go")
		})
	})

})
<|endoftext|>