<commit_before>// +build windows

package godirwalk

// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
// that ReadDirents, ReadDirnames, Scanner, and Walk will use when reading file
// entries from the operating system. During program startup it is initialized
// to the result from calling `os.Getpagesize()` for non Windows environments,
// and 0 for Windows.
var MinimumScratchBufferSize = 0

func newScratchBuffer() []byte { return nil }

func readDirents(osDirname string, _ []byte) (Dirents, error) {
	var entries Dirents
	scanner, err := NewScanner(osDirname)
	if err != nil {
		return nil, err
	}
	for scanner.Scan() {
		if dirent, err := scanner.Dirent(); err == nil {
			entries = append(entries, dirent)
		}
	}
	if err = scanner.Err(); err != nil {
		return nil, err
	}
	return entries, nil
}

func readDirnames(osDirname string, _ []byte) ([]string, error) {
	var entries []string
	scanner, err := NewScanner(osDirname)
	if err != nil {
		return nil, err
	}
	for scanner.Scan() {
		entries = append(entries, scanner.Name())
	}
	if err = scanner.Err(); err != nil {
		return nil, err
	}
	return entries, nil
}
<commit_msg>readDirents, readDirnames on windows<commit_after>// +build windows

package godirwalk

import "os"

// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
// that ReadDirents, ReadDirnames, Scanner, and Walk will use when reading file
// entries from the operating system. During program startup it is initialized
// to the result from calling `os.Getpagesize()` for non Windows environments,
// and 0 for Windows.
var MinimumScratchBufferSize = 0

func newScratchBuffer() []byte { return nil }

func readDirents(osDirname string, _ []byte) ([]*Dirent, error) {
	dh, err := os.Open(osDirname)
	if err != nil {
		return nil, err
	}

	fileinfos, err := dh.Readdir(-1)
	if err != nil {
		_ = dh.Close()
		return nil, err
	}

	entries := make([]*Dirent, len(fileinfos))

	for i, fi := range fileinfos {
		entries[i] = &Dirent{
			name:     fi.Name(),
			path:     osDirname,
			modeType: fi.Mode() & os.ModeType,
		}
	}

	if err = dh.Close(); err != nil {
		return nil, err
	}
	return entries, nil
}

func readDirnames(osDirname string, _ []byte) ([]string, error) {
	dh, err := os.Open(osDirname)
	if err != nil {
		return nil, err
	}

	fileinfos, err := dh.Readdir(-1)
	if err != nil {
		_ = dh.Close()
		return nil, err
	}

	entries := make([]string, len(fileinfos))

	for i, fi := range fileinfos {
		entries[i] = fi.Name()
	}

	if err = dh.Close(); err != nil {
		return nil, err
	}
	return entries, nil
}
<|endoftext|>


<commit_before>package readers

import (
	"strings"
	"testing"
)

func TestNewPs(t *testing.T) {
	n := NewPs()
	if n.Data == nil {
		t.Error("Reader data should never be nil.")
	}
}

func TestNewPsRun(t *testing.T) {
	n := NewPs()
	err := n.Run()
	if err != nil {
		t.Errorf("Parsing df data should always be successful. Error: %v", err)
	}
}

func TestNewPsToJson(t *testing.T) {
	n := NewPs()
	err := n.Run()
	if err != nil {
		t.Errorf("Parsing df data should always be successful. Error: %v", err)
	}

	jsonData, err := n.ToJson()
	if err != nil {
		t.Errorf("Marshalling df data should always be successful. Error: %v", err)
	}

	jsonDataString := string(jsonData)

	if strings.Contains(jsonDataString, "Error") {
		t.Errorf("jsonDataString shouldn't return error: %v", jsonDataString)
	}

	keysToTest := []string{"Name", "Pid", "ParentPid", "StartTime", "RunTime", "MemoryResident", "MemoryMaps", "IOCounters", "CtxSwitches"}

	for _, key := range keysToTest {
		if !strings.Contains(jsonDataString, key) {
			t.Errorf("jsonDataString does not contain '%v' key. jsonDataString: %v", key, jsonDataString)
		}
	}
}
<commit_msg>Fixed ps_test. Some data were Linux only.<commit_after>package readers

import (
	"runtime"
	"strings"
	"testing"
)

func TestNewPs(t *testing.T) {
	n := NewPs()
	if n.Data == nil {
		t.Error("Reader data should never be nil.")
	}
}

func TestNewPsRun(t *testing.T) {
	n := NewPs()
	err := n.Run()
	if err != nil {
		t.Errorf("Parsing df data should always be successful. Error: %v", err)
	}
}

