<commit_before>// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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.

// +build integration,go1.7

package testing

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
	"testing"
	"text/template"
	"time"

	"cloud.google.com/go/profiler/proftest"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
)

var (
	repo   = flag.String("repo", "https://github.com/googleapis/cloud-profiler-nodejs.git", "git repo to test")
	branch = flag.String("branch", "", "git branch to test")
	commit = flag.String("commit", "", "git commit to test")
	pr     = flag.Int("pr", 0, "git pull request to test")

	runID             = strings.Replace(time.Now().Format("2006-01-02-15-04-05.000000-0700"), ".", "-", -1)
	benchFinishString = "busybench finished profiling"
	errorString       = "failed to set up or run the benchmark"
)

const cloudScope = "https://www.googleapis.com/auth/cloud-platform"

const startupTemplate = `
{{- template "prologue" . }}

npm_install() {
	timeout 60 npm install "${@}"
}

# Install git
retry apt-get update >/dev/null
retry apt-get -y -q install git >/dev/null

# Install desired version of Node.js
retry curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash >/dev/null
export NVM_DIR="$HOME/.nvm" >/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" >/dev/null

# nvm install writes to stderr and stdout on successful install, so both are
# redirected to serial port 3.
retry nvm install {{.NodeVersion}} &>/dev/ttyS2
npm -v
node -v
NODEDIR=$(dirname $(dirname $(which node)))

# Install agent
retry git clone {{.Repo}}
cd cloud-profiler-nodejs
retry git fetch origin {{if .PR}}pull/{{.PR}}/head{{else}}{{.Branch}}{{end}}:pull_branch
git checkout pull_branch
git reset --hard {{.Commit}}

retry npm_install --nodedir="$NODEDIR" &>/dev/ttyS2

npm run compile
npm pack --nodedir="$NODEDIR" >/dev/null
VERSION=$(node -e "console.log(require('./package.json').version);")
PROFILER="$HOME/cloud-profiler-nodejs/google-cloud-profiler-$VERSION.tgz"

TESTDIR="$HOME/test"
mkdir -p "$TESTDIR"
cp -r "system-test/busybench" "$TESTDIR"
cd "$TESTDIR/busybench"

retry npm_install node-pre-gyp &>/dev/ttyS2
retry npm_install --nodedir="$NODEDIR" "$PROFILER" typescript gts &>/dev/ttyS2

npm run compile

# Run benchmark with agent
GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js 600

# Indicate to test that script has finished running
echo "{{.FinishString}}"

{{ template "epilogue" . -}}
`

type profileSummary struct {
	profileType  string
	functionName string
	sourceFile   string
}

type nodeGCETestCase struct {
	proftest.InstanceConfig
	name         string
	nodeVersion  string
	wantProfiles []profileSummary
}

func (tc *nodeGCETestCase) initializeStartUpScript(template *template.Template) error {
	var buf bytes.Buffer
	err := template.Execute(&buf,
		struct {
			Service      string
			NodeVersion  string
			Repo         string
			PR           int
			Branch       string
			Commit       string
			FinishString string
			ErrorString  string
		}{
			Service:      tc.name,
			NodeVersion:  tc.nodeVersion,
			Repo:         *repo,
			PR:           *pr,
			Branch:       *branch,
			Commit:       *commit,
			FinishString: benchFinishString,
			ErrorString:  errorString,
		})
	if err != nil {
		return fmt.Errorf("failed to render startup script for %s: %v", tc.name, err)
	}
	tc.StartupScript = buf.String()
	return nil
}

func TestAgentIntegration(t *testing.T) {
	projectID := os.Getenv("GCLOUD_TESTS_NODEJS_PROJECT_ID")
	if projectID == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_PROJECT_ID) got empty string")
	}

	zone := os.Getenv("GCLOUD_TESTS_NODEJS_ZONE")
	if zone == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_ZONE) got empty string")
	}

	if *commit == "" {
		t.Fatal("commit flag is not set")
	}

	ctx := context.Background()

	client, err := google.DefaultClient(ctx, cloudScope)
	if err != nil {
		t.Fatalf("failed to get default client: %v", err)
	}

	computeService, err := compute.New(client)
	if err != nil {
		t.Fatalf("failed to initialize compute Service: %v", err)
	}

	template, err := proftest.BaseStartupTmpl.Parse(startupTemplate)
	if err != nil {
		t.Fatalf("failed to parse startup script template: %v", err)
	}

	gceTr := proftest.GCETestRunner{
		TestRunner: proftest.TestRunner{
			Client: client,
		},
		ComputeService: computeService,
	}

	wantProfiles := []profileSummary{
		{"WALL", "busyLoop", "busybench.ts"},
		{"HEAP", "benchmark", "busybench.ts"},
	}

	testcases := []nodeGCETestCase{
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node8-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node8-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "8",
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node10-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node10-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "10",
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node11-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node11-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "11",
		},
	}

	// Allow test cases to run in parallel.
	runtime.GOMAXPROCS(len(testcases))

	for _, tc := range testcases {
		tc := tc // capture range variable
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := tc.initializeStartUpScript(template); err != nil {
				t.Fatalf("failed to initialize startup script: %v", err)
			}

			gceTr.StartInstance(ctx, &tc.InstanceConfig)
			defer func() {
				if gceTr.DeleteInstance(ctx, &tc.InstanceConfig); err != nil {
					t.Fatal(err)
				}
			}()

			timeoutCtx, cancel := context.WithTimeout(ctx, time.Minute*25)
			defer cancel()
			if err := gceTr.PollForSerialOutput(timeoutCtx, &tc.InstanceConfig, benchFinishString, errorString); err != nil {
				t.Fatal(err)
			}

			timeNow := time.Now()
			endTime := timeNow.Format(time.RFC3339)
			startTime := timeNow.Add(-1 * time.Hour).Format(time.RFC3339)
			for _, wantProfile := range tc.wantProfiles {
				pr, err := gceTr.TestRunner.QueryProfiles(tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType)
				if err != nil {
					t.Errorf("QueryProfiles(%s, %s, %s, %s, %s) got error: %v", tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, err)
					continue
				}
				if wantProfile.sourceFile != "" {
					if err := pr.HasFunctionInFile(wantProfile.functionName, wantProfile.sourceFile); err != nil {
						t.Errorf("Function %s not found in source file %s in profiles of type %s: %v", wantProfile.functionName, wantProfile.sourceFile, wantProfile.profileType, err)
					}
					continue
				}
				if err := pr.HasFunction(wantProfile.functionName); err != nil {
					t.Errorf("Function %s not found in profiles of type %s: %v", wantProfile.functionName, wantProfile.profileType, err)
				}
			}
		})
	}
}
<commit_msg>chore: confirm zone is set in e2e test (#518)<commit_after>// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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.

// +build integration,go1.7

package testing

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
	"testing"
	"text/template"
	"time"

	"cloud.google.com/go/profiler/proftest"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
)

var (
	repo   = flag.String("repo", "https://github.com/googleapis/cloud-profiler-nodejs.git", "git repo to test")
	branch = flag.String("branch", "", "git branch to test")
	commit = flag.String("commit", "", "git commit to test")
	pr     = flag.Int("pr", 0, "git pull request to test")

	runID             = strings.Replace(time.Now().Format("2006-01-02-15-04-05.000000-0700"), ".", "-", -1)
	benchFinishString = "busybench finished profiling"
	errorString       = "failed to set up or run the benchmark"
)

const cloudScope = "https://www.googleapis.com/auth/cloud-platform"

const startupTemplate = `
{{- template "prologue" . }}

npm_install() {
	timeout 60 npm install "${@}"
}

# Install git
retry apt-get update >/dev/null
retry apt-get -y -q install git >/dev/null

# Install desired version of Node.js
retry curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash >/dev/null
export NVM_DIR="$HOME/.nvm" >/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" >/dev/null

# nvm install writes to stderr and stdout on successful install, so both are
# redirected to serial port 3.
retry nvm install {{.NodeVersion}} &>/dev/ttyS2
npm -v
node -v
NODEDIR=$(dirname $(dirname $(which node)))

# Install agent
retry git clone {{.Repo}}
cd cloud-profiler-nodejs
retry git fetch origin {{if .PR}}pull/{{.PR}}/head{{else}}{{.Branch}}{{end}}:pull_branch
git checkout pull_branch
git reset --hard {{.Commit}}

retry npm_install --nodedir="$NODEDIR" &>/dev/ttyS2

npm run compile
npm pack --nodedir="$NODEDIR" >/dev/null
VERSION=$(node -e "console.log(require('./package.json').version);")
PROFILER="$HOME/cloud-profiler-nodejs/google-cloud-profiler-$VERSION.tgz"

TESTDIR="$HOME/test"
mkdir -p "$TESTDIR"
cp -r "system-test/busybench" "$TESTDIR"
cd "$TESTDIR/busybench"

retry npm_install node-pre-gyp &>/dev/ttyS2
retry npm_install --nodedir="$NODEDIR" "$PROFILER" typescript gts &>/dev/ttyS2

npm run compile

# Run benchmark with agent
GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js 600

# Indicate to test that script has finished running
echo "{{.FinishString}}"

{{ template "epilogue" . -}}
`

type profileSummary struct {
	profileType  string
	functionName string
	sourceFile   string
}

type nodeGCETestCase struct {
	proftest.InstanceConfig
	name         string
	nodeVersion  string
	wantProfiles []profileSummary
}

func (tc *nodeGCETestCase) initializeStartUpScript(template *template.Template) error {
	var buf bytes.Buffer
	err := template.Execute(&buf,
		struct {
			Service      string
			NodeVersion  string
			Repo         string
			PR           int
			Branch       string
			Commit       string
			FinishString string
			ErrorString  string
		}{
			Service:      tc.name,
			NodeVersion:  tc.nodeVersion,
			Repo:         *repo,
			PR:           *pr,
			Branch:       *branch,
			Commit:       *commit,
			FinishString: benchFinishString,
			ErrorString:  errorString,
		})
	if err != nil {
		return fmt.Errorf("failed to render startup script for %s: %v", tc.name, err)
	}
	tc.StartupScript = buf.String()
	return nil
}

