<commit_before>// Copyright 2011 Gary Burd
// Copyright 2013 Unknown
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Package doc implemented fetch projects from VCS and genarate AST.
package doc

import (
	"errors"
	"strings"
	"time"

	"github.com/Unknwon/gowalker/models"
	"github.com/astaxie/beego"
)

const (
	// Request type.
	HUMAN_REQUEST = iota
	REFRESH_REQUEST
)

const (
	_TIME_DAY      = 24 * time.Hour   // Time duration of one day.
	_FETCH_TIMEOUT = 10 * time.Second // Fetch package timeout duration.
	_REFRESH_LIMIT = 5 * time.Minute  // Package fresh time limitation.
)

// CheckDoc returns 'Package' by given import path and tag,
// or fetch from the VCS and render as needed.
// It returns error when error occurs in the underlying functions.
func CheckDoc(path, tag string, requestType int) (*Package, error) {
	// Package documentation and crawl sign.
	pdoc, needsCrawl := &Package{}, false

	// Trim prefix of standard library path.
	if i := strings.Index(path, "/src/pkg/"); i > -1 {
		path = path[i+len("/src/pkg/"):]
	}

	// For code.google.com.
	path = strings.Replace(path, "source/browse/", "", 1)

	// Get the package info.
	pinfo, err := models.GetPkgInfo(path, tag)
	switch {
	case err != nil:
		// Error means it does not exist.
		beego.Trace("doc.CheckDoc -> ", err)
		fallthrough
	case err != nil || !strings.HasPrefix(pinfo.Etag, PACKAGE_VER):
		// If PACKAGE_VER does not match, refresh anyway.
		needsCrawl = true
	default:
		// Check request type.
		switch requestType {
		case HUMAN_REQUEST:
		case REFRESH_REQUEST:
			if len(tag) > 0 {
				break // Things of Tag will not be changed.
			}

			// Check if the refresh operation is too frequently (within 5 minutes).
			needsCrawl = pinfo.Created.Add(_REFRESH_LIMIT).UTC().Before(time.Now().UTC())
			needsCrawl = true
			if !needsCrawl {
				// Return limit time information as error message.
				return nil, errors.New(pinfo.Created.Add(_REFRESH_LIMIT).UTC().String())
			}
		}
	}

	if needsCrawl {
		// Fetch package from VCS.
		c := make(chan crawlResult, 1)
		go func() {
			pdoc, err = crawlDoc(path, tag, pinfo)
			c <- crawlResult{pdoc, err}
		}()

		select {
		case cr := <-c:
			if cr.err == nil {
				pdoc = cr.pdoc
			}
			err = cr.err
		case <-time.After(_FETCH_TIMEOUT):
			err = errUpdateTimeout
		}

		if err == nil {
			pdoc.IsNeedRender = true
		} else {
			switch {
			case err == errNotModified:
				beego.Info("Serving(", path, ")without modified")
				pdoc = &Package{}
				pinfo.Created = time.Now().UTC()
				assginPkgInfo(pdoc, pinfo)
				return pdoc, nil
			case pdoc != nil && len(pdoc.ImportPath) > 0:
				return pdoc, err
			case err == errUpdateTimeout:
				// Handle timeout on packages never seen before as not found.
				beego.Error("Serving(", path, ")as not found after timeout")
				return nil, errors.New("doc.CheckDoc -> " + err.Error())
			}
		}
	} else {
		assginPkgInfo(pdoc, pinfo)
	}

	return pdoc, err
}

func assginPkgInfo(pdoc *Package, pinfo *models.PkgInfo) {
	// Assgin package information
	pdoc.Id = pinfo.Id
	pdoc.ImportPath = pinfo.Path
	pdoc.ProjectName = pinfo.ProName
	pdoc.Synopsis = pinfo.Synopsis
	pdoc.IsCmd = pinfo.IsCmd
	pdoc.Tags = strings.Split(pinfo.Tags, "|||")
	pdoc.Views = pinfo.Views
	pdoc.Created = pinfo.Created
	pdoc.Rank = pinfo.Rank
	pdoc.Etag = pinfo.Etag
	pdoc.Labels = pinfo.Labels
	pdoc.ImportedNum = pinfo.ImportedNum
	pdoc.ImportPid = pinfo.ImportPid
	pdoc.Note = pinfo.Note
}
<commit_msg>Fixed bug: output of examples does not show<commit_after>// Copyright 2011 Gary Burd
// Copyright 2013 Unknown
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Package doc implemented fetch projects from VCS and genarate AST.
package doc

import (
	"errors"
	"strings"
	"time"

	"github.com/Unknwon/gowalker/models"
	"github.com/astaxie/beego"
)

const (
	// Request type.
	HUMAN_REQUEST = iota
	REFRESH_REQUEST
)

const (
	_TIME_DAY      = 24 * time.Hour   // Time duration of one day.
	_FETCH_TIMEOUT = 10 * time.Second // Fetch package timeout duration.
	_REFRESH_LIMIT = 5 * time.Minute  // Package fresh time limitation.
)

// CheckDoc returns 'Package' by given import path and tag,
// or fetch from the VCS and render as needed.
// It returns error when error occurs in the underlying functions.
func CheckDoc(path, tag string, requestType int) (*Package, error) {
	// Package documentation and crawl sign.
	pdoc, needsCrawl := &Package{}, false

	// Trim prefix of standard library path.
	if i := strings.Index(path, "/src/pkg/"); i > -1 {
		path = path[i+len("/src/pkg/"):]
	}

	// For code.google.com.
	path = strings.Replace(path, "source/browse/", "", 1)

	// Get the package info.
	pinfo, err := models.GetPkgInfo(path, tag)
	switch {
	case err != nil:
		// Error means it does not exist.
		beego.Trace("doc.CheckDoc -> ", err)
		fallthrough
	case err != nil || !strings.HasPrefix(pinfo.Etag, PACKAGE_VER):
		// If PACKAGE_VER does not match, refresh anyway.
		needsCrawl = true
	default:
		// Check request type.
		switch requestType {
		case HUMAN_REQUEST:
		case REFRESH_REQUEST:
			if len(tag) > 0 {
				break // Things of Tag will not be changed.
			}

			// Check if the refresh operation is too frequently (within 5 minutes).
			needsCrawl = pinfo.Created.Add(_REFRESH_LIMIT).UTC().Before(time.Now().UTC())
			if !needsCrawl {
				// Return limit time information as error message.
				return nil, errors.New(pinfo.Created.Add(_REFRESH_LIMIT).UTC().String())
			}
		}
	}

	if needsCrawl {
		// Fetch package from VCS.
		c := make(chan crawlResult, 1)
		go func() {
			pdoc, err = crawlDoc(path, tag, pinfo)
			c <- crawlResult{pdoc, err}
		}()

		select {
		case cr := <-c:
			if cr.err == nil {
				pdoc = cr.pdoc
			}
			err = cr.err
		case <-time.After(_FETCH_TIMEOUT):
			err = errUpdateTimeout
		}

		if err == nil {
			pdoc.IsNeedRender = true
		} else {
			switch {
			case err == errNotModified:
				beego.Info("Serving(", path, ")without modified")
				pdoc = &Package{}
				pinfo.Created = time.Now().UTC()
				assginPkgInfo(pdoc, pinfo)
				return pdoc, nil
			case pdoc != nil && len(pdoc.ImportPath) > 0:
				return pdoc, err
			case err == errUpdateTimeout:
				// Handle timeout on packages never seen before as not found.
				beego.Error("Serving(", path, ")as not found after timeout")
				return nil, errors.New("doc.CheckDoc -> " + err.Error())
			}
		}
	} else {
		assginPkgInfo(pdoc, pinfo)
	}

	return pdoc, err
}

