<commit_before>package main

/*
	This program runs all unit tests in the repository.
*/

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/fileutil"
	"go.skia.org/infra/go/timer"
)

const (
	// This gets filled out and printed when a test fails.
	TEST_FAILURE = `
============================= TEST FAILURE =============================
Test: %s

Command: %s

Error:
%s

Full output:
------------------------------------------------------------------------
%s
------------------------------------------------------------------------
`
)

// flags
var (
	short = flag.Bool("short", false, "Whether or not to run the short version of the tests.")
)

var (
	// Error message shown when a required executable is not installed.
	ERR_NEED_INSTALL = "%s failed to run! Is it installed? Error: %v"

	// Directories in which to run "go test" and whether or not to invoke
	// with --alsologtostderr.
	GO_TEST_DIRS_AND_VERBOSITY = map[string]bool{
		".":               false,
		"alertserver":     false,
		"autoroll":        true,
		"build_scheduler": false,
		"ct":              false,
		"datahopper":      false,
		"fuzzer":          false,
		"golden":          false,
		"perf":            false,
		"status":          false,
	}

	// Directories with these names are skipped when searching for tests.
	NO_CRAWL_DIR_NAMES = []string{
		".git",
		"bower_components",
		"third_party",
		"node_modules",
	}

	// Directories with these paths, relative to the checkout root, are
	// skipped when searching for tests.
	NO_CRAWL_REL_PATHS = []string{
		"common",
	}

	POLYMER_PATHS = []string{
		"res/imp/9",
		"alertserver/res/imp/9",
		"autoroll/res/imp",
		"fuzzer/res/imp",
		"status/res/imp/9",
	}
)

// cmdTest returns a test which runs a command and fails if the command fails.
func cmdTest(cmd []string, cwd, name string) *test {
	return &test{
		Name: name,
		Cmd:  strings.Join(cmd, " "),
		run: func() (error, string) {
			command := exec.Command(cmd[0], cmd[1:]...)
			if cwd != "" {
				command.Dir = cwd
			}
			output, err := command.Output()
			if err != nil {
				if _, err2 := exec.LookPath(cmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, cmd[0], err), string(output)
				}
			}
			return err, string(output)
		},
	}
}

func polylintTest(cwd, fileName string) *test {
	cmd := []string{"polylint", "--no-recursion", "--root", cwd, "--input", fileName}
	return &test{
		Name: fmt.Sprintf("polylint in %s", filepath.Join(cwd, fileName)),
		Cmd:  strings.Join(cmd, " "),
		run: func() (error, string) {
			command := exec.Command(cmd[0], cmd[1:]...)
			outputBytes, err := command.Output()
			if err != nil {
				if _, err2 := exec.LookPath(cmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, cmd[0], err), string(outputBytes)
				}
				return err, string(outputBytes)
			}

			unresolvedProblems := ""
			count := 0

			for s := bufio.NewScanner(bytes.NewBuffer(outputBytes)); s.Scan(); {
				badFileLine := s.Text()
				if !s.Scan() {
					return fmt.Errorf("Unexpected end of polylint output after %q:\n%s", badFileLine, string(outputBytes)), string(outputBytes)
				}
				problemLine := s.Text()
				if !strings.Contains(unresolvedProblems, badFileLine) {
					unresolvedProblems = fmt.Sprintf("%s\n%s\n%s", unresolvedProblems, badFileLine, problemLine)
					count++
				}
			}

			if unresolvedProblems == "" {
				return nil, ""
			}
			return fmt.Errorf("%d unresolved polylint problems:\n%s\n", count, unresolvedProblems), ""
		},
	}
}

// buildPolymerFolder runs the Makefile in the given folder.  This sets up the symbolic links so dependencies can be located for polylint.
func buildPolymerFolder(cwd string) error {
	cmd := cmdTest([]string{"make"}, cwd, fmt.Sprintf("Polymer build in %s", cwd))
	return cmd.Run()
}

// polylintTestsForDir builds the folder once and then returns a list of tests for each Polymer file in the directory.  If the build fails, a dummy test is returned that prints an error message.
func polylintTestsForDir(cwd string, fileNames ...string) []*test {
	if err := buildPolymerFolder(cwd); err != nil {
		return []*test{
			&test{
				Name: filepath.Join(cwd, "make"),
				Cmd:  filepath.Join(cwd, "make"),
				run: func() (error, string) {
					return fmt.Errorf("Could not build Polymer files in %s: %s", cwd, err), ""
				},
			},
		}
	}
	tests := make([]*test, 0, len(fileNames))
	for _, name := range fileNames {
		tests = append(tests, polylintTest(cwd, name))
	}
	return tests
}

// findPolymerFiles returns all files that probably contain polymer content (i.e. end with sk.html) in a given directory.
func findPolymerFiles(dirPath string) []string {
	dir := fileutil.MustOpen(dirPath)
	files := make([]string, 0)
	for _, info := range fileutil.MustReaddir(dir) {
		if n := info.Name(); strings.HasSuffix(info.Name(), "sk.html") {
			files = append(files, n)
		}
	}
	return files
}

//polylintTests creates a list of *test from all directories in POLYMER_PATHS
func polylintTests() []*test {
	tests := make([]*test, 0)
	for _, path := range POLYMER_PATHS {
		tests = append(tests, polylintTestsForDir(path, findPolymerFiles(path)...)...)
	}
	return tests
}

// goTest returns a test which runs `go test` in the given cwd.
func goTest(cwd string, extraLog bool) *test {
	cmd := []string{"go", "test", "-v", "./go/...", "-parallel", "1"}
	if extraLog {
		cmd = append(cmd, "--alsologtostderr")
	}
	if *short {
		cmd = append(cmd, "-test.short")
	}
	return cmdTest(cmd, cwd, fmt.Sprintf("go tests in %s", cwd))
}

// pythonTest returns a test which runs the given Python script and fails if
// the script fails.
func pythonTest(testPath string) *test {
	return cmdTest([]string{"python", testPath}, ".", path.Base(testPath))
}

// test is a struct which represents a single test to run.
type test struct {
	Name string
	Cmd  string
	run  func() (error, string)
}

// Run executes the function for the given test and returns an error if it fails.
func (t test) Run() error {
	defer timer.New(t.Name).Stop()
	err, output := t.run()
	if err != nil {
		return fmt.Errorf(TEST_FAILURE, t.Name, t.Cmd, err, output)
	}
	return nil
}

// Find and run tests.
func main() {
	defer common.LogPanic()
	common.Init()

	defer timer.New("Finished").Stop()

	_, filename, _, _ := runtime.Caller(0)
	rootDir := path.Dir(filename)

	// If we are running full tests make sure we have the latest
	// pdfium_test installed.
	if !*short {
		glog.Info("Installing pdfium_test if necessary.")
		pdfiumInstall := path.Join(rootDir, "pdfium", "install_pdfium.sh")
		if err := exec.Command(pdfiumInstall).Run(); err != nil {
			glog.Fatalf("Failed to install pdfium_test: %v", err)
		}
		glog.Info("Latest pdfium_test installed successfully.")
	}

	// Gather all of the tests to run.
	glog.Info("Searching for tests.")
	tests := []*test{}

	// Search for Python tests in the repo.
	if err := filepath.Walk(rootDir, func(p string, info os.FileInfo, err error) error {
		basename := path.Base(p)
		// Skip some directories.
		if info.IsDir() {
			for _, skip := range NO_CRAWL_DIR_NAMES {
				if basename == skip {
					return filepath.SkipDir
				}
			}
			for _, skip := range NO_CRAWL_REL_PATHS {
				if p == path.Join(rootDir, skip) {
					return filepath.SkipDir
				}
			}
		}
		if strings.HasSuffix(basename, "_test.py") {
			tests = append(tests, pythonTest(p))
		}
		return nil
	}); err != nil {
		glog.Fatal(err)
	}

	// Go tests.
	for goDir, extraLog := range GO_TEST_DIRS_AND_VERBOSITY {
		tests = append(tests, goTest(goDir, extraLog))
	}

	// Other tests.
	tests = append(tests, cmdTest([]string{"go", "vet", "./..."}, ".", "go vet"))
	tests = append(tests, cmdTest([]string{"errcheck", "-ignore", ":Close", "go.skia.org/infra/..."}, ".", "errcheck"))
	tests = append(tests, polylintTests()...)

	goimportsCmd := []string{"goimports", "-l", "."}
	tests = append(tests, &test{
		Name: "goimports",
		Cmd:  strings.Join(goimportsCmd, " "),
		run: func() (error, string) {
			command := exec.Command(goimportsCmd[0], goimportsCmd[1:]...)
			output, err := command.Output()
			outStr := strings.Trim(string(output), "\n")
			if err != nil {
				if _, err2 := exec.LookPath(goimportsCmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, goimportsCmd[0], err), outStr
				}
				return err, outStr
			}
			diffFiles := strings.Split(outStr, "\n")
			if len(diffFiles) > 0 && !(len(diffFiles) == 1 && diffFiles[0] == "") {
				return fmt.Errorf("goimports found diffs in the following files:\n  - %s", strings.Join(diffFiles, ",\n  - ")), outStr
			}
			return nil, ""

		},
	})

	// Run the tests.
	glog.Infof("Found %d tests.", len(tests))
	var mutex sync.Mutex
	errors := map[string]error{}
	var wg sync.WaitGroup
	for _, t := range tests {
		wg.Add(1)
		go func(t *test) {
			defer wg.Done()
			if err := t.Run(); err != nil {
				mutex.Lock()
				errors[t.Name] = err
				mutex.Unlock()
			}
		}(t)
	}
	wg.Wait()
	if len(errors) > 0 {
		for _, e := range errors {
			glog.Error(e)
		}
		os.Exit(1)
	}
	glog.Info("All tests succeeded.")
}
<commit_msg>Try to fix goimports<commit_after>package main

/*
	This program runs all unit tests in the repository.
*/

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/fileutil"
	"go.skia.org/infra/go/timer"
)

const (
	// This gets filled out and printed when a test fails.
	TEST_FAILURE = `
============================= TEST FAILURE =============================
Test: %s

Command: %s

Error:
%s

Full output:
------------------------------------------------------------------------
%s
------------------------------------------------------------------------
`
)

// flags
var (
	short = flag.Bool("short", false, "Whether or not to run the short version of the tests.")
)

var (
	// Error message shown when a required executable is not installed.
	ERR_NEED_INSTALL = "%s failed to run! Is it installed? Error: %v"

	// Directories in which to run "go test" and whether or not to invoke
	// with --alsologtostderr.
	GO_TEST_DIRS_AND_VERBOSITY = map[string]bool{
		".":               false,
		"alertserver":     false,
		"autoroll":        true,
		"build_scheduler": false,
		"ct":              false,
		"datahopper":      false,
		"fuzzer":          false,
		"golden":          false,
		"perf":            false,
		"status":          false,
	}

	// Directories with these names are skipped when searching for tests.
	NO_CRAWL_DIR_NAMES = []string{
		".git",
		"bower_components",
		"third_party",
		"node_modules",
	}

	// Directories with these paths, relative to the checkout root, are
	// skipped when searching for tests.
	NO_CRAWL_REL_PATHS = []string{
		"common",
	}

	POLYMER_PATHS = []string{
		"res/imp/9",
		"alertserver/res/imp/9",
		"autoroll/res/imp",
		"fuzzer/res/imp",
		"status/res/imp/9",
	}
)

// cmdTest returns a test which runs a command and fails if the command fails.
func cmdTest(cmd []string, cwd, name string) *test {
	return &test{
		Name: name,
		Cmd:  strings.Join(cmd, " "),
		run: func() (error, string) {
			command := exec.Command(cmd[0], cmd[1:]...)
			if cwd != "" {
				command.Dir = cwd
			}
			output, err := command.Output()
			if err != nil {
				if _, err2 := exec.LookPath(cmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, cmd[0], err), string(output)
				}
			}
			return err, string(output)
		},
	}
}