func TestAgentIntegration(t *testing.T) {
	projectID := os.Getenv("GCLOUD_TESTS_NODEJS_PROJECT_ID")
	if projectID == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_PROJECT_ID) got empty string")
	}

	zone := os.Getenv("GCLOUD_TESTS_NODEJS_ZONE")
	if zone == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_ZONE) got empty string")
	}

	if *commit == "" {
		t.Fatal("commit flag is not set")
	}

	ctx := context.Background()

	client, err := google.DefaultClient(ctx, cloudScope)
	if err != nil {
		t.Fatalf("failed to get default client: %v", err)
	}

	computeService, err := compute.New(client)
	if err != nil {
		t.Fatalf("failed to initialize compute Service: %v", err)
	}

	template, err := proftest.BaseStartupTmpl.Parse(startupTemplate)
	if err != nil {
		t.Fatalf("failed to parse startup script template: %v", err)
	}

	gceTr := proftest.GCETestRunner{
		TestRunner: proftest.TestRunner{
			Client: client,
		},
		ComputeService: computeService,
	}

	wantProfiles := []profileSummary{
		{"WALL", "busyLoop", "busybench.ts"},
		{"HEAP", "benchmark", "busybench.ts"},
	}

	testcases := []nodeGCETestCase{
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node8-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node8-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "8",
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node10-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node10-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "10",
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node11-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:         fmt.Sprintf("profiler-test-node11-%s-gce", runID),
			wantProfiles: wantProfiles,
			nodeVersion:  "11",
		},
	}

	// Allow test cases to run in parallel.
	runtime.GOMAXPROCS(len(testcases))

	for _, tc := range testcases {
		tc := tc // capture range variable
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := tc.initializeStartUpScript(template); err != nil {
				t.Fatalf("failed to initialize startup script: %v", err)
			}

			err := gceTr.StartInstance(ctx, &tc.InstanceConfig)
			if err != nil {
				t.Fatalf("failed to start GCE instance: %v", err)
			}
			defer func() {
				if gceTr.DeleteInstance(ctx, &tc.InstanceConfig); err != nil {
					t.Fatal(err)
				}
			}()

			timeoutCtx, cancel := context.WithTimeout(ctx, time.Minute*25)
			defer cancel()
			if err := gceTr.PollForSerialOutput(timeoutCtx, &tc.InstanceConfig, benchFinishString, errorString); err != nil {
				t.Fatal(err)
			}

			timeNow := time.Now()
			endTime := timeNow.Format(time.RFC3339)
			startTime := timeNow.Add(-1 * time.Hour).Format(time.RFC3339)
			for _, wantProfile := range tc.wantProfiles {
				pr, err := gceTr.TestRunner.QueryProfilesWithZone(tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, tc.Zone)
				if err != nil {
					t.Errorf("QueryProfiles(%s, %s, %s, %s, %s) got error: %v", tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, err)
					continue
				}
				if wantProfile.sourceFile != "" {
					if err := pr.HasFunctionInFile(wantProfile.functionName, wantProfile.sourceFile); err != nil {
						t.Errorf("Function %s not found in source file %s in profiles of type %s: %v", wantProfile.functionName, wantProfile.sourceFile, wantProfile.profileType, err)
					}
					continue
				}
				if err := pr.HasFunction(wantProfile.functionName); err != nil {
					t.Errorf("Function %s not found in profiles of type %s: %v", wantProfile.functionName, wantProfile.profileType, err)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>package packages

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"gopkg.in/yaml.v2"
)

// PackageManager manages packages at a given path
type PackageManager struct {
	InstallPath string
}

// NewPackageManager creates a new PackageManager
func NewPackageManager(path string) *PackageManager {
	return &PackageManager{InstallPath: path}
}

// Install installs a package
func (pm *PackageManager) Install(pkg *Package) error {
	d, err := yaml.Marshal(&pkg)
	if err != nil {
		return err
	}

	packagePath := path.Join(pm.InstallPath, pkg.Name)

	if _, err := os.Stat(packagePath); err == nil {
		return fmt.Errorf("'%s' already exists", packagePath)
	}

	d = append([]byte("#!/usr/bin/env whalebrew\n"), d...)
	return ioutil.WriteFile(packagePath, d, 0755)
}

// List lists installed packages
func (pm *PackageManager) List() (map[string]*Package, error) {
	packages := make(map[string]*Package)
	files, err := ioutil.ReadDir(pm.InstallPath)
	if err != nil {
		return packages, err
	}
	for _, file := range files {
		isPackage, err := IsPackage(path.Join(pm.InstallPath, file.Name()))
		if err != nil {
			// Check for various file errors here rather than in IsPackage so it
			// does not swallow errors when checking individual files.

			// permission denied
			if os.IsPermission(err) {
				continue
			}
			// dead symlink
			if os.IsNotExist(err) {
				continue
			}

			return packages, err
		}
		if isPackage {
			pkg, err := pm.Load(file.Name())
			if err != nil {
				return packages, err
			}
			packages[file.Name()] = pkg
		}
	}
	return packages, nil
}

// Load returns an installed package given its package name
func (pm *PackageManager) Load(name string) (*Package, error) {
	return LoadPackageFromPath(path.Join(pm.InstallPath, name))
}

// Uninstall uninstalls a package
func (pm *PackageManager) Uninstall(packageName string) error {
	p := path.Join(pm.InstallPath, packageName)
	isPackage, err := IsPackage(p)
	if err != nil {
		return err
	}
	if !isPackage {
		return fmt.Errorf("%s is not a Whalebrew package", p)
	}
	return os.Remove(p)
}

// IsPackage returns true if the given path is a whalebrew package
func IsPackage(path string) (bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	info, err := f.Stat()

	if err != nil {
		return false, err
	}

	if info.IsDir() {
		return false, nil
	}

	reader := bufio.NewReader(f)
	firstTwoBytes := make([]byte, 2)
	_, err = reader.Read(firstTwoBytes)

	if err == io.EOF {
		return false, nil
	}

	if err != nil {
		return false, err
	}

	if string(firstTwoBytes) != "#!" {
		return false, nil
	}

	line, _, err := reader.ReadLine()

	if err == io.EOF {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	if strings.HasPrefix(string(line), "/usr/bin/env whalebrew") {
		return true, nil
	}

	return false, nil
}
<commit_msg>Add batch file support (for working on windows)<commit_after>package packages

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"runtime"
	"strings"

	"gopkg.in/yaml.v2"
)

// PackageManager manages packages at a given path
type PackageManager struct {
	InstallPath string
}

// NewPackageManager creates a new PackageManager
func NewPackageManager(path string) *PackageManager {
	return &PackageManager{InstallPath: path}
}

// Install installs a package
func (pm *PackageManager) Install(pkg *Package) error {
	d, err := yaml.Marshal(&pkg)
	if err != nil {
		return err
	}

	packagePath := path.Join(pm.InstallPath, pkg.Name)

	if _, err := os.Stat(packagePath); err == nil {
		return fmt.Errorf("'%s' already exists", packagePath)
	}

	if runtime.GOOS == "windows" {
		d = append([]byte(":: |\n  @( whalebrew run %~f0 %* || exit /b %ERRORLEVEL% ) && exit /b 0\n"), d...)
		packagePath = packagePath + ".bat"
	} else {
		d = append([]byte("#!/usr/bin/env whalebrew\n"), d...)
	}
	return ioutil.WriteFile(packagePath, d, 0755)
}

// List lists installed packages
func (pm *PackageManager) List() (map[string]*Package, error) {
	packages := make(map[string]*Package)
	files, err := ioutil.ReadDir(pm.InstallPath)
	if err != nil {
		return packages, err
	}
	for _, file := range files {
		isPackage, err := IsPackage(path.Join(pm.InstallPath, file.Name()))
		if err != nil {
			// Check for various file errors here rather than in IsPackage so it
			// does not swallow errors when checking individual files.

			// permission denied
			if os.IsPermission(err) {
				continue
			}
			// dead symlink
			if os.IsNotExist(err) {
				continue
			}

			return packages, err
		}
		if isPackage {
			pkg, err := pm.Load(file.Name())
			if err != nil {
				return packages, err
			}
			packages[file.Name()] = pkg
		}
	}
	return packages, nil
}

// Load returns an installed package given its package name
func (pm *PackageManager) Load(name string) (*Package, error) {
	return LoadPackageFromPath(path.Join(pm.InstallPath, name))
}

// Uninstall uninstalls a package
func (pm *PackageManager) Uninstall(packageName string) error {
	p := path.Join(pm.InstallPath, packageName)
	if runtime.GOOS == "windows" {
		p = p + ".bat"
	}
	isPackage, err := IsPackage(p)
	if err != nil {
		return err
	}
	if !isPackage {
		return fmt.Errorf("%s is not a Whalebrew package", p)
	}
	return os.Remove(p)
}

// IsPackage returns true if the given path is a whalebrew package
func IsPackage(path string) (bool, error) {
	if runtime.GOOS == "windows" && !strings.HasSuffix(path, ".bat") {
		return false, nil
	}

	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	info, err := f.Stat()

	if err != nil {
		return false, err
	}

	if info.IsDir() {
		return false, nil
	}

	reader := bufio.NewReader(f)

	if runtime.GOOS == "windows" {
		line, _, err := reader.ReadLine()

		if err == io.EOF {
			return false, nil
		}
		if err != nil {
			return false, err
		}
		if strings.HasPrefix(string(line), ":: |") {
			return true, nil
		}
		return false, nil
	}

	firstTwoBytes := make([]byte, 2)
	_, err = reader.Read(firstTwoBytes)

	if err == io.EOF {
		return false, nil
	}

	if err != nil {
		return false, err
	}

	if string(firstTwoBytes) != "#!" {
		return false, nil
	}

	line, _, err := reader.ReadLine()

	if err == io.EOF {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	if strings.HasPrefix(string(line), "/usr/bin/env whalebrew") {
		return true, nil
	}

	return false, nil
}
<|endoftext|>


<commit_before>package lbapi

import (
	"fmt"
	"net/url"
	"time"

	"github.com/Urethramancer/lbapi/countries"
)

// CustomerList is what client software gets.
// It's not guaranteed to hold all records, so check Count against MaxRecords.
type CustomerList struct {
	// Count of records returned in this structure.
	Count int64
	// MaxRecords is the total available for this query.
	MaxRecords int64
	// Domains for the specified search query.
	Customers Customers
}

type Customers []*Customer

func (slice Customers) Len() int {
	return len(slice)
}

func (slice Customers) Less(i, j int) bool {
	return slice[i].ID < slice[j].ID
}

func (slice Customers) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

// Customer is the simple overview returned by bulk search of customers.
type Customer struct {
	ID            int64
	Email         string
	Name          string
	Company       string
	City          string
	Country       string
	Status        string
	TotalReceipts string

	Websites int64
}

// CustomerDetails contains the personal details returned by single-user fetch.
type CustomerDetails struct {
	Created        time.Time
	ID             int64
	ParentReseller int64

	Name          string
	Email         string
	Phone         string
	Address       string
	Zip           string
	City          string
	State         string
	Country       string
	Language      string
	PIN           string
	Status        string
	TotalReceipts string

	Twofactor bool
}

func (c *Client) CustomerByID(cid int64) (*CustomerDetails, error) {
	var err error
	u, err := url.Parse(c.URL)
	if err != nil {
		return nil, err
	}

	u.Path = API_CUSTOMERS_DETAILS_BY_ID
	q := u.Query()
	q.Set("auth-userid", c.ID)
	q.Set("api-key", c.Key)
	q.Set("customer-id", fmt.Sprintf("%d", cid))
	u.RawQuery = q.Encode()

	res, err := c.getResponse(u.String())
	if err != nil {
		return nil, err
	}

	in := *res
	cust := parseCustomerDetails(in)
	return cust, nil
}

func parseCustomerDetails(in interface{}) *CustomerDetails {
	data := in.(maplist)
	phone := "+" + data["telnocc"].(string) + " " + data["telno"].(string)

	return &CustomerDetails{
		Created:        time.Unix(atoi(data["creationdt"].(string)), 0),
		ID:             atoi(data["customerid"].(string)),
		ParentReseller: atoi(data["resellerid"].(string)),
		Name:           data["name"].(string),
		Email:          data["useremail"].(string),
		Phone:          phone,
		Address:        data["address1"].(string),
		Zip:            data["zip"].(string),
		City:           data["city"].(string),
		State:          data["state"].(string),
		Country:        countries.CodeToCountry(data["country"].(string)),
		Language:       data["langpref"].(string),
		PIN:            data["pin"].(string),
		Status:         data["customerstatus"].(string),
		TotalReceipts:  data["totalreceipts"].(string),
		Twofactor:      parseBool(data["twofactorauth_enabled"].(string)),
	}
}

func (c *Client) Customers(page int) (*CustomerList, error) {
	var err error
	u, err := url.Parse(c.URL)
	if err != nil {
		return nil, err
	}

	u.Path = "api/customers/search.json"
	q := u.Query()
	q.Set("auth-userid", c.ID)
	q.Set("api-key", c.Key)
	q.Set("no-of-records", "500")
	if page == 0 {
		page = 1
	}
	q.Set("page-no", fmt.Sprintf("%d", page))
	u.RawQuery = q.Encode()

	res, err := c.getResponse(u.String())
	if err != nil {
		return nil, err
	}

	list := *res
	cl := CustomerList{
		Count:      atoi(fmt.Sprintf("%v", list["recsonpage"])),
		MaxRecords: atoi(fmt.Sprintf("%v", list["recsindb"])),
	}
	delete(list, "recsonpage")
	delete(list, "recsindb")

	for _, customers := range list {
		cust := parseCustomer(customers)
		if cust != nil {
			cl.Customers = append(cl.Customers, cust)
		}
	}
	return &cl, nil
}

func parseCustomer(in interface{}) *Customer {
	data := in.(map[string]interface{})
	return &Customer{
		ID:            atoi(data["customer.customerid"].(string)),
		Email:         data["customer.username"].(string),
		Name:          data["customer.name"].(string),
		Company:       data["customer.company"].(string),
		City:          data["customer.city"].(string),
		Country:       data["customer.country"].(string),
		Status:        data["customer.customerstatus"].(string),
		TotalReceipts: data["customer.totalreceipts"].(string),
		Websites:      atoi(data["customer.websitecount"].(string)),
	}
}
<commit_msg>Broke out countries package to its own repo.<commit_after>package lbapi

import (
	"fmt"
	"net/url"
	"time"

	"github.com/Urethramancer/countries"
)

// CustomerList is what client software gets.
// It's not guaranteed to hold all records, so check Count against MaxRecords.
type CustomerList struct {
	// Count of records returned in this structure.
	Count int64
	// MaxRecords is the total available for this query.
	MaxRecords int64
	// Domains for the specified search query.
	Customers Customers
}

type Customers []*Customer

func (slice Customers) Len() int {
	return len(slice)
}

func (slice Customers) Less(i, j int) bool {
	return slice[i].ID < slice[j].ID
}

func (slice Customers) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

// Customer is the simple overview returned by bulk search of customers.
type Customer struct {
	ID            int64
	Email         string
	Name          string
	Company       string
	City          string
	Country       string
	Status        string
	TotalReceipts string

	Websites int64
}

// CustomerDetails contains the personal details returned by single-user fetch.
type CustomerDetails struct {
	Created        time.Time
	ID             int64
	ParentReseller int64

	Name          string
	Email         string
	Phone         string
	Address       string
	Zip           string
	City          string
	State         string
	Country       string
	Language      string
	PIN           string
	Status        string
	TotalReceipts string

	Twofactor bool
}

func (c *Client) CustomerByID(cid int64) (*CustomerDetails, error) {
	var err error
	u, err := url.Parse(c.URL)
	if err != nil {
		return nil, err
	}

	u.Path = API_CUSTOMERS_DETAILS_BY_ID
	q := u.Query()
	q.Set("auth-userid", c.ID)
	q.Set("api-key", c.Key)
	q.Set("customer-id", fmt.Sprintf("%d", cid))
	u.RawQuery = q.Encode()

	res, err := c.getResponse(u.String())
	if err != nil {
		return nil, err
	}

	in := *res
	cust := parseCustomerDetails(in)
	return cust, nil
}

func parseCustomerDetails(in interface{}) *CustomerDetails {
	data := in.(maplist)
	phone := "+" + data["telnocc"].(string) + " " + data["telno"].(string)

	return &CustomerDetails{
		Created:        time.Unix(atoi(data["creationdt"].(string)), 0),
		ID:             atoi(data["customerid"].(string)),
		ParentReseller: atoi(data["resellerid"].(string)),
		Name:           data["name"].(string),
		Email:          data["useremail"].(string),
		Phone:          phone,
		Address:        data["address1"].(string),
		Zip:            data["zip"].(string),
		City:           data["city"].(string),
		State:          data["state"].(string),
		Country:        countries.CodeToCountry(data["country"].(string)),
		Language:       data["langpref"].(string),
		PIN:            data["pin"].(string),
		Status:         data["customerstatus"].(string),
		TotalReceipts:  data["totalreceipts"].(string),
		Twofactor:      parseBool(data["twofactorauth_enabled"].(string)),
	}
}

func (c *Client) Customers(page int) (*CustomerList, error) {
	var err error
	u, err := url.Parse(c.URL)
	if err != nil {
		return nil, err
	}

	u.Path = "api/customers/search.json"
	q := u.Query()
	q.Set("auth-userid", c.ID)
	q.Set("api-key", c.Key)
	q.Set("no-of-records", "500")
	if page == 0 {
		page = 1
	}
	q.Set("page-no", fmt.Sprintf("%d", page))
	u.RawQuery = q.Encode()

	res, err := c.getResponse(u.String())
	if err != nil {
		return nil, err
	}

	list := *res
	cl := CustomerList{
		Count:      atoi(fmt.Sprintf("%v", list["recsonpage"])),
		MaxRecords: atoi(fmt.Sprintf("%v", list["recsindb"])),
	}
	delete(list, "recsonpage")
	delete(list, "recsindb")

	for _, customers := range list {
		cust := parseCustomer(customers)
		if cust != nil {
			cl.Customers = append(cl.Customers, cust)
		}
	}
	return &cl, nil
}

func parseCustomer(in interface{}) *Customer {
	data := in.(map[string]interface{})
	return &Customer{
		ID:            atoi(data["customer.customerid"].(string)),
		Email:         data["customer.username"].(string),
		Name:          data["customer.name"].(string),
		Company:       data["customer.company"].(string),
		City:          data["customer.city"].(string),
		Country:       data["customer.country"].(string),
		Status:        data["customer.customerstatus"].(string),
		TotalReceipts: data["customer.totalreceipts"].(string),
		Websites:      atoi(data["customer.websitecount"].(string)),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"net/http"
	"github.com/gorilla/mux"
)

const mysqlURL string = "root:a@tcp(sql:3306)/v"

func main() {
	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	r := mux.NewRouter().StrictSlash(true)
	r.HandleFunc("/", Index(r))
	r.HandleFunc("/variants", VariantsIndex)
	r.HandleFunc("/variants/{variantId}", VariantsShow)

	log.Fatal(http.ListenAndServe(":8080", r))

	//db, err := sql.Open("mysql", mysqlURL)
	//check(err)
	//defer db.Close()
	//err = db.Ping()
	//check(err)
}

func Index(router *mux.Router) http.HandlerFunc {
	fn := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Simple variant store API")
		router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
			t, err := route.GetPathTemplate()
			if err != nil {
				return err
			}
			fmt.Fprintln(w, t)
			return nil
		})
	}
	return http.HandlerFunc(fn)
}

func VariantsIndex(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "VARIANTS!")
}