func assginPkgInfo(pdoc *Package, pinfo *models.PkgInfo) {
	// Assgin package information
	pdoc.Id = pinfo.Id
	pdoc.ImportPath = pinfo.Path
	pdoc.ProjectName = pinfo.ProName
	pdoc.Synopsis = pinfo.Synopsis
	pdoc.IsCmd = pinfo.IsCmd
	pdoc.Tags = strings.Split(pinfo.Tags, "|||")
	pdoc.Views = pinfo.Views
	pdoc.Created = pinfo.Created
	pdoc.Rank = pinfo.Rank
	pdoc.Etag = pinfo.Etag
	pdoc.Labels = pinfo.Labels
	pdoc.ImportedNum = pinfo.ImportedNum
	pdoc.ImportPid = pinfo.ImportPid
	pdoc.Note = pinfo.Note
}
<|endoftext|>


<commit_before>package bamstats

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"runtime"
	"testing"

	"github.com/guigolab/bamstats/stats"
)

func checkTest(err error, t *testing.T) {
	if err != nil {
		t.Error(err)
	}
}

var (
	bamFile      = "data/process-test.bam"
	expectedJSON = map[string]string{
		"general":      "data/expected-general.json",
		"coverage":     "data/expected-coverage.json",
		"coverageUniq": "data/expected-coverage-uniq.json",
		"rnaseq":       "data/expected-rnaseq.json",
	}
	annotationFiles = []string{"data/coverage-test.bed", "data/coverage-test.gtf.gz", "data/coverage-test-shuffled.bed", "data/coverage-test-shuffled.gtf.gz"}
	maxBuf          = 1000000
	reads           = -1
)

func TestGeneral(t *testing.T) {
	var b bytes.Buffer
	out, err := Process(bamFile, "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l > 1 {
		t.Errorf("(Process) Expected StatsMap of length 1, got %d", l)
	}
	_, ok := out["general"].(*stats.GeneralStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
	}
	out.OutputJSON(&b)
	stats := readStats([]string{"general"}, t)
	// stats := readExpected(expectedGeneralJSON, t)
	if len(b.Bytes()) != len(stats) {
		err := dump(b, "observed-general.json")
		if err != nil {
			t.Errorf("(Process) Debug dump error: %s", err)
		}
		t.Error("(Process) GeneralStats are different")
	}
}

func TestIssue18(t *testing.T) {
	out, err := Process("data/issue18.bam", "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l != 1 {
		t.Errorf("(Process) Expected StatsMap of length 1, got %d", l)
	}
	_, ok := out["general"].(*stats.GeneralStats).Reads.Mapped[1]
	if !ok {
		t.Errorf("(Process) Bad NH tag in read stats")
	}
}

func TestCoverage(t *testing.T) {
	var b bytes.Buffer
	expectedMapLen := 3
	for _, annotationFile := range annotationFiles {
		b.Reset()
		out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
		checkTest(err, t)
		l := len(out)
		if l > expectedMapLen {
			t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
		}
		_, ok := out["general"].(*stats.GeneralStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
		}
		_, ok = out["coverage"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
		}
		stats.NewMap(out["coverage"]).OutputJSON(&b)
		stats := readStats([]string{"coverage"}, t)
		if len(b.Bytes()) != len(stats) {
			err := dump(b, "observed-coverage.json")
			if err != nil {
				t.Errorf("(Process) Debug dump error: %s", err)
			}
			t.Error("(Process) CoverageStats are different")
		}
	}
}

func TestCoverageUniq(t *testing.T) {
	var b bytes.Buffer
	expectedMapLen := 4
	for _, annotationFile := range annotationFiles {
		b.Reset()
		out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, true)
		checkTest(err, t)
		l := len(out)
		if l > expectedMapLen {
			t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
		}
		_, ok := out["general"].(*stats.GeneralStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
		}
		_, ok = out["coverage"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
		}
		_, ok = out["coverageUniq"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverageUniq"])
		}
		stats.NewMap(out["coverageUniq"]).OutputJSON(&b)
		stats := readStats([]string{"coverageUniq"}, t)
		if len(b.Bytes()) != len(stats) {
			err := dump(b, "observed-coverage-uniq.json")
			if err != nil {
				t.Errorf("(Process) Debug dump error: %s", err)
			}
			t.Error("(Process) CoverageStats are different")
		}
	}
}

func TestRNAseq(t *testing.T) {
	var b bytes.Buffer
	bamFile := "data/rnaseq-test.bam"
	annotationFile := "data/rnaseq.gtf.gz"
	expectedMapLen := 3
	out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l > expectedMapLen {
		t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
	}
	_, ok := out["general"].(*stats.GeneralStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
	}
	_, ok = out["coverage"].(*stats.CoverageStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
	}
	_, ok = out["rnaseq"].(*stats.RNAseqStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected RNAseqStats, got %T", out["rnaseq"])
	}
	stats.NewMap(out["rnaseq"]).OutputJSON(&b)
	stats := readStats([]string{"rnaseq"}, t)
	if len(b.Bytes()) != len(stats) {
		err := dump(b, "observed-rnaseq.json")
		if err != nil {
			t.Errorf("(Process) Debug dump error: %s", err)
		}
		t.Error("(Process) RNAseqStats are different")
	}
}