func polylintTest(cwd, fileName string) *test {
	cmd := []string{"polylint", "--no-recursion", "--root", cwd, "--input", fileName}
	return &test{
		Name: fmt.Sprintf("polylint in %s", filepath.Join(cwd, fileName)),
		Cmd:  strings.Join(cmd, " "),
		run: func() (error, string) {
			command := exec.Command(cmd[0], cmd[1:]...)
			outputBytes, err := command.Output()
			if err != nil {
				if _, err2 := exec.LookPath(cmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, cmd[0], err), string(outputBytes)
				}
				return err, string(outputBytes)
			}

			unresolvedProblems := ""
			count := 0

			for s := bufio.NewScanner(bytes.NewBuffer(outputBytes)); s.Scan(); {
				badFileLine := s.Text()
				if !s.Scan() {
					return fmt.Errorf("Unexpected end of polylint output after %q:\n%s", badFileLine, string(outputBytes)), string(outputBytes)
				}
				problemLine := s.Text()
				if !strings.Contains(unresolvedProblems, badFileLine) {
					unresolvedProblems = fmt.Sprintf("%s\n%s\n%s", unresolvedProblems, badFileLine, problemLine)
					count++
				}
			}

			if unresolvedProblems == "" {
				return nil, ""
			}
			return fmt.Errorf("%d unresolved polylint problems:\n%s\n", count, unresolvedProblems), ""
		},
	}
}

// buildPolymerFolder runs the Makefile in the given folder.  This sets up the symbolic links so dependencies can be located for polylint.
func buildPolymerFolder(cwd string) error {
	cmd := cmdTest([]string{"make"}, cwd, fmt.Sprintf("Polymer build in %s", cwd))
	return cmd.Run()
}

// polylintTestsForDir builds the folder once and then returns a list of tests for each Polymer file in the directory.  If the build fails, a dummy test is returned that prints an error message.
func polylintTestsForDir(cwd string, fileNames ...string) []*test {
	if err := buildPolymerFolder(cwd); err != nil {
		return []*test{
			&test{
				Name: filepath.Join(cwd, "make"),
				Cmd:  filepath.Join(cwd, "make"),
				run: func() (error, string) {
					return fmt.Errorf("Could not build Polymer files in %s: %s", cwd, err), ""
				},
			},
		}
	}
	tests := make([]*test, 0, len(fileNames))
	for _, name := range fileNames {
		tests = append(tests, polylintTest(cwd, name))
	}
	return tests
}

// findPolymerFiles returns all files that probably contain polymer content (i.e. end with sk.html) in a given directory.
func findPolymerFiles(dirPath string) []string {
	dir := fileutil.MustOpen(dirPath)
	files := make([]string, 0)
	for _, info := range fileutil.MustReaddir(dir) {
		if n := info.Name(); strings.HasSuffix(info.Name(), "sk.html") {
			files = append(files, n)
		}
	}
	return files
}

//polylintTests creates a list of *test from all directories in POLYMER_PATHS
func polylintTests() []*test {
	tests := make([]*test, 0)
	for _, path := range POLYMER_PATHS {
		tests = append(tests, polylintTestsForDir(path, findPolymerFiles(path)...)...)
	}
	return tests
}

// goTest returns a test which runs `go test` in the given cwd.
func goTest(cwd string, extraLog bool) *test {
	cmd := []string{"go", "test", "-v", "./go/...", "-parallel", "1"}
	if extraLog {
		cmd = append(cmd, "--alsologtostderr")
	}
	if *short {
		cmd = append(cmd, "-test.short")
	}
	return cmdTest(cmd, cwd, fmt.Sprintf("go tests in %s", cwd))
}

// pythonTest returns a test which runs the given Python script and fails if
// the script fails.
func pythonTest(testPath string) *test {
	return cmdTest([]string{"python", testPath}, ".", path.Base(testPath))
}

// test is a struct which represents a single test to run.
type test struct {
	Name string
	Cmd  string
	run  func() (error, string)
}

// Run executes the function for the given test and returns an error if it fails.
func (t test) Run() error {
	defer timer.New(t.Name).Stop()
	err, output := t.run()
	if err != nil {
		return fmt.Errorf(TEST_FAILURE, t.Name, t.Cmd, err, output)
	}
	return nil
}

// Find and run tests.
func main() {
	defer common.LogPanic()
	common.Init()

	defer timer.New("Finished").Stop()

	_, filename, _, _ := runtime.Caller(0)
	rootDir := path.Dir(filename)

	// If we are running full tests make sure we have the latest
	// pdfium_test installed.
	if !*short {
		glog.Info("Installing pdfium_test if necessary.")
		pdfiumInstall := path.Join(rootDir, "pdfium", "install_pdfium.sh")
		if err := exec.Command(pdfiumInstall).Run(); err != nil {
			glog.Fatalf("Failed to install pdfium_test: %v", err)
		}
		glog.Info("Latest pdfium_test installed successfully.")
	}

	// Gather all of the tests to run.
	glog.Info("Searching for tests.")
	tests := []*test{}

	// Search for Python tests in the repo.
	if err := filepath.Walk(rootDir, func(p string, info os.FileInfo, err error) error {
		basename := path.Base(p)
		// Skip some directories.
		if info.IsDir() {
			for _, skip := range NO_CRAWL_DIR_NAMES {
				if basename == skip {
					return filepath.SkipDir
				}
			}
			for _, skip := range NO_CRAWL_REL_PATHS {
				if p == path.Join(rootDir, skip) {
					return filepath.SkipDir
				}
			}
		}
		if strings.HasSuffix(basename, "_test.py") {
			tests = append(tests, pythonTest(p))
		}
		return nil
	}); err != nil {
		glog.Fatal(err)
	}

	// Go tests.
	for goDir, extraLog := range GO_TEST_DIRS_AND_VERBOSITY {
		tests = append(tests, goTest(goDir, extraLog))
	}

	// Other tests.
	tests = append(tests, cmdTest([]string{"go", "vet", "./..."}, ".", "go vet"))
	tests = append(tests, cmdTest([]string{"errcheck", "-ignore", ":Close", "go.skia.org/infra/..."}, ".", "errcheck"))
	tests = append(tests, polylintTests()...)

	goimportsCmd := []string{"goimports", "-l", "."}
	tests = append(tests, &test{
		Name: "goimports",
		Cmd:  strings.Join(goimportsCmd, " "),
		run: func() (error, string) {
			command := exec.Command(goimportsCmd[0], goimportsCmd[1:]...)
			output, err := command.Output()
			outStr := strings.Trim(string(output), "\n")
			if err != nil {
				if _, err2 := exec.LookPath(goimportsCmd[0]); err2 != nil {
					return fmt.Errorf(ERR_NEED_INSTALL, goimportsCmd[0], err), outStr
				}
				// Sometimes goimports returns exit code 2, but gives no reason.
				if outStr != "" {
					return err, fmt.Sprintf("goimports output: %q", outStr)
				}
			}
			diffFiles := strings.Split(outStr, "\n")
			if len(diffFiles) > 0 && !(len(diffFiles) == 1 && diffFiles[0] == "") {
				return fmt.Errorf("goimports found diffs in the following files:\n  - %s", strings.Join(diffFiles, ",\n  - ")), outStr
			}
			return nil, ""

		},
	})

	// Run the tests.
	glog.Infof("Found %d tests.", len(tests))
	var mutex sync.Mutex
	errors := map[string]error{}
	var wg sync.WaitGroup
	for _, t := range tests {
		wg.Add(1)
		go func(t *test) {
			defer wg.Done()
			if err := t.Run(); err != nil {
				mutex.Lock()
				errors[t.Name] = err
				mutex.Unlock()
			}
		}(t)
	}
	wg.Wait()
	if len(errors) > 0 {
		for _, e := range errors {
			glog.Error(e)
		}
		os.Exit(1)
	}
	glog.Info("All tests succeeded.")
}
<|endoftext|>


<commit_before>package runner

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
	"github.com/yext/edward/commandline"
	"github.com/yext/edward/config"
	"github.com/yext/edward/services"
)

var runnerInstance = &Runner{}

var Command = cli.Command{
	Name:   "run",
	Hidden: true,
	Action: runnerInstance.run,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:        "no-watch",
			Usage:       "Disable autorestart",
			Destination: &(runnerInstance.noWatch),
		},
	},
}

type Runner struct {
	service    *services.ServiceConfig
	command    *exec.Cmd
	logFile    *os.File
	messageLog *RunnerLog

	commandWait sync.WaitGroup
	noWatch     bool
}

type Logger interface {
	Printf(format string, a ...interface{})
}

func (r *Runner) run(c *cli.Context) error {
	args := c.Args()
	if len(args) < 1 {
		return errors.New("a service name is required")
	}

	var ok bool
	r.service, ok = config.GetServiceMap()[args[0]]
	if !ok {
		return errors.New("service not found")
	}

	logLocation := r.service.GetRunLog()
	os.Remove(logLocation)

	var err error
	r.logFile, err = os.Create(logLocation)
	if err != nil {
		return errors.WithStack(err)
	}

	r.messageLog = &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "messages",
	}
	defer r.messageLog.Printf("Service stopped\n")

	r.commandWait.Add(1)
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}

	if !r.noWatch {
		closeWatchers, err := BeginWatch(r.service, r.restartService, r.messageLog)
		if err != nil {
			r.messageLog.Printf("Could not enable auto-restart: %v\n", err)
		} else {
			r.messageLog.Printf("Auto-restart enabled. This service will restart when files in its watch directories are edited.\nThis can be disabled using the --no-watch flag.\n")
			defer closeWatchers()
		}
	}

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	go func() {
		for _ = range signalChan {
			r.messageLog.Printf("Received interrupt\n")
			err := r.stopService()
			if err != nil {
				r.messageLog.Printf("Could not stop service: %v", err)
			}
		}
	}()

	r.commandWait.Wait()
	return nil
}

func (r *Runner) restartService() error {
	r.messageLog.Printf("Restarting service\n")

	// Increment the counter to prevent exiting unexpectedly
	r.commandWait.Add(1)

	err := r.stopService()
	if err != nil {
		return errors.WithStack(err)
	}
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

func (r *Runner) stopService() error {
	r.messageLog.Printf("stopService")
	command, err := r.service.GetCommand()
	if err != nil {
		r.messageLog.Printf("Could not get service command: %v\n", err)
	}

	var scriptErr error
	var scriptOutput []byte
	if r.service.Commands.Stop != "" {
		r.messageLog.Printf("Running stop script for %v.\n", r.service.Name)
		scriptOutput, scriptErr = command.RunStopScript()
		if scriptErr != nil {
			r.messageLog.Printf("%v\n", string(scriptOutput))
			r.messageLog.Printf("Stop script failed: %v\n", scriptErr)
		}
		if r.waitForCompletionWithTimeout(1 * time.Second) {
			return nil
		}
		r.messageLog.Printf("Stop script did not effectively stop service, sending interrupt\n")
	}

	err = r.command.Process.Signal(os.Interrupt)
	if err != nil {
		return errors.WithStack(err)
	}

	if r.waitForCompletionWithTimeout(1 * time.Second) {
		r.messageLog.Printf("Interrupt succeeded")
		return nil
	}
	r.messageLog.Printf("Stop script did not effectively stop service, sending kill\n")

	err = r.command.Process.Kill()
	if err != nil {
		return errors.WithStack(err)
	}

	if r.waitForCompletionWithTimeout(1 * time.Second) {
		return nil
	}
	return errors.New("kill did not stop service")
}

func (r *Runner) waitForCompletionWithTimeout(timeout time.Duration) bool {
	var completed = make(chan struct{})
	defer close(completed)
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	go func() {
		r.command.Wait()
		r.messageLog.Printf("Command completed")
		completed <- struct{}{}
	}()

	select {
	case <-completed:
		return true
	case <-timer.C:
		return false
	}
}

func (r *Runner) startService() error {
	r.messageLog.Printf("Service starting\n")
	command, cmdArgs, err := commandline.ParseCommand(os.ExpandEnv(r.service.Commands.Launch))
	if err != nil {
		return errors.WithStack(err)
	}

	standardLog := &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stdout",
	}
	errorLog := &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stderr",
	}

	cmd := exec.Command(command, cmdArgs...)
	if r.service.Path != nil {
		cmd.Dir = os.ExpandEnv(*r.service.Path)
	}
	cmd.Stdout = standardLog
	cmd.Stderr = errorLog

	r.command = cmd

	go func() {
		cmd.Run()
		r.messageLog.Printf("command run finished, sending done")
		r.commandWait.Done()
	}()
	return nil
}