func VariantsShow(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	variantId := vars["variantId"]
	fmt.Fprintln(w, "variant:", variantId)
}

func loadData(file string, Db *sql.DB) {
	basename := filepath.Base(file)
	name := strings.TrimSuffix(basename, filepath.Ext(file))
	query := fmt.Sprintf("LOAD DATA INFILE '/var/lib/mysql-files/%s' INTO TABLE `%s`", basename, name)
	fmt.Printf("Loading %s\n", name)
	_, err := Db.Exec(query)
	check(err)
}

func check(err error) {
	if err == nil {
		return
	}
	panic(err)
}<commit_msg>connect to db<commit_after>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"net/http"
	"github.com/gorilla/mux"
)

func main() {
	var dsn string
	flag.StringVar(&dsn, "d", "root:root@tcp(127.0.0.1:3306)/v", "mysql dsn: [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]")

	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	db, err := sql.Open("mysql", dsn)
	check(err)
	defer db.Close()
	err = db.Ping()
	check(err)

	r := mux.NewRouter().StrictSlash(true)
	r.HandleFunc("/", Index(r))
	r.HandleFunc("/variants", VariantsIndex(db))
	r.HandleFunc("/variants/{variantId}", VariantsShow)

	log.Fatal(http.ListenAndServe(":8080", r))
}

func Index(router *mux.Router) http.HandlerFunc {
	fn := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Simple variant store API")
		router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
			t, err := route.GetPathTemplate()
			if err != nil {
				return err
			}
			fmt.Fprintln(w, t)
			return nil
		})
	}
	return http.HandlerFunc(fn)
}

func VariantsIndex(db *sql.DB) http.HandlerFunc {
	fn := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "VARIANTS!")
	}
	return http.HandlerFunc(fn)
}

func VariantsShow(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	variantId := vars["variantId"]
	fmt.Fprintln(w, "variant:", variantId)
}

func loadData(file string, Db *sql.DB) {
	basename := filepath.Base(file)
	name := strings.TrimSuffix(basename, filepath.Ext(file))
	query := fmt.Sprintf("LOAD DATA INFILE '/var/lib/mysql-files/%s' INTO TABLE `%s`", basename, name)
	fmt.Printf("Loading %s\n", name)
	_, err := Db.Exec(query)
	check(err)
}

func check(err error) {
	if err == nil {
		return
	}
	panic(err)
}<|endoftext|>


<commit_before>package main

import (
	"strconv"
	"time"

	"github.com/codegangsta/cli"
	"github.com/leancloud/lean-cli/lean/api"
	"github.com/leancloud/lean-cli/lean/apps"
	"github.com/leancloud/lean-cli/lean/console"
	"github.com/leancloud/lean-cli/lean/runtimes"
)

// get the console port. now console port is just runtime port plus one.
func getConsolePort(runtimePort string) (string, error) {
	port, err := strconv.Atoi(runtimePort)
	if err != nil {
		return "", nil
	}
	return strconv.Itoa(port + 1), nil
}

func upAction(c *cli.Context) error {
	// TODO: get port from args
	port := "3000"
	consPort, err := getConsolePort(port)
	if err != nil {
		return newCliError(err)
	}

	// TODO:
	apiServerURL := "https://api.leancloud.cn"

	appID, err := apps.GetCurrentAppID("")
	if err != nil {
		return newCliError(err)
	}

	rtm, err := runtimes.DetectRuntime("")
	if err != nil {
		return newCliError(err)
	}
	rtm.Port = port

	appInfo, err := api.GetAppInfo(appID)
	if err != nil {
		return newCliError(err)
	}

	envs := []string{
		"LC_APP_ID=" + appInfo.AppID,
		"LC_APP_KEY=" + appInfo.AppKey,
		"LC_APP_MASTER_KEY=" + appInfo.MasterKey,
		"LC_APP_PORT=" + port,
		"LC_API_SERVER=" + apiServerURL,
		"LEANCLOUD_APP_ID=" + appInfo.AppID,
		"LEANCLOUD_APP_KEY=" + appInfo.AppKey,
		"LEANCLOUD_APP_MASTER_KEY=" + appInfo.MasterKey,
		"LEANCLOUD_APP_PORT=" + port,
		"LEANCLOUD_API_SERVER=" + apiServerURL,
	}
	for _, env := range envs {
		rtm.Envs = append(envs, env)
	}

	cons := &console.Server{
		AppID:       appInfo.AppID,
		AppKey:      appInfo.AppKey,
		MasterKey:   appInfo.MasterKey,
		AppPort:     port,
		ConsolePort: consPort,
		Errors:      make(chan error),
	}

	rtm.Run()
	rtm.Watch(3 * time.Second)
	cons.Run()

	for {
		select {
		case err = <-cons.Errors:
			panic(err)
		case err = <-rtm.Errors:
			panic(err)
		}
	}
}
<commit_msg>fix: panic when user's process exited<commit_after>package main

import (
	"os/exec"
	"strconv"
	"time"

	"github.com/codegangsta/cli"
	"github.com/leancloud/lean-cli/lean/api"
	"github.com/leancloud/lean-cli/lean/apps"
	"github.com/leancloud/lean-cli/lean/console"
	"github.com/leancloud/lean-cli/lean/runtimes"
)

// get the console port. now console port is just runtime port plus one.
func getConsolePort(runtimePort string) (string, error) {
	port, err := strconv.Atoi(runtimePort)
	if err != nil {
		return "", nil
	}
	return strconv.Itoa(port + 1), nil
}

func upAction(c *cli.Context) error {
	// TODO: get port from args
	port := "3000"
	consPort, err := getConsolePort(port)
	if err != nil {
		return newCliError(err)
	}

	// TODO:
	apiServerURL := "https://api.leancloud.cn"

	appID, err := apps.GetCurrentAppID("")
	if err != nil {
		return newCliError(err)
	}

	rtm, err := runtimes.DetectRuntime("")
	if err != nil {
		return newCliError(err)
	}
	rtm.Port = port

	appInfo, err := api.GetAppInfo(appID)
	if err != nil {
		return newCliError(err)
	}

	envs := []string{
		"LC_APP_ID=" + appInfo.AppID,
		"LC_APP_KEY=" + appInfo.AppKey,
		"LC_APP_MASTER_KEY=" + appInfo.MasterKey,
		"LC_APP_PORT=" + port,
		"LC_API_SERVER=" + apiServerURL,
		"LEANCLOUD_APP_ID=" + appInfo.AppID,
		"LEANCLOUD_APP_KEY=" + appInfo.AppKey,
		"LEANCLOUD_APP_MASTER_KEY=" + appInfo.MasterKey,
		"LEANCLOUD_APP_PORT=" + port,
		"LEANCLOUD_API_SERVER=" + apiServerURL,
	}
	for _, env := range envs {
		rtm.Envs = append(envs, env)
	}

	cons := &console.Server{
		AppID:       appInfo.AppID,
		AppKey:      appInfo.AppKey,
		MasterKey:   appInfo.MasterKey,
		AppPort:     port,
		ConsolePort: consPort,
		Errors:      make(chan error),
	}

	rtm.Run()
	rtm.Watch(3 * time.Second)
	cons.Run()

	for {
		select {
		case err = <-cons.Errors:
			panic(err)
		case err = <-rtm.Errors:
			if _, ok := err.(*exec.ExitError); ok {
				return cli.NewExitError("", 1)
			}
			panic(err)
		}
	}
}
<|endoftext|>