func BenchmarkGeneral(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Process(bamFile, "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	}
}

func BenchmarkCoverage(b *testing.B) {
	for _, annotationFile := range annotationFiles {
		for i := 0; i < b.N; i++ {
			Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
		}
	}
}

func dump(b bytes.Buffer, fname string) error {
	s, err := os.Create(fname)
	if err != nil {
		return err
	}
	defer s.Close()
	_, err = s.Write(b.Bytes())
	return err
}

func readStats(keys []string, t *testing.T) []byte {
	stats := make(map[string]interface{})
	for _, k := range keys {
		stats[k] = readJSON(expectedJSON[k], t)
	}
	b, err := json.MarshalIndent(stats, "", "\t")
	if err != nil {
		return nil
	}
	return b
}

func readJSON(path string, t *testing.T) interface{} {
	b, err := ioutil.ReadFile(path)
	checkTest(err, t)
	var m map[string]interface{}
	json.Unmarshal(b, &m)
	return m
}
<commit_msg>Update process tests<commit_after>package bamstats

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"runtime"
	"testing"

	"github.com/guigolab/bamstats/stats"
)

func checkTest(err error, t *testing.T) {
	if err != nil {
		t.Error(err)
	}
}

var (
	bamFile      = "data/process-test.bam"
	expectedJSON = map[string]string{
		"general":      "data/expected-general.json",
		"coverage":     "data/expected-coverage.json",
		"coverageUniq": "data/expected-coverage-uniq.json",
		"rnaseq":       "data/expected-rnaseq.json",
	}
	expectedMapLenCoverage     = 3
	expectedMapLenCoverageUniq = 4
	annotationFiles            = []string{"data/coverage-test.bed", "data/coverage-test.gtf.gz", "data/coverage-test-shuffled.bed", "data/coverage-test-shuffled.gtf.gz"}
	maxBuf                     = 1000000
	reads                      = -1
)

func TestGeneral(t *testing.T) {
	var b bytes.Buffer
	out, err := Process(bamFile, "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l > 1 {
		t.Errorf("(Process) Expected StatsMap of length 1, got %d", l)
	}
	_, ok := out["general"].(*stats.GeneralStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
	}
	out.OutputJSON(&b)
	stats := readStats([]string{"general"}, t)
	// stats := readExpected(expectedGeneralJSON, t)
	if len(b.Bytes()) != len(stats) {
		err := dump(b, "observed-general.json")
		if err != nil {
			t.Errorf("(Process) Debug dump error: %s", err)
		}
		t.Error("(Process) GeneralStats are different")
	}
}

func TestIssue18(t *testing.T) {
	out, err := Process("data/issue18.bam", "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l != 1 {
		t.Errorf("(Process) Expected StatsMap of length 1, got %d", l)
	}
	_, ok := out["general"].(*stats.GeneralStats).Reads.Mapped[1]
	if !ok {
		t.Errorf("(Process) Bad NH tag in read stats")
	}
}

func TestCoverage(t *testing.T) {
	var b bytes.Buffer
	expectedMapLen := expectedMapLenCoverage
	for _, annotationFile := range annotationFiles {
		b.Reset()
		out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
		checkTest(err, t)
		l := len(out)
		if l > expectedMapLen {
			t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
		}
		_, ok := out["general"].(*stats.GeneralStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
		}
		_, ok = out["coverage"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
		}
		stats.NewMap(out["coverage"]).OutputJSON(&b)
		stats := readStats([]string{"coverage"}, t)
		if len(b.Bytes()) != len(stats) {
			err := dump(b, "observed-coverage.json")
			if err != nil {
				t.Errorf("(Process) Debug dump error: %s", err)
			}
			t.Error("(Process) CoverageStats are different")
		}
	}
}

func TestCoverageUniq(t *testing.T) {
	var b bytes.Buffer
	expectedMapLen := expectedMapLenCoverageUniq
	for _, annotationFile := range annotationFiles {
		b.Reset()
		out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, true)
		checkTest(err, t)
		l := len(out)
		if l > expectedMapLen {
			t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
		}
		_, ok := out["general"].(*stats.GeneralStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
		}
		_, ok = out["coverage"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
		}
		_, ok = out["coverageUniq"].(*stats.CoverageStats)
		if !ok {
			t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverageUniq"])
		}
		stats.NewMap(out["coverageUniq"]).OutputJSON(&b)
		stats := readStats([]string{"coverageUniq"}, t)
		if len(b.Bytes()) != len(stats) {
			err := dump(b, "observed-coverage-uniq.json")
			if err != nil {
				t.Errorf("(Process) Debug dump error: %s", err)
			}
			t.Error("(Process) CoverageStats are different")
		}
	}
}

func TestRNAseq(t *testing.T) {
	var b bytes.Buffer
	bamFile := "data/rnaseq-test.bam"
	annotationFile := "data/rnaseq.gtf.gz"
	expectedMapLen := expectedMapLenCoverage
	out, err := Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	checkTest(err, t)
	l := len(out)
	if l > expectedMapLen {
		t.Errorf("(Process) Expected StatsMap of length %d, got %d", expectedMapLen, l)
	}
	_, ok := out["general"].(*stats.GeneralStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected GeneralStats, got %T", out["general"])
	}
	_, ok = out["coverage"].(*stats.CoverageStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected CoverageStats, got %T", out["coverage"])
	}
	_, ok = out["rnaseq"].(*stats.RNAseqStats)
	if !ok {
		t.Errorf("(Process) Wrong return type - expected RNAseqStats, got %T", out["rnaseq"])
	}
	stats.NewMap(out["rnaseq"]).OutputJSON(&b)
	stats := readStats([]string{"rnaseq"}, t)
	if len(b.Bytes()) != len(stats) {
		err := dump(b, "observed-rnaseq.json")
		if err != nil {
			t.Errorf("(Process) Debug dump error: %s", err)
		}
		t.Error("(Process) RNAseqStats are different")
	}
}

func BenchmarkGeneral(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Process(bamFile, "", runtime.GOMAXPROCS(-1), maxBuf, reads, false)
	}
}

func BenchmarkCoverage(b *testing.B) {
	for _, annotationFile := range annotationFiles {
		for i := 0; i < b.N; i++ {
			Process(bamFile, annotationFile, runtime.GOMAXPROCS(-1), maxBuf, reads, false)
		}
	}
}

func dump(b bytes.Buffer, fname string) error {
	s, err := os.Create(fname)
	if err != nil {
		return err
	}
	defer s.Close()
	_, err = s.Write(b.Bytes())
	return err
}

func readStats(keys []string, t *testing.T) []byte {
	stats := make(map[string]interface{})
	for _, k := range keys {
		stats[k] = readJSON(expectedJSON[k], t)
	}
	b, err := json.MarshalIndent(stats, "", "\t")
	if err != nil {
		return nil
	}
	return b
}