// RunnerLog provides the io.Writer interface to publish service logs to file
type RunnerLog struct {
	file   *os.File
	name   string
	stream string
}

func (r *RunnerLog) Printf(format string, a ...interface{}) {
	msg := fmt.Sprintf(format, a...)
	r.Write([]byte(msg))
}

func (r *RunnerLog) Write(p []byte) (int, error) {
	fmt.Println(strings.TrimRight(string(p), "\n"))
	lineData := LogLine{
		Name:    r.name,
		Time:    time.Now(),
		Stream:  r.stream,
		Message: strings.TrimSpace(string(p)),
	}

	jsonContent, err := json.Marshal(lineData)
	if err != nil {
		return 0, errors.Wrap(err, "could not prepare log line")
	}

	line := fmt.Sprintln(string(jsonContent))
	count, err := r.file.Write([]byte(line))
	if err != nil {
		fmt.Println("Error")
		return count, errors.Wrap(err, "could not write log line")
	}
	return len(p), nil
}
<commit_msg>Fix issues with stopping services that have children.<commit_after>package runner

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
	"github.com/yext/edward/commandline"
	"github.com/yext/edward/config"
	"github.com/yext/edward/services"
)

var runnerInstance = &Runner{}

var Command = cli.Command{
	Name:   "run",
	Hidden: true,
	Action: runnerInstance.run,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:        "no-watch",
			Usage:       "Disable autorestart",
			Destination: &(runnerInstance.noWatch),
		},
	},
}

type Runner struct {
	service    *services.ServiceConfig
	command    *RunningCommand
	logFile    *os.File
	messageLog *RunnerLog

	commandWait sync.WaitGroup
	noWatch     bool
}

func NewRunningCommand(cmd *exec.Cmd, commandWait *sync.WaitGroup) *RunningCommand {
	return &RunningCommand{
		command:     cmd,
		done:        make(chan struct{}),
		commandWait: commandWait,
	}
}

type RunningCommand struct {
	command     *exec.Cmd
	done        chan struct{}
	commandWait *sync.WaitGroup
}

func (c *RunningCommand) Start() {
	go func() {
		c.command.Run()
		c.commandWait.Done()
		close(c.done)
	}()
}

func (c *RunningCommand) Interrupt() error {
	syscall.Kill(-c.command.Process.Pid, syscall.SIGINT)
	return nil
}

func (c *RunningCommand) Kill() error {
	syscall.Kill(-c.command.Process.Pid, syscall.SIGKILL)
	return nil
}

func (c *RunningCommand) Wait() {
	<-c.done
}

type Logger interface {
	Printf(format string, a ...interface{})
}

func (r *Runner) run(c *cli.Context) error {
	args := c.Args()
	if len(args) < 1 {
		return errors.New("a service name is required")
	}

	var ok bool
	r.service, ok = config.GetServiceMap()[args[0]]
	if !ok {
		return errors.New("service not found")
	}

	logLocation := r.service.GetRunLog()
	os.Remove(logLocation)

	var err error
	r.logFile, err = os.Create(logLocation)
	if err != nil {
		return errors.WithStack(err)
	}

	r.messageLog = &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "messages",
	}
	defer r.messageLog.Printf("Service stopped\n")

	r.commandWait.Add(1)
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}

	if !r.noWatch {
		closeWatchers, err := BeginWatch(r.service, r.restartService, r.messageLog)
		if err != nil {
			r.messageLog.Printf("Could not enable auto-restart: %v\n", err)
		} else {
			r.messageLog.Printf("Auto-restart enabled. This service will restart when files in its watch directories are edited.\nThis can be disabled using the --no-watch flag.\n")
			defer closeWatchers()
		}
	}

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	go func() {
		for _ = range signalChan {
			r.messageLog.Printf("Received interrupt\n")
			err := r.stopService()
			if err != nil {
				r.messageLog.Printf("Could not stop service: %v", err)
			}
		}
	}()

	r.commandWait.Wait()
	return nil
}

func (r *Runner) restartService() error {
	r.messageLog.Printf("Restarting service\n")

	// Increment the counter to prevent exiting unexpectedly
	r.commandWait.Add(1)

	err := r.stopService()
	if err != nil {
		return errors.WithStack(err)
	}
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

func (r *Runner) stopService() error {
	command, err := r.service.GetCommand()
	if err != nil {
		r.messageLog.Printf("Could not get service command: %v\n", err)
	}

	var scriptErr error
	var scriptOutput []byte
	if r.service.Commands.Stop != "" {
		r.messageLog.Printf("Running stop script for %v.\n", r.service.Name)
		scriptOutput, scriptErr = command.RunStopScript()
		if scriptErr != nil {
			r.messageLog.Printf("%v\n", string(scriptOutput))
			r.messageLog.Printf("Stop script failed: %v\n", scriptErr)
		}
		if r.waitForCompletionWithTimeout(1 * time.Second) {
			return nil
		}
		r.messageLog.Printf("Stop script did not effectively stop service, sending interrupt\n")
	}

	err = r.command.Interrupt()
	if err != nil {
		return errors.WithStack(err)
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	r.messageLog.Printf("Interrupt did not effectively stop service, sending kill\n")

	err = r.command.Kill()
	if err != nil {
		return errors.WithMessage(err, "Kill failed")
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	return errors.New("kill did not stop service")
}

func (r *Runner) waitForCompletionWithTimeout(timeout time.Duration) bool {
	var completed = make(chan struct{})
	defer close(completed)
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	go func() {
		r.command.Wait()
		completed <- struct{}{}
	}()

	select {
	case <-completed:
		return true
	case <-timer.C:
		return false
	}
}

func (r *Runner) startService() error {
	r.messageLog.Printf("Service starting\n")
	command, cmdArgs, err := commandline.ParseCommand(os.ExpandEnv(r.service.Commands.Launch))
	if err != nil {
		return errors.WithStack(err)
	}

	standardLog := &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stdout",
	}
	errorLog := &RunnerLog{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stderr",
	}

	cmd := exec.Command(command, cmdArgs...)
	if r.service.Path != nil {
		cmd.Dir = os.ExpandEnv(*r.service.Path)
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	cmd.Stdout = standardLog
	cmd.Stderr = errorLog

	r.command = NewRunningCommand(cmd, &r.commandWait)
	r.command.Start()

	return nil
}

// RunnerLog provides the io.Writer interface to publish service logs to file
type RunnerLog struct {
	file   *os.File
	name   string
	stream string
}

func (r *RunnerLog) Printf(format string, a ...interface{}) {
	msg := fmt.Sprintf(format, a...)
	r.Write([]byte(msg))
}

func (r *RunnerLog) Write(p []byte) (int, error) {
	fmt.Println(strings.TrimRight(string(p), "\n"))
	lineData := LogLine{
		Name:    r.name,
		Time:    time.Now(),
		Stream:  r.stream,
		Message: strings.TrimSpace(string(p)),
	}

	jsonContent, err := json.Marshal(lineData)
	if err != nil {
		return 0, errors.Wrap(err, "could not prepare log line")
	}

	line := fmt.Sprintln(string(jsonContent))
	count, err := r.file.Write([]byte(line))
	if err != nil {
		fmt.Println("Error")
		return count, errors.Wrap(err, "could not write log line")
	}
	return len(p), nil
}
<|endoftext|>


<commit_before>package runner

import (
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
	"github.com/yext/edward/commandline"
	"github.com/yext/edward/config"
	"github.com/yext/edward/services"
)

var runnerInstance = &Runner{}

// Command defines the CLI config for the 'run' command
var Command = cli.Command{
	Name:   "run",
	Hidden: true,
	Action: runnerInstance.run,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:        "no-watch",
			Usage:       "Disable autorestart",
			Destination: &(runnerInstance.noWatch),
		},
	},
}

// Runner provides state and functions for running a given service
type Runner struct {
	service    *services.ServiceConfig
	command    *RunningCommand
	logFile    *os.File
	messageLog *Log

	commandWait sync.WaitGroup
	noWatch     bool
}

// NewRunningCommand creates a RunningCommand for a given service and exec.Cmd
func NewRunningCommand(service *services.ServiceConfig, cmd *exec.Cmd, commandWait *sync.WaitGroup) *RunningCommand {
	return &RunningCommand{
		service:     service,
		command:     cmd,
		done:        make(chan struct{}),
		commandWait: commandWait,
	}
}

// RunningCommand provides state and functions for running a service
type RunningCommand struct {
	service     *services.ServiceConfig
	command     *exec.Cmd
	done        chan struct{}
	commandWait *sync.WaitGroup
}

// Start starts a command running in a goroutine
func (c *RunningCommand) Start(errorLog Logger) {
	go func() {
		err := c.command.Run()
		if err != nil {
			errorLog.Printf("%v", err)
		}
		c.commandWait.Done()
		close(c.done)
	}()
}

// Interrupt sends an interrupt to a running command
func (c *RunningCommand) Interrupt() error {
	return errors.WithStack(
		services.InterruptGroup(services.OperationConfig{}, c.command.Process.Pid, c.service),
	)
}

// Kill sends a kill signal to a running command
func (c *RunningCommand) Kill() error {
	return errors.WithStack(
		services.KillGroup(services.OperationConfig{}, c.command.Process.Pid, c.service),
	)
}

// Wait blocks until this command has exited
func (c *RunningCommand) Wait() {
	<-c.done
}

// Logger provides a simple interface for logging
type Logger interface {
	Printf(format string, a ...interface{})
}

func (r *Runner) run(c *cli.Context) error {
	args := c.Args()
	if len(args) < 1 {
		return errors.New("a service name is required")
	}

	var ok bool
	r.service, ok = config.GetServiceMap()[args[0]]
	if !ok {
		return errors.New("service not found")
	}

	err := r.configureLogs()
	if err != nil {
		return errors.WithStack(err)
	}
	defer r.messageLog.Printf("Service stopped\n")

	r.commandWait.Add(1)
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}

	closeWatchers := r.configureWatch()
	if closeWatchers != nil {
		defer closeWatchers()
	}
	r.configureSignals()

	r.commandWait.Wait()
	return nil
}

func (r *Runner) configureLogs() error {
	logLocation := r.service.GetRunLog()
	os.Remove(logLocation)

	var err error
	r.logFile, err = os.Create(logLocation)
	if err != nil {
		return errors.WithStack(err)
	}

	r.messageLog = &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "messages",
	}
	return nil
}

func (r *Runner) configureSignals() {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	go func() {
		for range signalChan {
			r.messageLog.Printf("Received interrupt\n")
			err := r.stopService()
			if err != nil {
				r.messageLog.Printf("Could not stop service: %v", err)
			}
		}
	}()
}

func (r *Runner) configureWatch() func() {
	if !r.noWatch {
		closeWatchers, err := BeginWatch(r.service, r.restartService, r.messageLog)
		if err != nil {
			r.messageLog.Printf("Could not enable auto-restart: %v\n", err)
			return nil
		}
		r.messageLog.Printf("Auto-restart enabled. This service will restart when files in its watch directories are edited.\nThis can be disabled using the --no-watch flag.\n")
		return closeWatchers
	}
	return nil
}