<commit_before>package fit

import (
	"fmt"
	"reflect"
)

// Fit represents a decoded FIT file.
type Fit struct {
	// Header is the FIT file header.
	Header Header

	// CRC is the FIT file CRC.
	CRC uint16

	// Common messages for all FIT file types.
	FileId               FileIdMsg
	FileCreator          FileCreatorMsg
	TimestampCorrelation TimestampCorrelationMsg
	DeviceInfo           DeviceInfoMsg

	// UnknownMessages is a map that maps an unknown message number to how
	// many times the message was encountered during encoding.
	UnknownMessages map[MesgNum]int

	// UnknownFields is a map that maps an unknown field to how many times
	// the field was encountered during encoding.
	UnknownFields map[UnknownField]int

	msgAdder msgAdder

	activity        *ActivityFile
	device          *DeviceFile
	settings        *SettingsFile
	sport           *SportFile
	workout         *WorkoutFile
	course          *CourseFile
	schedules       *SchedulesFile
	weight          *WeightFile
	totals          *TotalsFile
	goals           *GoalsFile
	bloodPressure   *BloodPressureFile
	monitoringA     *MonitoringAFile
	activitySummary *ActivitySummaryFile
	monitoringDaily *MonitoringDailyFile
	monitoringB     *MonitoringBFile
	segment         *SegmentFile
	segmentList     *SegmentListFile
}

// UnknownField represents an unknown FIT message field not found in the
// official profile. It contains the global message and field number.
type UnknownField struct {
	MesgNum  MesgNum
	FieldNum byte
}

type msgAdder interface {
	add(reflect.Value)
}

func (f *Fit) add(msg reflect.Value) {
	x := msg.Interface()
	switch x.(type) {
	case FileIdMsg:
		f.FileId = x.(FileIdMsg)
	case FileCreatorMsg:
		f.FileCreator = x.(FileCreatorMsg)
	case TimestampCorrelationMsg:
		f.TimestampCorrelation = x.(TimestampCorrelationMsg)
	case DeviceInfoMsg:
		f.DeviceInfo = x.(DeviceInfoMsg)
	default:
		f.msgAdder.add(msg)
	}
}

// FileType returns the FIT file type.
func (f *Fit) FileType() File {
	return f.FileId.Type
}

type wrongFileTypeError struct {
	actual, requested File
}

func (e wrongFileTypeError) Error() string {
	return fmt.Sprintf("fit file is type is %v, not %v", e.actual, e.requested)
}

// Activity returns f's Activity file. An error is returned if the FIT file is
// not of type activity.
func (f *Fit) Activity() (*ActivityFile, error) {
	if !(f.FileId.Type == FileActivity) {
		return nil, wrongFileTypeError{f.FileId.Type, FileActivity}
	}
	return f.activity, nil
}

// Device returns f's Device file. An error is returned if the FIT file is
// not of type device.
func (f *Fit) Device() (*DeviceFile, error) {
	if !(f.FileId.Type == FileDevice) {
		return nil, wrongFileTypeError{f.FileId.Type, FileDevice}
	}
	return f.device, nil
}

// Settings returns f's Settings file. An error is returned if the FIT file is
// not of type settings.
func (f *Fit) Settings() (*SettingsFile, error) {
	if !(f.FileId.Type == FileSettings) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSettings}
	}
	return f.settings, nil
}

// Sport returns f's Sport file. An error is returned if the FIT file is
// not of type sport.
func (f *Fit) Sport() (*SportFile, error) {
	if !(f.FileId.Type == FileSport) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSport}
	}
	return f.sport, nil
}

// Workout returns f's Workout file. An error is returned if the FIT file is
// not of type workout.
func (f *Fit) Workout() (*WorkoutFile, error) {
	if !(f.FileId.Type == FileWorkout) {
		return nil, wrongFileTypeError{f.FileId.Type, FileWorkout}
	}
	return f.workout, nil
}

// Course returns f's Course file. An error is returned if the FIT file is
// not of type course.
func (f *Fit) Course() (*CourseFile, error) {
	if !(f.FileId.Type == FileCourse) {
		return nil, wrongFileTypeError{f.FileId.Type, FileCourse}
	}
	return f.course, nil
}

// Schedules returns f's Schedules file. An error is returned if the FIT file is
// not of type schedules.
func (f *Fit) Schedules() (*SchedulesFile, error) {
	if !(f.FileId.Type == FileSchedules) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSchedules}
	}
	return f.schedules, nil
}

// Weight returns f's Weight file. An error is returned if the FIT file is
// not of type weight.
func (f *Fit) Weight() (*WeightFile, error) {
	if !(f.FileId.Type == FileWeight) {
		return nil, wrongFileTypeError{f.FileId.Type, FileWeight}
	}
	return f.weight, nil
}

// Totals returns f's Totals file. An error is returned if the FIT file is
// not of type totals.
func (f *Fit) Totals() (*TotalsFile, error) {
	if !(f.FileId.Type == FileTotals) {
		return nil, wrongFileTypeError{f.FileId.Type, FileTotals}
	}
	return f.totals, nil
}

// Goals returns f's Goals file. An error is returned if the FIT file is
// not of type goals.
func (f *Fit) Goals() (*GoalsFile, error) {
	if !(f.FileId.Type == FileGoals) {
		return nil, wrongFileTypeError{f.FileId.Type, FileGoals}
	}
	return f.goals, nil
}

// BloodPressure returns f's BloodPressure file. An error is returned if the FIT file is
// not of type blood pressure.
func (f *Fit) BloodPressure() (*BloodPressureFile, error) {
	if !(f.FileId.Type == FileBloodPressure) {
		return nil, wrongFileTypeError{f.FileId.Type, FileBloodPressure}
	}
	return f.bloodPressure, nil
}

// MonitoringA returns f's MonitoringA file. An error is returned if the FIT file is
// not of type monitoring A.
func (f *Fit) MonitoringA() (*MonitoringAFile, error) {
	if !(f.FileId.Type == FileMonitoringA) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringA}
	}
	return f.monitoringA, nil
}

// ActivitySummary returns f's ActivitySummary file. An error is returned if the FIT file is
// not of type activity summary.
func (f *Fit) ActivitySummary() (*ActivitySummaryFile, error) {
	if !(f.FileId.Type == FileActivitySummary) {
		return nil, wrongFileTypeError{f.FileId.Type, FileActivitySummary}
	}
	return f.activitySummary, nil
}

// MonitoringDaily returns f's MonitoringDaily file. An error is returned if the FIT file is
// not of type monitoring daily.
func (f *Fit) MonitoringDaily() (*MonitoringDailyFile, error) {
	if !(f.FileId.Type == FileMonitoringDaily) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringDaily}
	}
	return f.monitoringDaily, nil
}

// MonitoringB returns f's MonitoringB file. An error is returned if the FIT file is
// not of type monitoring B.
func (f *Fit) MonitoringB() (*MonitoringBFile, error) {
	if !(f.FileId.Type == FileMonitoringB) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringB}
	}
	return f.monitoringB, nil
}

// Segment returns f's Segment file. An error is returned if the FIT file is
// not of type segment.
func (f *Fit) Segment() (*SegmentFile, error) {
	if !(f.FileId.Type == FileSegment) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSegment}
	}
	return f.segment, nil
}

// SegmentList returns f's SegmentList file. An error is returned if the FIT file is
// not of type segment list.
func (f *Fit) SegmentList() (*SegmentListFile, error) {
	if !(f.FileId.Type == FileSegmentList) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSegmentList}
	}
	return f.segmentList, nil
}
<commit_msg>fit: Use pointer to store common FIT messages<commit_after>package fit

import (
	"fmt"
	"reflect"
)

// Fit represents a decoded FIT file.
type Fit struct {
	// Header is the FIT file header.
	Header Header

	// CRC is the FIT file CRC.
	CRC uint16

	// FileId is a message required for all FIT files.
	FileId FileIdMsg

	// Common messages for all FIT file types.
	FileCreator          *FileCreatorMsg
	TimestampCorrelation *TimestampCorrelationMsg
	DeviceInfo           *DeviceInfoMsg

	// UnknownMessages is a map that maps an unknown message number to how
	// many times the message was encountered during encoding.
	UnknownMessages map[MesgNum]int

	// UnknownFields is a map that maps an unknown field to how many times
	// the field was encountered during encoding.
	UnknownFields map[UnknownField]int

	msgAdder msgAdder

	activity        *ActivityFile
	device          *DeviceFile
	settings        *SettingsFile
	sport           *SportFile
	workout         *WorkoutFile
	course          *CourseFile
	schedules       *SchedulesFile
	weight          *WeightFile
	totals          *TotalsFile
	goals           *GoalsFile
	bloodPressure   *BloodPressureFile
	monitoringA     *MonitoringAFile
	activitySummary *ActivitySummaryFile
	monitoringDaily *MonitoringDailyFile
	monitoringB     *MonitoringBFile
	segment         *SegmentFile
	segmentList     *SegmentListFile
}

// UnknownField represents an unknown FIT message field not found in the
// official profile. It contains the global message and field number.
type UnknownField struct {
	MesgNum  MesgNum
	FieldNum byte
}

type msgAdder interface {
	add(reflect.Value)
}

func (f *Fit) add(msg reflect.Value) {
	x := msg.Interface()
	switch x.(type) {
	case FileIdMsg:
		f.FileId = x.(FileIdMsg)
	case FileCreatorMsg:
		tmp := x.(FileCreatorMsg)
		f.FileCreator = &tmp
	case TimestampCorrelationMsg:
		tmp := x.(TimestampCorrelationMsg)
		f.TimestampCorrelation = &tmp
	case DeviceInfoMsg:
		tmp := x.(DeviceInfoMsg)
		f.DeviceInfo = &tmp
	default:
		f.msgAdder.add(msg)
	}
}

// FileType returns the FIT file type.
func (f *Fit) FileType() File {
	return f.FileId.Type
}

type wrongFileTypeError struct {
	actual, requested File
}

func (e wrongFileTypeError) Error() string {
	return fmt.Sprintf("fit file is type is %v, not %v", e.actual, e.requested)
}

// Activity returns f's Activity file. An error is returned if the FIT file is
// not of type activity.
func (f *Fit) Activity() (*ActivityFile, error) {
	if !(f.FileId.Type == FileActivity) {
		return nil, wrongFileTypeError{f.FileId.Type, FileActivity}
	}
	return f.activity, nil
}

// Device returns f's Device file. An error is returned if the FIT file is
// not of type device.
func (f *Fit) Device() (*DeviceFile, error) {
	if !(f.FileId.Type == FileDevice) {
		return nil, wrongFileTypeError{f.FileId.Type, FileDevice}
	}
	return f.device, nil
}

// Settings returns f's Settings file. An error is returned if the FIT file is
// not of type settings.
func (f *Fit) Settings() (*SettingsFile, error) {
	if !(f.FileId.Type == FileSettings) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSettings}
	}
	return f.settings, nil
}

// Sport returns f's Sport file. An error is returned if the FIT file is
// not of type sport.
func (f *Fit) Sport() (*SportFile, error) {
	if !(f.FileId.Type == FileSport) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSport}
	}
	return f.sport, nil
}

// Workout returns f's Workout file. An error is returned if the FIT file is
// not of type workout.
func (f *Fit) Workout() (*WorkoutFile, error) {
	if !(f.FileId.Type == FileWorkout) {
		return nil, wrongFileTypeError{f.FileId.Type, FileWorkout}
	}
	return f.workout, nil
}

// Course returns f's Course file. An error is returned if the FIT file is
// not of type course.
func (f *Fit) Course() (*CourseFile, error) {
	if !(f.FileId.Type == FileCourse) {
		return nil, wrongFileTypeError{f.FileId.Type, FileCourse}
	}
	return f.course, nil
}