func readJSON(path string, t *testing.T) interface{} {
	b, err := ioutil.ReadFile(path)
	checkTest(err, t)
	var m map[string]interface{}
	json.Unmarshal(b, &m)
	return m
}
<|endoftext|>


<commit_before>package profile

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

type checkFn func(t *testing.T, stdout, stderr []byte, err error)

var profileTests = []struct {
	name   string
	code   string
	checks []checkFn
}{{
	name: "default profile (cpu)",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start().Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: cpu profiling enabled"),
		NoErr,
	},
}, {
	name: "memory profile",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.MemProfile).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: memory profiling enabled"),
		NoErr,
	},
}, {
	name: "memory profile (rate 2048)",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.MemProfileRate(2048)).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: memory profiling enabled (rate 2048)"),
		NoErr,
	},
}, {
	name: "double start",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start().Stop()
	defer profile.Start().Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("cpu profiling enabled", "profile: Start() already called"),
		Err,
	},
}, {
	name: "block profile",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.BlockProfile).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: block profiling enabled"),
		NoErr,
	},
}, {
	name: "profile path",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.ProfilePath(".")).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: cpu profiling enabled, cpu.pprof"),
		NoErr,
	},
}, {
	name: "profile path error",
	code: `
package main

import "github.com/pkg/profile"

func main() {
		defer profile.Start(profile.ProfilePath("README.md")).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("could not create initial output"),
		Err,
	},
}, {
	name: "profile quiet",
	code: `
package main

import "github.com/pkg/profile"

func main() {
        defer profile.Start(profile.Quiet).Stop()
}
`,
	checks: []checkFn{NoStdout, NoStderr, NoErr},
}}

func TestProfile(t *testing.T) {
	for _, tt := range profileTests {
		t.Log(tt.name)
		stdout, stderr, err := runTest(t, tt.code)
		for _, f := range tt.checks {
			f(t, stdout, stderr, err)
		}
	}
}

// Stdout verifies that the given lines match the output from stdout
func Stdout(lines ...string) checkFn {
	return func(t *testing.T, stdout, _ []byte, _ error) {
		buf := bytes.NewBuffer(stdout)
		if !validateOutput(buf, lines) {
			t.Errorf("stderr: wanted '%s', got '%s'", lines, stdout)
		}

	}
}

// NoStdout checks that stdout was blank.
func NoStdout(t *testing.T, stdout, _ []byte, _ error) {
	if len := len(stdout); len > 0 {
		t.Errorf("stdout: wanted 0 bytes, got %d", len)
	}
}

// Stderr verifies that the given lines match the output from stderr
func Stderr(lines ...string) checkFn {
	return func(t *testing.T, stdout, stderr []byte, _ error) {
		buf := bytes.NewBuffer(stderr)
		if !validateOutput(buf, lines) {
			t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr)
		}
	}
}

// NoStderr checks that stderr was blank.
func NoStderr(t *testing.T, _, stderr []byte, _ error) {
	if len := len(stderr); len > 0 {
		t.Errorf("stderr: wanted 0 bytes, got %d", len)
	}
}

// Err checks that there was an error returned
func Err(t *testing.T, _, _ []byte, err error) {
	if err == nil {
		t.Errorf("expected error")
	}
}

// NoErr checks that err was nil
func NoErr(t *testing.T, _, _ []byte, err error) {
	if err != nil {
		t.Errorf("error: expected nil, got %v", err)
	}
}

// validatedOutput validates the given slice of lines against a scanned buffer
func validateOutput(buf *bytes.Buffer, lines []string) bool {
	s := bufio.NewScanner(buf)
	for _, l := range lines {
		if !s.Scan() || !strings.Contains(s.Text(), l) {
			return false
		}
	}
	return true
}

// runTest executes the go program supplied and returns the contents of stdout,
// stderr, and an error which may contain status information about the result
// of the program.
func runTest(t *testing.T, code string) ([]byte, []byte, error) {
	chk := func(err error) {
		if err != nil {
			t.Fatal(err)
		}
	}
	gopath, err := ioutil.TempDir("", "profile-gopath")
	chk(err)
	defer os.RemoveAll(gopath)

	srcdir := filepath.Join(gopath, "src")
	err = os.Mkdir(srcdir, 0755)
	chk(err)
	src := filepath.Join(srcdir, "main.go")
	err = ioutil.WriteFile(src, []byte(code), 0644)
	chk(err)

	cmd := exec.Command("go", "run", src)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()
	return stdout.Bytes(), stderr.Bytes(), err
}
<commit_msg>Corrected typo in log<commit_after>package profile

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

type checkFn func(t *testing.T, stdout, stderr []byte, err error)

var profileTests = []struct {
	name   string
	code   string
	checks []checkFn
}{{
	name: "default profile (cpu)",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start().Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: cpu profiling enabled"),
		NoErr,
	},
}, {
	name: "memory profile",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.MemProfile).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: memory profiling enabled"),
		NoErr,
	},
}, {
	name: "memory profile (rate 2048)",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.MemProfileRate(2048)).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: memory profiling enabled (rate 2048)"),
		NoErr,
	},
}, {
	name: "double start",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start().Stop()
	defer profile.Start().Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("cpu profiling enabled", "profile: Start() already called"),
		Err,
	},
}, {
	name: "block profile",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.BlockProfile).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: block profiling enabled"),
		NoErr,
	},
}, {
	name: "profile path",
	code: `
package main

import "github.com/pkg/profile"

func main() {
	defer profile.Start(profile.ProfilePath(".")).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("profile: cpu profiling enabled, cpu.pprof"),
		NoErr,
	},
}, {
	name: "profile path error",
	code: `
package main

import "github.com/pkg/profile"

func main() {
		defer profile.Start(profile.ProfilePath("README.md")).Stop()
}
`,
	checks: []checkFn{
		NoStdout,
		Stderr("could not create initial output"),
		Err,
	},
}, {
	name: "profile quiet",
	code: `
package main

import "github.com/pkg/profile"

func main() {
        defer profile.Start(profile.Quiet).Stop()
}
`,
	checks: []checkFn{NoStdout, NoStderr, NoErr},
}}

func TestProfile(t *testing.T) {
	for _, tt := range profileTests {
		t.Log(tt.name)
		stdout, stderr, err := runTest(t, tt.code)
		for _, f := range tt.checks {
			f(t, stdout, stderr, err)
		}
	}
}