func (r *Runner) restartService() error {
	r.messageLog.Printf("Restarting service\n")

	// Increment the counter to prevent exiting unexpectedly
	r.commandWait.Add(1)

	err := r.stopService()
	if err != nil {
		return errors.WithStack(err)
	}
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

func (r *Runner) stopService() error {
	command, err := r.service.GetCommand(services.ContextOverride{})
	if err != nil {
		r.messageLog.Printf("Could not get service command: %v\n", err)
	}

	var scriptErr error
	var scriptOutput []byte
	if r.service.Commands.Stop != "" {
		r.messageLog.Printf("Running stop script for %v.\n", r.service.Name)
		scriptOutput, scriptErr = command.RunStopScript()
		if scriptErr != nil {
			r.messageLog.Printf("%v\n", string(scriptOutput))
			r.messageLog.Printf("Stop script failed: %v\n", scriptErr)
		}
		if r.waitForCompletionWithTimeout(1 * time.Second) {
			return nil
		}
		r.messageLog.Printf("Stop script did not effectively stop service, sending interrupt\n")
	}

	err = r.command.Interrupt()
	if err != nil {
		return errors.WithStack(err)
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	r.messageLog.Printf("Interrupt did not effectively stop service, sending kill\n")

	err = r.command.Kill()
	if err != nil {
		return errors.WithMessage(err, "Kill failed")
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	return errors.New("kill did not stop service")
}

func (r *Runner) waitForCompletionWithTimeout(timeout time.Duration) bool {
	var completed = make(chan struct{})
	defer close(completed)
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	go func() {
		r.command.Wait()
		completed <- struct{}{}
	}()

	select {
	case <-completed:
		return true
	case <-timer.C:
		return false
	}
}

func (r *Runner) startService() error {
	r.messageLog.Printf("Service starting\n")

	standardLog := &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stdout",
	}
	errorLog := &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stderr",
	}

	command, cmdArgs, err := commandline.ParseCommand(os.ExpandEnv(r.service.Commands.Launch))
	if err != nil {
		return errors.WithStack(err)
	}
	cmd := exec.Command(command, cmdArgs...)
	if r.service.Path != nil {
		cmd.Dir = os.ExpandEnv(*r.service.Path)
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	cmd.Stdout = standardLog
	cmd.Stderr = errorLog

	r.command = NewRunningCommand(r.service, cmd, &r.commandWait)
	r.command.Start(errorLog)

	return nil
}
<commit_msg>Lock the service when autorestarting (after the build phase).<commit_after>package runner

import (
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
	"github.com/yext/edward/commandline"
	"github.com/yext/edward/config"
	"github.com/yext/edward/services"
)

var runnerInstance = &Runner{}

// Command defines the CLI config for the 'run' command
var Command = cli.Command{
	Name:   "run",
	Hidden: true,
	Action: runnerInstance.run,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:        "no-watch",
			Usage:       "Disable autorestart",
			Destination: &(runnerInstance.noWatch),
		},
	},
}

// Runner provides state and functions for running a given service
type Runner struct {
	service    *services.ServiceConfig
	command    *RunningCommand
	logFile    *os.File
	messageLog *Log

	commandWait sync.WaitGroup
	noWatch     bool
}

// NewRunningCommand creates a RunningCommand for a given service and exec.Cmd
func NewRunningCommand(service *services.ServiceConfig, cmd *exec.Cmd, commandWait *sync.WaitGroup) *RunningCommand {
	return &RunningCommand{
		service:     service,
		command:     cmd,
		done:        make(chan struct{}),
		commandWait: commandWait,
	}
}

// RunningCommand provides state and functions for running a service
type RunningCommand struct {
	service     *services.ServiceConfig
	command     *exec.Cmd
	done        chan struct{}
	commandWait *sync.WaitGroup
}

// Start starts a command running in a goroutine
func (c *RunningCommand) Start(errorLog Logger) {
	go func() {
		err := c.command.Run()
		if err != nil {
			errorLog.Printf("%v", err)
		}
		c.commandWait.Done()
		close(c.done)
	}()
}

// Interrupt sends an interrupt to a running command
func (c *RunningCommand) Interrupt() error {
	return errors.WithStack(
		services.InterruptGroup(services.OperationConfig{}, c.command.Process.Pid, c.service),
	)
}

// Kill sends a kill signal to a running command
func (c *RunningCommand) Kill() error {
	return errors.WithStack(
		services.KillGroup(services.OperationConfig{}, c.command.Process.Pid, c.service),
	)
}

// Wait blocks until this command has exited
func (c *RunningCommand) Wait() {
	<-c.done
}

// Logger provides a simple interface for logging
type Logger interface {
	Printf(format string, a ...interface{})
}

func (r *Runner) run(c *cli.Context) error {
	args := c.Args()
	if len(args) < 1 {
		return errors.New("a service name is required")
	}

	var ok bool
	r.service, ok = config.GetServiceMap()[args[0]]
	if !ok {
		return errors.New("service not found")
	}

	err := r.configureLogs()
	if err != nil {
		return errors.WithStack(err)
	}
	defer r.messageLog.Printf("Service stopped\n")

	r.commandWait.Add(1)
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}

	closeWatchers := r.configureWatch()
	if closeWatchers != nil {
		defer closeWatchers()
	}
	r.configureSignals()

	r.commandWait.Wait()
	return nil
}

func (r *Runner) configureLogs() error {
	logLocation := r.service.GetRunLog()
	os.Remove(logLocation)

	var err error
	r.logFile, err = os.Create(logLocation)
	if err != nil {
		return errors.WithStack(err)
	}

	r.messageLog = &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "messages",
	}
	return nil
}

func (r *Runner) configureSignals() {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	go func() {
		for range signalChan {
			r.messageLog.Printf("Received interrupt\n")
			err := r.stopService()
			if err != nil {
				r.messageLog.Printf("Could not stop service: %v", err)
			}
		}
	}()
}

func (r *Runner) configureWatch() func() {
	if !r.noWatch {
		closeWatchers, err := BeginWatch(r.service, r.restartService, r.messageLog)
		if err != nil {
			r.messageLog.Printf("Could not enable auto-restart: %v\n", err)
			return nil
		}
		r.messageLog.Printf("Auto-restart enabled. This service will restart when files in its watch directories are edited.\nThis can be disabled using the --no-watch flag.\n")
		return closeWatchers
	}
	return nil
}

func (r *Runner) restartService() error {
	r.messageLog.Printf("Restarting service\n")

	// Increment the counter to prevent exiting unexpectedly
	r.commandWait.Add(1)
	lockedService, done, err := r.service.ObtainLock("autorestart")
	if err != nil {
		return errors.WithStack(err)
	}
	oldService := r.service
	r.service = lockedService
	defer func() {
		done()
		r.service = oldService
	}()

	err = r.stopService()
	if err != nil {
		return errors.WithStack(err)
	}
	err = r.startService()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

func (r *Runner) stopService() error {
	command, err := r.service.GetCommand(services.ContextOverride{})
	if err != nil {
		r.messageLog.Printf("Could not get service command: %v\n", err)
	}

	var scriptErr error
	var scriptOutput []byte
	if r.service.Commands.Stop != "" {
		r.messageLog.Printf("Running stop script for %v.\n", r.service.Name)
		scriptOutput, scriptErr = command.RunStopScript()
		if scriptErr != nil {
			r.messageLog.Printf("%v\n", string(scriptOutput))
			r.messageLog.Printf("Stop script failed: %v\n", scriptErr)
		}
		if r.waitForCompletionWithTimeout(1 * time.Second) {
			return nil
		}
		r.messageLog.Printf("Stop script did not effectively stop service, sending interrupt\n")
	}

	err = r.command.Interrupt()
	if err != nil {
		return errors.WithStack(err)
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	r.messageLog.Printf("Interrupt did not effectively stop service, sending kill\n")

	err = r.command.Kill()
	if err != nil {
		return errors.WithMessage(err, "Kill failed")
	}

	if r.waitForCompletionWithTimeout(2 * time.Second) {
		return nil
	}
	return errors.New("kill did not stop service")
}

func (r *Runner) waitForCompletionWithTimeout(timeout time.Duration) bool {
	var completed = make(chan struct{})
	defer close(completed)
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	go func() {
		r.command.Wait()
		completed <- struct{}{}
	}()

	select {
	case <-completed:
		return true
	case <-timer.C:
		return false
	}
}

func (r *Runner) startService() error {
	r.messageLog.Printf("Service starting\n")

	standardLog := &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stdout",
	}
	errorLog := &Log{
		file:   r.logFile,
		name:   r.service.Name,
		stream: "stderr",
	}

	command, cmdArgs, err := commandline.ParseCommand(os.ExpandEnv(r.service.Commands.Launch))
	if err != nil {
		return errors.WithStack(err)
	}
	cmd := exec.Command(command, cmdArgs...)
	if r.service.Path != nil {
		cmd.Dir = os.ExpandEnv(*r.service.Path)
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	cmd.Stdout = standardLog
	cmd.Stderr = errorLog

	r.command = NewRunningCommand(r.service, cmd, &r.commandWait)
	r.command.Start(errorLog)

	return nil
}
<|endoftext|>


<commit_before>package runner

import (
	"io"
	"log"
	"os"
	"os/exec"
	"regexp"
)

type Runner interface {
	Run()
}
type subProcess struct {
	port   string
	name   string
	args   []string
	signal chan os.Signal
}

func New(port string, name string, args []string, signal chan os.Signal) Runner {
	return &subProcess{port, name, args, signal}
}

func (sp *subProcess) Run() {

	cmd := exec.Command(sp.name, sp.args...)

	cmd.Env = sp.envForSubProcess()
	stdout, err := cmd.StdoutPipe()
	sp.checkError(err)
	stderr, err := cmd.StderrPipe()
	sp.checkError(err)
	stdin, err := cmd.StderrPipe()
	sp.checkError(err)

	err = cmd.Start()
	sp.checkError(err)

	cmd.Wait()

	go func() { cmd.Process.Signal(<-sp.signal) }()

	go io.Copy(os.Stdout, stdout)
	go io.Copy(os.Stderr, stderr)
	go io.Copy(os.Stdin, stdin)

}

func (sp *subProcess) checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func (sp *subProcess) envForSubProcess() []string {
	return sp.addNewPort(sp.removeOldPortFromEnv())
}

func (sp *subProcess) addNewPort(env []string) []string {
	return append(env, "PORT="+sp.port)
}

func (sp *subProcess) removeOldPortFromEnv() []string {
	var newEnv []string
	for _, v := range os.Environ() {
		if match, _ := regexp.MatchString("PORT", v); match == false {
			newEnv = append(newEnv, v)
		}
	}
	return newEnv
}
<commit_msg>megafeat: add deafault port to runner<commit_after>package runner

import (
	"io"
	"log"
	"os"
	"os/exec"
	"regexp"
)

type Runner interface {
	Run()
}
type subProcess struct {
	port   string
	name   string
	args   []string
	signal chan os.Signal
}

func New(port string, name string, args []string, signal chan os.Signal) Runner {
	return &subProcess{port: "9292", name: name, args: args, signal: signal}
}

func (sp *subProcess) Run() {

	cmd := exec.Command(sp.name, sp.args...)

	cmd.Env = sp.envForSubProcess()
	stdout, err := cmd.StdoutPipe()
	sp.checkError(err)
	stderr, err := cmd.StderrPipe()
	sp.checkError(err)
	stdin, err := cmd.StderrPipe()
	sp.checkError(err)

	err = cmd.Start()
	sp.checkError(err)

	cmd.Wait()

	go func() { cmd.Process.Signal(<-sp.signal) }()

	go io.Copy(os.Stdout, stdout)
	go io.Copy(os.Stderr, stderr)
	go io.Copy(os.Stdin, stdin)

}

func (sp *subProcess) checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func (sp *subProcess) envForSubProcess() []string {
	return sp.addNewPort(sp.removeOldPortFromEnv())
}

func (sp *subProcess) addNewPort(env []string) []string {
	return append(env, "PORT="+sp.port)
}

func (sp *subProcess) removeOldPortFromEnv() []string {
	var newEnv []string
	for _, v := range os.Environ() {
		if match, _ := regexp.MatchString("PORT", v); match == false {
			newEnv = append(newEnv, v)
		}
	}
	return newEnv
}
<|endoftext|>


<commit_before>package ampl

/* A package to wrap the AMPL CLI and automate running commands */

import (
	"bufio"
	"fmt"
	"os/exec"
	"os"
	"strings"
	"time"
	"io"
	"io/ioutil"
)

const (
	// A file to read by default with the path to the AMPL executable
	AMPLLocFile = "amplloc.txt"

	// Time to wait for an error from AMPL by default. Each command will take this long to return
	defaultTimeout = 500 * time.Millisecond
)

/* An instance of the AMPL binary */
type Runner struct {
	command *exec.Cmd
	stdout io.ReadCloser
	stdin io.WriteCloser
	output chan string
}

/* Look for a file called `amplloc.txt` and read the contents if it exists */
func GetAMPLLoc() (string, error) {
	amplLoc, err := ioutil.ReadFile(AMPLLocFile)
	// If the file is missing, return an empty string
	if os.IsNotExist(err) {
		return "", nil
	}
	return string(amplLoc), err
}

/* Present a prompt and try to get the AMPL location on stdin. Cache the location in `amplloc.txt` */
func PromptAMPLLoc() (string, error) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("AMPL binary location: ")
	amplLoc, err := reader.ReadString('\n')
	if err != nil {
		return "", err
	}
	amplLoc = strings.TrimSpace(amplLoc)
	fmt.Printf("amplLoc: %q\n", amplLoc)
	ioutil.WriteFile(AMPLLocFile, []byte(amplLoc), os.ModePerm)
	return amplLoc, nil
}

/* Open a new runner using the AMPL binary at `amplLoc` */
func NewRunner(amplLoc string) (*Runner, error) {
	cmd := exec.Command(amplLoc)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		return nil, err
	}
	outputChan := make(chan string)
	go func() {
		reader := bufio.NewReader(stdout)
		var output string
		for {
			l, err := reader.ReadString('\n')
			if err != nil {
				outputChan <- output
				break
			}
			// AMPL ends every error with an extra carriage return
			if l == "\r\r\n" {
				outputChan <- output
				output = ""
			} else {
				output += string(l)
			}
		}
	}()
	return &Runner {
		command: cmd,
		stdout: stdout,
		stdin: stdin,
		output: outputChan,
	}, nil
}