// Schedules returns f's Schedules file. An error is returned if the FIT file is
// not of type schedules.
func (f *Fit) Schedules() (*SchedulesFile, error) {
	if !(f.FileId.Type == FileSchedules) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSchedules}
	}
	return f.schedules, nil
}

// Weight returns f's Weight file. An error is returned if the FIT file is
// not of type weight.
func (f *Fit) Weight() (*WeightFile, error) {
	if !(f.FileId.Type == FileWeight) {
		return nil, wrongFileTypeError{f.FileId.Type, FileWeight}
	}
	return f.weight, nil
}

// Totals returns f's Totals file. An error is returned if the FIT file is
// not of type totals.
func (f *Fit) Totals() (*TotalsFile, error) {
	if !(f.FileId.Type == FileTotals) {
		return nil, wrongFileTypeError{f.FileId.Type, FileTotals}
	}
	return f.totals, nil
}

// Goals returns f's Goals file. An error is returned if the FIT file is
// not of type goals.
func (f *Fit) Goals() (*GoalsFile, error) {
	if !(f.FileId.Type == FileGoals) {
		return nil, wrongFileTypeError{f.FileId.Type, FileGoals}
	}
	return f.goals, nil
}

// BloodPressure returns f's BloodPressure file. An error is returned if the FIT file is
// not of type blood pressure.
func (f *Fit) BloodPressure() (*BloodPressureFile, error) {
	if !(f.FileId.Type == FileBloodPressure) {
		return nil, wrongFileTypeError{f.FileId.Type, FileBloodPressure}
	}
	return f.bloodPressure, nil
}

// MonitoringA returns f's MonitoringA file. An error is returned if the FIT file is
// not of type monitoring A.
func (f *Fit) MonitoringA() (*MonitoringAFile, error) {
	if !(f.FileId.Type == FileMonitoringA) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringA}
	}
	return f.monitoringA, nil
}

// ActivitySummary returns f's ActivitySummary file. An error is returned if the FIT file is
// not of type activity summary.
func (f *Fit) ActivitySummary() (*ActivitySummaryFile, error) {
	if !(f.FileId.Type == FileActivitySummary) {
		return nil, wrongFileTypeError{f.FileId.Type, FileActivitySummary}
	}
	return f.activitySummary, nil
}

// MonitoringDaily returns f's MonitoringDaily file. An error is returned if the FIT file is
// not of type monitoring daily.
func (f *Fit) MonitoringDaily() (*MonitoringDailyFile, error) {
	if !(f.FileId.Type == FileMonitoringDaily) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringDaily}
	}
	return f.monitoringDaily, nil
}

// MonitoringB returns f's MonitoringB file. An error is returned if the FIT file is
// not of type monitoring B.
func (f *Fit) MonitoringB() (*MonitoringBFile, error) {
	if !(f.FileId.Type == FileMonitoringB) {
		return nil, wrongFileTypeError{f.FileId.Type, FileMonitoringB}
	}
	return f.monitoringB, nil
}

// Segment returns f's Segment file. An error is returned if the FIT file is
// not of type segment.
func (f *Fit) Segment() (*SegmentFile, error) {
	if !(f.FileId.Type == FileSegment) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSegment}
	}
	return f.segment, nil
}

// SegmentList returns f's SegmentList file. An error is returned if the FIT file is
// not of type segment list.
func (f *Fit) SegmentList() (*SegmentListFile, error) {
	if !(f.FileId.Type == FileSegmentList) {
		return nil, wrongFileTypeError{f.FileId.Type, FileSegmentList}
	}
	return f.segmentList, nil
}
<|endoftext|>


<commit_before>package horizon

import (
	"fmt"
	"github.com/jagregory/halgo"
	"github.com/stellar/go-horizon/db"
	"github.com/stellar/go-horizon/render"
	"github.com/stellar/go-horizon/render/problem"
	"github.com/zenazn/goji/web"
	"golang.org/x/net/context"
	"math"
	"net/http"
	"time"
)

type LedgerResource struct {
	halgo.Links
	Id               string    `json:"id"`
	Hash             string    `json:"hash"`
	PrevHash         string    `json:"prev_hash"`
	Sequence         int32     `json:"sequence"`
	TransactionCount int32     `json:"transaction_count"`
	OperationCount   int32     `json:"operation_count"`
	ClosedAt         time.Time `json:"closed_at"`
}

func (l LedgerResource) SseData() interface{} { return l }
func (l LedgerResource) Err() error           { return nil }

//TODO: return the paging token for the ledger, not the id
func (l LedgerResource) SseId() string { return l.Id }

func NewLedgerResource(in db.LedgerRecord) LedgerResource {
	self := fmt.Sprintf("/ledgers/%d", in.Sequence)
	return LedgerResource{
		Links: halgo.Links{}.
			Self(self).
			Link("transactions", self+"/transactions{?after}{?limit}{?order}").
			Link("operations", self+"/operations{?after}{?limit}{?order}").
			Link("effects", self+"/effects{?after}{?limit}{?order}"),
		Id:       in.LedgerHash,
		Hash:     in.LedgerHash,
		PrevHash: in.PreviousLedgerHash,
		Sequence: in.Sequence,
	}
}

func ledgerIndexAction(c web.C, w http.ResponseWriter, r *http.Request) {
	ah := &ActionHelper{c: c, r: r}
	app := ah.App()
	_, order, limit := ah.GetPagingParams()
	after := ah.GetInt32("after")

	if ah.Err() != nil {
		http.Error(w, ah.Err().Error(), http.StatusBadRequest)
		return
	}

	if after == 0 && order == "desc" {
		after = math.MaxInt32
	}

	query := db.LedgerPageQuery{app.HistoryQuery(), after, order, limit}

	render.Collection(w, r, query, ledgerRecordToResource)
}

func ledgerShowAction(c web.C, w http.ResponseWriter, r *http.Request) {
	ah := &ActionHelper{c: c, r: r}
	app := ah.App()
	sequence := ah.GetInt32("id")

	if ah.Err() != nil {
		problem.Render(context.TODO(), w, problem.NotFound)
		return
	}

	query := db.LedgerBySequenceQuery{app.HistoryQuery(), sequence}

	render.Single(w, r, query, ledgerRecordToResource)
}

func ledgerRecordToResource(record db.Record) (render.Resource, error) {
	return NewLedgerResource(record.(db.LedgerRecord)), nil
}
<commit_msg>Fix after param breakage<commit_after>package horizon

import (
	"fmt"
	"github.com/jagregory/halgo"
	"github.com/stellar/go-horizon/db"
	"github.com/stellar/go-horizon/render"
	"github.com/stellar/go-horizon/render/problem"
	"github.com/zenazn/goji/web"
	"golang.org/x/net/context"
	"math"
	"net/http"
	"time"
)

type LedgerResource struct {
	halgo.Links
	Id               string    `json:"id"`
	Hash             string    `json:"hash"`
	PrevHash         string    `json:"prev_hash"`
	Sequence         int32     `json:"sequence"`
	TransactionCount int32     `json:"transaction_count"`
	OperationCount   int32     `json:"operation_count"`
	ClosedAt         time.Time `json:"closed_at"`
}

func (l LedgerResource) SseData() interface{} { return l }
func (l LedgerResource) Err() error           { return nil }

//TODO: return the paging token for the ledger, not the id
func (l LedgerResource) SseId() string { return l.Id }

func NewLedgerResource(in db.LedgerRecord) LedgerResource {
	self := fmt.Sprintf("/ledgers/%d", in.Sequence)
	return LedgerResource{
		Links: halgo.Links{}.
			Self(self).
			Link("transactions", self+"/transactions{?after}{?limit}{?order}").
			Link("operations", self+"/operations{?after}{?limit}{?order}").
			Link("effects", self+"/effects{?after}{?limit}{?order}"),
		Id:       in.LedgerHash,
		Hash:     in.LedgerHash,
		PrevHash: in.PreviousLedgerHash,
		Sequence: in.Sequence,
	}
}

func ledgerIndexAction(c web.C, w http.ResponseWriter, r *http.Request) {
	ah := &ActionHelper{c: c, r: r}
	app := ah.App()
	_, order, limit := ah.GetPagingParams()
	after := ah.GetInt64("after")

	if ah.Err() != nil {
		http.Error(w, ah.Err().Error(), http.StatusBadRequest)
		return
	}

	if after == 0 && order == "desc" {
		after = math.MaxInt64
	}

	query := db.LedgerPageQuery{
		app.HistoryQuery(),
		after,
		order,
		limit,
	}

	render.Collection(w, r, query, ledgerRecordToResource)
}

func ledgerShowAction(c web.C, w http.ResponseWriter, r *http.Request) {
	ah := &ActionHelper{c: c, r: r}
	app := ah.App()
	sequence := ah.GetInt32("id")

	if ah.Err() != nil {
		problem.Render(context.TODO(), w, problem.NotFound)
		return
	}

	query := db.LedgerBySequenceQuery{app.HistoryQuery(), sequence}

	render.Single(w, r, query, ledgerRecordToResource)
}

func ledgerRecordToResource(record db.Record) (render.Resource, error) {
	return NewLedgerResource(record.(db.LedgerRecord)), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"code.google.com/p/go.tools/astutil"
)

func fixImports(f *ast.File) error {
	// refs are a set of possible package references currently unsatisified by imports.
	// first key: either base package (e.g. "fmt") or renamed package
	// second key: referenced package symbol (e.g. "Println")
	refs := make(map[string]map[string]bool)

	// decls are the current package imports. key is base package or renamed package.
	decls := make(map[string]*ast.ImportSpec)

	// collect potential uses of packages.
	var visitor visitFn
	visitor = visitFn(func(node ast.Node) ast.Visitor {
		if node == nil {
			return visitor
		}
		switch v := node.(type) {
		case *ast.ImportSpec:
			if v.Name != nil {
				decls[v.Name.Name] = v
			} else {
				local := path.Base(strings.Trim(v.Path.Value, `\"`))
				decls[local] = v
			}
		case *ast.SelectorExpr:
			xident, ok := v.X.(*ast.Ident)
			if !ok {
				break
			}
			if xident.Obj != nil {
				// if the parser can resolve it, it's not a package ref
				break
			}
			pkgName := xident.Name
			if refs[pkgName] == nil {
				refs[pkgName] = make(map[string]bool)
			}
			if decls[pkgName] == nil {
				refs[pkgName][v.Sel.Name] = true
			}
		}
		return visitor
	})
	ast.Walk(visitor, f)

	// Search for imports matching potential package references.
	searches := 0
	type result struct {
		ipath string
		err   error
	}
	results := make(chan result)
	for pkgName, symbols := range refs {
		if len(symbols) == 0 {
			continue // skip over packages already imported
		}
		go func(pkgName string, symbols map[string]bool) {
			ipath, err := findImport(pkgName, symbols)
			results <- result{ipath, err}
		}(pkgName, symbols)
		searches++
	}
	for i := 0; i < searches; i++ {
		result := <-results
		if result.err != nil {
			return result.err
		}
		if result.ipath != "" {
			astutil.AddImport(f, result.ipath)
		}
	}

	// Nil out any unused ImportSpecs, to be removed in following passes
	unusedImport := map[string]bool{}
	for pkg, is := range decls {
		if refs[pkg] == nil && pkg != "_" && pkg != "." {
			unusedImport[strings.Trim(is.Path.Value, `"`)] = true
		}
	}
	for ipath := range unusedImport {
		astutil.DeleteImport(f, ipath)
	}

	return nil
}

type pkg struct {
	importpath string // full pkg import path, e.g. "net/http"
	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
}

var pkgIndexOnce sync.Once

var pkgIndex struct {
	sync.Mutex
	m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
}

func loadPkgIndex() {
	pkgIndex.Lock()
	pkgIndex.m = make(map[string][]pkg)
	pkgIndex.Unlock()

	var wg sync.WaitGroup
	for _, path := range build.Default.SrcDirs() {
		f, err := os.Open(path)
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		children, err := f.Readdir(-1)
		f.Close()
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		for _, child := range children {
			if child.IsDir() {
				wg.Add(1)
				go func(path, name string) {
					defer wg.Done()
					loadPkg(&wg, path, name)
				}(path, child.Name())
			}
		}
	}
	wg.Wait()
}