func TestNewPsToJson(t *testing.T) {
	n := NewPs()
	err := n.Run()
	if err != nil {
		t.Errorf("Parsing df data should always be successful. Error: %v", err)
	}

	jsonData, err := n.ToJson()
	if err != nil {
		t.Errorf("Marshalling df data should always be successful. Error: %v", err)
	}

	jsonDataString := string(jsonData)

	if strings.Contains(jsonDataString, "Error") {
		t.Errorf("jsonDataString shouldn't return error: %v", jsonDataString)
	}

	keysToTest := []string{"Name", "Pid", "ParentPid", "StartTime", "RunTime", "MemoryResident"}

	if runtime.GOOS == "linux" {
		keysToTest = []string{"Name", "Pid", "ParentPid", "StartTime", "RunTime", "MemoryResident", "MemoryMaps", "IOCounters", "CtxSwitches"}
	}

	for _, key := range keysToTest {
		if !strings.Contains(jsonDataString, key) {
			t.Errorf("jsonDataString does not contain '%v' key. jsonDataString: %v", key, jsonDataString)
		}
	}
}
<|endoftext|>


<commit_before>package sprockets

import (
	"encoding/json"
	"errors"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
)

type SprocketsServer struct {
	baseUrl      string
	manifestPath string
	assets       map[string]string
}

func NewSprocketsServer(baseUrl, manifestPath string) (ViewHelper, error) {
	s := SprocketsServer{baseUrl: baseUrl, manifestPath: manifestPath}
	err := s.loadManifest()
	return ViewHelper{&s}, err
}

func (s *SprocketsServer) GetAssetUrl(name string) (template.HTMLAttr, error) {
	src, ok := s.assets[name]
	if !ok {
		return template.HTMLAttr(s.baseUrl + "__NOT_FOUND__" + name), errors.New("Asset not found: " + name)
	}
	return template.HTMLAttr(s.baseUrl + src), nil
}

func (s *SprocketsServer) GetAssetContents(name string) ([]byte, error) {
	resp, err := http.Get(s.baseUrl + name)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}

func (s *SprocketsServer) loadManifest() error {
	var err error
	if s.assets == nil {
		s.assets, err = s.fetchAssetList(s.baseUrl + s.manifestPath)
	}
	return err
}

func (s *SprocketsServer) fetchAssetList(url string) (map[string]string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	manifest, err := s.parseAssets(resp.Body)
	return manifest, err
}

func (s *SprocketsServer) parseAssets(r io.Reader) (map[string]string, error) {
	manifest := make(map[string]string)
	err := json.NewDecoder(r).Decode(&manifest)
	return manifest, err
}
<commit_msg>Improve error message when we can't fetch assets<commit_after>package sprockets

import (
	"encoding/json"
	"errors"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
)

type SprocketsServer struct {
	baseUrl      string
	manifestPath string
	assets       map[string]string
}

func NewSprocketsServer(baseUrl, manifestPath string) (ViewHelper, error) {
	s := SprocketsServer{baseUrl: baseUrl, manifestPath: manifestPath}
	err := s.loadManifest()
	return ViewHelper{&s}, err
}

func (s *SprocketsServer) GetAssetUrl(name string) (template.HTMLAttr, error) {
	src, ok := s.assets[name]
	if !ok {
		return template.HTMLAttr(s.baseUrl + "__NOT_FOUND__" + name), errors.New("Asset not found: " + name)
	}
	return template.HTMLAttr(s.baseUrl + src), nil
}

func (s *SprocketsServer) GetAssetContents(name string) ([]byte, error) {
	resp, err := http.Get(s.baseUrl + name)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}

func (s *SprocketsServer) loadManifest() error {
	var err error
	if s.assets == nil {
		s.assets, err = s.fetchAssetList(s.baseUrl + s.manifestPath)
	}
	return err
}

func (s *SprocketsServer) fetchAssetList(url string) (map[string]string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("Couldn't read assets from " + url)
	}

	return s.parseAssets(resp.Body)
}

func (s *SprocketsServer) parseAssets(r io.Reader) (map[string]string, error) {
	manifest := make(map[string]string)
	err := json.NewDecoder(r).Decode(&manifest)
	return manifest, err
}
<|endoftext|>


<commit_before>package main