/* Submit a command to the running AMPL binary, with the default timeout for a response. Returns an AMPLCommandError if the command fails, or nil if everything appears successful */
func (self *Runner) RunCommand (cmd string) (error) {
	return self.RunCommandTimeout(cmd, defaultTimeout)
}

/* Submit a command to the running AMPL binary. Waits for up to `timeout` for an error message from AMPL. Returns an AMPLCommandError
   if the command fails, or nil if there is no response. Note that if `timeout` is not long enough we may miss the error message. */
func (self *Runner) RunCommandTimeout (cmd string, timeout time.Duration) (error) {
	// Append a semicolon and newline to ensure the command is executed
	if cmd[len(cmd)-1] != ';' {
		cmd = cmd + ";"
	}
	_, err := self.stdin.Write([]byte(cmd+"\r\n"))

	if err != nil {
		return err
	}

	/* Unfortunately, the only feedback AMPL gives is when something goes wrong - listen for a configurable timeout after each command to see if it succeeded */
	select {
	case errorString := <- self.output:
		return &AMPLCommandError {
			command: cmd,
			errorString: string(errorString),
		}
	case <- time.After(timeout):
		return nil
	}
}

/* Returned when AMPL returns an error trying to run a command */
type AMPLCommandError struct {
  /* The command being run when the error happened */
  command string

  /* The text of the error */
  errorString string
}

func (self *AMPLCommandError) Error() string {
  return fmt.Sprintf("Error running %q - %v", self.command, self.errorString)
}
<commit_msg>Try to find AMPL binary when path is provided<commit_after>package ampl

/* A package to wrap the AMPL CLI and automate running commands */

import (
	"bufio"
	"fmt"
	"os/exec"
	"os"
	"strings"
	"time"
	"io"
	"io/ioutil"
)

const (
	// A file to read by default with the path to the AMPL executable
	AMPLLocFile = "amplloc.txt"

	// Time to wait for an error from AMPL by default. Each command will take this long to return
	defaultTimeout = 500 * time.Millisecond
)

/* An instance of the AMPL binary */
type Runner struct {
	command *exec.Cmd
	stdout io.ReadCloser
	stdin io.WriteCloser
	output chan string
}

/* Look for a file called `amplloc.txt` and read the contents if it exists */
func GetAMPLLoc() (string, error) {
	amplLoc, err := ioutil.ReadFile(AMPLLocFile)
	// If the file is missing, return an empty string
	if os.IsNotExist(err) {
		return "", nil
	}
	return string(amplLoc), err
}

/* Present a prompt and try to get the AMPL location on stdin. Cache the location in `amplloc.txt` */
func PromptAMPLLoc() (string, error) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Path to the AMPL executable: ")
	amplLoc, err := reader.ReadString('\n')
	if err != nil {
		return "", err
	}
	amplLoc = strings.TrimSpace(amplLoc)
	// Check if the provided path is the AMPL binary
	stat, err := os.Stat(amplLoc)
	if err != nil {
		return "", err
	}
	if stat.IsDir() {
		amplLoc = amplLoc + "/ampl.exe"
	}
	ioutil.WriteFile(AMPLLocFile, []byte(amplLoc), os.ModePerm)
	return amplLoc, nil
}

/* Remove the cached location stored in `amplloc.txt` */
func ClearAMPLLoc() error {
	return os.Remove(AMPLLocFile)
}

/* Open a new runner using the AMPL binary at `amplLoc` */
func NewRunner(amplLoc string) (*Runner, error) {
	cmd := exec.Command(amplLoc)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		return nil, err
	}
	outputChan := make(chan string)
	go func() {
		reader := bufio.NewReader(stdout)
		var output string
		for {
			l, err := reader.ReadString('\n')
			if err != nil {
				outputChan <- output
				break
			}
			// AMPL ends every error with an extra carriage return
			if l == "\r\r\n" {
				outputChan <- output
				output = ""
			} else {
				output += string(l)
			}
		}
	}()
	return &Runner {
		command: cmd,
		stdout: stdout,
		stdin: stdin,
		output: outputChan,
	}, nil
}

/* Submit a command to the running AMPL binary, with the default timeout for a response. Returns an AMPLCommandError if the command fails, or nil if everything appears successful */
func (self *Runner) RunCommand (cmd string) (error) {
	return self.RunCommandTimeout(cmd, defaultTimeout)
}

/* Submit a command to the running AMPL binary. Waits for up to `timeout` for an error message from AMPL. Returns an AMPLCommandError
   if the command fails, or nil if there is no response. Note that if `timeout` is not long enough we may miss the error message. */
func (self *Runner) RunCommandTimeout (cmd string, timeout time.Duration) (error) {
	// Append a semicolon and newline to ensure the command is executed
	if cmd[len(cmd)-1] != ';' {
		cmd = cmd + ";"
	}
	_, err := self.stdin.Write([]byte(cmd+"\r\n"))

	if err != nil {
		return err
	}

	/* Unfortunately, the only feedback AMPL gives is when something goes wrong - listen for a configurable timeout after each command to see if it succeeded */
	select {
	case errorString := <- self.output:
		return &AMPLCommandError {
			command: cmd,
			errorString: string(errorString),
		}
	case <- time.After(timeout):
		return nil
	}
}

/* Returned when AMPL returns an error trying to run a command */
type AMPLCommandError struct {
  /* The command being run when the error happened */
  command string

  /* The text of the error */
  errorString string
}

func (self *AMPLCommandError) Error() string {
  return fmt.Sprintf("Error running %q - %v", self.command, self.errorString)
}
<|endoftext|>


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

// Package draw provides basic graphics and drawing primitives,
// in the style of the Plan 9 graphics library
// (see http://plan9.bell-labs.com/magic/man2html/2/draw)
// and the X Render extension.
package draw

// BUG(rsc): This is a toy library and not ready for production use.

import "image"

// A Porter-Duff compositing operator.
type Op int

const (
	// Over specifies ``(src in mask) over dst''.
	Over Op = iota
	// Src specifies ``src in mask''.
	Src
)

var zeroColor image.Color = image.AlphaColor{0}

// A draw.Image is an image.Image with a Set method to change a single pixel.
type Image interface {
	image.Image
	Set(x, y int, c image.Color)
}

// Draw calls DrawMask with a nil mask and an Over op.
func Draw(dst Image, r Rectangle, src image.Image, sp Point) {
	DrawMask(dst, r, src, sp, nil, ZP, Over)
}

// DrawMask aligns r.Min in dst with sp in src and mp in mask and then replaces the rectangle r
// in dst with the result of a Porter-Duff composition. A nil mask is treated as opaque.
// The implementation is simple and slow.
// TODO(nigeltao): Optimize this.
func DrawMask(dst Image, r Rectangle, src image.Image, sp Point, mask image.Image, mp Point, op Op) {
	dx, dy := src.Width()-sp.X, src.Height()-sp.Y
	if mask != nil {
		if dx > mask.Width()-mp.X {
			dx = mask.Width() - mp.X
		}
		if dy > mask.Height()-mp.Y {
			dy = mask.Height() - mp.Y
		}
	}
	if r.Dx() > dx {
		r.Max.X = r.Min.X + dx
	}
	if r.Dy() > dy {
		r.Max.Y = r.Min.Y + dy
	}

	// TODO(nigeltao): Clip r to dst's bounding box, and handle the case when sp or mp has negative X or Y.
	// TODO(nigeltao): Ensure that r is well formed, i.e. r.Max.X >= r.Min.X and likewise for Y.

	// Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation.
	if dst0, ok := dst.(*image.RGBA); ok {
		if op == Over {
			if mask0, ok := mask.(*image.Alpha); ok {
				if src0, ok := src.(image.ColorImage); ok {
					drawGlyphOver(dst0, r, src0, mask0, mp)
					return
				}
			}
		} else {
			if mask == nil {
				if src0, ok := src.(image.ColorImage); ok {
					drawFill(dst0, r, src0)
					return
				}
				if src0, ok := src.(*image.RGBA); ok {
					if dst0 == src0 && r.Overlaps(r.Add(sp.Sub(r.Min))) {
						// TODO(nigeltao): Implement a fast path for the overlapping case.
					} else {
						drawCopy(dst0, r, src0, sp)
						return
					}
				}
			}
		}
	}

	x0, x1, dx := r.Min.X, r.Max.X, 1
	y0, y1, dy := r.Min.Y, r.Max.Y, 1
	if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
		// Rectangles overlap: process backward?
		if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
			x0, x1, dx = x1-1, x0-1, -1
			y0, y1, dy = y1-1, y0-1, -1
		}
	}

	var out *image.RGBA64Color
	sy := sp.Y + y0 - r.Min.Y
	my := mp.Y + y0 - r.Min.Y
	for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
		sx := sp.X + x0 - r.Min.X
		mx := mp.X + x0 - r.Min.X
		for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
			// A nil mask is equivalent to a fully opaque, infinitely large mask.
			// We work in 16-bit color, so that multiplying two values does not overflow a uint32.
			const M = 1<<16 - 1
			ma := uint32(M)
			if mask != nil {
				_, _, _, ma = mask.At(mx, my).RGBA()
				ma >>= 16
			}
			switch {
			case ma == 0:
				if op == Over {
					// No-op.
				} else {
					dst.Set(x, y, zeroColor)
				}
			case ma == M && op == Src:
				dst.Set(x, y, src.At(sx, sy))
			default:
				sr, sg, sb, sa := src.At(sx, sy).RGBA()
				sr >>= 16
				sg >>= 16
				sb >>= 16
				sa >>= 16
				if out == nil {
					out = new(image.RGBA64Color)
				}
				if op == Over {
					dr, dg, db, da := dst.At(x, y).RGBA()
					dr >>= 16
					dg >>= 16
					db >>= 16
					da >>= 16
					a := M - (sa * ma / M)
					out.R = uint16((dr*a + sr*ma) / M)
					out.G = uint16((dg*a + sg*ma) / M)
					out.B = uint16((db*a + sb*ma) / M)
					out.A = uint16((da*a + sa*ma) / M)
				} else {
					out.R = uint16(sr * ma / M)
					out.G = uint16(sg * ma / M)
					out.B = uint16(sb * ma / M)
					out.A = uint16(sa * ma / M)
				}
				dst.Set(x, y, out)
			}
		}
	}
}