var fset = token.NewFileSet()

func loadPkg(wg *sync.WaitGroup, root, importpath string) {
	shortName := path.Base(importpath)

	dir := filepath.Join(root, importpath)
	pkgIndex.Lock()
	pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
		importpath: importpath,
		dir:        dir,
	})
	pkgIndex.Unlock()

	pkgDir, err := os.Open(dir)
	if err != nil {
		return
	}
	children, err := pkgDir.Readdir(-1)
	pkgDir.Close()
	if err != nil {
		return
	}
	for _, child := range children {
		if strings.HasPrefix(child.Name(), ".") {
			continue
		}
		if child.IsDir() {
			wg.Add(1)
			go func(root, name string) {
				defer wg.Done()
				loadPkg(wg, root, name)
			}(root, filepath.Join(importpath, child.Name()))
		}
	}
}

// loadExports returns a list exports for a package.
func loadExports(dir string) map[string]bool {
	exports := make(map[string]bool)
	buildPkg, err := build.ImportDir(dir, 0)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not import %q: %v", dir, err)
		return nil
	}
	for _, file := range buildPkg.GoFiles {
		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not parse %q: %v", file, err)
			continue
		}
		for name := range f.Scope.Objects {
			if ast.IsExported(name) {
				exports[name] = true
			}
		}
	}
	return exports
}

// findImport searches for a package with the given symbols.
// If no package is found, findImport returns "".
// Declared as a variable rather than a function so goimports can be easily
// extended by adding a file with an init function.
var findImport = func(pkgName string, symbols map[string]bool) (string, error) {
	pkgIndexOnce.Do(loadPkgIndex)

	// Collect exports for packages with matching names.
	var wg sync.WaitGroup
	var pkgsMu sync.Mutex // guards pkgs
	// full importpath => exported symbol => True
	// e.g. "net/http" => "Client" => True
	pkgs := make(map[string]map[string]bool)
	pkgIndex.Lock()
	for _, pkg := range pkgIndex.m[pkgName] {
		wg.Add(1)
		go func(importpath, dir string) {
			defer wg.Done()
			exports := loadExports(dir)
			if exports != nil {
				pkgsMu.Lock()
				pkgs[importpath] = exports
				pkgsMu.Unlock()
			}
		}(pkg.importpath, pkg.dir)
	}
	pkgIndex.Unlock()
	wg.Wait()

	// Filter out packages missing required exported symbols.
	for symbol := range symbols {
		for importpath, exports := range pkgs {
			if !exports[symbol] {
				delete(pkgs, importpath)
			}
		}
	}
	for importpath := range pkgs {
		return importpath, nil
	}
	return "", nil
}

type visitFn func(node ast.Node) ast.Visitor

func (fn visitFn) Visit(node ast.Node) ast.Visitor {
	return fn(node)
}
<commit_msg>Prefer shortest match<commit_after>package main

import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"code.google.com/p/go.tools/astutil"
)

func fixImports(f *ast.File) error {
	// refs are a set of possible package references currently unsatisified by imports.
	// first key: either base package (e.g. "fmt") or renamed package
	// second key: referenced package symbol (e.g. "Println")
	refs := make(map[string]map[string]bool)

	// decls are the current package imports. key is base package or renamed package.
	decls := make(map[string]*ast.ImportSpec)

	// collect potential uses of packages.
	var visitor visitFn
	visitor = visitFn(func(node ast.Node) ast.Visitor {
		if node == nil {
			return visitor
		}
		switch v := node.(type) {
		case *ast.ImportSpec:
			if v.Name != nil {
				decls[v.Name.Name] = v
			} else {
				local := path.Base(strings.Trim(v.Path.Value, `\"`))
				decls[local] = v
			}
		case *ast.SelectorExpr:
			xident, ok := v.X.(*ast.Ident)
			if !ok {
				break
			}
			if xident.Obj != nil {
				// if the parser can resolve it, it's not a package ref
				break
			}
			pkgName := xident.Name
			if refs[pkgName] == nil {
				refs[pkgName] = make(map[string]bool)
			}
			if decls[pkgName] == nil {
				refs[pkgName][v.Sel.Name] = true
			}
		}
		return visitor
	})
	ast.Walk(visitor, f)

	// Search for imports matching potential package references.
	searches := 0
	type result struct {
		ipath string
		err   error
	}
	results := make(chan result)
	for pkgName, symbols := range refs {
		if len(symbols) == 0 {
			continue // skip over packages already imported
		}
		go func(pkgName string, symbols map[string]bool) {
			ipath, err := findImport(pkgName, symbols)
			results <- result{ipath, err}
		}(pkgName, symbols)
		searches++
	}
	for i := 0; i < searches; i++ {
		result := <-results
		if result.err != nil {
			return result.err
		}
		if result.ipath != "" {
			astutil.AddImport(f, result.ipath)
		}
	}

	// Nil out any unused ImportSpecs, to be removed in following passes
	unusedImport := map[string]bool{}
	for pkg, is := range decls {
		if refs[pkg] == nil && pkg != "_" && pkg != "." {
			unusedImport[strings.Trim(is.Path.Value, `"`)] = true
		}
	}
	for ipath := range unusedImport {
		astutil.DeleteImport(f, ipath)
	}

	return nil
}

type pkg struct {
	importpath string // full pkg import path, e.g. "net/http"
	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
}

var pkgIndexOnce sync.Once

var pkgIndex struct {
	sync.Mutex
	m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
}

func loadPkgIndex() {
	pkgIndex.Lock()
	pkgIndex.m = make(map[string][]pkg)
	pkgIndex.Unlock()

	var wg sync.WaitGroup
	for _, path := range build.Default.SrcDirs() {
		f, err := os.Open(path)
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		children, err := f.Readdir(-1)
		f.Close()
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		for _, child := range children {
			if child.IsDir() {
				wg.Add(1)
				go func(path, name string) {
					defer wg.Done()
					loadPkg(&wg, path, name)
				}(path, child.Name())
			}
		}
	}
	wg.Wait()
}

var fset = token.NewFileSet()

func loadPkg(wg *sync.WaitGroup, root, importpath string) {
	shortName := path.Base(importpath)

	dir := filepath.Join(root, importpath)
	pkgIndex.Lock()
	pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
		importpath: importpath,
		dir:        dir,
	})
	pkgIndex.Unlock()

	pkgDir, err := os.Open(dir)
	if err != nil {
		return
	}
	children, err := pkgDir.Readdir(-1)
	pkgDir.Close()
	if err != nil {
		return
	}
	for _, child := range children {
		if strings.HasPrefix(child.Name(), ".") {
			continue
		}
		if child.IsDir() {
			wg.Add(1)
			go func(root, name string) {
				defer wg.Done()
				loadPkg(wg, root, name)
			}(root, filepath.Join(importpath, child.Name()))
		}
	}
}

// loadExports returns a list exports for a package.
func loadExports(dir string) map[string]bool {
	exports := make(map[string]bool)
	buildPkg, err := build.ImportDir(dir, 0)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not import %q: %v", dir, err)
		return nil
	}
	for _, file := range buildPkg.GoFiles {
		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not parse %q: %v", file, err)
			continue
		}
		for name := range f.Scope.Objects {
			if ast.IsExported(name) {
				exports[name] = true
			}
		}
	}
	return exports
}

// findImport searches for a package with the given symbols.
// If no package is found, findImport returns "".
// Declared as a variable rather than a function so goimports can be easily
// extended by adding a file with an init function.
var findImport = func(pkgName string, symbols map[string]bool) (string, error) {
	pkgIndexOnce.Do(loadPkgIndex)

	// Collect exports for packages with matching names.
	var wg sync.WaitGroup
	var pkgsMu sync.Mutex // guards pkgs
	// full importpath => exported symbol => True
	// e.g. "net/http" => "Client" => True
	pkgs := make(map[string]map[string]bool)
	pkgIndex.Lock()
	for _, pkg := range pkgIndex.m[pkgName] {
		wg.Add(1)
		go func(importpath, dir string) {
			defer wg.Done()
			exports := loadExports(dir)
			if exports != nil {
				pkgsMu.Lock()
				pkgs[importpath] = exports
				pkgsMu.Unlock()
			}
		}(pkg.importpath, pkg.dir)
	}
	pkgIndex.Unlock()
	wg.Wait()

	// Filter out packages missing required exported symbols.
	for symbol := range symbols {
		for importpath, exports := range pkgs {
			if !exports[symbol] {
				delete(pkgs, importpath)
			}
		}
	}
	if len(pkgs) == 0 {
		return "", nil
	}

	// If there are multiple candidate packages, the shortest one wins.
	// This is a heuristic to prefer the standard library (e.g. "bytes")
	// over e.g. "github.com/foo/bar/bytes".
	shortest := ""
	for importPath := range pkgs {
		if shortest == "" || len(importPath) < len(shortest) {
			shortest = importPath
		}
	}
	return shortest, nil
}

type visitFn func(node ast.Node) ast.Visitor

func (fn visitFn) Visit(node ast.Node) ast.Visitor {
	return fn(node)
}
<|endoftext|>


<commit_before>package vcsclient

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path"

	"sort"

	"golang.org/x/tools/godoc/vfs"
	"sourcegraph.com/sourcegraph/go-vcs/vcs"
	"sourcegraph.com/sqs/pbtypes"
)

type FileSystem interface {
	vfs.FileSystem
	Get(path string) (*TreeEntry, error)
}

type repositoryFS struct {
	at   vcs.CommitID
	repo *repository
}

var _ FileSystem = &repositoryFS{}

func (fs *repositoryFS) Open(name string) (vfs.ReadSeekCloser, error) {
	e, err := fs.Get(name)
	if err != nil {
		return nil, err
	}

	return nopCloser{bytes.NewReader(e.Contents)}, nil
}

func (fs *repositoryFS) Lstat(path string) (os.FileInfo, error) {
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	return e.Stat()
}

func (fs *repositoryFS) Stat(path string) (os.FileInfo, error) {
	// TODO(sqs): follow symlinks (as Stat specification requires)
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	return e.Stat()
}

func (fs *repositoryFS) ReadDir(path string) ([]os.FileInfo, error) {
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	fis := make([]os.FileInfo, len(e.Entries))
	for i, e := range e.Entries {
		fis[i], err = e.Stat()
		if err != nil {
			return nil, err
		}
	}

	return fis, nil
}

func (fs *repositoryFS) String() string {
	return fmt.Sprintf("repository %s commit %s (client)", fs.repo.repoPath, fs.at)
}

// Get returns the whole TreeEntry struct for a tree entry.
func (fs *repositoryFS) Get(path string) (*TreeEntry, error) {
	url, err := fs.url(path, nil)
	if err != nil {
		return nil, err
	}

	req, err := fs.repo.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, err
	}

	var entry *TreeEntry
	_, err = fs.repo.client.Do(req, &entry)
	if err != nil {
		return nil, err
	}

	return entry, nil
}

// FileWithRange is returned by GetFileWithOptions and includes the
// returned file's TreeEntry as well as the actual range of lines and
// bytes returned (based on the GetFileOptions parameters). That is,
// if Start/EndLine are set in GetFileOptions, this struct's
// Start/EndByte will be set to the actual start and end bytes of
// those specified lines, and so on for the other fields in
// GetFileOptions.
type FileWithRange struct {
	*TreeEntry
	FileRange // range of actual returned tree entry contents within file
}

// GetFileWithOptions gets a file and allows additional configuration
// of the range to return, etc.
func (fs *repositoryFS) GetFileWithOptions(path string, opt GetFileOptions) (*FileWithRange, error) {
	url, err := fs.url(path, opt)
	if err != nil {
		return nil, err
	}

	req, err := fs.repo.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, err
	}

	var file *FileWithRange
	_, err = fs.repo.client.Do(req, &file)
	if err != nil {
		return nil, err
	}

	return file, nil
}

// A FileGetter is a repository FileSystem that can get files with
// extended range options (GetFileWithOptions).
//
// It's generally more efficient to use the client's implementation of
// the GetFileWithOptions method instead of calling the
// vcsclient.GetFileWithOptions func because the former causes only
// the requested range to be sent over the network, while the latter
// requests the whole file and narrows the range on the client side.
type FileGetter interface {
	GetFileWithOptions(path string, opt GetFileOptions) (*FileWithRange, error)
}