// Stdout verifies that the given lines match the output from stdout
func Stdout(lines ...string) checkFn {
	return func(t *testing.T, stdout, _ []byte, _ error) {
		buf := bytes.NewBuffer(stdout)
		if !validateOutput(buf, lines) {
			t.Errorf("stdout: wanted '%s', got '%s'", lines, stdout)
		}

	}
}

// NoStdout checks that stdout was blank.
func NoStdout(t *testing.T, stdout, _ []byte, _ error) {
	if len := len(stdout); len > 0 {
		t.Errorf("stdout: wanted 0 bytes, got %d", len)
	}
}

// Stderr verifies that the given lines match the output from stderr
func Stderr(lines ...string) checkFn {
	return func(t *testing.T, stdout, stderr []byte, _ error) {
		buf := bytes.NewBuffer(stderr)
		if !validateOutput(buf, lines) {
			t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr)
		}
	}
}

// NoStderr checks that stderr was blank.
func NoStderr(t *testing.T, _, stderr []byte, _ error) {
	if len := len(stderr); len > 0 {
		t.Errorf("stderr: wanted 0 bytes, got %d", len)
	}
}

// Err checks that there was an error returned
func Err(t *testing.T, _, _ []byte, err error) {
	if err == nil {
		t.Errorf("expected error")
	}
}

// NoErr checks that err was nil
func NoErr(t *testing.T, _, _ []byte, err error) {
	if err != nil {
		t.Errorf("error: expected nil, got %v", err)
	}
}

// validatedOutput validates the given slice of lines against a scanned buffer
func validateOutput(buf *bytes.Buffer, lines []string) bool {
	s := bufio.NewScanner(buf)
	for _, l := range lines {
		if !s.Scan() || !strings.Contains(s.Text(), l) {
			return false
		}
	}
	return true
}

// runTest executes the go program supplied and returns the contents of stdout,
// stderr, and an error which may contain status information about the result
// of the program.
func runTest(t *testing.T, code string) ([]byte, []byte, error) {
	chk := func(err error) {
		if err != nil {
			t.Fatal(err)
		}
	}
	gopath, err := ioutil.TempDir("", "profile-gopath")
	chk(err)
	defer os.RemoveAll(gopath)

	srcdir := filepath.Join(gopath, "src")
	err = os.Mkdir(srcdir, 0755)
	chk(err)
	src := filepath.Join(srcdir, "main.go")
	err = ioutil.WriteFile(src, []byte(code), 0644)
	chk(err)

	cmd := exec.Command("go", "run", src)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()
	return stdout.Bytes(), stderr.Bytes(), err
}
<|endoftext|>


<commit_before>package picasso

import (
	"image"
	"image/draw"

	"github.com/disintegration/gift"
)

type Node interface {
	Draw(width, height int) image.Image
}

type Picture struct {
	Picture image.Image
}

func (n Picture) Draw(width, height int) image.Image {
	g := gift.New(
		gift.ResizeToFill(width, height, gift.LanczosResampling, gift.CenterAnchor),
	)
	dst := image.NewRGBA(g.Bounds(n.Picture.Bounds()))
	g.Draw(dst, n.Picture)
	return dst
}

type VerticalSplit struct {
	Left  Node
	Right Node
	Ratio float32
}

func (n VerticalSplit) Draw(width, height int) image.Image {
	rightWidth := n.rightWidth(width)
	leftWidth := width - rightWidth

	dst := image.NewRGBA(image.Rect(0, 0, width, height))

	leftImage := n.Left.Draw(leftWidth, height)
	leftRect := image.Rect(0, 0, leftWidth, height)
	draw.Draw(dst, leftRect, leftImage, image.ZP, draw.Over)

	rightImage := n.Right.Draw(rightWidth, height)
	rightRect := image.Rect(leftWidth, 0, rightWidth, height)
	draw.Draw(dst, rightRect, rightImage, image.ZP, draw.Over)

	return dst
}

func (n VerticalSplit) rightWidth(width int) int {
	// Go doesn't have a simple round function and the rounding direction doesn't really matter here,
	// so we'll just coerce the result to an int which discards the fraction.
	return int(float32(width) / (n.Ratio + 1))
}
<commit_msg>Fix vertical split only drawing part of the right image<commit_after>package picasso

import (
	"image"
	"image/draw"

	"github.com/disintegration/gift"
)

type Node interface {
	Draw(width, height int) image.Image
}

type Picture struct {
	Picture image.Image
}

func (n Picture) Draw(width, height int) image.Image {
	g := gift.New(
		gift.ResizeToFill(width, height, gift.LanczosResampling, gift.CenterAnchor),
	)
	dst := image.NewRGBA(g.Bounds(n.Picture.Bounds()))
	g.Draw(dst, n.Picture)
	return dst
}

type VerticalSplit struct {
	Left  Node
	Right Node
	Ratio float32
}

func (n VerticalSplit) Draw(width, height int) image.Image {
	rightWidth := n.rightWidth(width)
	leftWidth := width - rightWidth

	dst := image.NewRGBA(image.Rect(0, 0, width, height))

	leftImage := n.Left.Draw(leftWidth, height)
	leftRect := image.Rect(0, 0, leftWidth, height)
	draw.Draw(dst, leftRect, leftImage, image.ZP, draw.Over)

	rightImage := n.Right.Draw(rightWidth, height)
	rightRect := image.Rect(leftWidth, 0, width, height)
	draw.Draw(dst, rightRect, rightImage, image.ZP, draw.Over)

	return dst
}

func (n VerticalSplit) rightWidth(width int) int {
	// Go doesn't have a simple round function and the rounding direction doesn't really matter here,
	// so we'll just coerce the result to an int which discards the fraction.
	return int(float32(width) / (n.Ratio + 1))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"

	"github.com/andrew-d/go-termutil"
	"github.com/codegangsta/cli"
	"github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}

func main() {
	app := cli.NewApp()
	app.Name = "pipecat"
	app.Usage = "Connect unix pipes and message queues"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "amqpUri",
			Value:  "amqp://guest:guest@localhost:5672/",
			Usage:  "AMQP URI",
			EnvVar: "AMQP_URI",
		},
	}

	app.Action = func(c *cli.Context) {
		list := c.Args().First()
		if list == "" {
			fmt.Println("Please provide name of the queue")
			os.Exit(1)
		}

		conn, err := amqp.Dial(c.String("amqpUri"))
		failOnError(err, "Failed to connect to AMPQ broker")
		defer conn.Close()

		channel, err := conn.Channel()
		failOnError(err, "Failed to open a channel")
		defer channel.Close()

		q, err := channel.QueueDeclare(
			list,  // name
			true,  // durable
			false, // delete when unused
			false, // exclusive
			false, // no-wait
			nil,   // arguments
		)
		failOnError(err, "Failed to declare a queue")

		if termutil.Isatty(os.Stdin.Fd()) {
			//readAll(list, conn)
		} else {
			scanner := bufio.NewScanner(os.Stdin)
			for scanner.Scan() {
				line := scanner.Text()
				body := line
				err = channel.Publish(
					"",     // exchange
					q.Name, // routing key
					false,  // mandatory
					false,  // immediate
					amqp.Publishing{
						ContentType: "text/plain",
						Body:        []byte(body),
					})
				log.Printf(" [x] Sent %s", body)
				failOnError(err, "Failed to publish a message")
			}
			if err := scanner.Err(); err != nil {
				fmt.Fprintln(os.Stderr, "Reading standard input:", err)
			}
		}
	}

	app.Run(os.Args)
}
<commit_msg>Read from queue<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"

	"github.com/andrew-d/go-termutil"
	"github.com/codegangsta/cli"
	"github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}