func drawGlyphOver(dst *image.RGBA, r Rectangle, src image.ColorImage, mask *image.Alpha, mp Point) {
	x0, x1 := r.Min.X, r.Max.X
	y0, y1 := r.Min.Y, r.Max.Y
	cr, cg, cb, ca := src.RGBA()
	cr >>= 16
	cg >>= 16
	cb >>= 16
	ca >>= 16
	for y, my := y0, mp.Y; y != y1; y, my = y+1, my+1 {
		for x, mx := x0, mp.X; x != x1; x, mx = x+1, mx+1 {
			ma := uint32(mask.Pixel[my][mx].A)
			if ma == 0 {
				continue
			}
			ma |= ma << 8
			dr := uint32(dst.Pixel[y][x].R)
			dg := uint32(dst.Pixel[y][x].G)
			db := uint32(dst.Pixel[y][x].B)
			da := uint32(dst.Pixel[y][x].A)
			// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
			// We work in 16-bit color, and so would normally do:
			// dr |= dr << 8
			// and similarly for dg, db and da, but instead we multiply a
			// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
			// This yields the same result, but is fewer arithmetic operations.
			const M = 1<<16 - 1
			a := M - (ca * ma / M)
			a *= 0x101
			dr = (dr*a + cr*ma) / M
			dg = (dg*a + cg*ma) / M
			db = (db*a + cb*ma) / M
			da = (da*a + ca*ma) / M
			dst.Pixel[y][x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
		}
	}
}

func drawFill(dst *image.RGBA, r Rectangle, src image.ColorImage) {
	if r.Dy() < 1 {
		return
	}
	cr, cg, cb, ca := src.RGBA()
	color := image.RGBAColor{uint8(cr >> 24), uint8(cg >> 24), uint8(cb >> 24), uint8(ca >> 24)}
	// The built-in copy function is faster than a straightforward for loop to fill the destination with
	// the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and
	// then use the first row as the slice source for the remaining rows.
	dx0, dx1 := r.Min.X, r.Max.X
	dy0, dy1 := r.Min.Y, r.Max.Y
	firstRow := dst.Pixel[dy0]
	for x := dx0; x < dx1; x++ {
		firstRow[x] = color
	}
	copySrc := firstRow[dx0:dx1]
	for y := dy0 + 1; y < dy1; y++ {
		copy(dst.Pixel[y][dx0:dx1], copySrc)
	}
}

func drawCopy(dst *image.RGBA, r Rectangle, src *image.RGBA, sp Point) {
	dx0, dx1 := r.Min.X, r.Max.X
	dy0, dy1 := r.Min.Y, r.Max.Y
	sx0, sx1 := sp.X, sp.X+dx1-dx0
	for y, sy := dy0, sp.Y; y < dy1; y, sy = y+1, sy+1 {
		copy(dst.Pixel[y][dx0:dx1], src.Pixel[sy][sx0:sx1])
	}
}

// Border aligns r.Min in dst with sp in src and then replaces pixels
// in a w-pixel border around r in dst with the result of the Porter-Duff compositing
// operation ``src over dst.''  If w is positive, the border extends w pixels inside r.
// If w is negative, the border extends w pixels outside r.
func Border(dst Image, r Rectangle, w int, src image.Image, sp Point) {
	i := w
	if i > 0 {
		// inside r
		Draw(dst, Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+i), src, sp)                          // top
		Draw(dst, Rect(r.Min.X, r.Min.Y+i, r.Min.X+i, r.Max.Y-i), src, sp.Add(Pt(0, i)))        // left
		Draw(dst, Rect(r.Max.X-i, r.Min.Y+i, r.Max.X, r.Max.Y-i), src, sp.Add(Pt(r.Dx()-i, i))) // right
		Draw(dst, Rect(r.Min.X, r.Max.Y-i, r.Max.X, r.Max.Y), src, sp.Add(Pt(0, r.Dy()-i)))     // bottom
		return
	}

	// outside r;
	i = -i
	Draw(dst, Rect(r.Min.X-i, r.Min.Y-i, r.Max.X+i, r.Min.Y), src, sp.Add(Pt(-i, -i))) // top
	Draw(dst, Rect(r.Min.X-i, r.Min.Y, r.Min.X, r.Max.Y), src, sp.Add(Pt(-i, 0)))      // left
	Draw(dst, Rect(r.Max.X, r.Min.Y, r.Max.X+i, r.Max.Y), src, sp.Add(Pt(r.Dx(), 0)))  // right
	Draw(dst, Rect(r.Min.X-i, r.Max.Y, r.Max.X+i, r.Max.Y+i), src, sp.Add(Pt(-i, 0)))  // bottom
}
<commit_msg>drawGlyphOver optimization.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package draw provides basic graphics and drawing primitives,
// in the style of the Plan 9 graphics library
// (see http://plan9.bell-labs.com/magic/man2html/2/draw)
// and the X Render extension.
package draw

// BUG(rsc): This is a toy library and not ready for production use.

import "image"

// A Porter-Duff compositing operator.
type Op int

const (
	// Over specifies ``(src in mask) over dst''.
	Over Op = iota
	// Src specifies ``src in mask''.
	Src
)

var zeroColor image.Color = image.AlphaColor{0}

// A draw.Image is an image.Image with a Set method to change a single pixel.
type Image interface {
	image.Image
	Set(x, y int, c image.Color)
}

// Draw calls DrawMask with a nil mask and an Over op.
func Draw(dst Image, r Rectangle, src image.Image, sp Point) {
	DrawMask(dst, r, src, sp, nil, ZP, Over)
}

// DrawMask aligns r.Min in dst with sp in src and mp in mask and then replaces the rectangle r
// in dst with the result of a Porter-Duff composition. A nil mask is treated as opaque.
// The implementation is simple and slow.
// TODO(nigeltao): Optimize this.
func DrawMask(dst Image, r Rectangle, src image.Image, sp Point, mask image.Image, mp Point, op Op) {
	dx, dy := src.Width()-sp.X, src.Height()-sp.Y
	if mask != nil {
		if dx > mask.Width()-mp.X {
			dx = mask.Width() - mp.X
		}
		if dy > mask.Height()-mp.Y {
			dy = mask.Height() - mp.Y
		}
	}
	if r.Dx() > dx {
		r.Max.X = r.Min.X + dx
	}
	if r.Dy() > dy {
		r.Max.Y = r.Min.Y + dy
	}

	// TODO(nigeltao): Clip r to dst's bounding box, and handle the case when sp or mp has negative X or Y.
	// TODO(nigeltao): Ensure that r is well formed, i.e. r.Max.X >= r.Min.X and likewise for Y.

	// Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation.
	if dst0, ok := dst.(*image.RGBA); ok {
		if op == Over {
			if mask0, ok := mask.(*image.Alpha); ok {
				if src0, ok := src.(image.ColorImage); ok {
					drawGlyphOver(dst0, r, src0, mask0, mp)
					return
				}
			}
		} else {
			if mask == nil {
				if src0, ok := src.(image.ColorImage); ok {
					drawFill(dst0, r, src0)
					return
				}
				if src0, ok := src.(*image.RGBA); ok {
					if dst0 == src0 && r.Overlaps(r.Add(sp.Sub(r.Min))) {
						// TODO(nigeltao): Implement a fast path for the overlapping case.
					} else {
						drawCopy(dst0, r, src0, sp)
						return
					}
				}
			}
		}
	}

	x0, x1, dx := r.Min.X, r.Max.X, 1
	y0, y1, dy := r.Min.Y, r.Max.Y, 1
	if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
		// Rectangles overlap: process backward?
		if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
			x0, x1, dx = x1-1, x0-1, -1
			y0, y1, dy = y1-1, y0-1, -1
		}
	}

	var out *image.RGBA64Color
	sy := sp.Y + y0 - r.Min.Y
	my := mp.Y + y0 - r.Min.Y
	for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
		sx := sp.X + x0 - r.Min.X
		mx := mp.X + x0 - r.Min.X
		for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
			// A nil mask is equivalent to a fully opaque, infinitely large mask.
			// We work in 16-bit color, so that multiplying two values does not overflow a uint32.
			const M = 1<<16 - 1
			ma := uint32(M)
			if mask != nil {
				_, _, _, ma = mask.At(mx, my).RGBA()
				ma >>= 16
			}
			switch {
			case ma == 0:
				if op == Over {
					// No-op.
				} else {
					dst.Set(x, y, zeroColor)
				}
			case ma == M && op == Src:
				dst.Set(x, y, src.At(sx, sy))
			default:
				sr, sg, sb, sa := src.At(sx, sy).RGBA()
				sr >>= 16
				sg >>= 16
				sb >>= 16
				sa >>= 16
				if out == nil {
					out = new(image.RGBA64Color)
				}
				if op == Over {
					dr, dg, db, da := dst.At(x, y).RGBA()
					dr >>= 16
					dg >>= 16
					db >>= 16
					da >>= 16
					a := M - (sa * ma / M)
					out.R = uint16((dr*a + sr*ma) / M)
					out.G = uint16((dg*a + sg*ma) / M)
					out.B = uint16((db*a + sb*ma) / M)
					out.A = uint16((da*a + sa*ma) / M)
				} else {
					out.R = uint16(sr * ma / M)
					out.G = uint16(sg * ma / M)
					out.B = uint16(sb * ma / M)
					out.A = uint16(sa * ma / M)
				}
				dst.Set(x, y, out)
			}
		}
	}
}