// GetFileWithOptions gets a file and observes the options specified
// in opt. If fs implements FileGetter, fs.GetFileWithOptions is
// called; otherwise the options are applied on the client side after
// fetching the whole file.
func GetFileWithOptions(fs vfs.FileSystem, path string, opt GetFileOptions) (*FileWithRange, error) {
	if fg, ok := fs.(FileGetter); ok {
		return fg.GetFileWithOptions(path, opt)
	}

	fi, err := fs.Lstat(path)
	if err != nil {
		return nil, err
	}

	e := newTreeEntry(fi)
	fwr := FileWithRange{TreeEntry: e}

	if fi.Mode().IsDir() {
		ee, err := readDir(fs, path, opt.RecurseSingleSubfolder, true)
		if err != nil {
			return nil, err
		}
		sort.Sort(TreeEntriesByTypeByName(ee))
		e.Entries = ee
	} else if fi.Mode().IsRegular() {
		f, err := fs.Open(path)
		if err != nil {
			return nil, err
		}
		defer f.Close()

		contents, err := ioutil.ReadAll(f)
		if err != nil {
			return nil, err
		}

		e.Contents = contents

		if empty := (GetFileOptions{}); opt != empty {
			fr, _, err := ComputeFileRange(contents, opt)
			if err != nil {
				return nil, err
			}

			// Trim to only requested range.
			e.Contents = e.Contents[fr.StartByte:fr.EndByte]
			fwr.FileRange = *fr
		}
	}

	return &fwr, nil
}

// readDir uses the passed vfs.FileSystem to read from starting at the base path.
// If recurseSingleSubfolder is true, it will descend and include sub-folders
// with a single sub-folder inside. first should always be set to true, other values are used internally.
func readDir(fs vfs.FileSystem, base string, recurseSingleSubfolder bool, first bool) ([]*TreeEntry, error) {
	entries, err := fs.ReadDir(base)
	if err != nil {
		return nil, err
	}
	if recurseSingleSubfolder && !first && !singleSubDir(entries) {
		return nil, nil
	}
	te := make([]*TreeEntry, len(entries))
	for i, fi := range entries {
		te[i] = newTreeEntry(fi)
		if fi.Mode().IsDir() && recurseSingleSubfolder {
			ee, err := readDir(fs, path.Join(base, fi.Name()), recurseSingleSubfolder, false)
			if err != nil {
				return nil, err
			}
			te[i].Entries = ee
		}
	}
	return te, nil
}

func singleSubDir(entries []os.FileInfo) bool {
	return len(entries) == 1 && entries[0].IsDir()
}

func newTreeEntry(fi os.FileInfo) *TreeEntry {
	e := &TreeEntry{
		Name:    fi.Name(),
		Size:    fi.Size(),
		ModTime: pbtypes.NewTimestamp(fi.ModTime()),
	}
	if fi.Mode().IsDir() {
		e.Type = DirEntry
	} else if fi.Mode().IsRegular() {
		e.Type = FileEntry
	} else if fi.Mode()&os.ModeSymlink != 0 {
		e.Type = SymlinkEntry
	}
	return e
}

// url generates the URL to RouteRepoTreeEntry for the given path (all other
// route vars are taken from repositoryFS fields).
func (fs *repositoryFS) url(path string, opt interface{}) (*url.URL, error) {
	return fs.repo.url(RouteRepoTreeEntry, map[string]string{
		"CommitID": string(fs.at),
		"Path":     path,
	}, opt)
}

type nopCloser struct {
	io.ReadSeeker
}

func (nc nopCloser) Close() error { return nil }
<commit_msg>limit recurseSingleSubfolder option to 20<commit_after>package vcsclient

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path"

	"sort"

	"golang.org/x/tools/godoc/vfs"
	"sourcegraph.com/sourcegraph/go-vcs/vcs"
	"sourcegraph.com/sqs/pbtypes"
)

type FileSystem interface {
	vfs.FileSystem
	Get(path string) (*TreeEntry, error)
}

type repositoryFS struct {
	at   vcs.CommitID
	repo *repository
}

var _ FileSystem = &repositoryFS{}

func (fs *repositoryFS) Open(name string) (vfs.ReadSeekCloser, error) {
	e, err := fs.Get(name)
	if err != nil {
		return nil, err
	}

	return nopCloser{bytes.NewReader(e.Contents)}, nil
}

func (fs *repositoryFS) Lstat(path string) (os.FileInfo, error) {
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	return e.Stat()
}

func (fs *repositoryFS) Stat(path string) (os.FileInfo, error) {
	// TODO(sqs): follow symlinks (as Stat specification requires)
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	return e.Stat()
}

func (fs *repositoryFS) ReadDir(path string) ([]os.FileInfo, error) {
	e, err := fs.Get(path)
	if err != nil {
		return nil, err
	}

	fis := make([]os.FileInfo, len(e.Entries))
	for i, e := range e.Entries {
		fis[i], err = e.Stat()
		if err != nil {
			return nil, err
		}
	}

	return fis, nil
}

func (fs *repositoryFS) String() string {
	return fmt.Sprintf("repository %s commit %s (client)", fs.repo.repoPath, fs.at)
}

// Get returns the whole TreeEntry struct for a tree entry.
func (fs *repositoryFS) Get(path string) (*TreeEntry, error) {
	url, err := fs.url(path, nil)
	if err != nil {
		return nil, err
	}

	req, err := fs.repo.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, err
	}

	var entry *TreeEntry
	_, err = fs.repo.client.Do(req, &entry)
	if err != nil {
		return nil, err
	}

	return entry, nil
}

// FileWithRange is returned by GetFileWithOptions and includes the
// returned file's TreeEntry as well as the actual range of lines and
// bytes returned (based on the GetFileOptions parameters). That is,
// if Start/EndLine are set in GetFileOptions, this struct's
// Start/EndByte will be set to the actual start and end bytes of
// those specified lines, and so on for the other fields in
// GetFileOptions.
type FileWithRange struct {
	*TreeEntry
	FileRange // range of actual returned tree entry contents within file
}

// GetFileWithOptions gets a file and allows additional configuration
// of the range to return, etc.
func (fs *repositoryFS) GetFileWithOptions(path string, opt GetFileOptions) (*FileWithRange, error) {
	url, err := fs.url(path, opt)
	if err != nil {
		return nil, err
	}

	req, err := fs.repo.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, err
	}

	var file *FileWithRange
	_, err = fs.repo.client.Do(req, &file)
	if err != nil {
		return nil, err
	}

	return file, nil
}

// A FileGetter is a repository FileSystem that can get files with
// extended range options (GetFileWithOptions).
//
// It's generally more efficient to use the client's implementation of
// the GetFileWithOptions method instead of calling the
// vcsclient.GetFileWithOptions func because the former causes only
// the requested range to be sent over the network, while the latter
// requests the whole file and narrows the range on the client side.
type FileGetter interface {
	GetFileWithOptions(path string, opt GetFileOptions) (*FileWithRange, error)
}

// GetFileWithOptions gets a file and observes the options specified
// in opt. If fs implements FileGetter, fs.GetFileWithOptions is
// called; otherwise the options are applied on the client side after
// fetching the whole file.
func GetFileWithOptions(fs vfs.FileSystem, path string, opt GetFileOptions) (*FileWithRange, error) {
	if fg, ok := fs.(FileGetter); ok {
		return fg.GetFileWithOptions(path, opt)
	}

	fi, err := fs.Lstat(path)
	if err != nil {
		return nil, err
	}

	e := newTreeEntry(fi)
	fwr := FileWithRange{TreeEntry: e}

	if fi.Mode().IsDir() {
		ee, err := readDir(fs, path, opt.RecurseSingleSubfolder, true)
		if err != nil {
			return nil, err
		}
		sort.Sort(TreeEntriesByTypeByName(ee))
		e.Entries = ee
	} else if fi.Mode().IsRegular() {
		f, err := fs.Open(path)
		if err != nil {
			return nil, err
		}
		defer f.Close()

		contents, err := ioutil.ReadAll(f)
		if err != nil {
			return nil, err
		}

		e.Contents = contents

		if empty := (GetFileOptions{}); opt != empty {
			fr, _, err := ComputeFileRange(contents, opt)
			if err != nil {
				return nil, err
			}

			// Trim to only requested range.
			e.Contents = e.Contents[fr.StartByte:fr.EndByte]
			fwr.FileRange = *fr
		}
	}

	return &fwr, nil
}

// readDir uses the passed vfs.FileSystem to read from starting at the base path.
// If recurseSingleSubfolder is true, it will descend and include sub-folders
// with a single sub-folder inside. first should always be set to true, other values are used internally.
func readDir(fs vfs.FileSystem, base string, recurseSingleSubfolder bool, first bool) ([]*TreeEntry, error) {
	entries, err := fs.ReadDir(base)
	if err != nil {
		return nil, err
	}
	if recurseSingleSubfolder && !first && !singleSubDir(entries) {
		return nil, nil
	}
	te := make([]*TreeEntry, len(entries))
	for i, fi := range entries {
		te[i] = newTreeEntry(fi)
	}

	if recurseSingleSubfolder {
		dirEntries := make(map[int]os.FileInfo)
		for i, fi := range entries {
			if len(dirEntries) > 20 { // expand at most 20 subdirectories
				break
			}

			if fi.Mode().IsDir() {
				dirEntries[i] = fi
			}
		}
		for i, fi := range dirEntries {
			ee, err := readDir(fs, path.Join(base, fi.Name()), recurseSingleSubfolder, false)
			if err != nil {
				return nil, err
			}
			te[i].Entries = ee
		}
	}

	return te, nil
}

func singleSubDir(entries []os.FileInfo) bool {
	return len(entries) == 1 && entries[0].IsDir()
}

func newTreeEntry(fi os.FileInfo) *TreeEntry {
	e := &TreeEntry{
		Name:    fi.Name(),
		Size:    fi.Size(),
		ModTime: pbtypes.NewTimestamp(fi.ModTime()),
	}
	if fi.Mode().IsDir() {
		e.Type = DirEntry
	} else if fi.Mode().IsRegular() {
		e.Type = FileEntry
	} else if fi.Mode()&os.ModeSymlink != 0 {
		e.Type = SymlinkEntry
	}
	return e
}

// url generates the URL to RouteRepoTreeEntry for the given path (all other
// route vars are taken from repositoryFS fields).
func (fs *repositoryFS) url(path string, opt interface{}) (*url.URL, error) {
	return fs.repo.url(RouteRepoTreeEntry, map[string]string{
		"CommitID": string(fs.at),
		"Path":     path,
	}, opt)
}

type nopCloser struct {
	io.ReadSeeker
}

func (nc nopCloser) Close() error { return nil }
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
	defaultPrompt    = ">> "
)

var originalSttyState bytes.Buffer
var winRows uint16
var winCols uint16

type winsize struct {
	rows, cols, xpixel, ypixel uint16
}

func getWinsize() winsize {
	ws := winsize{}
	syscall.Syscall(syscall.SYS_IOCTL,
		uintptr(0), uintptr(syscall.TIOCGWINSZ),
		uintptr(unsafe.Pointer(&ws)))
	return ws
}

func NewTTY() (t *TTY, err error) {
	fh, err := os.OpenFile("/dev/tty", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	t = &TTY{fh, defaultPrompt}
	return
}

type TTY struct {
	*os.File
	prompt string
}

func (t *TTY) getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = t.File
	cmd.Stdout = state
	return cmd.Run()
}

func (t *TTY) setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = t.File
	cmd.Stdout = t.File
	return cmd.Run()
}

// Clears the screen and sets the cursor to first row, first column
func (t *TTY) resetScreen() {
	// TODO: this is probably wrong since it does not remove the clutter from
	// the tty, but only pushes it to the top where its hidden
	// Instead of using reset screen, we need to go back and redraw the screen.
	fmt.Fprint(t.File, ansiEraseDisplay+ansiResetCursor)
}

// Print prompt with `in`
func (t *TTY) printPrompt(in []byte) {
	fmt.Fprintf(t.File, t.prompt+"%s", in)
}

// Positions the cursor after the prompt and `inlen` colums to the right
func (t *TTY) cursorAfterPrompt(inlen int) {
	t.setCursorPos(0, len(t.prompt)+inlen)
}