import (
	"strconv"

	"github.com/piotrkowalczuk/charon"
	"github.com/piotrkowalczuk/sklog"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

// Login ...
func (rs *rpcServer) Login(ctx context.Context, r *charon.LoginRequest) (*charon.LoginResponse, error) {
	if r.Username == "" {
		sklog.Debug(rs.logger, "login failed, empty username")

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: empty username")
	}
	if r.Password == "" {
		sklog.Debug(rs.logger, "login failed, empty password", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: empty password")
	}

	user, err := rs.userRepository.FindOneByUsername(r.Username)
	if err != nil {
		sklog.Debug(rs.logger, "login failed, user with such username does not exists", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: the username and password do not match")
	}

	if matches := rs.passwordHasher.Compare(user.Password, r.Password); !matches {
		sklog.Debug(rs.logger, "login failed, wrong password", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: the username and password do not match")
	}

	if !user.IsConfirmed {
		sklog.Debug(rs.logger, "login failed, email confirmation is missing", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: user is not confirmed")
	}

	if !user.IsActive {
		sklog.Debug(rs.logger, "login failed, user is not active", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: user is not active")
	}

	session, err := rs.session.Create(ctx, map[string]string{
		"user_id":    strconv.FormatInt(user.ID, 10),
		"username":   user.Username,
		"first_name": user.FirstName,
		"last_name":  user.LastName,
	})
	if err != nil {
		sklog.Error(rs.logger, err, "username", r.Username)

		return nil, err
	}

	err = rs.userRepository.UpdateLastLoginAt(user.ID)
	if err != nil {
		sklog.Error(rs.logger, err, "username", r.Username)

		return nil, grpc.Errorf(codes.Internal, "charond: last login update failure: %s", err)
	}

	return &charon.LoginResponse{Token: session.Token}, nil
}

// Logout ...
func (rs *rpcServer) Logout(ctx context.Context, r *charon.LogoutRequest) (*charon.LogoutResponse, error) {
	if r.Token.String() == "" { // TODO: probably wrong, implement IsEmpty method for ID
		return nil, grpc.Errorf(codes.InvalidArgument, "charond: empty session id, logout aborted")
	}

	abandoned, err := rs.session.Abandon(ctx)
	if err != nil {
		sklog.Error(rs.logger, err, "session_id", r.Token)

		return nil, err
	}

	if !abandoned {
		sklog.Debug(rs.logger, "mnemosyne responded without error but session was not abandoned, propably does not exists", "session_id", r.Token)
	} else {
		sklog.Debug(rs.logger, "successful logout", "session_id", r.Token)
	}

	return &charon.LogoutResponse{}, nil
}

// IsGranted ...
func (rs *rpcServer) IsGranted(ctx context.Context, r *charon.IsGrantedRequest) (*charon.IsGrantedResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "is granted is not implemented yet")
}

// BelongsTo ...
func (rs *rpcServer) BelongsTo(ctx context.Context, r *charon.BelongsToRequest) (*charon.BelongsToResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "belongs to is not implemented yet")
}

// IsAuthenticated ...
func (rs *rpcServer) IsAuthenticated(ctx context.Context, r *charon.IsAuthenticatedRequest) (*charon.IsAuthenticatedResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "is authenticated is not implemented yet")
}
<commit_msg>changes forced by Mnemosyne interface changes<commit_after>package main

import (
	"strconv"

	"github.com/piotrkowalczuk/charon"
	"github.com/piotrkowalczuk/sklog"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

// Login ...
func (rs *rpcServer) Login(ctx context.Context, r *charon.LoginRequest) (*charon.LoginResponse, error) {
	if r.Username == "" {
		sklog.Debug(rs.logger, "login failed, empty username")

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: empty username")
	}
	if r.Password == "" {
		sklog.Debug(rs.logger, "login failed, empty password", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: empty password")
	}

	user, err := rs.userRepository.FindOneByUsername(r.Username)
	if err != nil {
		sklog.Debug(rs.logger, "login failed, user with such username does not exists", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: the username and password do not match")
	}

	if matches := rs.passwordHasher.Compare(user.Password, r.Password); !matches {
		sklog.Debug(rs.logger, "login failed, wrong password", "username", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: the username and password do not match")
	}

	if !user.IsConfirmed {
		sklog.Debug(rs.logger, "login failed, email confirmation is missing", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: user is not confirmed")
	}

	if !user.IsActive {
		sklog.Debug(rs.logger, "login failed, user is not active", r.Username)

		return nil, grpc.Errorf(codes.Unauthenticated, "charond: user is not active")
	}

	session, err := rs.session.Create(ctx, map[string]string{
		"user_id":    strconv.FormatInt(user.ID, 10),
		"username":   user.Username,
		"first_name": user.FirstName,
		"last_name":  user.LastName,
	})
	if err != nil {
		sklog.Error(rs.logger, err, "username", r.Username)

		return nil, err
	}

	err = rs.userRepository.UpdateLastLoginAt(user.ID)
	if err != nil {
		sklog.Error(rs.logger, err, "username", r.Username)

		return nil, grpc.Errorf(codes.Internal, "charond: last login update failure: %s", err)
	}

	return &charon.LoginResponse{Token: session.Token}, nil
}

// Logout ...
func (rs *rpcServer) Logout(ctx context.Context, r *charon.LogoutRequest) (*charon.LogoutResponse, error) {
	if r.Token.String() == "" { // TODO: probably wrong, implement IsEmpty method for ID
		return nil, grpc.Errorf(codes.InvalidArgument, "charond: empty session id, logout aborted")
	}

	err := rs.session.Abandon(ctx)
	if err != nil {
		sklog.Error(rs.logger, err, "session_id", r.Token)

		return nil, err
	}

	sklog.Debug(rs.logger, "successful logout", "session_id", r.Token)

	return &charon.LogoutResponse{}, nil
}

// IsGranted ...
func (rs *rpcServer) IsGranted(ctx context.Context, r *charon.IsGrantedRequest) (*charon.IsGrantedResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "is granted is not implemented yet")
}

// BelongsTo ...
func (rs *rpcServer) BelongsTo(ctx context.Context, r *charon.BelongsToRequest) (*charon.BelongsToResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "belongs to is not implemented yet")
}

// IsAuthenticated ...
func (rs *rpcServer) IsAuthenticated(ctx context.Context, r *charon.IsAuthenticatedRequest) (*charon.IsAuthenticatedResponse, error) {
	return nil, grpc.Errorf(codes.Unimplemented, "is authenticated is not implemented yet")
}
<|endoftext|>


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

package main

import (
	"bytes"
	"code.google.com/p/bencode-go"
	"crypto/sha1"
	"errors"
	"launchpad.net/tomb"
	"log"
	"os"
)

type Torrent struct {
	metaInfo MetaInfo
	infoHash []byte
	peer     chan PeerTuple
	Stats    Stats
	t        tomb.Tomb
}

type Stats struct {
	Left       int
	Uploaded   int
	Downloaded int
}

// Metainfo File Structure
type MetaInfo struct {
	Info struct {
		PieceLength int "piece length"
		Pieces      string
		Private     int
		Name        string
		Length      int
		Md5sum      string
		Files       []struct {
			Length int
			Md5sum string
			Path   []string
		}
	}
	Announce     string
	AnnounceList [][]string "announce-list"
	CreationDate int        "creation date"
	Comment      string
	CreatedBy    string "created by"
	Encoding     string
}

// ParseTorrentFile opens the torrent filename specified and parses it,
// returning a Torrent structure with the MetaInfo and SHA-1 hash of the
// Info dictionary.
func ParseTorrentFile(filename string) (torrent Torrent, err error) {
	file, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// Decode the file into a generic bencode representation
	m, err := bencode.Decode(file)
	if err != nil {
		return
	}
	// WTF?: Understand the next line
	metaMap, ok := m.(map[string]interface{})
	if !ok {
		err = errors.New("Couldn't parse torrent file")
		return
	}
	infoDict, ok := metaMap["info"]
	if !ok {
		err = errors.New("Unable to locate info dict in torrent file")
		return
	}

	// Create an Info dict based on the decoded file
	var b bytes.Buffer
	err = bencode.Marshal(&b, infoDict)
	if err != nil {
		return
	}

	// Compute the info hash
	h := sha1.New()
	h.Write(b.Bytes())
	torrent.infoHash = append(torrent.infoHash, h.Sum(nil)...)

	// Populate the metaInfo structure
	file.Seek(0, 0)
	err = bencode.Unmarshal(file, &torrent.metaInfo)
	if err != nil {
		return
	}

	// Print a summary about the torrent file
	log.Printf("Parse : ParseTorrentFile : Successfully parsed %s", filename)
	log.Printf("Parse : ParseTorrentFile : Determined that %d pieces exist in the torrent", (len(torrent.metaInfo.Info.Pieces) / 20))

	return
}

// Init completes the initalization of the Torrent structure
func (t *Torrent) Init() {
	// Initialize bytes left to download
	if len(t.metaInfo.Info.Files) > 0 {
		for _, file := range t.metaInfo.Info.Files {
			t.Stats.Left += file.Length
		}
	} else {
		t.Stats.Left = t.metaInfo.Info.Length
	}
	// TODO: Read in the file and adjust bytes left
}

// Stop stops this Torrent session
func (t *Torrent) Stop() error {
	log.Println("Torrent : Stop : Stopping")
	t.t.Kill(nil)
	return t.t.Wait()
}

// Run starts the Torrent session and orchestrates all the child processes
func (t *Torrent) Run() {
	log.Println("Torrent : Run : Started")
	defer t.t.Done()
	defer log.Println("Torrent : Run : Completed")
	t.Init()

	pieceHashes := make([][]byte, 0)
	for offset := 0; offset <= len(t.metaInfo.Info.Pieces)-20; offset += 20 {
		pieceHashes = append(pieceHashes, []byte(t.metaInfo.Info.Pieces[offset:offset+20]))
	}

	diskIO := NewDiskIO(t.metaInfo)
	diskIO.Init()
	pieces := diskIO.Verify()

	go diskIO.Run()

	server := NewServer()
	trackerManager := NewTrackerManager(server.Port)
	peerManager := NewPeerManager(t.infoHash, len(pieceHashes), t.metaInfo.Info.PieceLength, diskIO.peerChans, server.peerChans, trackerManager.peerChans)

	controller := NewController(pieces, pieceHashes, diskIO.contChans, peerManager.contChans, peerManager.peerContChans)

	go controller.Run()
	go peerManager.Run()
	go server.Run()
	go trackerManager.Run(t.metaInfo, t.infoHash)

	for {
		select {
		case <-t.t.Dying():
			server.Stop()
			peerManager.Stop()
			trackerManager.Stop()
			diskIO.Stop()
			return
		}
	}
}
<commit_msg>Added the totalLength argument for PeerManager, which is needed to know the size of the last piece<commit_after>// Copyright 2013 Jari Takkala. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"code.google.com/p/bencode-go"
	"crypto/sha1"
	"errors"
	"launchpad.net/tomb"
	"log"
	"os"
)

type Torrent struct {
	metaInfo MetaInfo
	infoHash []byte
	peer     chan PeerTuple
	Stats    Stats
	t        tomb.Tomb
}

type Stats struct {
	Left       int
	Uploaded   int
	Downloaded int
}

// Metainfo File Structure
type MetaInfo struct {
	Info struct {
		PieceLength int "piece length"
		Pieces      string
		Private     int
		Name        string
		Length      int
		Md5sum      string
		Files       []struct {
			Length int
			Md5sum string
			Path   []string
		}
	}
	Announce     string
	AnnounceList [][]string "announce-list"
	CreationDate int        "creation date"
	Comment      string
	CreatedBy    string "created by"
	Encoding     string
}

// ParseTorrentFile opens the torrent filename specified and parses it,
// returning a Torrent structure with the MetaInfo and SHA-1 hash of the
// Info dictionary.
func ParseTorrentFile(filename string) (torrent Torrent, err error) {
	file, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// Decode the file into a generic bencode representation
	m, err := bencode.Decode(file)
	if err != nil {
		return
	}
	// WTF?: Understand the next line
	metaMap, ok := m.(map[string]interface{})
	if !ok {
		err = errors.New("Couldn't parse torrent file")
		return
	}
	infoDict, ok := metaMap["info"]
	if !ok {
		err = errors.New("Unable to locate info dict in torrent file")
		return
	}

	// Create an Info dict based on the decoded file
	var b bytes.Buffer
	err = bencode.Marshal(&b, infoDict)
	if err != nil {
		return
	}

	// Compute the info hash
	h := sha1.New()
	h.Write(b.Bytes())
	torrent.infoHash = append(torrent.infoHash, h.Sum(nil)...)

	// Populate the metaInfo structure
	file.Seek(0, 0)
	err = bencode.Unmarshal(file, &torrent.metaInfo)
	if err != nil {
		return
	}

	// Print a summary about the torrent file
	log.Printf("Parse : ParseTorrentFile : Successfully parsed %s", filename)
	log.Printf("Parse : ParseTorrentFile : Determined that %d pieces exist in the torrent", (len(torrent.metaInfo.Info.Pieces) / 20))

	return
}

// Init completes the initalization of the Torrent structure
func (t *Torrent) Init() {
	// Initialize bytes left to download
	if len(t.metaInfo.Info.Files) > 0 {
		for _, file := range t.metaInfo.Info.Files {
			t.Stats.Left += file.Length
		}
	} else {
		t.Stats.Left = t.metaInfo.Info.Length
	}
	// TODO: Read in the file and adjust bytes left
}

// Stop stops this Torrent session
func (t *Torrent) Stop() error {
	log.Println("Torrent : Stop : Stopping")
	t.t.Kill(nil)
	return t.t.Wait()
}

// Run starts the Torrent session and orchestrates all the child processes
func (t *Torrent) Run() {
	log.Println("Torrent : Run : Started")
	defer t.t.Done()
	defer log.Println("Torrent : Run : Completed")
	t.Init()

	pieceHashes := make([][]byte, 0)
	for offset := 0; offset <= len(t.metaInfo.Info.Pieces)-20; offset += 20 {
		pieceHashes = append(pieceHashes, []byte(t.metaInfo.Info.Pieces[offset:offset+20]))
	}

	diskIO := NewDiskIO(t.metaInfo)
	diskIO.Init()
	pieces := diskIO.Verify()

	go diskIO.Run()

	var totalLength int
	if t.metaInfo.Info.Length != 0 {
		// There is a single file
		totalLength = t.metaInfo.Info.Length
	} else {
		// There are multiple files
		for i := 0; i < len(t.metaInfo.Info.Files); i++ {
			totalLength += t.metaInfo.Info.Files[i].Length
		}
	}

	server := NewServer()
	trackerManager := NewTrackerManager(server.Port)
	peerManager := NewPeerManager(t.infoHash, len(pieceHashes), t.metaInfo.Info.PieceLength, totalLength, diskIO.peerChans, server.peerChans, trackerManager.peerChans)

	controller := NewController(pieces, pieceHashes, diskIO.contChans, peerManager.contChans, peerManager.peerContChans)

	go controller.Run()
	go peerManager.Run()
	go server.Run()
	go trackerManager.Run(t.metaInfo, t.infoHash)

	for {
		select {
		case <-t.t.Dying():
			server.Stop()
			peerManager.Stop()
			trackerManager.Stop()
			diskIO.Stop()
			return
		}
	}
}
<|endoftext|>


<commit_before>package flowgraph_test

import (
	"fmt"
	"github.com/vectaport/fgbase"
	"github.com/vectaport/flowgraph"
	"os"
	"testing"
	"time"
)

/*=====================================================================*/

func TestMain(m *testing.M) {
	fgbase.ConfigByFlag(map[string]interface{}{"trace": "QQ"})
	os.Exit(m.Run())
}

/*=====================================================================*/

func TestNewEqual(t *testing.T) {

	fmt.Printf("BEGIN:  TestNewEqual\n")

	// Different allocations should not be equal.
	if flowgraph.New("abc") == flowgraph.New("abc") {
		t.Errorf(`New("abc") == New("abc")`)
	}
	if flowgraph.New("abc") == flowgraph.New("xyz") {
		t.Errorf(`New("abc") == New("xyz")`)
	}

	// Same allocation should be equal to itself (not crash).
	g := flowgraph.New("jkl")
	if g != g {
		t.Errorf(`graph != graph`)
	}

	fmt.Printf("END:    TestNewEqual\n")
}

/*=====================================================================*/

type getter struct {
	cnt int
}

func (g *getter) Get(node flowgraph.Node) (interface{}, error) {
	i := g.cnt
	g.cnt++
	return i, nil
}

func TestInsertIncoming(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertIncoming\n")

	fg := flowgraph.New("TestInsertIncoming")
	// n := fg.NewIncoming(&getter{})
	// fg.InsertNode("incoming", n)
	fg.InsertIncoming("incoming", &getter{})
	fg.InsertSink("sink")
	// n := fg.NewSink("sink")
	// fg.InsertNode(n)

	fg.Run()

	fmt.Printf("END:    TestInsertIncoming\n")
}

/*=====================================================================*/

type putter struct {
	sum int
}

func (p *putter) Put(node flowgraph.Node, v interface{}) error {
	p.sum += v.(int)
	return nil
}

func TestInsertOutgoing(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertOutgoing\n")

	fg := flowgraph.New("TestInsertOutgoing")
	fg.InsertConst("one", 1)
	fg.InsertOutgoing("outgoing", &putter{})

	fg.Run()

	fmt.Printf("END:    TestInsertOutgoing\n")
}

/*=====================================================================*/

type transformer struct {
}

func (t *transformer) Transform(node flowgraph.Node, v ...interface{}) ([]interface{}, error) {
	xv := v[0].(int) * 2
	return []interface{}{xv}, nil
}

func TestInsertAllOf(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertAllOf\n")

	fg := flowgraph.New("TestInsertAllOf")
	fg.InsertConst("one", 1)
	fg.InsertAllOf("double", &transformer{})
	fg.InsertSink("sink")

	fg.Run()

	fmt.Printf("END:    TestInsertAllOf\n")
}

/*=====================================================================*/

func TestInsertArray(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertArray\n")

	arr := []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	fg := flowgraph.New("TestInsertArray")
	fg.InsertArray("array", arr)
	fg.InsertSink("sink")

	fg.Run()

	s := fg.FindNode("sink").Auxiliary().(fgbase.SinkStats)

	if s.Cnt != len(arr) {
		t.Fatalf("SinkStats.Cnt %d != len(arr) %d\n", s.Cnt)
	}
	if s.Sum != 45 {
		t.Fatalf("SinkStats.Sum %d != sum(arr)\n", s.Sum)
	}

	fmt.Printf("END:    TestInsertArray\n")
}

/*=====================================================================*/

type pass int

func (p pass) Transform(n flowgraph.Node, c ...interface{}) ([]interface{}, error) {
	return c, nil
}

var p pass

func TestInsertChain(t *testing.T) {
	now := time.Now().UTC()
	fmt.Printf("BEGIN:  TestInsertChain\n")

	arr := []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	fg := flowgraph.New("TestInsertChain")
	fg.InsertArray("array", arr)
	fg.InsertAllOf("pass", p)
	for i := 0; i < 1024*1024; i++ {
		fg.InsertAllOf("pass", p)
	}
	fg.InsertSink("sink")

	fmt.Printf("time since:  %v\n", time.Since(now))
	fg.Run()

	s := fg.FindNode("sink").Auxiliary().(fgbase.SinkStats)

	if s.Cnt != len(arr) {
		t.Fatalf("SinkStats.Cnt %d != len(arr)\n", s.Cnt)
	}
	if s.Sum != 45 {
		t.Fatalf("SinkStats.Sum %d != sum(arr)\n", s.Sum)
	}

	fmt.Printf("END:    TestInsertChain\n")
}
<commit_msg>migrate to pointer slices<commit_after>package flowgraph_test

import (
	"fmt"
	"github.com/vectaport/fgbase"
	"github.com/vectaport/flowgraph"
	"os"
	"testing"
	"time"
)

/*=====================================================================*/

func TestMain(m *testing.M) {
	fgbase.ConfigByFlag(map[string]interface{}{"trace": "QQ"})
	os.Exit(m.Run())
}

/*=====================================================================*/

func TestNewEqual(t *testing.T) {

	fmt.Printf("BEGIN:  TestNewEqual\n")

	// Different allocations should not be equal.
	if flowgraph.New("abc") == flowgraph.New("abc") {
		t.Errorf(`New("abc") == New("abc")`)
	}
	if flowgraph.New("abc") == flowgraph.New("xyz") {
		t.Errorf(`New("abc") == New("xyz")`)
	}

	// Same allocation should be equal to itself (not crash).
	g := flowgraph.New("jkl")
	if g != g {
		t.Errorf(`graph != graph`)
	}

	fmt.Printf("END:    TestNewEqual\n")
}

/*=====================================================================*/

type getter struct {
	cnt int
}

func (g *getter) Get(node flowgraph.Node) (interface{}, error) {
	i := g.cnt
	g.cnt++
	return i, nil
}

func TestInsertIncoming(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertIncoming\n")

	fg := flowgraph.New("TestInsertIncoming")
	// n := fg.NewIncoming(&getter{})
	// fg.InsertNode("incoming", n)
	fg.InsertIncoming("incoming", &getter{})
	fg.InsertSink("sink")
	// n := fg.NewSink("sink")
	// fg.InsertNode(n)

	fg.Run()

	fmt.Printf("END:    TestInsertIncoming\n")
}

/*=====================================================================*/

type putter struct {
	sum int
}

func (p *putter) Put(node flowgraph.Node, v interface{}) error {
	p.sum += v.(int)
	return nil
}

func TestInsertOutgoing(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertOutgoing\n")

	fg := flowgraph.New("TestInsertOutgoing")
	fg.InsertConst("one", 1)
	fg.InsertOutgoing("outgoing", &putter{})

	fg.Run()

	fmt.Printf("END:    TestInsertOutgoing\n")
}

/*=====================================================================*/

type transformer struct {
}

func (t *transformer) Transform(node flowgraph.Node, v ...interface{}) ([]interface{}, error) {
	xv := v[0].(int) * 2
	return []interface{}{xv}, nil
}

func TestInsertAllOf(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertAllOf\n")

	fg := flowgraph.New("TestInsertAllOf")
	fg.InsertConst("one", 1)
	fg.InsertAllOf("double", &transformer{})
	fg.InsertSink("sink")

	fg.Run()

	fmt.Printf("END:    TestInsertAllOf\n")
}

/*=====================================================================*/

func TestInsertArray(t *testing.T) {

	fmt.Printf("BEGIN:  TestInsertArray\n")

	arr := []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	fg := flowgraph.New("TestInsertArray")
	fg.InsertArray("array", arr)
	fg.InsertSink("sink")

	fg.Run()

	s := fg.FindNode("sink").Auxiliary().(fgbase.SinkStats)

	if s.Cnt != len(arr) {
		t.Fatalf("SinkStats.Cnt %d != len(arr) %d\n", s.Cnt)
	}
	if s.Sum != 45 {
		t.Fatalf("SinkStats.Sum %d != sum(arr)\n", s.Sum)
	}

	fmt.Printf("END:    TestInsertArray\n")
}

/*=====================================================================*/

type pass int

func (p pass) Transform(n flowgraph.Node, c ...interface{}) ([]interface{}, error) {
	return c, nil
}

var p pass

func TestInsertChain(t *testing.T) {
	now := time.Now().UTC()
	fmt.Printf("BEGIN:  TestInsertChain\n")

	arr := []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	fg := flowgraph.New("TestInsertChain")
	fg.InsertArray("array", arr)
	fg.InsertAllOf("pass", p)
	for i := 0; i < 1024*1024; i++ {
		fg.InsertAllOf("pass", p)
	}
	fg.InsertSink("sink")

	fg.Run()

	s := fg.FindNode("sink").Auxiliary().(fgbase.SinkStats)

	if s.Cnt != len(arr) {
		t.Fatalf("SinkStats.Cnt %d != len(arr)\n", s.Cnt)
	}
	if s.Sum != 45 {
		t.Fatalf("SinkStats.Sum %d != sum(arr)\n", s.Sum)
	}

	fmt.Printf("END:    TestInsertChain\n")
}
<|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 installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := gode.Packages()
		if err != nil {
			panic(errors.New("Error listing plugins\nGet more debugging output with GODE_DEBUG=info"))
		}
		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 + `');
	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(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>only show actual plugins<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) {
		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(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 logging

import (
	"os"
	"io"
	"errors"
	"strconv"
)

// A WriterPlugin implements OutputPlugin by using a function to choose an io.Writer.
type WriterPlugin func(options map[string]string) (writer io.Writer, err error)

func (chooser WriterPlugin) CreateOutputter(options map[string]string) (result Outputter, err error) {

	// Setup formatter
	format := options["format"]
	if format == "" {
		return nil, errors.New("console formatting string not specified")
	}
	formatter := NewBasicFormatter(format)

	// Determine output stream to use
	output, err := chooser(options)
	if err != nil {
		return
	}

	return StringOutputter{
		Writer: IOWriter{output},
		Formatter: formatter,
	}, nil
}

var consolePlugin = WriterPlugin(func(options map[string]string) (output io.Writer, err error) {
	stream := options["stream"]
	switch {
	case stream == "stdout":
		output = os.Stdout
	case stream == "stderr":
		output = os.Stderr
	case stream == "":
		err = errors.New("console stream not specified")
	default:
		if fd, err := strconv.Atoi(stream); err == nil {
			output = os.NewFile(uintptr(fd), "logging_output")
		} else {
			err = errors.New("invalid console stream: " + stream)
		}
	}
	return
})

var filePlugin = WriterPlugin(func(options map[string]string) (output io.Writer, err error) {
	path := options["file"]
	if path == "" {
		err = errors.New("file option not specified")
	} else {
		output, err = os.OpenFile(path, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644)
	}
	return
})

func init() {
	RegisterOutputPlugin("console", consolePlugin)
	RegisterOutputPlugin("file", filePlugin)
}
<commit_msg>Formatted plugins.go<commit_after>package logging

import (
	"errors"
	"io"
	"os"
	"strconv"
)

// A WriterPlugin implements OutputPlugin by using a function to choose an io.Writer.
type WriterPlugin func(options map[string]string) (writer io.Writer, err error)

// Creates and returns a new StringOutputter. The format is taken from the "format" option, which must exist. The output
// StringWriter is obtained by calling the WriterPlugin (which is a function).
func (chooser WriterPlugin) CreateOutputter(options map[string]string) (result Outputter, err error) {

	// Setup formatter
	format := options["format"]
	if format == "" {
		return nil, errors.New("console formatting string not specified")
	}
	formatter := NewBasicFormatter(format)

	// Determine output stream to use
	output, err := chooser(options)
	if err != nil {
		return
	}

	return StringOutputter{
		Writer:    IOWriter{output},
		Formatter: formatter,
	}, nil
}

var consolePlugin = WriterPlugin(func(options map[string]string) (output io.Writer, err error) {
	stream := options["stream"]
	switch {
	case stream == "stdout":
		output = os.Stdout
	case stream == "stderr":
		output = os.Stderr
	case stream == "":
		err = errors.New("console stream not specified")
	default:
		if fd, err := strconv.Atoi(stream); err == nil {
			output = os.NewFile(uintptr(fd), "logging_output")
		} else {
			err = errors.New("invalid console stream: " + stream)
		}
	}
	return
})

var filePlugin = WriterPlugin(func(options map[string]string) (output io.Writer, err error) {
	path := options["file"]
	if path == "" {
		err = errors.New("file option not specified")
	} else {
		output, err = os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	}
	return
})

func init() {
	RegisterOutputPlugin("console", consolePlugin)
	RegisterOutputPlugin("file", filePlugin)
}
<|endoftext|>


<commit_before>package main

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

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

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

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

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

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

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

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

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

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

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, true)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		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 " + cyan("npm install") + "?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	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(names ...string) error {
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(names...)
	plugins := make([]*Plugin, 0, len(names))
	for _, name := range names {
		plugins = append(plugins, getPlugin(name, true))
	}
	AddPluginsToCache(plugins...)
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}

func checkIfPluginIsInstalled(plugin string) {
	if exists, _ := fileExists(filepath.Join(AppDir(), "node_modules", plugin)); !exists {
		installPlugins(plugin)
	}
}
<commit_msg>do not error if plugin does not exist<commit_after>package main

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

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

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

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

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

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

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

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

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

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

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, true)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		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 " + cyan("npm install") + "?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	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(names ...string) error {
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(names...)
	plugins := make([]*Plugin, 0, len(names))
	for _, name := range names {
		plugins = append(plugins, getPlugin(name, true))
	}
	AddPluginsToCache(plugins...)
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}

func checkIfPluginIsInstalled(plugin string) {
	if exists, _ := fileExists(filepath.Join(AppDir(), "node_modules", plugin)); !exists {
		installPlugins(plugin)
	}
}
<|endoftext|>


<commit_before>package main

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

	"github.com/dickeyxxx/gode"
)

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

var node = gode.NewClient(AppDir)

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

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

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

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

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

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

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

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

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

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

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

  Example:
  $ heroku plugins`,

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

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

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

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

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

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

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

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

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

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

	"github.com/dickeyxxx/gode"
)

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

var node = gode.NewClient(AppDir)

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

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

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

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

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

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

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

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

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

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

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

  Example:
  $ heroku plugins`,

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

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

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

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

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

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

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

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

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