func drawGlyphOver(dst *image.RGBA, r Rectangle, src image.ColorImage, mask *image.Alpha, mp Point) {
	x0, x1 := r.Min.X, r.Max.X
	y0, y1 := r.Min.Y, r.Max.Y
	cr, cg, cb, ca := src.RGBA()
	cr >>= 16
	cg >>= 16
	cb >>= 16
	ca >>= 16
	for y, my := y0, mp.Y; y != y1; y, my = y+1, my+1 {
		for x, mx := x0, mp.X; x != x1; x, mx = x+1, mx+1 {
			ma := uint32(mask.Pixel[my][mx].A)
			if ma == 0 {
				continue
			}
			ma |= ma << 8
			rgba := dst.Pixel[y][x]
			dr := uint32(rgba.R)
			dg := uint32(rgba.G)
			db := uint32(rgba.B)
			da := uint32(rgba.A)
			// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
			// We work in 16-bit color, and so would normally do:
			// dr |= dr << 8
			// and similarly for dg, db and da, but instead we multiply a
			// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
			// This yields the same result, but is fewer arithmetic operations.
			const M = 1<<16 - 1
			a := M - (ca * ma / M)
			a *= 0x101
			dr = (dr*a + cr*ma) / M
			dg = (dg*a + cg*ma) / M
			db = (db*a + cb*ma) / M
			da = (da*a + ca*ma) / M
			dst.Pixel[y][x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
		}
	}
}

func drawFill(dst *image.RGBA, r Rectangle, src image.ColorImage) {
	if r.Dy() < 1 {
		return
	}
	cr, cg, cb, ca := src.RGBA()
	color := image.RGBAColor{uint8(cr >> 24), uint8(cg >> 24), uint8(cb >> 24), uint8(ca >> 24)}
	// The built-in copy function is faster than a straightforward for loop to fill the destination with
	// the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and
	// then use the first row as the slice source for the remaining rows.
	dx0, dx1 := r.Min.X, r.Max.X
	dy0, dy1 := r.Min.Y, r.Max.Y
	firstRow := dst.Pixel[dy0]
	for x := dx0; x < dx1; x++ {
		firstRow[x] = color
	}
	copySrc := firstRow[dx0:dx1]
	for y := dy0 + 1; y < dy1; y++ {
		copy(dst.Pixel[y][dx0:dx1], copySrc)
	}
}

func drawCopy(dst *image.RGBA, r Rectangle, src *image.RGBA, sp Point) {
	dx0, dx1 := r.Min.X, r.Max.X
	dy0, dy1 := r.Min.Y, r.Max.Y
	sx0, sx1 := sp.X, sp.X+dx1-dx0
	for y, sy := dy0, sp.Y; y < dy1; y, sy = y+1, sy+1 {
		copy(dst.Pixel[y][dx0:dx1], src.Pixel[sy][sx0:sx1])
	}
}

// Border aligns r.Min in dst with sp in src and then replaces pixels
// in a w-pixel border around r in dst with the result of the Porter-Duff compositing
// operation ``src over dst.''  If w is positive, the border extends w pixels inside r.
// If w is negative, the border extends w pixels outside r.
func Border(dst Image, r Rectangle, w int, src image.Image, sp Point) {
	i := w
	if i > 0 {
		// inside r
		Draw(dst, Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+i), src, sp)                          // top
		Draw(dst, Rect(r.Min.X, r.Min.Y+i, r.Min.X+i, r.Max.Y-i), src, sp.Add(Pt(0, i)))        // left
		Draw(dst, Rect(r.Max.X-i, r.Min.Y+i, r.Max.X, r.Max.Y-i), src, sp.Add(Pt(r.Dx()-i, i))) // right
		Draw(dst, Rect(r.Min.X, r.Max.Y-i, r.Max.X, r.Max.Y), src, sp.Add(Pt(0, r.Dy()-i)))     // bottom
		return
	}

	// outside r;
	i = -i
	Draw(dst, Rect(r.Min.X-i, r.Min.Y-i, r.Max.X+i, r.Min.Y), src, sp.Add(Pt(-i, -i))) // top
	Draw(dst, Rect(r.Min.X-i, r.Min.Y, r.Min.X, r.Max.Y), src, sp.Add(Pt(-i, 0)))      // left
	Draw(dst, Rect(r.Max.X, r.Min.Y, r.Max.X+i, r.Max.Y), src, sp.Add(Pt(r.Dx(), 0)))  // right
	Draw(dst, Rect(r.Min.X-i, r.Max.Y, r.Max.X+i, r.Max.Y+i), src, sp.Add(Pt(-i, 0)))  // bottom
}
<|endoftext|>


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

package ast

import "go/token"

// ----------------------------------------------------------------------------
// Export filtering

// exportFilter is a special filter function to extract exported nodes.
func exportFilter(name string) bool {
	return IsExported(name)
}

// FileExports trims the AST for a Go source file in place such that
// only exported nodes remain: all top-level identifiers which are not exported
// and their associated information (such as type, initial value, or function
// body) are removed. Non-exported fields and methods of exported types are
// stripped, and the function bodies of exported functions are set to nil.
// The File.Comments list is not changed.
//
// FileExports returns true if there are exported declarationa;
// it returns false otherwise.
//
func FileExports(src *File) bool {
	return FilterFile(src, exportFilter)
}

// PackageExports trims the AST for a Go package in place such that
// only exported nodes remain. The pkg.Files list is not changed, so that
// file names and top-level package comments don't get lost.
//
// PackageExports returns true if there are exported declarations;
// it returns false otherwise.
//
func PackageExports(pkg *Package) bool {
	return FilterPackage(pkg, exportFilter)
}

// ----------------------------------------------------------------------------
// General filtering

type Filter func(string) bool

func filterIdentList(list []*Ident, f Filter) []*Ident {
	j := 0
	for _, x := range list {
		if f(x.Name) {
			list[j] = x
			j++
		}
	}
	return list[0:j]
}

// fieldName assumes that x is the type of an anonymous field and
// returns the corresponding field name. If x is not an acceptable
// anonymous field, the result is nil.
//
func fieldName(x Expr) *Ident {
	switch t := x.(type) {
	case *Ident:
		return t
	case *SelectorExpr:
		if _, ok := t.X.(*Ident); ok {
			return t.Sel
		}
	case *StarExpr:
		return fieldName(t.X)
	}
	return nil
}

func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) {
	if fields == nil {
		return false
	}
	list := fields.List
	j := 0
	for _, f := range list {
		keepField := false
		if len(f.Names) == 0 {
			// anonymous field
			name := fieldName(f.Type)
			keepField = name != nil && filter(name.Name)
		} else {
			n := len(f.Names)
			f.Names = filterIdentList(f.Names, filter)
			if len(f.Names) < n {
				removedFields = true
			}
			keepField = len(f.Names) > 0
		}
		if keepField {
			if filter == exportFilter {
				filterType(f.Type, filter)
			}
			list[j] = f
			j++
		}
	}
	if j < len(list) {
		removedFields = true
	}
	fields.List = list[0:j]
	return
}

func filterParamList(fields *FieldList, filter Filter) bool {
	if fields == nil {
		return false
	}
	var b bool
	for _, f := range fields.List {
		if filterType(f.Type, filter) {
			b = true
		}
	}
	return b
}

func filterType(typ Expr, f Filter) bool {
	switch t := typ.(type) {
	case *Ident:
		return f(t.Name)
	case *ParenExpr:
		return filterType(t.X, f)
	case *ArrayType:
		return filterType(t.Elt, f)
	case *StructType:
		if filterFieldList(t.Fields, f) {
			t.Incomplete = true
		}
		return len(t.Fields.List) > 0
	case *FuncType:
		b1 := filterParamList(t.Params, f)
		b2 := filterParamList(t.Results, f)
		return b1 || b2
	case *InterfaceType:
		if filterFieldList(t.Methods, f) {
			t.Incomplete = true
		}
		return len(t.Methods.List) > 0
	case *MapType:
		b1 := filterType(t.Key, f)
		b2 := filterType(t.Value, f)
		return b1 || b2
	case *ChanType:
		return filterType(t.Value, f)
	}
	return false
}

func filterSpec(spec Spec, f Filter) bool {
	switch s := spec.(type) {
	case *ValueSpec:
		s.Names = filterIdentList(s.Names, f)
		if len(s.Names) > 0 {
			if f == exportFilter {
				filterType(s.Type, f)
			}
			return true
		}
	case *TypeSpec:
		if f(s.Name.Name) {
			if f == exportFilter {
				filterType(s.Type, f)
			}
			return true
		}
		if f != exportFilter {
			// For general filtering (not just exports),
			// filter type even if name is not filtered
			// out.
			// If the type contains filtered elements,
			// keep the declaration.
			return filterType(s.Type, f)
		}
	}
	return false
}

func filterSpecList(list []Spec, f Filter) []Spec {
	j := 0
	for _, s := range list {
		if filterSpec(s, f) {
			list[j] = s
			j++
		}
	}
	return list[0:j]
}

// FilterDecl trims the AST for a Go declaration in place by removing
// all names (including struct field and interface method names, but
// not from parameter lists) that don't pass through the filter f.
//
// FilterDecl returns true if there are any declared names left after
// filtering; it returns false otherwise.
//
func FilterDecl(decl Decl, f Filter) bool {
	switch d := decl.(type) {
	case *GenDecl:
		d.Specs = filterSpecList(d.Specs, f)
		return len(d.Specs) > 0
	case *FuncDecl:
		if f == exportFilter {
			d.Body = nil // strip body
		}
		return f(d.Name.Name)
	}
	return false
}

// FilterFile trims the AST for a Go file in place by removing all
// names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. The File.Comments list
// is not changed.
//
// FilterFile returns true if there are any top-level declarations
// left after filtering; it returns false otherwise.
//
func FilterFile(src *File, f Filter) bool {
	j := 0
	for _, d := range src.Decls {
		if FilterDecl(d, f) {
			src.Decls[j] = d
			j++
		}
	}
	src.Decls = src.Decls[0:j]
	return j > 0
}

// FilterPackage trims the AST for a Go package in place by removing
// all names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. The pkg.Files list is not
// changed, so that file names and top-level package comments don't get
// lost.
//
// FilterPackage returns true if there are any top-level declarations
// left after filtering; it returns false otherwise.
//
func FilterPackage(pkg *Package, f Filter) bool {
	hasDecls := false
	for _, src := range pkg.Files {
		if FilterFile(src, f) {
			hasDecls = true
		}
	}
	return hasDecls
}

// ----------------------------------------------------------------------------
// Merging of package files

// The MergeMode flags control the behavior of MergePackageFiles.
type MergeMode uint

const (
	// If set, duplicate function declarations are excluded.
	FilterFuncDuplicates MergeMode = 1 << iota
	// If set, comments that are not associated with a specific
	// AST node (as Doc or Comment) are excluded.
	FilterUnassociatedComments
	// If set, duplicate import declarations are excluded.
	FilterImportDuplicates
)

// separator is an empty //-style comment that is interspersed between
// different comment groups when they are concatenated into a single group
//
var separator = &Comment{noPos, "//"}

// MergePackageFiles creates a file AST by merging the ASTs of the
// files belonging to a package. The mode flags control merging behavior.
//
func MergePackageFiles(pkg *Package, mode MergeMode) *File {
	// Count the number of package docs, comments and declarations across
	// all package files.
	ndocs := 0
	ncomments := 0
	ndecls := 0
	for _, f := range pkg.Files {
		if f.Doc != nil {
			ndocs += len(f.Doc.List) + 1 // +1 for separator
		}
		ncomments += len(f.Comments)
		ndecls += len(f.Decls)
	}

	// Collect package comments from all package files into a single
	// CommentGroup - the collected package documentation. The order
	// is unspecified. In general there should be only one file with
	// a package comment; but it's better to collect extra comments
	// than drop them on the floor.
	var doc *CommentGroup
	var pos token.Pos
	if ndocs > 0 {
		list := make([]*Comment, ndocs-1) // -1: no separator before first group
		i := 0
		for _, f := range pkg.Files {
			if f.Doc != nil {
				if i > 0 {
					// not the first group - add separator
					list[i] = separator
					i++
				}
				for _, c := range f.Doc.List {
					list[i] = c
					i++
				}
				if f.Package > pos {
					// Keep the maximum package clause position as
					// position for the package clause of the merged
					// files.
					pos = f.Package
				}
			}
		}
		doc = &CommentGroup{list}
	}

	// Collect declarations from all package files.
	var decls []Decl
	if ndecls > 0 {
		decls = make([]Decl, ndecls)
		funcs := make(map[string]int) // map of global function name -> decls index
		i := 0                        // current index
		n := 0                        // number of filtered entries
		for _, f := range pkg.Files {
			for _, d := range f.Decls {
				if mode&FilterFuncDuplicates != 0 {
					// A language entity may be declared multiple
					// times in different package files; only at
					// build time declarations must be unique.
					// For now, exclude multiple declarations of
					// functions - keep the one with documentation.
					//
					// TODO(gri): Expand this filtering to other
					//            entities (const, type, vars) if
					//            multiple declarations are common.
					if f, isFun := d.(*FuncDecl); isFun {
						name := f.Name.Name
						if j, exists := funcs[name]; exists {
							// function declared already
							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
								// existing declaration has no documentation;
								// ignore the existing declaration
								decls[j] = nil
							} else {
								// ignore the new declaration
								d = nil
							}
							n++ // filtered an entry
						} else {
							funcs[name] = i
						}
					}
				}
				decls[i] = d
				i++
			}
		}

		// Eliminate nil entries from the decls list if entries were
		// filtered. We do this using a 2nd pass in order to not disturb
		// the original declaration order in the source (otherwise, this
		// would also invalidate the monotonically increasing position
		// info within a single file).
		if n > 0 {
			i = 0
			for _, d := range decls {
				if d != nil {
					decls[i] = d
					i++
				}
			}
			decls = decls[0:i]
		}
	}

	// Collect import specs from all package files.
	var imports []*ImportSpec
	if mode&FilterImportDuplicates != 0 {
		seen := make(map[string]bool)
		for _, f := range pkg.Files {
			for _, imp := range f.Imports {
				if path := imp.Path.Value; !seen[path] {
					// TODO: consider handling cases where:
					// - 2 imports exist with the same import path but
					//   have different local names (one should probably
					//   keep both of them)
					// - 2 imports exist but only one has a comment
					// - 2 imports exist and they both have (possibly
					//   different) comments
					imports = append(imports, imp)
					seen[path] = true
				}
			}
		}
	} else {
		for _, f := range pkg.Files {
			imports = append(imports, f.Imports...)
		}
	}

	// Collect comments from all package files.
	var comments []*CommentGroup
	if mode&FilterUnassociatedComments == 0 {
		comments = make([]*CommentGroup, ncomments)
		i := 0
		for _, f := range pkg.Files {
			i += copy(comments[i:], f.Comments)
		}
	}

	// TODO(gri) need to compute unresolved identifiers!
	return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
}
<commit_msg>go/ast: don't remove function bodies when filtering exports<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ast