// Sets the cursor to `line` and `col`
func (t *TTY) setCursorPos(line int, col int) {
	fmt.Fprintf(t.File, "\033[%d;%dH", line+1, col+1)
}

func init() {
	ws := getWinsize()
	winRows = ws.rows
	winCols = ws.cols
}

func main() {
	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	// TODO: this needs to be run when the process is interrupted
	defer tty.setSttyState(&originalSttyState)

	tty.setSttyState(bytes.NewBufferString("cbreak -echo"))

	cmdTemplate := "ag {{}}"
	placeholder := "{{}}"

	printer := NewPrinter(tty, int(winCols), int(winRows)-3)

	runner := &Runner{
		printer:     printer,
		template:    cmdTemplate,
		placeholder: placeholder,
		buf:         new(bytes.Buffer),
	}

	input := make([]byte, 0)
	b := make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			fmt.Fprintf(tty, "\n")

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<commit_msg>Fix setting of stty state<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	ansiEraseDisplay = "\033[2J"
	ansiResetCursor  = "\033[H"
	carriageReturn   = "\015"
	defaultPrompt    = ">> "
)

var originalSttyState bytes.Buffer
var winRows uint16
var winCols uint16

type winsize struct {
	rows, cols, xpixel, ypixel uint16
}

func getWinsize() winsize {
	ws := winsize{}
	syscall.Syscall(syscall.SYS_IOCTL,
		uintptr(0), uintptr(syscall.TIOCGWINSZ),
		uintptr(unsafe.Pointer(&ws)))
	return ws
}

func NewTTY() (t *TTY, err error) {
	fh, err := os.OpenFile("/dev/tty", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	t = &TTY{fh, defaultPrompt}
	return
}

type TTY struct {
	*os.File
	prompt string
}

func (t *TTY) getSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", "-g")
	cmd.Stdin = t.File
	cmd.Stdout = state
	return cmd.Run()
}

func (t *TTY) setSttyState(state *bytes.Buffer) (err error) {
	cmd := exec.Command("stty", state.String())
	cmd.Stdin = t.File
	cmd.Stdout = t.File
	return cmd.Run()
}

// Clears the screen and sets the cursor to first row, first column
func (t *TTY) resetScreen() {
	// TODO: this is probably wrong since it does not remove the clutter from
	// the tty, but only pushes it to the top where its hidden
	// Instead of using reset screen, we need to go back and redraw the screen.
	fmt.Fprint(t.File, ansiEraseDisplay+ansiResetCursor)
}

// Print prompt with `in`
func (t *TTY) printPrompt(in []byte) {
	fmt.Fprintf(t.File, t.prompt+"%s", in)
}

// Positions the cursor after the prompt and `inlen` colums to the right
func (t *TTY) cursorAfterPrompt(inlen int) {
	t.setCursorPos(0, len(t.prompt)+inlen)
}

// Sets the cursor to `line` and `col`
func (t *TTY) setCursorPos(line int, col int) {
	fmt.Fprintf(t.File, "\033[%d;%dH", line+1, col+1)
}

func init() {
	ws := getWinsize()
	winRows = ws.rows
	winCols = ws.cols
}

func main() {
	tty, err := NewTTY()
	if err != nil {
		log.Fatal(err)
	}

	err = tty.getSttyState(&originalSttyState)
	if err != nil {
		log.Fatal(err)
	}
	// TODO: this needs to be run when the process is interrupted
	defer tty.setSttyState(&originalSttyState)

	tty.setSttyState(bytes.NewBufferString("cbreak"))
	tty.setSttyState(bytes.NewBufferString("-echo"))

	cmdTemplate := "ag {{}}"
	placeholder := "{{}}"

	printer := NewPrinter(tty, int(winCols), int(winRows)-3)

	runner := &Runner{
		printer:     printer,
		template:    cmdTemplate,
		placeholder: placeholder,
		buf:         new(bytes.Buffer),
	}

	input := make([]byte, 0)
	b := make([]byte, 1)

	for {
		tty.resetScreen()
		tty.printPrompt(input[:len(input)])

		if len(input) > 0 {
			runner.killCurrent()

			fmt.Fprintf(tty, "\n")

			go func() {
				runner.runWithInput(input[:len(input)])
				tty.cursorAfterPrompt(len(input))
			}()
		}

		tty.Read(b)
		switch b[0] {
		case 127:
			// Backspace
			if len(input) > 1 {
				input = input[:len(input)-1]
			} else if len(input) == 1 {
				input = nil
			}
		case 4, 10, 13:
			// Ctrl-D, line feed, carriage return
			tty.resetScreen()
			runner.writeCmdStdout(os.Stdout)
			return
		default:
			// TODO: Default is wrong here. Only append printable characters to
			// input
			input = append(input, b...)
		}
	}
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package caddy

import (
	"strings"

	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/handler/tool"
	"github.com/admpub/nging/application/library/common"
	"github.com/admpub/nging/application/model"
	"github.com/admpub/tail"
	ua "github.com/admpub/useragent"
	"github.com/webx-top/echo"
)

func init() {
	handler.RegisterToGroup(`/caddy`, func(g echo.RouteRegister) {
		e := handler.Echo()
		g.Route(`GET,POST`, `/restart`, e.MetaHandler(echo.H{`name`: `重启Caddy服务`}, Restart))
		g.Route(`GET,POST`, `/stop`, e.MetaHandler(echo.H{`name`: `停止Caddy服务`}, Stop))
		g.Route(`GET,POST`, `/log`, e.MetaHandler(echo.H{`name`: `显示Caddy日志`}, Log))
		g.Route(`GET`, `/addon_form`, e.MetaHandler(echo.H{`name`: `Caddy配置表单`}, AddonForm))
		g.Route(`GET`, `/vhost`, e.MetaHandler(echo.H{`name`: `网站列表`}, VhostIndex))
		g.Route(`GET,POST`, `/vhost_add`, e.MetaHandler(echo.H{`name`: `添加网站`}, VhostAdd))
		g.Route(`GET,POST`, `/vhost_edit`, e.MetaHandler(echo.H{`name`: `编辑网站`}, VhostEdit))
		g.Route(`GET,POST`, `/vhost_delete`, e.MetaHandler(echo.H{`name`: `删除网站`}, VhostDelete))
		g.Route(`GET,POST`, `/vhost_file`, e.MetaHandler(echo.H{`name`: `管理网站文件`}, VhostFile))
		g.Route(`GET`, `/vhost_build`, e.MetaHandler(echo.H{`name`: `生成Caddyfile`}, Vhostbuild))
		g.Route(`GET,POST`, `/log_show`, LogShow)
		g.Route(`GET,POST`, `/vhost_log`, VhostLog)

		g.Route(`GET`, `/group`, Group)
		g.Route(`GET,POST`, `/group_add`, GroupAdd)
		g.Route(`GET,POST`, `/group_edit`, GroupEdit)
		g.Route(`GET,POST`, `/group_delete`, GroupDelete)
	})

	common.LogParsers[`access`] = func(line *tail.Line) (interface{}, error) {
		logM := model.NewAccessLog(nil)
		err := logM.Parse(line.Text)
		res := logM.AsMap()
		realIP := logM.RemoteAddr
		if len(logM.XForwardFor) > 0 {
			realIP = strings.TrimSpace(strings.SplitN(logM.XForwardFor, ",", 2)[0])
		} else if len(logM.XRealIp) > 0 {
			realIP = logM.XRealIp
		}
		delete(res, `Id`)
		delete(res, `Created`)
		delete(res, `VhostId`)
		delete(res, `RemoteAddr`)
		delete(res, `XForwardFor`)
		delete(res, `XRealIp`)
		delete(res, `Minute`)
		delete(res, `HitStatus`)
		delete(res, `Host`)
		delete(res, `LocalAddr`)
		if ipInfo, _err := tool.IPInfo(realIP); _err == nil {
			res[`ClientRegion`] = ipInfo.Country + " - " + ipInfo.Region + " - " + ipInfo.Province + " - " + ipInfo.City + " " + ipInfo.ISP + " (" + realIP + ")"
		} else {
			res[`ClientRegion`] = realIP
		}
		infoUA := ua.Parse(logM.UserAgent)
		if len(infoUA.OSVersion) > 0 {
			infoUA.OS += ` (` + infoUA.OSVersion + `)`
		}
		res[`OS`] = infoUA.OS
		if len(infoUA.Version) > 0 {
			logM.BrowerName += ` (` + infoUA.Version + `)`
		}
		return res, err
	}
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package caddy

import (
	"strings"

	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/handler/tool"
	"github.com/admpub/nging/application/library/common"
	"github.com/admpub/nging/application/model"
	"github.com/admpub/tail"
	ua "github.com/admpub/useragent"
	"github.com/webx-top/echo"
)

func init() {
	handler.RegisterToGroup(`/caddy`, func(g echo.RouteRegister) {
		e := handler.Echo()
		g.Route(`GET,POST`, `/restart`, e.MetaHandler(echo.H{`name`: `重启Caddy服务`}, Restart))
		g.Route(`GET,POST`, `/stop`, e.MetaHandler(echo.H{`name`: `停止Caddy服务`}, Stop))
		g.Route(`GET,POST`, `/log`, e.MetaHandler(echo.H{`name`: `显示Caddy日志`}, Log))
		g.Route(`GET`, `/addon_form`, e.MetaHandler(echo.H{`name`: `Caddy配置表单`}, AddonForm))
		g.Route(`GET`, `/vhost`, e.MetaHandler(echo.H{`name`: `网站列表`}, VhostIndex))
		g.Route(`GET,POST`, `/vhost_add`, e.MetaHandler(echo.H{`name`: `添加网站`}, VhostAdd))
		g.Route(`GET,POST`, `/vhost_edit`, e.MetaHandler(echo.H{`name`: `编辑网站`}, VhostEdit))
		g.Route(`GET,POST`, `/vhost_delete`, e.MetaHandler(echo.H{`name`: `删除网站`}, VhostDelete))
		g.Route(`GET,POST`, `/vhost_file`, e.MetaHandler(echo.H{`name`: `管理网站文件`}, VhostFile))
		g.Route(`GET`, `/vhost_build`, e.MetaHandler(echo.H{`name`: `生成Caddyfile`}, Vhostbuild))
		g.Route(`GET,POST`, `/log_show`, LogShow)
		g.Route(`GET,POST`, `/vhost_log`, VhostLog)

		g.Route(`GET`, `/group`, Group)
		g.Route(`GET,POST`, `/group_add`, GroupAdd)
		g.Route(`GET,POST`, `/group_edit`, GroupEdit)
		g.Route(`GET,POST`, `/group_delete`, GroupDelete)
	})

	common.LogParsers[`access`] = func(line *tail.Line) (interface{}, error) {
		logM := model.NewAccessLog(nil)
		err := logM.Parse(line.Text)
		res := logM.AsMap()
		realIP := logM.RemoteAddr
		if len(logM.XForwardFor) > 0 {
			realIP = strings.TrimSpace(strings.SplitN(logM.XForwardFor, ",", 2)[0])
		} else if len(logM.XRealIp) > 0 {
			realIP = logM.XRealIp
		}
		delete(res, `Id`)
		delete(res, `Created`)
		delete(res, `VhostId`)
		delete(res, `RemoteAddr`)
		delete(res, `XForwardFor`)
		delete(res, `XRealIp`)
		delete(res, `Minute`)
		delete(res, `HitStatus`)
		delete(res, `Host`)
		delete(res, `LocalAddr`)
		if ipInfo, _err := tool.IPInfo(realIP); _err == nil {
			res[`ClientRegion`] = ipInfo.Country + " - " + ipInfo.Region + " - " + ipInfo.Province + " - " + ipInfo.City + " " + ipInfo.ISP + " (" + realIP + ")"
		} else {
			res[`ClientRegion`] = realIP
		}
		infoUA := ua.Parse(logM.UserAgent)
		if len(infoUA.OSVersion) > 0 {
			infoUA.OS += ` (` + infoUA.OSVersion + `)`
		}
		res[`OS`] = infoUA.OS
		if len(infoUA.Version) > 0 {
			logM.BrowerName += ` (` + infoUA.Version + `)`
		}
		res[`BrowerName`] = logM.BrowerName
		return res, err
	}
}
<|endoftext|>