func main() {
	app := cli.NewApp()
	app.Name = "pipecat"
	app.Usage = "Connect unix pipes and message queues"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "amqpUri",
			Value:  "amqp://guest:guest@localhost:5672/",
			Usage:  "AMQP URI",
			EnvVar: "AMQP_URI",
		},
	}

	app.Action = func(c *cli.Context) {
		list := c.Args().First()
		if list == "" {
			fmt.Println("Please provide name of the queue")
			os.Exit(1)
		}

		conn, err := amqp.Dial(c.String("amqpUri"))
		failOnError(err, "Failed to connect to AMPQ broker")
		defer conn.Close()

		channel, err := conn.Channel()
		failOnError(err, "Failed to open a channel")
		defer channel.Close()

		q, err := channel.QueueDeclare(
			list,  // name
			true,  // durable
			false, // delete when unused
			false, // exclusive
			false, // no-wait
			nil,   // arguments
		)
		failOnError(err, "Failed to declare a queue")

		if termutil.Isatty(os.Stdin.Fd()) {
			msgs, err := channel.Consume(
				q.Name, // queue
				"",     // consumer
				false,  // auto-ack
				false,  // exclusive
				false,  // no-local
				false,  // no-wait
				nil,    // args
			)
			failOnError(err, "Failed to register a consumer")
			for msg := range msgs {
				fmt.Printf("%s", msg.Body)
				msg.Ack(true)
			}
			//readAll(list, conn)
		} else {
			scanner := bufio.NewScanner(os.Stdin)
			for scanner.Scan() {
				line := scanner.Text()
				body := line
				err = channel.Publish(
					"",     // exchange
					q.Name, // routing key
					false,  // mandatory
					false,  // immediate
					amqp.Publishing{
						ContentType: "text/plain",
						Body:        []byte(body),
					})
				fmt.Printf("%s", body)
				failOnError(err, "Failed to publish a message")
			}
			if err := scanner.Err(); err != nil {
				fmt.Fprintln(os.Stderr, "Reading standard input:", err)
			}
		}
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"github.com/op/go-logging"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var log = logging.MustGetLogger("bootsmann")
var format = logging.MustStringFormatter(
	"%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.5s} %{id:03x}%{color:reset} %{message}",
)

var configFile string

func init() {
	flag.StringVar(&configFile, "config", "/etc/bootsmann.cf", "Configuration file")
}

func main() {
	flag.Parse()

	backend := logging.NewLogBackend(os.Stderr, "", 0)
	backendFormatter := logging.NewBackendFormatter(backend, format)
	logging.SetBackend(backendFormatter)
	doProcess(configFile)
}

func doProcess(cf string) {
	buf, err := ioutil.ReadFile(cf)
	if err != nil {
		log.Error("No such config: %s", cf)
		os.Exit(1)
	}
	lines := strings.Split(string(buf), "\n")

	directives := make(map[string]map[string]string)
	vars := make(map[string]map[string]string)
	global := false
	var currentPath string
	sparks := 0
	sparkStop := make(chan bool)

	globalPath := "$global"

	for lineNum, l := range lines {
		if (len(l) > 2) && (l[1:len(l)-1] == globalPath) {
			if global {
				log.Critical("Second [$global] section on line %d", lineNum)
				os.Exit(1)
			}
			global = true
			currentPath = getPath(l, lineNum)
			directives[currentPath] = make(map[string]string)
			vars[currentPath] = make(map[string]string)
		} else if (len(l) > 2) && (string(l[0]) == "[") {
			// do processing of previous path section in goroutine
			if currentPath != globalPath {
				sparks += processPath(currentPath, sparkStop,
					vars["$global"], vars[currentPath],
					directives["$global"], directives[currentPath])
			}

			currentPath = getPath(l, lineNum)
			directives[currentPath] = make(map[string]string)
			vars[currentPath] = make(map[string]string)
		} else if (len(l) > 0) && (l[0] == '#') {
			// ignore comments
		} else {
			if vn, vv, ok := getVar(l, lineNum); ok {
				if vn[0] == '$' {
					// Special directive
					directives[currentPath][vn] = vv
				} else {
					vars[currentPath][vn] = vv
				}
			}
		}
	}
	sparks += processPath(currentPath, sparkStop,
		vars["$global"], vars[currentPath],
		directives["$global"], directives[currentPath])

	for i := 0; i < sparks; i++ {
		<-sparkStop
	}
}

func getPath(line string, _ int) (path string) {
	path = line[1 : len(line)-1]
	return
}

func getVar(line string, lineNum int) (vn string, vv string, ok bool) {
	out := strings.SplitN(line, "=", 2)
	if len(out) < 2 {
		if len(strings.Trim(line, " ")) > 0 {
			log.Error("Bad variable declaration on %d", lineNum)
		}
		return
	} else {
		vn = strings.Trim(out[0], " ")
		vv = strings.Trim(out[1], " ")
		ok = true
	}
	return
}

func mergeMaps(to, from map[string]string) (out map[string]string) {
	out = make(map[string]string)
	for k, v := range to {
		out[k] = v
	}
	for k, v := range from {
		out[k] = v
	}
	return
}

func processPath(path string, sparkStop chan bool,
	gvars map[string]string, lvars map[string]string,
	gdirs map[string]string, ldirs map[string]string) int {
	log.Info(path)
	vars := mergeMaps(gvars, lvars)
	dirs := mergeMaps(gdirs, ldirs)

	files, err := filepath.Glob(path)
	if err != nil {
		log.Warning("Bad pattern: %s", path)
	}
	if files == nil {
		return 0
	} else {
		for _, file := range files {
			go doFile(file, sparkStop, vars, dirs)
		}
		return len(files)
	}
}

func makeTemplate(dirs map[string]string, vn string) (tmpl string, err error) {
	t := "#{{ }}"
	if template, ok := dirs["$template"]; ok {
		t = template
	}
	ts := strings.Split(t, " ")
	if len(ts) != 2 {
		err = errors.New("Malformed template format: %s")
	} else {
		tmpl = strings.Join([]string{ts[0], vn, ts[1]}, "")
	}
	return
}

func doFile(file string, sparkStop chan bool, vars, dirs map[string]string) {
	log.Info(file)

	buf, err := ioutil.ReadFile(file)
	if err != nil {
		log.Error("Cannot open file %s: %s", file, err)
	}

	s := string(buf)
	for vn, vv := range vars {
		tmpl, err := makeTemplate(dirs, vn)
		if err != nil {
			log.Error(err.Error())
		} else {
			s = strings.Replace(s, tmpl, vv, -1)
		}
	}

	err = ioutil.WriteFile(file, []byte(s), 0644)
	if err != nil {
		log.Error("Cannot write to file %s: %s", file, err)
	}

	sparkStop <- true
}
<commit_msg>Do nothing with file if it not available<commit_after>package main

import (
	"errors"
	"flag"
	"github.com/op/go-logging"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var log = logging.MustGetLogger("bootsmann")
var format = logging.MustStringFormatter(
	"%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.5s} %{id:03x}%{color:reset} %{message}",
)

var configFile string

func init() {
	flag.StringVar(&configFile, "config", "/etc/bootsmann.cf", "Configuration file")
}

func main() {
	flag.Parse()

	backend := logging.NewLogBackend(os.Stderr, "", 0)
	backendFormatter := logging.NewBackendFormatter(backend, format)
	logging.SetBackend(backendFormatter)
	doProcess(configFile)
}

func doProcess(cf string) {
	buf, err := ioutil.ReadFile(cf)
	if err != nil {
		log.Error("No such config: %s", cf)
		os.Exit(1)
	}
	lines := strings.Split(string(buf), "\n")

	directives := make(map[string]map[string]string)
	vars := make(map[string]map[string]string)
	global := false
	var currentPath string
	sparks := 0
	sparkStop := make(chan bool)

	globalPath := "$global"

	for lineNum, l := range lines {
		if (len(l) > 2) && (l[1:len(l)-1] == globalPath) {
			if global {
				log.Critical("Second [$global] section on line %d", lineNum)
				os.Exit(1)
			}
			global = true
			currentPath = getPath(l, lineNum)
			directives[currentPath] = make(map[string]string)
			vars[currentPath] = make(map[string]string)
		} else if (len(l) > 2) && (string(l[0]) == "[") {
			// do processing of previous path section in goroutine
			if currentPath != globalPath {
				sparks += processPath(currentPath, sparkStop,
					vars["$global"], vars[currentPath],
					directives["$global"], directives[currentPath])
			}

			currentPath = getPath(l, lineNum)
			directives[currentPath] = make(map[string]string)
			vars[currentPath] = make(map[string]string)
		} else if (len(l) > 0) && (l[0] == '#') {
			// ignore comments
		} else {
			if vn, vv, ok := getVar(l, lineNum); ok {
				if vn[0] == '$' {
					// Special directive
					directives[currentPath][vn] = vv
				} else {
					vars[currentPath][vn] = vv
				}
			}
		}
	}
	sparks += processPath(currentPath, sparkStop,
		vars["$global"], vars[currentPath],
		directives["$global"], directives[currentPath])

	for i := 0; i < sparks; i++ {
		<-sparkStop
	}
}

func getPath(line string, _ int) (path string) {
	path = line[1 : len(line)-1]
	return
}

func getVar(line string, lineNum int) (vn string, vv string, ok bool) {
	out := strings.SplitN(line, "=", 2)
	if len(out) < 2 {
		if len(strings.Trim(line, " ")) > 0 {
			log.Error("Bad variable declaration on %d", lineNum)
		}
		return
	} else {
		vn = strings.Trim(out[0], " ")
		vv = strings.Trim(out[1], " ")
		ok = true
	}
	return
}

func mergeMaps(to, from map[string]string) (out map[string]string) {
	out = make(map[string]string)
	for k, v := range to {
		out[k] = v
	}
	for k, v := range from {
		out[k] = v
	}
	return
}

func processPath(path string, sparkStop chan bool,
	gvars map[string]string, lvars map[string]string,
	gdirs map[string]string, ldirs map[string]string) int {
	log.Info(path)
	vars := mergeMaps(gvars, lvars)
	dirs := mergeMaps(gdirs, ldirs)

	files, err := filepath.Glob(path)
	if err != nil {
		log.Warning("Bad pattern: %s", path)
	}
	if files == nil {
		return 0
	} else {
		for _, file := range files {
			go doFile(file, sparkStop, vars, dirs)
		}
		return len(files)
	}
}

func makeTemplate(dirs map[string]string, vn string) (tmpl string, err error) {
	t := "#{{ }}"
	if template, ok := dirs["$template"]; ok {
		t = template
	}
	ts := strings.Split(t, " ")
	if len(ts) != 2 {
		err = errors.New("Malformed template format: %s")
	} else {
		tmpl = strings.Join([]string{ts[0], vn, ts[1]}, "")
	}
	return
}

func doFile(file string, sparkStop chan bool, vars, dirs map[string]string) {
	log.Info(file)

	buf, err := ioutil.ReadFile(file)
	if err != nil {
		log.Error("Cannot open file %s: %s", file, err)
	} else {

		s := string(buf)
		for vn, vv := range vars {
			tmpl, err := makeTemplate(dirs, vn)
			if err != nil {
				log.Error(err.Error())
			} else {
				s = strings.Replace(s, tmpl, vv, -1)
			}
		}

		err = ioutil.WriteFile(file, []byte(s), 0644)
		if err != nil {
			log.Error("Cannot write to file %s: %s", file, err)
		}
	}

	sparkStop <- true
}
<|endoftext|>


<commit_before>// +build windows

/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package dockershim

import (
	"os"

	"github.com/blang/semver"
	dockertypes "github.com/docker/docker/api/types"
	dockercontainer "github.com/docker/docker/api/types/container"
	dockerfilters "github.com/docker/docker/api/types/filters"
	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
)

func DefaultMemorySwap() int64 {
	return 0
}

func (ds *dockerService) getSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, separator rune) ([]string, error) {
	hasSeccompSetting := false
	annotations := sandboxConfig.GetAnnotations()
	if _, ok := annotations[v1.SeccompContainerAnnotationKeyPrefix+containerName]; !ok {
		_, hasSeccompSetting = annotations[v1.SeccompPodAnnotationKey]
	} else {
		hasSeccompSetting = true
	}

	if hasSeccompSetting {
		glog.Warningf("seccomp annotations found, but it is not supported on windows")
	}

	return nil, nil
}

func (ds *dockerService) updateCreateConfig(
	createConfig *dockertypes.ContainerCreateConfig,
	config *runtimeapi.ContainerConfig,
	sandboxConfig *runtimeapi.PodSandboxConfig,
	podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
	if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode != "" {
		createConfig.HostConfig.NetworkMode = dockercontainer.NetworkMode(networkMode)
	}

	return nil
}

func (ds *dockerService) determinePodIPBySandboxID(sandboxID string) string {
	opts := dockertypes.ContainerListOptions{
		All:     true,
		Filters: dockerfilters.NewArgs(),
	}

	f := newDockerFilter(&opts.Filters)
	f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
	f.AddLabel(sandboxIDLabelKey, sandboxID)
	containers, err := ds.client.ListContainers(opts)
	if err != nil {
		return ""
	}

	for _, c := range containers {
		r, err := ds.client.InspectContainer(c.ID)
		if err != nil {
			continue
		}
		if containerIP := getContainerIP(r); containerIP != "" {
			return containerIP
		}
	}

	return ""
}

func getContainerIP(container *dockertypes.ContainerJSON) string {
	if container.NetworkSettings != nil {
		for _, network := range container.NetworkSettings.Networks {
			if network.IPAddress != "" {
				return network.IPAddress
			}
		}
	}
	return ""
}
<commit_msg>Fix make cross build failure<commit_after>// +build windows

/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package dockershim

import (
	"os"

	"github.com/blang/semver"
	dockertypes "github.com/docker/docker/api/types"
	dockercontainer "github.com/docker/docker/api/types/container"
	dockerfilters "github.com/docker/docker/api/types/filters"
	"github.com/golang/glog"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
)

func DefaultMemorySwap() int64 {
	return 0
}

func (ds *dockerService) getSecurityOpts(seccompProfile string, separator rune) ([]string, error) {
	if seccompProfile != "" {
		glog.Warningf("seccomp annotations are not supported on windows")
	}
	return nil, nil
}

func (ds *dockerService) updateCreateConfig(
	createConfig *dockertypes.ContainerCreateConfig,
	config *runtimeapi.ContainerConfig,
	sandboxConfig *runtimeapi.PodSandboxConfig,
	podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
	if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode != "" {
		createConfig.HostConfig.NetworkMode = dockercontainer.NetworkMode(networkMode)
	}

	return nil
}

func (ds *dockerService) determinePodIPBySandboxID(sandboxID string) string {
	opts := dockertypes.ContainerListOptions{
		All:     true,
		Filters: dockerfilters.NewArgs(),
	}

	f := newDockerFilter(&opts.Filters)
	f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
	f.AddLabel(sandboxIDLabelKey, sandboxID)
	containers, err := ds.client.ListContainers(opts)
	if err != nil {
		return ""
	}

	for _, c := range containers {
		r, err := ds.client.InspectContainer(c.ID)
		if err != nil {
			continue
		}
		if containerIP := getContainerIP(r); containerIP != "" {
			return containerIP
		}
	}

	return ""
}

func getContainerIP(container *dockertypes.ContainerJSON) string {
	if container.NetworkSettings != nil {
		for _, network := range container.NetworkSettings.Networks {
			if network.IPAddress != "" {
				return network.IPAddress
			}
		}
	}
	return ""
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"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 {
		Errf("Setting up node-v%s...", gode.Version)
		ExitIfError(gode.Setup())
		Errln(" done")
	}
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		Errf("Setting up node-v%s...", gode.Version)
		PrintError(gode.Setup())
		Errln(" 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)
		}
		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))
		}
		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.\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... ")
		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 := gode.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 := gode.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(plugin *Plugin, module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		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);`, module, plugin.Version, topic, command, processTitle(ctx), 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 + `');
	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(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(plugin, name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			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
}

// 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 ignorePlugin(plugin string) bool {
	ignored := []string{".bin", ".DS_Store", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

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() {
	plugins := difference(BuiltinPlugins, PluginNames())
	if len(plugins) == 0 {
		return
	}
	noun := "plugins"
	if len(plugins) == 1 {
		noun = "plugin"
	}
	Errf("Installing core %s %s...", noun, strings.Join(plugins, ", "))
	err := installPlugins(plugins...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	ClearPluginCache()
	WritePluginCache(GetPlugins())
	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>remove duplicate help text<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"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 {
		Errf("Setting up node-v%s...", gode.Version)
		ExitIfError(gode.Setup())
		Errln(" done")
	}
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		Errf("Setting up node-v%s...", gode.Version)
		PrintError(gode.Setup())
		Errln(" 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)
		}
		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))
		}
		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.\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... ")
		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 := 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) {
		packages, err := gode.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(plugin *Plugin, module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		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);`, module, plugin.Version, topic, command, processTitle(ctx), 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 + `');
	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(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(plugin, name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			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
}

// 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 ignorePlugin(plugin string) bool {
	ignored := []string{".bin", ".DS_Store", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

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() {
	plugins := difference(BuiltinPlugins, PluginNames())
	if len(plugins) == 0 {
		return
	}
	noun := "plugins"
	if len(plugins) == 1 {
		noun = "plugin"
	}
	Errf("Installing core %s %s...", noun, strings.Join(plugins, ", "))
	err := installPlugins(plugins...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	ClearPluginCache()
	WritePluginCache(GetPlugins())
	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"
	"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 []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))
		}
		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.\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... ")
		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 := 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, module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		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);`, module, plugin.Version, topic, command, processTitle(ctx), 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)
	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(plugin, name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			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
}

// 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 ignorePlugin(plugin string) bool {
	ignored := []string{".bin", ".DS_Store", "node-inspector", ".staging"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

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() {
	plugins := difference(BuiltinPlugins, PluginNames())
	if len(plugins) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(plugins...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	ClearPluginCache()
	WritePluginCache(GetPlugins())
	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>json escape process title string<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"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 []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))
		}
		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.\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... ")
		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 := 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, module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		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);`, module, 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)
	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(plugin, name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			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
}

// 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 ignorePlugin(plugin string) bool {
	ignored := []string{".bin", ".DS_Store", "node-inspector", ".staging"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

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() {
	plugins := difference(BuiltinPlugins, PluginNames())
	if len(plugins) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(plugins...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	ClearPluginCache()
	WritePluginCache(GetPlugins())
	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|>