import "go/token"

// ----------------------------------------------------------------------------
// Export filtering

// exportFilter is a special filter function to extract exported nodes.
func exportFilter(name string) bool {
	return IsExported(name)
}

// FileExports trims the AST for a Go source file in place such that
// only exported nodes remain: all top-level identifiers which are not exported
// and their associated information (such as type, initial value, or function
// body) are removed. Non-exported fields and methods of exported types are
// stripped. The File.Comments list is not changed.
//
// FileExports returns true if there are exported declarationa;
// it returns false otherwise.
//
func FileExports(src *File) bool {
	return FilterFile(src, exportFilter)
}

// PackageExports trims the AST for a Go package in place such that
// only exported nodes remain. The pkg.Files list is not changed, so that
// file names and top-level package comments don't get lost.
//
// PackageExports returns true if there are exported declarations;
// it returns false otherwise.
//
func PackageExports(pkg *Package) bool {
	return FilterPackage(pkg, exportFilter)
}

// ----------------------------------------------------------------------------
// General filtering

type Filter func(string) bool

func filterIdentList(list []*Ident, f Filter) []*Ident {
	j := 0
	for _, x := range list {
		if f(x.Name) {
			list[j] = x
			j++
		}
	}
	return list[0:j]
}

// fieldName assumes that x is the type of an anonymous field and
// returns the corresponding field name. If x is not an acceptable
// anonymous field, the result is nil.
//
func fieldName(x Expr) *Ident {
	switch t := x.(type) {
	case *Ident:
		return t
	case *SelectorExpr:
		if _, ok := t.X.(*Ident); ok {
			return t.Sel
		}
	case *StarExpr:
		return fieldName(t.X)
	}
	return nil
}

func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) {
	if fields == nil {
		return false
	}
	list := fields.List
	j := 0
	for _, f := range list {
		keepField := false
		if len(f.Names) == 0 {
			// anonymous field
			name := fieldName(f.Type)
			keepField = name != nil && filter(name.Name)
		} else {
			n := len(f.Names)
			f.Names = filterIdentList(f.Names, filter)
			if len(f.Names) < n {
				removedFields = true
			}
			keepField = len(f.Names) > 0
		}
		if keepField {
			if filter == exportFilter {
				filterType(f.Type, filter)
			}
			list[j] = f
			j++
		}
	}
	if j < len(list) {
		removedFields = true
	}
	fields.List = list[0:j]
	return
}

func filterParamList(fields *FieldList, filter Filter) bool {
	if fields == nil {
		return false
	}
	var b bool
	for _, f := range fields.List {
		if filterType(f.Type, filter) {
			b = true
		}
	}
	return b
}

func filterType(typ Expr, f Filter) bool {
	switch t := typ.(type) {
	case *Ident:
		return f(t.Name)
	case *ParenExpr:
		return filterType(t.X, f)
	case *ArrayType:
		return filterType(t.Elt, f)
	case *StructType:
		if filterFieldList(t.Fields, f) {
			t.Incomplete = true
		}
		return len(t.Fields.List) > 0
	case *FuncType:
		b1 := filterParamList(t.Params, f)
		b2 := filterParamList(t.Results, f)
		return b1 || b2
	case *InterfaceType:
		if filterFieldList(t.Methods, f) {
			t.Incomplete = true
		}
		return len(t.Methods.List) > 0
	case *MapType:
		b1 := filterType(t.Key, f)
		b2 := filterType(t.Value, f)
		return b1 || b2
	case *ChanType:
		return filterType(t.Value, f)
	}
	return false
}

func filterSpec(spec Spec, f Filter) bool {
	switch s := spec.(type) {
	case *ValueSpec:
		s.Names = filterIdentList(s.Names, f)
		if len(s.Names) > 0 {
			if f == exportFilter {
				filterType(s.Type, f)
			}
			return true
		}
	case *TypeSpec:
		if f(s.Name.Name) {
			if f == exportFilter {
				filterType(s.Type, f)
			}
			return true
		}
		if f != exportFilter {
			// For general filtering (not just exports),
			// filter type even if name is not filtered
			// out.
			// If the type contains filtered elements,
			// keep the declaration.
			return filterType(s.Type, f)
		}
	}
	return false
}

func filterSpecList(list []Spec, f Filter) []Spec {
	j := 0
	for _, s := range list {
		if filterSpec(s, f) {
			list[j] = s
			j++
		}
	}
	return list[0:j]
}

// FilterDecl trims the AST for a Go declaration in place by removing
// all names (including struct field and interface method names, but
// not from parameter lists) that don't pass through the filter f.
//
// FilterDecl returns true if there are any declared names left after
// filtering; it returns false otherwise.
//
func FilterDecl(decl Decl, f Filter) bool {
	switch d := decl.(type) {
	case *GenDecl:
		d.Specs = filterSpecList(d.Specs, f)
		return len(d.Specs) > 0
	case *FuncDecl:
		return f(d.Name.Name)
	}
	return false
}

// FilterFile trims the AST for a Go file in place by removing all
// names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. The File.Comments list
// is not changed.
//
// FilterFile returns true if there are any top-level declarations
// left after filtering; it returns false otherwise.
//
func FilterFile(src *File, f Filter) bool {
	j := 0
	for _, d := range src.Decls {
		if FilterDecl(d, f) {
			src.Decls[j] = d
			j++
		}
	}
	src.Decls = src.Decls[0:j]
	return j > 0
}

// FilterPackage trims the AST for a Go package in place by removing
// all names from top-level declarations (including struct field and
// interface method names, but not from parameter lists) that don't
// pass through the filter f. If the declaration is empty afterwards,
// the declaration is removed from the AST. The pkg.Files list is not
// changed, so that file names and top-level package comments don't get
// lost.
//
// FilterPackage returns true if there are any top-level declarations
// left after filtering; it returns false otherwise.
//
func FilterPackage(pkg *Package, f Filter) bool {
	hasDecls := false
	for _, src := range pkg.Files {
		if FilterFile(src, f) {
			hasDecls = true
		}
	}
	return hasDecls
}

// ----------------------------------------------------------------------------
// Merging of package files

// The MergeMode flags control the behavior of MergePackageFiles.
type MergeMode uint

const (
	// If set, duplicate function declarations are excluded.
	FilterFuncDuplicates MergeMode = 1 << iota
	// If set, comments that are not associated with a specific
	// AST node (as Doc or Comment) are excluded.
	FilterUnassociatedComments
	// If set, duplicate import declarations are excluded.
	FilterImportDuplicates
)

// separator is an empty //-style comment that is interspersed between
// different comment groups when they are concatenated into a single group
//
var separator = &Comment{noPos, "//"}

// MergePackageFiles creates a file AST by merging the ASTs of the
// files belonging to a package. The mode flags control merging behavior.
//
func MergePackageFiles(pkg *Package, mode MergeMode) *File {
	// Count the number of package docs, comments and declarations across
	// all package files.
	ndocs := 0
	ncomments := 0
	ndecls := 0
	for _, f := range pkg.Files {
		if f.Doc != nil {
			ndocs += len(f.Doc.List) + 1 // +1 for separator
		}
		ncomments += len(f.Comments)
		ndecls += len(f.Decls)
	}

	// Collect package comments from all package files into a single
	// CommentGroup - the collected package documentation. The order
	// is unspecified. In general there should be only one file with
	// a package comment; but it's better to collect extra comments
	// than drop them on the floor.
	var doc *CommentGroup
	var pos token.Pos
	if ndocs > 0 {
		list := make([]*Comment, ndocs-1) // -1: no separator before first group
		i := 0
		for _, f := range pkg.Files {
			if f.Doc != nil {
				if i > 0 {
					// not the first group - add separator
					list[i] = separator
					i++
				}
				for _, c := range f.Doc.List {
					list[i] = c
					i++
				}
				if f.Package > pos {
					// Keep the maximum package clause position as
					// position for the package clause of the merged
					// files.
					pos = f.Package
				}
			}
		}
		doc = &CommentGroup{list}
	}

	// Collect declarations from all package files.
	var decls []Decl
	if ndecls > 0 {
		decls = make([]Decl, ndecls)
		funcs := make(map[string]int) // map of global function name -> decls index
		i := 0                        // current index
		n := 0                        // number of filtered entries
		for _, f := range pkg.Files {
			for _, d := range f.Decls {
				if mode&FilterFuncDuplicates != 0 {
					// A language entity may be declared multiple
					// times in different package files; only at
					// build time declarations must be unique.
					// For now, exclude multiple declarations of
					// functions - keep the one with documentation.
					//
					// TODO(gri): Expand this filtering to other
					//            entities (const, type, vars) if
					//            multiple declarations are common.
					if f, isFun := d.(*FuncDecl); isFun {
						name := f.Name.Name
						if j, exists := funcs[name]; exists {
							// function declared already
							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
								// existing declaration has no documentation;
								// ignore the existing declaration
								decls[j] = nil
							} else {
								// ignore the new declaration
								d = nil
							}
							n++ // filtered an entry
						} else {
							funcs[name] = i
						}
					}
				}
				decls[i] = d
				i++
			}
		}

		// Eliminate nil entries from the decls list if entries were
		// filtered. We do this using a 2nd pass in order to not disturb
		// the original declaration order in the source (otherwise, this
		// would also invalidate the monotonically increasing position
		// info within a single file).
		if n > 0 {
			i = 0
			for _, d := range decls {
				if d != nil {
					decls[i] = d
					i++
				}
			}
			decls = decls[0:i]
		}
	}

	// Collect import specs from all package files.
	var imports []*ImportSpec
	if mode&FilterImportDuplicates != 0 {
		seen := make(map[string]bool)
		for _, f := range pkg.Files {
			for _, imp := range f.Imports {
				if path := imp.Path.Value; !seen[path] {
					// TODO: consider handling cases where:
					// - 2 imports exist with the same import path but
					//   have different local names (one should probably
					//   keep both of them)
					// - 2 imports exist but only one has a comment
					// - 2 imports exist and they both have (possibly
					//   different) comments
					imports = append(imports, imp)
					seen[path] = true
				}
			}
		}
	} else {
		for _, f := range pkg.Files {
			imports = append(imports, f.Imports...)
		}
	}

	// Collect comments from all package files.
	var comments []*CommentGroup
	if mode&FilterUnassociatedComments == 0 {
		comments = make([]*CommentGroup, ncomments)
		i := 0
		for _, f := range pkg.Files {
			i += copy(comments[i:], f.Comments)
		}
	}

	// TODO(gri) need to compute unresolved identifiers!
	return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
}
<|endoftext|>