<commit_before>// Version 1.82
// Supports Windows, Linux, Mac, and Raspberry Pi, Beagle Bone Black

package main

import (
	"flag"
	"os"
	"os/user"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"text/template"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/carlescere/scheduler"
	"github.com/gin-gonic/gin"
	"github.com/itsjamie/gin-cors"
	"github.com/kardianos/osext"
	"github.com/vharitonsky/iniflags"
	//"github.com/sanbornm/go-selfupdate/selfupdate" #included in update.go to change heavily
)

var (
	version              = "x.x.x-dev" //don't modify it, Jenkins will take care
	git_revision         = "xxxxxxxx"  //don't modify it, Jenkins will take care
	embedded_autoupdate  = true
	embedded_autoextract = false
	hibernate            = flag.Bool("hibernate", false, "start hibernated")
	verbose              = flag.Bool("v", true, "show debug logging")
	//verbose = flag.Bool("v", false, "show debug logging")
	isLaunchSelf = flag.Bool("ls", false, "launch self 5 seconds later")
	configIni    = flag.String("configFile", "config.ini", "config file path")
	regExpFilter = flag.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
	gcType       = flag.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
	logDump      = flag.String("log", "off", "off = (default)")
	// hostname. allow user to override, otherwise we look it up
	hostname       = flag.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
	updateUrl      = flag.String("updateUrl", "", "")
	appName        = flag.String("appName", "", "")
	globalToolsMap = make(map[string]string)
	tempToolsPath  = createToolsDir()
	port           string
	portSSL        string
)

type NullWriter int

func (NullWriter) Write([]byte) (int, error) { return 0, nil }

type logWriter struct{}

func (u *logWriter) Write(p []byte) (n int, err error) {
	h.broadcastSys <- p
	return 0, nil
}

var logger_ws logWriter

func createToolsDir() string {
	usr, _ := user.Current()
	return usr.HomeDir + "/.arduino-create"
}

func homeHandler(c *gin.Context) {
	homeTemplate.Execute(c.Writer, c.Request.Host)
}

func launchSelfLater() {
	log.Println("Going to launch myself 2 seconds later.")
	time.Sleep(2 * 1000 * time.Millisecond)
	log.Println("Done waiting 2 secs. Now launching...")
}

func main() {

	flag.Parse()

	if *hibernate == false {

		go func() {

			// autoextract self
			src, _ := osext.Executable()
			dest := filepath.Dir(src)

			os.Mkdir(tempToolsPath, 0777)
			hideFile(tempToolsPath)

			if embedded_autoextract {
				// save the config.ini (if it exists)
				if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
					log.Println("First run, unzipping self")
					err := Unzip(src, dest)
					log.Println("Self extraction, err:", err)
				}

				if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
					flag.Parse()
					log.Println("No config.ini at", *configIni)
				} else {
					flag.Parse()
					flag.Set("config", dest+"/"+*configIni)
					iniflags.Parse()
				}
			} else {
				flag.Set("config", dest+"/"+*configIni)
				iniflags.Parse()
			}

			//log.SetFormatter(&log.JSONFormatter{})

			log.SetLevel(log.InfoLevel)

			log.SetOutput(os.Stderr)

			// see if we are supposed to wait 5 seconds
			if *isLaunchSelf {
				launchSelfLater()
			}

			if embedded_autoupdate {

				var updater = &Updater{
					CurrentVersion: version,
					ApiURL:         *updateUrl,
					BinURL:         *updateUrl,
					DiffURL:        "",
					Dir:            "update/",
					CmdName:        *appName,
				}

				if updater != nil {
					updater_job := func() {
						go updater.BackgroundRun()
					}
					scheduler.Every(5).Minutes().Run(updater_job)
				}
			}

			log.Println("Version:" + version)

			// hostname
			hn, _ := os.Hostname()
			if *hostname == "unknown-hostname" {
				*hostname = hn
			}
			log.Println("Hostname:", *hostname)

			// turn off garbage collection
			// this is dangerous, as u could overflow memory
			//if *isGC {
			if *gcType == "std" {
				log.Println("Garbage collection is on using Standard mode, meaning we just let Golang determine when to garbage collect.")
			} else if *gcType == "max" {
				log.Println("Garbage collection is on for MAXIMUM real-time collecting on each send/recv from serial port. Higher CPU, but less stopping of the world to garbage collect since it is being done on a constant basis.")
			} else {
				log.Println("Garbage collection is off. Memory use will grow unbounded. You WILL RUN OUT OF RAM unless you send in the gc command to manually force garbage collection. Lower CPU, but progressive memory footprint.")
				debug.SetGCPercent(-1)
			}

			// see if they provided a regex filter
			if len(*regExpFilter) > 0 {
				log.Printf("You specified a serial port regular expression filter: %v\n", *regExpFilter)
			}

			// list serial ports
			portList, _ := GetList(false)
			log.Println("Your serial ports:")
			if len(portList) == 0 {
				log.Println("\tThere are no serial ports to list.")
			}
			for _, element := range portList {
				log.Printf("\t%v\n", element)

			}

			if !*verbose {
				log.Println("You can enter verbose mode to see all logging by starting with the -v command line switch.")
				log.SetOutput(new(NullWriter)) //route all logging to nullwriter
			}

			// launch the hub routine which is the singleton for the websocket server
			go h.run()
			// launch our serial port routine
			go sh.run()
			// launch our dummy data routine
			//go d.run()

			go discoverLoop()

			r := gin.New()

			socketHandler := wsHandler().ServeHTTP

			r.Use(cors.Middleware(cors.Config{
				Origins:         "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://webide.arduino.cc:8080, http://create-staging.arduino.cc, https://create-staging.arduino.cc, http://localhost:8989, https://localhost:8990",
				Methods:         "GET, PUT, POST, DELETE",
				RequestHeaders:  "Origin, Authorization, Content-Type",
				ExposedHeaders:  "",
				MaxAge:          50 * time.Second,
				Credentials:     true,
				ValidateHeaders: false,
			}))

			r.GET("/", homeHandler)
			r.POST("/upload", uploadHandler)
			r.GET("/socket.io/", socketHandler)
			r.POST("/socket.io/", socketHandler)
			r.Handle("WS", "/socket.io/", socketHandler)
			r.Handle("WSS", "/socket.io/", socketHandler)
			r.GET("/info", infoHandler)
			go func() {
				start := 49152
				end := 49162
				i := start
				for i < end {
					i = i + 1
					portSSL = ":" + strconv.Itoa(i)
					if err := r.RunTLS(portSSL, filepath.Join(dest, "cert.pem"), filepath.Join(dest, "key.pem")); err != nil {
						log.Printf("Error trying to bind to port: %v, so exiting...", err)
						continue
					} else {
						ip := "0.0.0.0"
						log.Print("Starting server and websocket (SSL) on " + ip + "" + port)
						break
					}
				}
			}()

			go func() {
				start := 49152
				end := 49162
				i := start
				for i < end {
					i = i + 1
					port = ":" + strconv.Itoa(i)
					if err := r.Run(port); err != nil {
						log.Printf("Error trying to bind to port: %v, so exiting...", err)
						continue
					} else {
						ip := "0.0.0.0"
						log.Print("Starting server and websocket on " + ip + "" + port)
						break
					}
				}
			}()

		}()
	}
	setupSysTray()
}

var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHtml))

// If you navigate to this server's homepage, you'll get this HTML
// so you can directly interact with the serial port server
const homeTemplateHtml = `<!DOCTYPE html>

<title>Serial Port Example</title>

<form id="form">
    <input type="submit" value="Send" />
    <input type="text" id="msg" size="64"/>
    <input name="pause" type="checkbox" value="pause" id="myCheck"/> Pause

</form>

`
<commit_msg>change ports for websocket connection<commit_after>// Version 1.82
// Supports Windows, Linux, Mac, and Raspberry Pi, Beagle Bone Black

package main

import (
	"flag"
	"os"
	"os/user"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"text/template"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/carlescere/scheduler"
	"github.com/gin-gonic/gin"
	"github.com/itsjamie/gin-cors"
	"github.com/kardianos/osext"
	"github.com/vharitonsky/iniflags"
	//"github.com/sanbornm/go-selfupdate/selfupdate" #included in update.go to change heavily
)

var (
	version              = "x.x.x-dev" //don't modify it, Jenkins will take care
	git_revision         = "xxxxxxxx"  //don't modify it, Jenkins will take care
	embedded_autoupdate  = true
	embedded_autoextract = false
	hibernate            = flag.Bool("hibernate", false, "start hibernated")
	verbose              = flag.Bool("v", true, "show debug logging")
	//verbose = flag.Bool("v", false, "show debug logging")
	isLaunchSelf = flag.Bool("ls", false, "launch self 5 seconds later")
	configIni    = flag.String("configFile", "config.ini", "config file path")
	regExpFilter = flag.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
	gcType       = flag.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
	logDump      = flag.String("log", "off", "off = (default)")
	// hostname. allow user to override, otherwise we look it up
	hostname       = flag.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
	updateUrl      = flag.String("updateUrl", "", "")
	appName        = flag.String("appName", "", "")
	globalToolsMap = make(map[string]string)
	tempToolsPath  = createToolsDir()
	port           string
	portSSL        string
)

type NullWriter int

func (NullWriter) Write([]byte) (int, error) { return 0, nil }

type logWriter struct{}

func (u *logWriter) Write(p []byte) (n int, err error) {
	h.broadcastSys <- p
	return 0, nil
}

var logger_ws logWriter

func createToolsDir() string {
	usr, _ := user.Current()
	return usr.HomeDir + "/.arduino-create"
}

func homeHandler(c *gin.Context) {
	homeTemplate.Execute(c.Writer, c.Request.Host)
}

func launchSelfLater() {
	log.Println("Going to launch myself 2 seconds later.")
	time.Sleep(2 * 1000 * time.Millisecond)
	log.Println("Done waiting 2 secs. Now launching...")
}

func main() {

	flag.Parse()

	if *hibernate == false {

		go func() {

			// autoextract self
			src, _ := osext.Executable()
			dest := filepath.Dir(src)

			os.Mkdir(tempToolsPath, 0777)
			hideFile(tempToolsPath)

			if embedded_autoextract {
				// save the config.ini (if it exists)
				if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
					log.Println("First run, unzipping self")
					err := Unzip(src, dest)
					log.Println("Self extraction, err:", err)
				}

				if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
					flag.Parse()
					log.Println("No config.ini at", *configIni)
				} else {
					flag.Parse()
					flag.Set("config", dest+"/"+*configIni)
					iniflags.Parse()
				}
			} else {
				flag.Set("config", dest+"/"+*configIni)
				iniflags.Parse()
			}

			//log.SetFormatter(&log.JSONFormatter{})

			log.SetLevel(log.InfoLevel)

			log.SetOutput(os.Stderr)

			// see if we are supposed to wait 5 seconds
			if *isLaunchSelf {
				launchSelfLater()
			}

			if embedded_autoupdate {

				var updater = &Updater{
					CurrentVersion: version,
					ApiURL:         *updateUrl,
					BinURL:         *updateUrl,
					DiffURL:        "",
					Dir:            "update/",
					CmdName:        *appName,
				}

				if updater != nil {
					updater_job := func() {
						go updater.BackgroundRun()
					}
					scheduler.Every(5).Minutes().Run(updater_job)
				}
			}

			log.Println("Version:" + version)

			// hostname
			hn, _ := os.Hostname()
			if *hostname == "unknown-hostname" {
				*hostname = hn
			}
			log.Println("Hostname:", *hostname)

			// turn off garbage collection
			// this is dangerous, as u could overflow memory
			//if *isGC {
			if *gcType == "std" {
				log.Println("Garbage collection is on using Standard mode, meaning we just let Golang determine when to garbage collect.")
			} else if *gcType == "max" {
				log.Println("Garbage collection is on for MAXIMUM real-time collecting on each send/recv from serial port. Higher CPU, but less stopping of the world to garbage collect since it is being done on a constant basis.")
			} else {
				log.Println("Garbage collection is off. Memory use will grow unbounded. You WILL RUN OUT OF RAM unless you send in the gc command to manually force garbage collection. Lower CPU, but progressive memory footprint.")
				debug.SetGCPercent(-1)
			}

			// see if they provided a regex filter
			if len(*regExpFilter) > 0 {
				log.Printf("You specified a serial port regular expression filter: %v\n", *regExpFilter)
			}

			// list serial ports
			portList, _ := GetList(false)
			log.Println("Your serial ports:")
			if len(portList) == 0 {
				log.Println("\tThere are no serial ports to list.")
			}
			for _, element := range portList {
				log.Printf("\t%v\n", element)

			}

			if !*verbose {
				log.Println("You can enter verbose mode to see all logging by starting with the -v command line switch.")
				log.SetOutput(new(NullWriter)) //route all logging to nullwriter
			}

			// launch the hub routine which is the singleton for the websocket server
			go h.run()
			// launch our serial port routine
			go sh.run()
			// launch our dummy data routine
			//go d.run()

			go discoverLoop()

			r := gin.New()

			socketHandler := wsHandler().ServeHTTP

			r.Use(cors.Middleware(cors.Config{
				Origins:         "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://webide.arduino.cc:8080, http://create-staging.arduino.cc, https://create-staging.arduino.cc, http://localhost:8989, https://localhost:8990",
				Methods:         "GET, PUT, POST, DELETE",
				RequestHeaders:  "Origin, Authorization, Content-Type",
				ExposedHeaders:  "",
				MaxAge:          50 * time.Second,
				Credentials:     true,
				ValidateHeaders: false,
			}))

			r.GET("/", homeHandler)
			r.POST("/upload", uploadHandler)
			r.GET("/socket.io/", socketHandler)
			r.POST("/socket.io/", socketHandler)
			r.Handle("WS", "/socket.io/", socketHandler)
			r.Handle("WSS", "/socket.io/", socketHandler)
			r.GET("/info", infoHandler)
			go func() {
				start := 8990
				end := 9000
				i := start
				for i < end {
					i = i + 1
					portSSL = ":" + strconv.Itoa(i)
					if err := r.RunTLS(portSSL, filepath.Join(dest, "cert.pem"), filepath.Join(dest, "key.pem")); err != nil {
						log.Printf("Error trying to bind to port: %v, so exiting...", err)
						continue
					} else {
						ip := "0.0.0.0"
						log.Print("Starting server and websocket (SSL) on " + ip + "" + port)
						break
					}
				}
			}()

			go func() {
				start := 8990
				end := 9000
				i := start
				for i < end {
					i = i + 1
					port = ":" + strconv.Itoa(i)
					if err := r.Run(port); err != nil {
						log.Printf("Error trying to bind to port: %v, so exiting...", err)
						continue
					} else {
						ip := "0.0.0.0"
						log.Print("Starting server and websocket on " + ip + "" + port)
						break
					}
				}
			}()

		}()
	}
	setupSysTray()
}

var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHtml))

// If you navigate to this server's homepage, you'll get this HTML
// so you can directly interact with the serial port server
const homeTemplateHtml = `<!DOCTYPE html>

<title>Serial Port Example</title>

<form id="form">
    <input type="submit" value="Send" />
    <input type="text" id="msg" size="64"/>
    <input name="pause" type="checkbox" value="pause" id="myCheck"/> Pause

</form>

`
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/gophergala/go_report/check"
	"gopkg.in/mgo.v2"
	"labix.org/v2/mgo/bson"
)

var (
	mongoURL        = "mongodb://localhost:27017"
	mongoDatabase   = "goreportcard"
	mongoCollection = "reports"
)

func getMongoCollection() (*mgo.Collection, error) {
	session, err := mgo.Dial(mongoURL)
	if err != nil {
		return nil, err
	}
	c := session.DB(mongoDatabase).C(mongoCollection)
	return c, nil
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving home page")
	if r.URL.Path[1:] == "" {
		http.ServeFile(w, r, "templates/home.html")
	} else {
		http.NotFound(w, r)
	}
}

func assetsHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving " + r.URL.Path[1:])
	http.ServeFile(w, r, r.URL.Path[1:])
}

func orgRepoNames(url string) (string, string) {
	dir := strings.TrimSuffix(url, ".git")
	split := strings.Split(dir, "/")
	org := split[len(split)-2]
	repoName := split[len(split)-1]

	return org, repoName
}

func dirName(url string) string {
	org, repoName := orgRepoNames(url)

	return fmt.Sprintf("repos/src/github.com/%s/%s", org, repoName)
}

func clone(url string) error {
	org, _ := orgRepoNames(url)
	if err := os.Mkdir(fmt.Sprintf("repos/src/github.com/%s", org), 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("could not create dir: %v", err)
	}
	dir := dirName(url)
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		cmd := exec.Command("timeout", "120", "git", "clone", "--depth", "1", "--single-branch", url, dir)
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("could not run git clone: %v", err)
		}
	} else if err != nil {
		return fmt.Errorf("could not stat dir: %v", err)
	} else {
		cmd := exec.Command("git", "-C", dir, "pull")
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("could not pull repo: %v", err)
		}
	}

	return nil
}

type score struct {
	Name          string              `json:"name"`
	Description   string              `json:"description"`
	FileSummaries []check.FileSummary `json:"file_summaries"`
	Percentage    float64             `json:"percentage"`
}

type checksResp struct {
	Checks      []score   `json:"checks"`
	Average     float64   `json:"average"`
	Files       int       `json:"files"`
	Issues      int       `json:"issues"`
	Repo        string    `json:"repo"`
	LastRefresh time.Time `json:"last_refresh"`
}

func getFromCache(repo string) (checksResp, error) {
	// try and fetch from mongo
	coll, err := getMongoCollection()
	if err != nil {
		return checksResp{}, fmt.Errorf("Failed to get mongo collection during GET: %v", err)
	}
	resp := checksResp{}
	err = coll.Find(bson.M{"repo": repo}).One(&resp)
	if err != nil {
		return checksResp{}, fmt.Errorf("Failed to fetch %q from mongo: %v", repo, err)
	}

	resp.LastRefresh = resp.LastRefresh.UTC()

	return resp, nil
}

func checkHandler(w http.ResponseWriter, r *http.Request) {
	repo := r.FormValue("repo")
	url := repo
	if !strings.HasPrefix(url, "https://github.com/") {
		url = "https://github.com/" + url
	}

	w.Header().Set("Content-Type", "application/json")

	// if this is a GET request, fetch from cached version in mongo
	if r.Method == "GET" {
		resp, err := getFromCache(repo)
		if err != nil {
			// just log the error and continue
			log.Println(err)
		} else {
			b, err := json.Marshal(resp)
			if err != nil {
				log.Println("ERROR: could not marshal json:", err)
				http.Error(w, err.Error(), 500)
				return
			}
			w.Write(b)
			log.Println("Loaded from cache!", repo)
			return
		}
	}

	err := clone(url)
	if err != nil {
		log.Println("ERROR: could not clone repo: ", err)
		http.Error(w, fmt.Sprintf("Could not clone repo: %v", err), 500)
		return
	}

	dir := dirName(url)
	filenames, err := check.GoFiles(dir)
	if err != nil {
		log.Println("ERROR: could not get filenames: ", err)
		http.Error(w, fmt.Sprintf("Could not get filenames: %v", err), 500)
		return
	}
	checks := []check.Check{check.GoFmt{Dir: dir, Filenames: filenames},
		check.GoVet{Dir: dir, Filenames: filenames},
		check.GoLint{Dir: dir, Filenames: filenames},
		check.GoCyclo{Dir: dir, Filenames: filenames},
	}

	ch := make(chan score)
	for _, c := range checks {
		go func(c check.Check) {
			p, summaries, err := c.Percentage()
			if err != nil {
				log.Printf("ERROR: (%s) %v", c.Name(), err)
			}
			s := score{
				Name:          c.Name(),
				Description:   c.Description(),
				FileSummaries: summaries,
				Percentage:    p,
			}
			ch <- s
		}(c)
	}

	resp := checksResp{Repo: repo,
		Files:       len(filenames),
		LastRefresh: time.Now().UTC()}
	var avg float64
	var issues = make(map[string]bool)
	for i := 0; i < len(checks); i++ {
		s := <-ch
		resp.Checks = append(resp.Checks, s)
		avg += s.Percentage
		for _, fs := range s.FileSummaries {
			issues[fs.Filename] = true
		}
	}

	resp.Average = avg / float64(len(checks))
	resp.Issues = len(issues)

	b, err := json.Marshal(resp)
	if err != nil {
		log.Println("ERROR: could not marshal json:", err)
		http.Error(w, err.Error(), 500)
		return
	}
	w.Write(b)

	// write to mongo
	coll, err := getMongoCollection()
	if err != nil {
		log.Println("Failed to get mongo collection: ", err)
	} else {
		log.Println("Writing to mongo...")
		_, err := coll.Upsert(bson.M{"Repo": repo}, resp)
		if err != nil {
			log.Println("Mongo writing error:", err)
		}
	}
}

func reportHandler(w http.ResponseWriter, r *http.Request, org, repo string) {
	http.ServeFile(w, r, "templates/home.html")
}

func makeReportHandler(fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		validPath := regexp.MustCompile(`^/report/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)

		m := validPath.FindStringSubmatch(r.URL.Path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r, m[1], m[2])
	}
}

func main() {
	if err := os.MkdirAll("repos/src/github.com", 0755); err != nil && !os.IsExist(err) {
		log.Fatal("ERROR: could not create repos dir: ", err)
	}

	http.HandleFunc("/assets/", assetsHandler)
	http.HandleFunc("/checks", checkHandler)
	http.HandleFunc("/report/", makeReportHandler(reportHandler))
	http.HandleFunc("/", homeHandler)

	fmt.Println("Running on 127.0.01:8080...")
	log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
<commit_msg>update import<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/gojp/goreportcard/check"
	"gopkg.in/mgo.v2"
	"labix.org/v2/mgo/bson"
)

var (
	mongoURL        = "mongodb://localhost:27017"
	mongoDatabase   = "goreportcard"
	mongoCollection = "reports"
)

func getMongoCollection() (*mgo.Collection, error) {
	session, err := mgo.Dial(mongoURL)
	if err != nil {
		return nil, err
	}
	c := session.DB(mongoDatabase).C(mongoCollection)
	return c, nil
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving home page")
	if r.URL.Path[1:] == "" {
		http.ServeFile(w, r, "templates/home.html")
	} else {
		http.NotFound(w, r)
	}
}

func assetsHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving " + r.URL.Path[1:])
	http.ServeFile(w, r, r.URL.Path[1:])
}

func orgRepoNames(url string) (string, string) {
	dir := strings.TrimSuffix(url, ".git")
	split := strings.Split(dir, "/")
	org := split[len(split)-2]
	repoName := split[len(split)-1]

	return org, repoName
}

func dirName(url string) string {
	org, repoName := orgRepoNames(url)

	return fmt.Sprintf("repos/src/github.com/%s/%s", org, repoName)
}

func clone(url string) error {
	org, _ := orgRepoNames(url)
	if err := os.Mkdir(fmt.Sprintf("repos/src/github.com/%s", org), 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("could not create dir: %v", err)
	}
	dir := dirName(url)
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		cmd := exec.Command("timeout", "120", "git", "clone", "--depth", "1", "--single-branch", url, dir)
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("could not run git clone: %v", err)
		}
	} else if err != nil {
		return fmt.Errorf("could not stat dir: %v", err)
	} else {
		cmd := exec.Command("git", "-C", dir, "pull")
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("could not pull repo: %v", err)
		}
	}

	return nil
}

type score struct {
	Name          string              `json:"name"`
	Description   string              `json:"description"`
	FileSummaries []check.FileSummary `json:"file_summaries"`
	Percentage    float64             `json:"percentage"`
}

type checksResp struct {
	Checks      []score   `json:"checks"`
	Average     float64   `json:"average"`
	Files       int       `json:"files"`
	Issues      int       `json:"issues"`
	Repo        string    `json:"repo"`
	LastRefresh time.Time `json:"last_refresh"`
}

func getFromCache(repo string) (checksResp, error) {
	// try and fetch from mongo
	coll, err := getMongoCollection()
	if err != nil {
		return checksResp{}, fmt.Errorf("Failed to get mongo collection during GET: %v", err)
	}
	resp := checksResp{}
	err = coll.Find(bson.M{"repo": repo}).One(&resp)
	if err != nil {
		return checksResp{}, fmt.Errorf("Failed to fetch %q from mongo: %v", repo, err)
	}

	resp.LastRefresh = resp.LastRefresh.UTC()

	return resp, nil
}

func checkHandler(w http.ResponseWriter, r *http.Request) {
	repo := r.FormValue("repo")
	url := repo
	if !strings.HasPrefix(url, "https://github.com/") {
		url = "https://github.com/" + url
	}

	w.Header().Set("Content-Type", "application/json")

	// if this is a GET request, fetch from cached version in mongo
	if r.Method == "GET" {
		resp, err := getFromCache(repo)
		if err != nil {
			// just log the error and continue
			log.Println(err)
		} else {
			b, err := json.Marshal(resp)
			if err != nil {
				log.Println("ERROR: could not marshal json:", err)
				http.Error(w, err.Error(), 500)
				return
			}
			w.Write(b)
			log.Println("Loaded from cache!", repo)
			return
		}
	}

	err := clone(url)
	if err != nil {
		log.Println("ERROR: could not clone repo: ", err)
		http.Error(w, fmt.Sprintf("Could not clone repo: %v", err), 500)
		return
	}

	dir := dirName(url)
	filenames, err := check.GoFiles(dir)
	if err != nil {
		log.Println("ERROR: could not get filenames: ", err)
		http.Error(w, fmt.Sprintf("Could not get filenames: %v", err), 500)
		return
	}
	checks := []check.Check{check.GoFmt{Dir: dir, Filenames: filenames},
		check.GoVet{Dir: dir, Filenames: filenames},
		check.GoLint{Dir: dir, Filenames: filenames},
		check.GoCyclo{Dir: dir, Filenames: filenames},
	}

	ch := make(chan score)
	for _, c := range checks {
		go func(c check.Check) {
			p, summaries, err := c.Percentage()
			if err != nil {
				log.Printf("ERROR: (%s) %v", c.Name(), err)
			}
			s := score{
				Name:          c.Name(),
				Description:   c.Description(),
				FileSummaries: summaries,
				Percentage:    p,
			}
			ch <- s
		}(c)
	}

	resp := checksResp{Repo: repo,
		Files:       len(filenames),
		LastRefresh: time.Now().UTC()}
	var avg float64
	var issues = make(map[string]bool)
	for i := 0; i < len(checks); i++ {
		s := <-ch
		resp.Checks = append(resp.Checks, s)
		avg += s.Percentage
		for _, fs := range s.FileSummaries {
			issues[fs.Filename] = true
		}
	}

	resp.Average = avg / float64(len(checks))
	resp.Issues = len(issues)

	b, err := json.Marshal(resp)
	if err != nil {
		log.Println("ERROR: could not marshal json:", err)
		http.Error(w, err.Error(), 500)
		return
	}
	w.Write(b)

	// write to mongo
	coll, err := getMongoCollection()
	if err != nil {
		log.Println("Failed to get mongo collection: ", err)
	} else {
		log.Println("Writing to mongo...")
		_, err := coll.Upsert(bson.M{"Repo": repo}, resp)
		if err != nil {
			log.Println("Mongo writing error:", err)
		}
	}
}

func reportHandler(w http.ResponseWriter, r *http.Request, org, repo string) {
	http.ServeFile(w, r, "templates/home.html")
}

func makeReportHandler(fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		validPath := regexp.MustCompile(`^/report/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)

		m := validPath.FindStringSubmatch(r.URL.Path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r, m[1], m[2])
	}
}

func main() {
	if err := os.MkdirAll("repos/src/github.com", 0755); err != nil && !os.IsExist(err) {
		log.Fatal("ERROR: could not create repos dir: ", err)
	}

	http.HandleFunc("/assets/", assetsHandler)
	http.HandleFunc("/checks", checkHandler)
	http.HandleFunc("/report/", makeReportHandler(reportHandler))
	http.HandleFunc("/", homeHandler)

	fmt.Println("Running on 127.0.01:8080...")
	log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
<|endoftext|>


<commit_before><commit_msg>Status is the default cmd<commit_after><|endoftext|>


<commit_before><commit_msg>Initial commit<commit_after><|endoftext|>


<commit_before><commit_msg>Forgot to move the error check for ParseSrc<commit_after><|endoftext|>


<commit_before><commit_msg>Added filename parameter to loadProgramList<commit_after><|endoftext|>


<commit_before><commit_msg>add config log<commit_after><|endoftext|>


<commit_before><commit_msg>main.go test....<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

const (
	DEPS_DIR  = "deps"
	BUILD_DIR = "tmp"
)

var BUILD_DIR_BIN = filepath.Join(BUILD_DIR, "bin")
var BUILD_DIR_SRC = filepath.Join(BUILD_DIR, "src")
var BUILD_DIR_PKG = filepath.Join(BUILD_DIR, "pkg")

var gaugePackages = []string{"common"}
var gaugeExecutables = []string{"gauge", "gauge-java"}

func hashDir(dirPath string) string {
	var b bytes.Buffer
	filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			contents, err := ioutil.ReadFile(path)
			if err != nil {
				panic(err)
			}
			h := sha1.New()
			h.Write(contents)
			b.WriteString(fmt.Sprintf("%x", h.Sum(nil)))
		}
		return nil
	})
	h := sha1.New()
	h.Write(b.Bytes())
	return fmt.Sprintf("%x", h.Sum(nil))
}

func isExecMode(mode os.FileMode) bool {
	return (mode & 0111) != 0
}

func mirrorFile(src, dst string) error {
	sfi, err := os.Stat(src)
	if err != nil {
		return err
	}
	if sfi.Mode()&os.ModeType != 0 {
		log.Fatalf("mirrorFile can't deal with non-regular file %s", src)
	}
	dfi, err := os.Stat(dst)
	if err == nil &&
		isExecMode(sfi.Mode()) == isExecMode(dfi.Mode()) &&
		(dfi.Mode()&os.ModeType == 0) &&
		dfi.Size() == sfi.Size() &&
		dfi.ModTime().Unix() == sfi.ModTime().Unix() {
		// Seems to not be modified.
		return nil
	}

	dstDir := filepath.Dir(dst)
	if err := os.MkdirAll(dstDir, 0755); err != nil {
		return err
	}

	df, err := os.Create(dst)
	if err != nil {
		return err
	}
	sf, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sf.Close()

	n, err := io.Copy(df, sf)
	if err == nil && n != sfi.Size() {
		err = fmt.Errorf("copied wrong size for %s -> %s: copied %d; want %d", src, dst, n, sfi.Size())
	}
	cerr := df.Close()
	if err == nil {
		err = cerr
	}
	if err == nil {
		err = os.Chmod(dst, sfi.Mode())
	}
	if err == nil {
		err = os.Chtimes(dst, sfi.ModTime(), sfi.ModTime())
	}
	return err
}

func mirrorDir(src, dst string) error {
	log.Printf("Copying '%s' -> '%s'\n", src, dst)
	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		suffix, err := filepath.Rel(src, path)
		if err != nil {
			return fmt.Errorf("Failed to find Rel(%q, %q): %v", src, path, err)
		}
		return mirrorFile(path, filepath.Join(dst, suffix))
	})
	return err
}

func createGoPathForBuild() {
	err := os.MkdirAll(BUILD_DIR_SRC, 0755)
	if err != nil {
		panic(err)
	}

	err = os.MkdirAll(BUILD_DIR_BIN, 0755)
	if err != nil {
		panic(err)
	}

	err = os.MkdirAll(BUILD_DIR_PKG, 0755)
	if err != nil {
		panic(err)
	}
}

func copyDepsToGoPath() {
	err := mirrorDir(DEPS_DIR, BUILD_DIR_SRC)
	if err != nil {
		panic(err)
	}
}

func copyGaugePackagesToGoPath() {
	for _, p := range gaugePackages {
		err := mirrorDir(p, filepath.Join(BUILD_DIR_SRC, p))
		if err != nil {
			panic(err)
		}
	}
	for _, p := range gaugeExecutables {
		err := mirrorDir(p, filepath.Join(BUILD_DIR_SRC, p))
		if err != nil {
			panic(err)
		}
	}
}

func setGoPath() {
	absBuildDir, err := filepath.Abs(BUILD_DIR)
	if err != nil {
		panic(err)
	}
	log.Printf("GOPATH = %s\n", absBuildDir)
	err = os.Setenv("GOPATH", absBuildDir)
	if err != nil {
		panic(err)
	}
}

func compilePackages() {
	setGoPath()
	args := []string{"install", "-v"}
	for _, p := range gaugeExecutables {
		args = append(args, p)
	}

	cmd := exec.Command("go", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = BUILD_DIR
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func compileJavaClasses() {
	cmd := exec.Command("ant", "jar")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = "gauge-java"
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func runTests(packageName string) {
	setGoPath()
	cmd := exec.Command("go", "test", packageName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = BUILD_DIR
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func copyBinaries() {
	err := os.MkdirAll("bin", 0755)
	if err != nil {
		panic(err)
	}

	err = mirrorDir(BUILD_DIR_BIN, "bin")
	if err != nil {
		panic(err)
	}

	absBin, err := filepath.Abs("bin")
	if err != nil {
		panic(err)
	}

	log.Printf("Binaries are available at: %s\n", absBin)
}

// key will be the source file and value will be the target
func installFiles(files map[string]string) {
	for src, dst := range files {
		base := filepath.Base(src)
		installDst := filepath.Join(*installPrefix, dst)
		log.Printf("Install %s -> %s\n", src, installDst)
		stat, err := os.Stat(src)
		if err != nil {
			panic(err)
		}
		if stat.IsDir() {
			err = mirrorDir(src, installDst)
		} else {
			err = mirrorFile(src, filepath.Join(installDst, base))
		}
		if err != nil {
			panic(err)
		}
	}
}

func saveHash(h, dir string) {
	hashFile := filepath.Join(BUILD_DIR, "."+dir)
	err := ioutil.WriteFile(hashFile, []byte(h), 0644)
	if err != nil {
		log.Println(err.Error())
	}
}

func hasChanges(h, dir string) bool {
	hashFile := filepath.Join(BUILD_DIR, "."+dir)
	contents, err := ioutil.ReadFile(hashFile)
	if err != nil {
		return true
	}
	return string(contents) != h
}

func installGaugeFiles() {
	files := make(map[string]string)
	if runtime.GOOS == "windows" {
		files[filepath.Join("bin", "gauge.exe")] = "bin"
	} else {
		files[filepath.Join("bin", "gauge")] = "bin"
	}
	files[filepath.Join("skel", "hello_world.spec")] = filepath.Join("share", "gauge", "skel")
	files[filepath.Join("skel", "default.properties")] = filepath.Join("share", "gauge", "skel", "env")
	installFiles(files)
}

func installGaugeJavaFiles() {
	files := make(map[string]string)
	if runtime.GOOS == "windows" {
		files[filepath.Join("bin", "gauge-java.exe")] = "bin"
	} else {
		files[filepath.Join("bin", "gauge-java")] = "bin"
	}
	files[filepath.Join("gauge-java", "java.json")] = filepath.Join("share", "gauge", "languages")
	files[filepath.Join("gauge-java", "skel", "StepImplementation.java")] = filepath.Join("share", "gauge", "skel", "java")
	files[filepath.Join("gauge-java", "libs")] = filepath.Join("lib", "gauge", "java", "libs")
	files[filepath.Join("gauge-java", "build", "jar")] = filepath.Join("lib", "gauge", "java")
	installFiles(files)
}

// Executes the specified target
// It also keeps a hash of all the contents in the target directory and avoid recompilation if contents are not changed
func executeTarget(target string) {
	opts, ok := targets[target]
	if !ok {
		log.Fatalf("Unknown target: %s\n", target)
	}

	if opts.lookForChanges {
		if hasChanges(hashDir(target), target) {
			opts.targetFunc()
			saveHash(hashDir(target), target)
		}
	} else {
		opts.targetFunc()
	}
}

type compileFunc func()

var test = flag.Bool("test", false, "Run the test cases")
var install = flag.Bool("install", false, "Install to the specified prefix")
var installPrefix = flag.String("prefix", "", "Specifies the prefix where files will be installed")
var compileTarget = flag.String("target", "", "Specifies the target to be executed")

type targetOpts struct {
	lookForChanges bool
	targetFunc     compileFunc
}

// Defines all the compile targets
// Each target name is the directory name
var targets = map[string]*targetOpts{
	"gauge":      &targetOpts{lookForChanges: true, targetFunc: compilePackages},
	"gauge-java": &targetOpts{lookForChanges: true, targetFunc: compileJavaClasses},
}

func main() {
	flag.Parse()
	createGoPathForBuild()
	copyDepsToGoPath()
	copyGaugePackagesToGoPath()

	if *test {
		runTests("gauge")
	} else if *install {
		if *installPrefix == "" {
			if runtime.GOOS == "windows" {
				*installPrefix = os.Getenv("PROGRAMFILES")
				if *installPrefix == "" {
					panic(fmt.Errorf("Failed to find programfiles"))
				}
				*installPrefix = filepath.Join(*installPrefix, "gauge")
			} else {
				*installPrefix = "/usr/local"
			}
		}
		installGaugeFiles()
		installGaugeJavaFiles()
	} else {
		if *compileTarget == "" {
			for target, _ := range targets {
				executeTarget(target)
			}
		} else {
			executeTarget(*compileTarget)
		}
		copyBinaries()
	}
}
<commit_msg>Installing java.properties file<commit_after>package main

import (
	"bytes"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

const (
	DEPS_DIR  = "deps"
	BUILD_DIR = "tmp"
)

var BUILD_DIR_BIN = filepath.Join(BUILD_DIR, "bin")
var BUILD_DIR_SRC = filepath.Join(BUILD_DIR, "src")
var BUILD_DIR_PKG = filepath.Join(BUILD_DIR, "pkg")

var gaugePackages = []string{"common"}
var gaugeExecutables = []string{"gauge", "gauge-java"}

func hashDir(dirPath string) string {
	var b bytes.Buffer
	filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			contents, err := ioutil.ReadFile(path)
			if err != nil {
				panic(err)
			}
			h := sha1.New()
			h.Write(contents)
			b.WriteString(fmt.Sprintf("%x", h.Sum(nil)))
		}
		return nil
	})
	h := sha1.New()
	h.Write(b.Bytes())
	return fmt.Sprintf("%x", h.Sum(nil))
}

func isExecMode(mode os.FileMode) bool {
	return (mode & 0111) != 0
}

func mirrorFile(src, dst string) error {
	sfi, err := os.Stat(src)
	if err != nil {
		return err
	}
	if sfi.Mode()&os.ModeType != 0 {
		log.Fatalf("mirrorFile can't deal with non-regular file %s", src)
	}
	dfi, err := os.Stat(dst)
	if err == nil &&
		isExecMode(sfi.Mode()) == isExecMode(dfi.Mode()) &&
		(dfi.Mode()&os.ModeType == 0) &&
		dfi.Size() == sfi.Size() &&
		dfi.ModTime().Unix() == sfi.ModTime().Unix() {
		// Seems to not be modified.
		return nil
	}

	dstDir := filepath.Dir(dst)
	if err := os.MkdirAll(dstDir, 0755); err != nil {
		return err
	}

	df, err := os.Create(dst)
	if err != nil {
		return err
	}
	sf, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sf.Close()

	n, err := io.Copy(df, sf)
	if err == nil && n != sfi.Size() {
		err = fmt.Errorf("copied wrong size for %s -> %s: copied %d; want %d", src, dst, n, sfi.Size())
	}
	cerr := df.Close()
	if err == nil {
		err = cerr
	}
	if err == nil {
		err = os.Chmod(dst, sfi.Mode())
	}
	if err == nil {
		err = os.Chtimes(dst, sfi.ModTime(), sfi.ModTime())
	}
	return err
}

func mirrorDir(src, dst string) error {
	log.Printf("Copying '%s' -> '%s'\n", src, dst)
	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		suffix, err := filepath.Rel(src, path)
		if err != nil {
			return fmt.Errorf("Failed to find Rel(%q, %q): %v", src, path, err)
		}
		return mirrorFile(path, filepath.Join(dst, suffix))
	})
	return err
}

func createGoPathForBuild() {
	err := os.MkdirAll(BUILD_DIR_SRC, 0755)
	if err != nil {
		panic(err)
	}

	err = os.MkdirAll(BUILD_DIR_BIN, 0755)
	if err != nil {
		panic(err)
	}

	err = os.MkdirAll(BUILD_DIR_PKG, 0755)
	if err != nil {
		panic(err)
	}
}

func copyDepsToGoPath() {
	err := mirrorDir(DEPS_DIR, BUILD_DIR_SRC)
	if err != nil {
		panic(err)
	}
}

func copyGaugePackagesToGoPath() {
	for _, p := range gaugePackages {
		err := mirrorDir(p, filepath.Join(BUILD_DIR_SRC, p))
		if err != nil {
			panic(err)
		}
	}
	for _, p := range gaugeExecutables {
		err := mirrorDir(p, filepath.Join(BUILD_DIR_SRC, p))
		if err != nil {
			panic(err)
		}
	}
}

func setGoPath() {
	absBuildDir, err := filepath.Abs(BUILD_DIR)
	if err != nil {
		panic(err)
	}
	log.Printf("GOPATH = %s\n", absBuildDir)
	err = os.Setenv("GOPATH", absBuildDir)
	if err != nil {
		panic(err)
	}
}

func compilePackages() {
	setGoPath()
	args := []string{"install", "-v"}
	for _, p := range gaugeExecutables {
		args = append(args, p)
	}

	cmd := exec.Command("go", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = BUILD_DIR
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func compileJavaClasses() {
	cmd := exec.Command("ant", "jar")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = "gauge-java"
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func runTests(packageName string) {
	setGoPath()
	cmd := exec.Command("go", "test", packageName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = BUILD_DIR
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func copyBinaries() {
	err := os.MkdirAll("bin", 0755)
	if err != nil {
		panic(err)
	}

	err = mirrorDir(BUILD_DIR_BIN, "bin")
	if err != nil {
		panic(err)
	}

	absBin, err := filepath.Abs("bin")
	if err != nil {
		panic(err)
	}

	log.Printf("Binaries are available at: %s\n", absBin)
}

// key will be the source file and value will be the target
func installFiles(files map[string]string) {
	for src, dst := range files {
		base := filepath.Base(src)
		installDst := filepath.Join(*installPrefix, dst)
		log.Printf("Install %s -> %s\n", src, installDst)
		stat, err := os.Stat(src)
		if err != nil {
			panic(err)
		}
		if stat.IsDir() {
			err = mirrorDir(src, installDst)
		} else {
			err = mirrorFile(src, filepath.Join(installDst, base))
		}
		if err != nil {
			panic(err)
		}
	}
}

func saveHash(h, dir string) {
	hashFile := filepath.Join(BUILD_DIR, "."+dir)
	err := ioutil.WriteFile(hashFile, []byte(h), 0644)
	if err != nil {
		log.Println(err.Error())
	}
}

func hasChanges(h, dir string) bool {
	hashFile := filepath.Join(BUILD_DIR, "."+dir)
	contents, err := ioutil.ReadFile(hashFile)
	if err != nil {
		return true
	}
	return string(contents) != h
}

func installGaugeFiles() {
	files := make(map[string]string)
	if runtime.GOOS == "windows" {
		files[filepath.Join("bin", "gauge.exe")] = "bin"
	} else {
		files[filepath.Join("bin", "gauge")] = "bin"
	}
	files[filepath.Join("skel", "hello_world.spec")] = filepath.Join("share", "gauge", "skel")
	files[filepath.Join("skel", "default.properties")] = filepath.Join("share", "gauge", "skel", "env")
	installFiles(files)
}

func installGaugeJavaFiles() {
	files := make(map[string]string)
	if runtime.GOOS == "windows" {
		files[filepath.Join("bin", "gauge-java.exe")] = "bin"
	} else {
		files[filepath.Join("bin", "gauge-java")] = "bin"
	}
	files[filepath.Join("gauge-java", "java.json")] = filepath.Join("share", "gauge", "languages")
	files[filepath.Join("gauge-java", "skel", "StepImplementation.java")] = filepath.Join("share", "gauge", "skel", "java")
	files[filepath.Join("gauge-java", "skel", "java.properties")] = filepath.Join("share", "gauge", "skel", "env")
	files[filepath.Join("gauge-java", "libs")] = filepath.Join("lib", "gauge", "java", "libs")
	files[filepath.Join("gauge-java", "build", "jar")] = filepath.Join("lib", "gauge", "java")
	installFiles(files)
}

// Executes the specified target
// It also keeps a hash of all the contents in the target directory and avoid recompilation if contents are not changed
func executeTarget(target string) {
	opts, ok := targets[target]
	if !ok {
		log.Fatalf("Unknown target: %s\n", target)
	}

	if opts.lookForChanges {
		if hasChanges(hashDir(target), target) {
			opts.targetFunc()
			saveHash(hashDir(target), target)
		}
	} else {
		opts.targetFunc()
	}
}

type compileFunc func()

var test = flag.Bool("test", false, "Run the test cases")
var install = flag.Bool("install", false, "Install to the specified prefix")
var installPrefix = flag.String("prefix", "", "Specifies the prefix where files will be installed")
var compileTarget = flag.String("target", "", "Specifies the target to be executed")

type targetOpts struct {
	lookForChanges bool
	targetFunc     compileFunc
}

// Defines all the compile targets
// Each target name is the directory name
var targets = map[string]*targetOpts{
	"gauge":      &targetOpts{lookForChanges: true, targetFunc: compilePackages},
	"gauge-java": &targetOpts{lookForChanges: true, targetFunc: compileJavaClasses},
}

func main() {
	flag.Parse()
	createGoPathForBuild()
	copyDepsToGoPath()
	copyGaugePackagesToGoPath()

	if *test {
		runTests("gauge")
	} else if *install {
		if *installPrefix == "" {
			if runtime.GOOS == "windows" {
				*installPrefix = os.Getenv("PROGRAMFILES")
				if *installPrefix == "" {
					panic(fmt.Errorf("Failed to find programfiles"))
				}
				*installPrefix = filepath.Join(*installPrefix, "gauge")
			} else {
				*installPrefix = "/usr/local"
			}
		}
		installGaugeFiles()
		installGaugeJavaFiles()
	} else {
		if *compileTarget == "" {
			for target, _ := range targets {
				executeTarget(target)
			}
		} else {
			executeTarget(*compileTarget)
		}
		copyBinaries()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/alanctgardner/gogen-avro/container"
	"github.com/alanctgardner/gogen-avro/generator"
	"github.com/alanctgardner/gogen-avro/types"
)

func main() {
	generateContainer := flag.Bool("container", false, "Whether to emit container file writer code")
	packageName := flag.String("package", "avro", "Name of generated package")
	flag.Parse()
	if flag.NArg() < 2 {
		fmt.Fprintf(os.Stderr, "Usage: gogen-avro [--container] [--package=<package name>] <target directory> <schema files>\n")
		os.Exit(1)
	}
	targetDir := flag.Arg(0)
	files := flag.Args()[1:]

	var err error
	pkg := generator.NewPackage(*packageName)

	if *generateContainer {
		err = addRecordDefinition([]byte(container.AVRO_BLOCK_SCHEMA), pkg, false)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error generating Avro container block schema - %v\n", err)
			os.Exit(2)
		}

		err = addRecordDefinition([]byte(container.AVRO_HEADER_SCHEMA), pkg, false)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error generating Avro container header schema - %v\n", err)
			os.Exit(2)
		}
	}

	for _, fileName := range files {
		schema, err := ioutil.ReadFile(fileName)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading file %q - %v\n", fileName, err)
			os.Exit(2)
		}

		err = addRecordDefinition(schema, pkg, *generateContainer)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error decoding schema for file %q - %v\n", fileName, err)
			os.Exit(3)
		}
	}

	// Add header comment to all generated files.
	// for _, f := range pkg.Files() {
	// 	pkg.AddHeader(f, codegenComment(files))
	// }

	err = pkg.WriteFiles(targetDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error writing source files to directory %q - %v\n", targetDir, err)
		os.Exit(4)
	}
}

func addRecordDefinition(schema []byte, pkg *generator.Package, generateContainer bool) error {
	recordDefinition, err := types.RecordDefinitionForSchema(schema)
	if err != nil {
		return err
	}
	recordDefinition.AddStruct(pkg)
	recordDefinition.AddSerializer(pkg)
	recordDefinition.AddDeserializer(pkg)

	if generateContainer {
		containerWriter := container.NewAvroContainerWriter(schema, recordDefinition)
		containerWriter.AddAvroContainerWriter(pkg)
	}
	return nil
}

// codegenComment generates a comment informing readers they are looking at
// generated code and lists the source avro files used to generate the code
//
// invariant: sources > 0
func codegenComment(sources []string) string {
	const fileComment = `/*
 * CODE GENERATED AUTOMATICALLY WITH github.com/alanctgardner/gogen-avro
 * THIS FILE SHOULD NOT BE EDITED BY HAND
 *
 * %s
 */`
	var sourceBlock []string
	if len(sources) == 1 {
		sourceBlock = append(sourceBlock, "SOURCE:")
	} else {
		sourceBlock = append(sourceBlock, "SOURCES:")
	}

	for _, source := range sources {
		sourceBlock = append(sourceBlock, fmt.Sprintf(" *     %s", source))
	}

	return fmt.Sprintf(fileComment, strings.Join(sourceBlock, "\n"))
}
<commit_msg>Uncomment code to write headers to files.<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/alanctgardner/gogen-avro/container"
	"github.com/alanctgardner/gogen-avro/generator"
	"github.com/alanctgardner/gogen-avro/types"
)

func main() {
	generateContainer := flag.Bool("container", false, "Whether to emit container file writer code")
	packageName := flag.String("package", "avro", "Name of generated package")
	flag.Parse()
	if flag.NArg() < 2 {
		fmt.Fprintf(os.Stderr, "Usage: gogen-avro [--container] [--package=<package name>] <target directory> <schema files>\n")
		os.Exit(1)
	}
	targetDir := flag.Arg(0)
	files := flag.Args()[1:]

	var err error
	pkg := generator.NewPackage(*packageName)

	if *generateContainer {
		err = addRecordDefinition([]byte(container.AVRO_BLOCK_SCHEMA), pkg, false)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error generating Avro container block schema - %v\n", err)
			os.Exit(2)
		}

		err = addRecordDefinition([]byte(container.AVRO_HEADER_SCHEMA), pkg, false)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error generating Avro container header schema - %v\n", err)
			os.Exit(2)
		}
	}

	for _, fileName := range files {
		schema, err := ioutil.ReadFile(fileName)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading file %q - %v\n", fileName, err)
			os.Exit(2)
		}

		err = addRecordDefinition(schema, pkg, *generateContainer)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error decoding schema for file %q - %v\n", fileName, err)
			os.Exit(3)
		}
	}

	// Add header comment to all generated files.
	for _, f := range pkg.Files() {
		pkg.AddHeader(f, codegenComment(files))
	}

	err = pkg.WriteFiles(targetDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error writing source files to directory %q - %v\n", targetDir, err)
		os.Exit(4)
	}
}

func addRecordDefinition(schema []byte, pkg *generator.Package, generateContainer bool) error {
	recordDefinition, err := types.RecordDefinitionForSchema(schema)
	if err != nil {
		return err
	}
	recordDefinition.AddStruct(pkg)
	recordDefinition.AddSerializer(pkg)
	recordDefinition.AddDeserializer(pkg)

	if generateContainer {
		containerWriter := container.NewAvroContainerWriter(schema, recordDefinition)
		containerWriter.AddAvroContainerWriter(pkg)
	}
	return nil
}

// codegenComment generates a comment informing readers they are looking at
// generated code and lists the source avro files used to generate the code
//
// invariant: sources > 0
func codegenComment(sources []string) string {
	const fileComment = `/*
 * CODE GENERATED AUTOMATICALLY WITH github.com/alanctgardner/gogen-avro
 * THIS FILE SHOULD NOT BE EDITED BY HAND
 *
 * %s
 */`
	var sourceBlock []string
	if len(sources) == 1 {
		sourceBlock = append(sourceBlock, "SOURCE:")
	} else {
		sourceBlock = append(sourceBlock, "SOURCES:")
	}

	for _, source := range sources {
		sourceBlock = append(sourceBlock, fmt.Sprintf(" *     %s", source))
	}

	return fmt.Sprintf(fileComment, strings.Join(sourceBlock, "\n"))
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"net"

	"github.com/orivej/e"
	"github.com/pkg/errors"
)

type ReadResult struct {
	n   int
	err error
	buf []byte
}

const (
	bufSize         = 4000
	clientTLSMarker = "<starttls"
	serverTLSMarker = "<proceed"
)

var (
	flListenPort = flag.Int("port", 5222, "listen port")
	flServer     = flag.String("server", "<server:port>", "target server:port")

	flKeyPath  = flag.String("key", "", "path to TLS certificate key")
	flCertPath = flag.String("cert", "", "path to TLS certificate")

	flReplaceLocal  = flag.String("replace-local", "", "")
	flReplaceRemote = flag.String("replace-remote", "", "")
)

func main() {
	flag.Parse()

	certificate, err := tls.X509KeyPair([]byte(localhostCert), []byte(localhostKey))
	e.Exit(err)
	if len(*flCertPath) > 0 {
		certificate, err = tls.LoadX509KeyPair(*flCertPath, *flKeyPath)
		e.Exit(errors.Wrap(err, "can not load TLS key pair"))
	}

	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", *flListenPort))
	e.Exit(err)

	for idx := 1; true; idx++ {
		client, err := listener.Accept()
		e.Exit(err)
		go func(idx int) {
			defer e.CloseOrPrint(client)
			err := serve(client, idx, certificate)
			fmt.Printf("X%d: %s\n", idx, err)
		}(idx)
	}
}

func serve(client net.Conn, idx int, certificate tls.Certificate) error {
	server, err := net.Dial("tcp", *flServer)
	if err != nil {
		return errors.Wrap(err, "failed to connect to server")
	}
	defer e.CloseOrPrint(server)

	err = proxy(client, server, idx, true)
	if err != nil {
		return errors.Wrap(err, "session terminated")
	}

	tlsClient := tls.Server(client, &tls.Config{
		Certificates: []tls.Certificate{certificate},
	})
	tlsServer := tls.Client(server, &tls.Config{
		InsecureSkipVerify: true,
	})
	fmt.Printf("X%d: eavesdropping TLS\n", idx)
	err = proxy(tlsClient, tlsServer, idx, false)
	return errors.Wrap(err, "TLS session terminated")
}

func proxy(client, server io.ReadWriter, idx int, untilTLS bool) error {
	clientNext, clientResults := startReader(client)
	serverNext, serverResults := startReader(server)

	rLocal := []byte(*flReplaceLocal)
	rRemote := []byte(*flReplaceRemote)
	replace := len(rLocal) > 0 && len(rRemote) > 0

	for {
		select {
		case result := <-clientResults:
			err := result.err
			if err != nil {
				return errors.Wrap(err, "failed to read from client")
			}

			buf := result.buf[:result.n]
			if replace {
				buf = bytes.Replace(buf, rLocal, rRemote, -1)
			}
			fmt.Printf("C%d: %#q\n", idx, buf)

			_, err = server.Write(buf)
			if err != nil {
				return errors.Wrap(err, "failed to write to server")
			}

			if untilTLS && bytes.Contains(buf, []byte(clientTLSMarker)) {
				fmt.Printf("X%d: client goes TLS\n", idx)
				clientNext <- false
				continue
			}

			clientNext <- true

		case result := <-serverResults:
			err := result.err
			if err != nil {
				return errors.Wrap(err, "failed to read from server")
			}

			buf := result.buf[:result.n]
			fmt.Printf("S%d: %#q\n", idx, buf)
			if replace {
				buf = bytes.Replace(buf, rRemote, rLocal, -1)
			}

			_, err = client.Write(buf)
			if err != nil {
				return errors.Wrap(err, "failed to write to client")
			}

			if untilTLS && bytes.Contains(buf, []byte(serverTLSMarker)) {
				fmt.Printf("X%d: server goes TLS\n", idx)
				serverNext <- false
				return nil
			}

			serverNext <- true
		}
	}
}

func startReader(reader io.Reader) (chan<- bool, chan ReadResult) {
	readNext := make(chan bool)
	buf := make([]byte, bufSize)
	results := make(chan ReadResult)
	go func() {
		for {
			n, err := reader.Read(buf)
			results <- ReadResult{n, err, buf}
			if !<-readNext {
				return
			}
		}
	}()
	return readNext, results
}
<commit_msg>Escape only newline and backslash in the log<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"net"
	"strings"

	"github.com/orivej/e"
	"github.com/pkg/errors"
)

type ReadResult struct {
	n   int
	err error
	buf []byte
}

const (
	bufSize         = 4000
	clientTLSMarker = "<starttls"
	serverTLSMarker = "<proceed"
)

var (
	flListenPort = flag.Int("port", 5222, "listen port")
	flServer     = flag.String("server", "<server:port>", "target server:port")

	flKeyPath  = flag.String("key", "", "path to TLS certificate key")
	flCertPath = flag.String("cert", "", "path to TLS certificate")

	flReplaceLocal  = flag.String("replace-local", "", "")
	flReplaceRemote = flag.String("replace-remote", "", "")
)

func main() {
	flag.Parse()

	certificate, err := tls.X509KeyPair([]byte(localhostCert), []byte(localhostKey))
	e.Exit(err)
	if len(*flCertPath) > 0 {
		certificate, err = tls.LoadX509KeyPair(*flCertPath, *flKeyPath)
		e.Exit(errors.Wrap(err, "can not load TLS key pair"))
	}

	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", *flListenPort))
	e.Exit(err)

	for idx := 1; true; idx++ {
		client, err := listener.Accept()
		e.Exit(err)
		go func(idx int) {
			defer e.CloseOrPrint(client)
			err := serve(client, idx, certificate)
			fmt.Printf("X%d: %s\n", idx, err)
		}(idx)
	}
}

func serve(client net.Conn, idx int, certificate tls.Certificate) error {
	server, err := net.Dial("tcp", *flServer)
	if err != nil {
		return errors.Wrap(err, "failed to connect to server")
	}
	defer e.CloseOrPrint(server)

	err = proxy(client, server, idx, true)
	if err != nil {
		return errors.Wrap(err, "session terminated")
	}

	tlsClient := tls.Server(client, &tls.Config{
		Certificates: []tls.Certificate{certificate},
	})
	tlsServer := tls.Client(server, &tls.Config{
		InsecureSkipVerify: true,
	})
	fmt.Printf("X%d: eavesdropping TLS\n", idx)
	err = proxy(tlsClient, tlsServer, idx, false)
	return errors.Wrap(err, "TLS session terminated")
}

func proxy(client, server io.ReadWriter, idx int, untilTLS bool) error {
	clientNext, clientResults := startReader(client)
	serverNext, serverResults := startReader(server)

	rLocal := []byte(*flReplaceLocal)
	rRemote := []byte(*flReplaceRemote)
	replace := len(rLocal) > 0 && len(rRemote) > 0

	for {
		select {
		case result := <-clientResults:
			err := result.err
			if err != nil {
				return errors.Wrap(err, "failed to read from client")
			}

			buf := result.buf[:result.n]
			if replace {
				buf = bytes.Replace(buf, rLocal, rRemote, -1)
			}
			fmt.Printf("C%d|%s|\n", idx, escape(string(buf)))

			_, err = server.Write(buf)
			if err != nil {
				return errors.Wrap(err, "failed to write to server")
			}

			if untilTLS && bytes.Contains(buf, []byte(clientTLSMarker)) {
				fmt.Printf("X%d: client goes TLS\n", idx)
				clientNext <- false
				continue
			}

			clientNext <- true

		case result := <-serverResults:
			err := result.err
			if err != nil {
				return errors.Wrap(err, "failed to read from server")
			}

			buf := result.buf[:result.n]
			fmt.Printf("S%d|%s|\n", idx, escape(string(buf)))
			if replace {
				buf = bytes.Replace(buf, rRemote, rLocal, -1)
			}

			_, err = client.Write(buf)
			if err != nil {
				return errors.Wrap(err, "failed to write to client")
			}

			if untilTLS && bytes.Contains(buf, []byte(serverTLSMarker)) {
				fmt.Printf("X%d: server goes TLS\n", idx)
				serverNext <- false
				return nil
			}

			serverNext <- true
		}
	}
}

func startReader(reader io.Reader) (chan<- bool, chan ReadResult) {
	readNext := make(chan bool)
	buf := make([]byte, bufSize)
	results := make(chan ReadResult)
	go func() {
		for {
			n, err := reader.Read(buf)
			results <- ReadResult{n, err, buf}
			if !<-readNext {
				return
			}
		}
	}()
	return readNext, results
}

func escape(s string) string {
	s = strings.Replace(s, "\\", "\\\\", -1)
	s = strings.Replace(s, "\n", "\\n", -1)
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"fmt"
	"os"
	"runtime"
	"time"

	"labix.org/v2/mgo"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
)

type Sensor struct {
	Holder string
	Time   time.Time
	ID     byte
	Prefix byte
	Code   byte
}

var session = new(mgo.Session)
var collection *mgo.Collection

func mgoSessionFinalizer(session *mgo.Session) {
	session.Close()
}

func init() {
	runtime.SetFinalizer(session, mgoSessionFinalizer)
	var err error
	//TODO: obtain MongoDB details from configuration
	session, err = mgo.Dial("db.skydome.io:27017")
	if err != nil {
		panic(err)
	}

	session.SetMode(mgo.Monotonic, true)
	collection = session.DB("sensors").C("raw")
}

var f MQTT.MessageHandler = func(client *MQTT.MqttClient, msg MQTT.Message) {
	//fmt.Printf("TOPIC: %s\n", msg.Topic())
	fmt.Println("Size of array : ", len(msg.Payload()))
	fmt.Println("MSG: ", msg.Payload())

	sensor := Sensor{hex.EncodeToString(msg.Payload()[:6]), time.Now(), msg.Payload()[6], msg.Payload()[7], msg.Payload()[8]}
	fmt.Println("sensor : ", sensor)
	collection.Insert(sensor)
}

func main() {
	opts := MQTT.NewClientOptions().SetBroker("tcp://api.skydome.io:1883").SetClientId("trial")
	opts.SetTraceLevel(MQTT.Off)
	opts.SetDefaultPublishHandler(f)

	c := MQTT.NewClient(opts)
	_, err := c.Start()
	if err != nil {
		panic(err)
	}

	filter, _ := MQTT.NewTopicFilter("skydome/#", 0)
	if receipt, err := c.StartSubscription(nil, filter); err != nil {
		fmt.Println(err)
		os.Exit(1)
	} else {
		<-receipt
	}

	for {
		time.Sleep(1 * time.Second)
	}
}
<commit_msg>server address mapped<commit_after>package main

import (
	"encoding/hex"
	"fmt"
	"os"
	"runtime"
	"time"

	"labix.org/v2/mgo"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
)

type Sensor struct {
	Holder   string
	Time     time.Time
	ServerID byte
	ID       byte
	Prefix   byte
	Code     byte
}

var session = new(mgo.Session)
var collection *mgo.Collection

func mgoSessionFinalizer(session *mgo.Session) {
	session.Close()
}

func init() {
	runtime.SetFinalizer(session, mgoSessionFinalizer)
	var err error
	//TODO: obtain MongoDB details from configuration
	session, err = mgo.Dial("db.skydome.io:27017")
	if err != nil {
		panic(err)
	}

	session.SetMode(mgo.Monotonic, true)
	collection = session.DB("sensors").C("raw")
}

var f MQTT.MessageHandler = func(client *MQTT.MqttClient, msg MQTT.Message) {
	//fmt.Printf("TOPIC: %s\n", msg.Topic())
	fmt.Println("Size of array : ", len(msg.Payload()))
	fmt.Println("MSG: ", msg.Payload())

	sensor := Sensor{hex.EncodeToString(msg.Payload()[:6]), time.Now(), msg.Payload()[6], msg.Payload()[7], msg.Payload()[8], msg.Payload()[9]}
	fmt.Println("sensor : ", sensor)
	collection.Insert(sensor)
}

func main() {
	opts := MQTT.NewClientOptions().SetBroker("tcp://api.skydome.io:1883").SetClientId("trial")
	opts.SetTraceLevel(MQTT.Off)
	opts.SetDefaultPublishHandler(f)

	c := MQTT.NewClient(opts)
	_, err := c.Start()
	if err != nil {
		panic(err)
	}

	filter, _ := MQTT.NewTopicFilter("skydome/#", 0)
	if receipt, err := c.StartSubscription(nil, filter); err != nil {
		fmt.Println(err)
		os.Exit(1)
	} else {
		<-receipt
	}

	for {
		time.Sleep(1 * time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"reflect"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/kingpin"
)

type Severity string

// Linter message severity levels.
const (
	Warning Severity = "warning"
	Error   Severity = "error"
)

type Linter string

func (l Linter) Command() string {
	s := lintersFlag[string(l)]
	return s[0:strings.Index(s, ":")]
}

func (l Linter) Pattern() string {
	s := lintersFlag[string(l)]
	return s[strings.Index(s, ":"):]
}

func (l Linter) InstallFrom() string {
	return installMap[string(l)]
}

func (l Linter) Severity() string {
	return linterSeverityFlag[string(l)]
}

func (l Linter) MessageOverride() string {
	return linterMessageOverrideFlag[string(l)]
}

var (
	predefinedPatterns = map[string]string{
		"PATH:LINE:COL:MESSAGE": `(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)`,
		"PATH:LINE:MESSAGE":     `(?P<path>[^:]+):(?P<line>\d+):\s*(?P<message>.*)`,
	}
	lintersFlag = map[string]string{
		// main.go:8:10: should omit type map[string]string from declaration of var linters; it will be inferred from the right-hand side
		"golint": "golint {path}:PATH:LINE:COL:MESSAGE",
		// test/stutter.go:19: missing argument for Printf("%d"): format reads arg 1, have only 0 args
		"vet":         "go vet {path}:PATH:LINE:MESSAGE",
		"gotype":      "gotype {path}:PATH:LINE:COL:MESSAGE",
		"errcheck":    `errcheck {path}:(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+)\t(?P<message>.*)`,
		"varcheck":    "varcheck {path}:PATH:LINE:MESSAGE",
		"structcheck": "structcheck {path}:PATH:LINE:MESSAGE",
		"defercheck":  "defercheck {path}:PATH:LINE:MESSAGE",
		"deadcode":    `deadcode {path}:deadcode: (?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)`,
		"gocyclo":     `gocyclo -over {mincyclo} {path}:(?P<cyclo>\d+)\s+\S+\s(?P<function>\S+)\s+(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+)`,
		"go-nyet":     `go-nyet {path}:PATH:LINE:COL:MESSAGE`,
	}
	linterMessageOverrideFlag = map[string]string{
		"errcheck":    "error return value not checked ({message})",
		"varcheck":    "unused global variable {message}",
		"structcheck": "unused struct field {message}",
		"gocyclo":     "cyclomatic complexity {cyclo} of function {function}() is high (> {mincyclo})",
	}
	linterSeverityFlag = map[string]string{
		"errcheck":    "warning",
		"golint":      "warning",
		"varcheck":    "warning",
		"structcheck": "warning",
		"deadcode":    "warning",
		"gocyclo":     "warning",
		"go-nyet":     "warning",
	}
	installMap = map[string]string{
		"golint":      "github.com/golang/lint/golint",
		"gotype":      "golang.org/x/tools/cmd/gotype",
		"errcheck":    "github.com/alecthomas/errcheck",
		"defercheck":  "github.com/opennota/check/cmd/defercheck",
		"varcheck":    "github.com/opennota/check/cmd/varcheck",
		"structcheck": "github.com/opennota/check/cmd/structcheck",
		"vet":         "golang.org/x/tools/cmd/vet",
		"deadcode":    "github.com/remyoudompheng/go-misc/deadcode",
		"gocyclo":     "github.com/alecthomas/gocyclo",
		"go-nyet":     "github.com/barakmich/go-nyet",
	}
	slowLinters = []string{"structcheck", "varcheck", "errcheck"}
	sortKeys    = []string{"none", "path", "line", "column", "severity", "message"}

	pathArg            = kingpin.Arg("path", "Directory to lint.").Default(".").String()
	fastFlag           = kingpin.Flag("fast", "Only run fast linters.").Bool()
	installFlag        = kingpin.Flag("install", "Attempt to install all known linters.").Short('i').Bool()
	updateFlag         = kingpin.Flag("update", "Pass -u to go tool when installing.").Short('u').Bool()
	disableLintersFlag = kingpin.Flag("disable", "List of linters to disable.").PlaceHolder("LINTER").Short('D').Strings()
	debugFlag          = kingpin.Flag("debug", "Display messages for failed linters, etc.").Short('d').Bool()
	concurrencyFlag    = kingpin.Flag("concurrency", "Number of concurrent linters to run.").Default("16").Short('j').Int()
	excludeFlag        = kingpin.Flag("exclude", "Exclude messages matching this regular expression.").PlaceHolder("REGEXP").String()
	cycloFlag          = kingpin.Flag("cyclo-over", "Report functions with cyclomatic complexity over N (using gocyclo).").Default("10").String()
	sortFlag           = kingpin.Flag("sort", fmt.Sprintf("Sort output by any of %s.", strings.Join(sortKeys, ", "))).Default("none").Enums(sortKeys...)
)

func init() {
	kingpin.Flag("linter", "Specify a linter.").PlaceHolder("NAME:COMMAND:PATTERN").StringMapVar(&lintersFlag)
	kingpin.Flag("message-overrides", "Override message from linter. {message} will be expanded to the original message.").PlaceHolder("LINTER:MESSAGE").StringMapVar(&linterMessageOverrideFlag)
	kingpin.Flag("severity", "Map of linter severities.").PlaceHolder("LINTER:SEVERITY").StringMapVar(&linterSeverityFlag)
}

type Issue struct {
	severity Severity
	path     string
	line     int
	col      int
	message  string
}

func (m *Issue) String() string {
	col := ""
	if m.col != 0 {
		col = fmt.Sprintf("%d", m.col)
	}
	return fmt.Sprintf("%s:%d:%s:%s: %s", m.path, m.line, col, m.severity, m.message)
}

func debug(format string, args ...interface{}) {
	if *debugFlag {
		fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...)
	}
}

func formatLinters() string {
	w := bytes.NewBuffer(nil)
	for name := range lintersFlag {
		linter := Linter(name)
		fmt.Fprintf(w, "    %s (%s)\n        %s\n        %s\n", name, linter.InstallFrom(), linter.Command(), linter.Pattern())
	}
	return w.String()
}

func formatSeverity() string {
	w := bytes.NewBuffer(nil)
	for name, severity := range linterSeverityFlag {
		fmt.Fprintf(w, "    %s -> %s\n", name, severity)
	}
	return w.String()
}

func exArgs() (arg0 string, arg1 string) {
	if runtime.GOOS == "windows" {
		arg0 = "cmd"
		arg1 = "/C"
	} else {
		arg0 = "/bin/sh"
		arg1 = "-c"
	}
	return
}

type Vars map[string]string

func (v Vars) Replace(s string) string {
	for k, v := range v {
		s = strings.Replace(s, fmt.Sprintf("{%s}", k), v, -1)
	}
	return s
}

func main() {
	kingpin.CommandLine.Help = fmt.Sprintf(`Aggregate and normalise the output of a whole bunch of Go linters.

Default linters:

%s

Severity override map (default is "error"):

%s
`, formatLinters(), formatSeverity())
	kingpin.Parse()
	var filter *regexp.Regexp
	if *excludeFlag != "" {
		filter = regexp.MustCompile(*excludeFlag)
	}

	if *fastFlag {
		*disableLintersFlag = append(*disableLintersFlag, slowLinters...)
	}

	if *installFlag {
		for name, target := range installMap {
			cmd := "go get"
			if *debugFlag {
				cmd += " -v"
			}
			if *updateFlag {
				cmd += " -u"
			}
			cmd += " " + target
			fmt.Printf("Installing %s -> %s\n", name, cmd)
			arg0, arg1 := exArgs()
			c := exec.Command(arg0, arg1, cmd)
			c.Stdout = os.Stdout
			c.Stderr = os.Stderr
			err := c.Run()
			if err != nil {
				kingpin.CommandLine.Errorf(os.Stderr, "failed to install %s: %s", name, err)
			}
		}
		return
	}

	runtime.GOMAXPROCS(*concurrencyFlag)

	disable := map[string]bool{}
	for _, linter := range *disableLintersFlag {
		disable[linter] = true
	}

	start := time.Now()
	paths := *pathArg
	concurrency := make(chan bool, *concurrencyFlag)
	incomingIssues := make(chan *Issue, 100000)
	processedIssues := maybeSortIssues(incomingIssues)
	wg := &sync.WaitGroup{}
	for name, description := range lintersFlag {
		if _, ok := disable[name]; ok {
			debug("linter %s disabled", name)
			continue
		}
		parts := strings.SplitN(description, ":", 2)
		command := parts[0]
		pattern := parts[1]

		wg.Add(1)
		vars := Vars{
			"mincyclo": *cycloFlag,
		}
		go func(name, command, pattern string) {
			concurrency <- true
			executeLinter(incomingIssues, name, command, pattern, paths, vars)
			<-concurrency
			wg.Done()
		}(name, command, pattern)
	}

	wg.Wait()
	close(incomingIssues)
	for issue := range processedIssues {
		if filter != nil && filter.MatchString(issue.String()) {
			continue
		}
		fmt.Printf("%s\n", issue)
	}
	elapsed := time.Now().Sub(start)
	debug("total elapsed time %s", elapsed)
}

type sortedIssues struct {
	issues []*Issue
	order  []string
}

func (s *sortedIssues) Len() int      { return len(s.issues) }
func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] }
func (s *sortedIssues) Less(i, j int) bool {
	l, r := s.issues[i], s.issues[j]
	for _, key := range s.order {
		switch key {
		case "path":
			if l.path >= r.path {
				return false
			}
		case "line":
			if l.line >= r.line {
				return false
			}
		case "column":
			if l.col >= r.col {
				return false
			}
		case "severity":
			if l.severity >= r.severity {
				return false
			}
		case "message":
			if l.message >= r.message {
				return false
			}
		}
	}
	return true
}

func maybeSortIssues(issues chan *Issue) chan *Issue {
	if reflect.DeepEqual([]string{"none"}, *sortFlag) {
		return issues
	}
	out := make(chan *Issue, 100000)
	sorted := &sortedIssues{
		issues: []*Issue{},
		order:  *sortFlag,
	}
	go func() {
		for issue := range issues {
			sorted.issues = append(sorted.issues, issue)
		}
		sort.Sort(sorted)
		for _, issue := range sorted.issues {
			out <- issue
		}
		close(out)
	}()
	return out
}

func executeLinter(issues chan *Issue, name, command, pattern, paths string, vars Vars) {
	debug("linting with %s: %s", name, command)

	start := time.Now()
	if p, ok := predefinedPatterns[pattern]; ok {
		pattern = p
	}
	regexp.Compile(pattern)
	re, err := regexp.Compile(pattern)
	kingpin.FatalIfError(err, "invalid pattern for '"+command+"'")

	vars["path"] = paths
	command = vars.Replace(command)
	debug("executing %s", command)
	arg0, arg1 := exArgs()
	cmd := exec.Command(arg0, arg1, command)
	out, err := cmd.CombinedOutput()
	if err != nil {
		if _, ok := err.(*exec.ExitError); !ok {
			debug("warning: %s failed: %s", command, err)
			return
		}
		debug("warning: %s returned %s", command, err)
	}

	processOutput(issues, name, vars, out, re)

	elapsed := time.Now().Sub(start)
	debug("%s linter took %s", name, elapsed)
}

func processOutput(issues chan *Issue, name string, vars Vars, out []byte, re *regexp.Regexp) {
	for _, line := range bytes.Split(out, []byte("\n")) {
		groups := re.FindAllSubmatch(line, -1)
		if groups == nil {
			debug("%s (didn't match): '%s'", name, line)
			continue
		}
		issue := &Issue{}
		for i, name := range re.SubexpNames() {
			part := string(groups[0][i])
			if name != "" {
				vars[name] = part
			}
			switch name {
			case "path":
				issue.path = part

			case "line":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "line matched invalid integer")
				issue.line = int(n)

			case "col":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "col matched invalid integer")
				issue.col = int(n)

			case "message":
				issue.message = part

			case "":
			}
		}
		if m, ok := linterMessageOverrideFlag[name]; ok {
			issue.message = vars.Replace(m)
		}
		if sev, ok := linterSeverityFlag[name]; ok {
			issue.severity = Severity(sev)
		} else {
			issue.severity = "error"
		}
		issues <- issue
	}
}
<commit_msg>More cleanup.<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"reflect"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/kingpin"
)

type Severity string

// Linter message severity levels.
const (
	Warning Severity = "warning"
	Error   Severity = "error"
)

type Linter string

func (l Linter) Command() string {
	s := lintersFlag[string(l)]
	return s[0:strings.Index(s, ":")]
}

func (l Linter) Pattern() string {
	s := lintersFlag[string(l)]
	return s[strings.Index(s, ":"):]
}

func (l Linter) InstallFrom() string {
	return installMap[string(l)]
}

func (l Linter) Severity() string {
	return linterSeverityFlag[string(l)]
}

func (l Linter) MessageOverride() string {
	return linterMessageOverrideFlag[string(l)]
}

type sortedIssues struct {
	issues []*Issue
	order  []string
}

func (s *sortedIssues) Len() int      { return len(s.issues) }
func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] }
func (s *sortedIssues) Less(i, j int) bool {
	l, r := s.issues[i], s.issues[j]
	for _, key := range s.order {
		switch key {
		case "path":
			if l.path >= r.path {
				return false
			}
		case "line":
			if l.line >= r.line {
				return false
			}
		case "column":
			if l.col >= r.col {
				return false
			}
		case "severity":
			if l.severity >= r.severity {
				return false
			}
		case "message":
			if l.message >= r.message {
				return false
			}
		}
	}
	return true
}

var (
	predefinedPatterns = map[string]string{
		"PATH:LINE:COL:MESSAGE": `(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)`,
		"PATH:LINE:MESSAGE":     `(?P<path>[^:]+):(?P<line>\d+):\s*(?P<message>.*)`,
	}
	lintersFlag = map[string]string{
		// main.go:8:10: should omit type map[string]string from declaration of var linters; it will be inferred from the right-hand side
		"golint": "golint {path}:PATH:LINE:COL:MESSAGE",
		// test/stutter.go:19: missing argument for Printf("%d"): format reads arg 1, have only 0 args
		"vet":         "go vet {path}:PATH:LINE:MESSAGE",
		"gotype":      "gotype {path}:PATH:LINE:COL:MESSAGE",
		"errcheck":    `errcheck {path}:(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+)\t(?P<message>.*)`,
		"varcheck":    "varcheck {path}:PATH:LINE:MESSAGE",
		"structcheck": "structcheck {path}:PATH:LINE:MESSAGE",
		"defercheck":  "defercheck {path}:PATH:LINE:MESSAGE",
		"deadcode":    `deadcode {path}:deadcode: (?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)`,
		"gocyclo":     `gocyclo -over {mincyclo} {path}:(?P<cyclo>\d+)\s+\S+\s(?P<function>\S+)\s+(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+)`,
		"go-nyet":     `go-nyet {path}:PATH:LINE:COL:MESSAGE`,
	}
	linterMessageOverrideFlag = map[string]string{
		"errcheck":    "error return value not checked ({message})",
		"varcheck":    "unused global variable {message}",
		"structcheck": "unused struct field {message}",
		"gocyclo":     "cyclomatic complexity {cyclo} of function {function}() is high (> {mincyclo})",
	}
	linterSeverityFlag = map[string]string{
		"errcheck":    "warning",
		"golint":      "warning",
		"varcheck":    "warning",
		"structcheck": "warning",
		"deadcode":    "warning",
		"gocyclo":     "warning",
		"go-nyet":     "warning",
	}
	installMap = map[string]string{
		"golint":      "github.com/golang/lint/golint",
		"gotype":      "golang.org/x/tools/cmd/gotype",
		"errcheck":    "github.com/alecthomas/errcheck",
		"defercheck":  "github.com/opennota/check/cmd/defercheck",
		"varcheck":    "github.com/opennota/check/cmd/varcheck",
		"structcheck": "github.com/opennota/check/cmd/structcheck",
		"vet":         "golang.org/x/tools/cmd/vet",
		"deadcode":    "github.com/remyoudompheng/go-misc/deadcode",
		"gocyclo":     "github.com/alecthomas/gocyclo",
		"go-nyet":     "github.com/barakmich/go-nyet",
	}
	slowLinters = []string{"structcheck", "varcheck", "errcheck"}
	sortKeys    = []string{"none", "path", "line", "column", "severity", "message"}

	pathArg            = kingpin.Arg("path", "Directory to lint.").Default(".").String()
	fastFlag           = kingpin.Flag("fast", "Only run fast linters.").Bool()
	installFlag        = kingpin.Flag("install", "Attempt to install all known linters.").Short('i').Bool()
	updateFlag         = kingpin.Flag("update", "Pass -u to go tool when installing.").Short('u').Bool()
	disableLintersFlag = kingpin.Flag("disable", "List of linters to disable.").PlaceHolder("LINTER").Short('D').Strings()
	debugFlag          = kingpin.Flag("debug", "Display messages for failed linters, etc.").Short('d').Bool()
	concurrencyFlag    = kingpin.Flag("concurrency", "Number of concurrent linters to run.").Default("16").Short('j').Int()
	excludeFlag        = kingpin.Flag("exclude", "Exclude messages matching this regular expression.").PlaceHolder("REGEXP").String()
	cycloFlag          = kingpin.Flag("cyclo-over", "Report functions with cyclomatic complexity over N (using gocyclo).").Default("10").String()
	sortFlag           = kingpin.Flag("sort", fmt.Sprintf("Sort output by any of %s.", strings.Join(sortKeys, ", "))).Default("none").Enums(sortKeys...)
)

func init() {
	kingpin.Flag("linter", "Specify a linter.").PlaceHolder("NAME:COMMAND:PATTERN").StringMapVar(&lintersFlag)
	kingpin.Flag("message-overrides", "Override message from linter. {message} will be expanded to the original message.").PlaceHolder("LINTER:MESSAGE").StringMapVar(&linterMessageOverrideFlag)
	kingpin.Flag("severity", "Map of linter severities.").PlaceHolder("LINTER:SEVERITY").StringMapVar(&linterSeverityFlag)
}

type Issue struct {
	severity Severity
	path     string
	line     int
	col      int
	message  string
}

func (m *Issue) String() string {
	col := ""
	if m.col != 0 {
		col = fmt.Sprintf("%d", m.col)
	}
	return fmt.Sprintf("%s:%d:%s:%s: %s", m.path, m.line, col, m.severity, m.message)
}

func debug(format string, args ...interface{}) {
	if *debugFlag {
		fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...)
	}
}

func formatLinters() string {
	w := bytes.NewBuffer(nil)
	for name := range lintersFlag {
		linter := Linter(name)
		fmt.Fprintf(w, "    %s (%s)\n        %s\n        %s\n", name, linter.InstallFrom(), linter.Command(), linter.Pattern())
	}
	return w.String()
}

func formatSeverity() string {
	w := bytes.NewBuffer(nil)
	for name, severity := range linterSeverityFlag {
		fmt.Fprintf(w, "    %s -> %s\n", name, severity)
	}
	return w.String()
}

func exArgs() (arg0 string, arg1 string) {
	if runtime.GOOS == "windows" {
		arg0 = "cmd"
		arg1 = "/C"
	} else {
		arg0 = "/bin/sh"
		arg1 = "-c"
	}
	return
}

type Vars map[string]string

func (v Vars) Replace(s string) string {
	for k, v := range v {
		s = strings.Replace(s, fmt.Sprintf("{%s}", k), v, -1)
	}
	return s
}

func main() {
	kingpin.CommandLine.Help = fmt.Sprintf(`Aggregate and normalise the output of a whole bunch of Go linters.

Default linters:

%s

Severity override map (default is "error"):

%s
`, formatLinters(), formatSeverity())
	kingpin.Parse()
	var filter *regexp.Regexp
	if *excludeFlag != "" {
		filter = regexp.MustCompile(*excludeFlag)
	}

	if *fastFlag {
		*disableLintersFlag = append(*disableLintersFlag, slowLinters...)
	}

	if *installFlag {
		doInstall()
		return
	}

	runtime.GOMAXPROCS(*concurrencyFlag)

	disable := map[string]bool{}
	for _, linter := range *disableLintersFlag {
		disable[linter] = true
	}

	start := time.Now()
	paths := *pathArg
	concurrency := make(chan bool, *concurrencyFlag)
	incomingIssues := make(chan *Issue, 100000)
	processedIssues := maybeSortIssues(incomingIssues)
	wg := &sync.WaitGroup{}
	for name, description := range lintersFlag {
		if _, ok := disable[name]; ok {
			debug("linter %s disabled", name)
			continue
		}
		parts := strings.SplitN(description, ":", 2)
		command := parts[0]
		pattern := parts[1]

		wg.Add(1)
		vars := Vars{
			"mincyclo": *cycloFlag,
		}
		go func(name, command, pattern string) {
			concurrency <- true
			executeLinter(incomingIssues, name, command, pattern, paths, vars)
			<-concurrency
			wg.Done()
		}(name, command, pattern)
	}

	wg.Wait()
	close(incomingIssues)
	for issue := range processedIssues {
		if filter != nil && filter.MatchString(issue.String()) {
			continue
		}
		fmt.Printf("%s\n", issue)
	}
	elapsed := time.Now().Sub(start)
	debug("total elapsed time %s", elapsed)
}

func doInstall() {
	for name, target := range installMap {
		cmd := "go get"
		if *debugFlag {
			cmd += " -v"
		}
		if *updateFlag {
			cmd += " -u"
		}
		cmd += " " + target
		fmt.Printf("Installing %s -> %s\n", name, cmd)
		arg0, arg1 := exArgs()
		c := exec.Command(arg0, arg1, cmd)
		c.Stdout = os.Stdout
		c.Stderr = os.Stderr
		err := c.Run()
		if err != nil {
			kingpin.CommandLine.Errorf(os.Stderr, "failed to install %s: %s", name, err)
		}
	}
}

func maybeSortIssues(issues chan *Issue) chan *Issue {
	if reflect.DeepEqual([]string{"none"}, *sortFlag) {
		return issues
	}
	out := make(chan *Issue, 100000)
	sorted := &sortedIssues{
		issues: []*Issue{},
		order:  *sortFlag,
	}
	go func() {
		for issue := range issues {
			sorted.issues = append(sorted.issues, issue)
		}
		sort.Sort(sorted)
		for _, issue := range sorted.issues {
			out <- issue
		}
		close(out)
	}()
	return out
}

func executeLinter(issues chan *Issue, name, command, pattern, paths string, vars Vars) {
	debug("linting with %s: %s", name, command)

	start := time.Now()
	if p, ok := predefinedPatterns[pattern]; ok {
		pattern = p
	}
	regexp.Compile(pattern)
	re, err := regexp.Compile(pattern)
	kingpin.FatalIfError(err, "invalid pattern for '"+command+"'")

	vars["path"] = paths
	command = vars.Replace(command)
	debug("executing %s", command)
	arg0, arg1 := exArgs()
	cmd := exec.Command(arg0, arg1, command)
	out, err := cmd.CombinedOutput()
	if err != nil {
		if _, ok := err.(*exec.ExitError); !ok {
			debug("warning: %s failed: %s", command, err)
			return
		}
		debug("warning: %s returned %s", command, err)
	}

	processOutput(issues, name, vars, out, re)

	elapsed := time.Now().Sub(start)
	debug("%s linter took %s", name, elapsed)
}

func processOutput(issues chan *Issue, name string, vars Vars, out []byte, re *regexp.Regexp) {
	for _, line := range bytes.Split(out, []byte("\n")) {
		groups := re.FindAllSubmatch(line, -1)
		if groups == nil {
			debug("%s (didn't match): '%s'", name, line)
			continue
		}
		issue := &Issue{}
		for i, name := range re.SubexpNames() {
			part := string(groups[0][i])
			if name != "" {
				vars[name] = part
			}
			switch name {
			case "path":
				issue.path = part

			case "line":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "line matched invalid integer")
				issue.line = int(n)

			case "col":
				n, err := strconv.ParseInt(part, 10, 32)
				kingpin.FatalIfError(err, "col matched invalid integer")
				issue.col = int(n)

			case "message":
				issue.message = part

			case "":
			}
		}
		if m, ok := linterMessageOverrideFlag[name]; ok {
			issue.message = vars.Replace(m)
		}
		if sev, ok := linterSeverityFlag[name]; ok {
			issue.severity = Severity(sev)
		} else {
			issue.severity = "error"
		}
		issues <- issue
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/user"
	"path"
	"runtime"
	"strings"

	"github.com/spf13/viper"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/cmd"
	"github.com/zaquestion/lab/internal/config"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

// version gets set on releases during build by goreleaser.
var version = "master"

func loadConfig() (string, string, string) {
	var home string
	switch runtime.GOOS {
	case "windows":
		// userprofile works for roaming AD profiles
		home = os.Getenv("USERPROFILE")
	default:
		// Assume linux or osx
		home = os.Getenv("HOME")
		if home == "" {
			u, err := user.Current()
			if err != nil {
				log.Fatalf("cannot retrieve current user: %v \n", err)
			}
			home = u.HomeDir
		}
	}
	confpath := path.Join(home, ".config")
	if _, err := os.Stat(confpath); os.IsNotExist(err) {
		os.Mkdir(confpath, 0700)
	}

	viper.SetConfigName("lab")
	viper.SetConfigType("hcl")
	viper.AddConfigPath(".")
	viper.AddConfigPath(confpath)

	viper.SetEnvPrefix("LAB")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	viper.AutomaticEnv()

	host, user, token := viper.GetString("core.host"), viper.GetString("core.user"), viper.GetString("core.token")
	if host != "" && user != "" && token != "" {
		return host, user, token
	} else if host != "" && token != "" {
		user = getUser(host, token)
		return host, user, token
	}

	// Attempt to auto-configure for GitLab CI
	host, user, token = config.CI()
	if host != "" && user != "" && token != "" {
		return host, user, token
	}

	if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok {
		if err := config.New(path.Join(confpath, "lab.hcl"), os.Stdin); err != nil {
			log.Fatal(err)
		}

		if err := viper.ReadInConfig(); err != nil {
			log.Fatal(err)
		}
	}

	c := viper.AllSettings()["core"]
	var cfg map[string]interface{}
	switch v := c.(type) {
	// Most run this is the type
	case []map[string]interface{}:
		cfg = v[0]
	// On the first run when the cfg is created it comes in as this type
	// for whatever reason
	case map[string]interface{}:
		cfg = v
	}

	for _, v := range []string{"host", "token"} {
		if cv, ok := cfg[v]; !ok {
			log.Println(cv)
			log.Fatalf("missing config value core.%s in %s", v, viper.ConfigFileUsed())
		}
	}

	// Set environment overrides
	// Note: the code below that uses `cfg["host"]` to access these values
	// is tough to simplify since cfg["host"] is accessing the array "core"
	// and viper.GetString("core.host") is expecting a non-array so it
	// doens't match
	if v := viper.GetString("core.host"); v != "" {
		cfg["host"] = v
	}
	if v := viper.GetString("core.token"); v != "" {
		cfg["token"] = v
	}
	host = cfg["host"].(string)
	token = cfg["token"].(string)
	user = getUser(host, token)
	viper.Set("core.user", user)
	return host, user, token
}

func getUser(host, token string) string {
	lab := gitlab.NewClient(nil, token)
	lab.SetBaseURL(host + "/api/v4")
	u, _, err := lab.Users.CurrentUser()
	if err != nil {
		log.Fatal(err)
	}
	return u.Username
}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	cmd.Version = version
	lab.Init(loadConfig())
	cmd.Execute()
}
<commit_msg>try XDG_CONFIG_HOME<commit_after>package main

import (
	"log"
	"os"
	"os/user"
	"path"
	"runtime"
	"strings"

	"github.com/spf13/viper"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/cmd"
	"github.com/zaquestion/lab/internal/config"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

// version gets set on releases during build by goreleaser.
var version = "master"

func loadConfig() (string, string, string) {
	var home string
	switch runtime.GOOS {
	case "windows":
		// userprofile works for roaming AD profiles
		home = os.Getenv("USERPROFILE")
	default:
		// Assume linux or osx
		home = os.Getenv("HOME")
		if home == "" {
			u, err := user.Current()
			if err != nil {
				log.Fatalf("cannot retrieve current user: %v \n", err)
			}
			home = u.HomeDir
		}
	}
	// Try XDG_CONFIG_HOME which is declared in XDG base directory specification
	confpath := os.Getenv("XDG_CONFIG_HOME")
	if confpath == "" {
		confpath = path.Join(home, ".config")
	}
	if _, err := os.Stat(confpath); os.IsNotExist(err) {
		os.Mkdir(confpath, 0700)
	}

	viper.SetConfigName("lab")
	viper.SetConfigType("hcl")
	viper.AddConfigPath(".")
	viper.AddConfigPath(confpath)

	viper.SetEnvPrefix("LAB")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	viper.AutomaticEnv()

	host, user, token := viper.GetString("core.host"), viper.GetString("core.user"), viper.GetString("core.token")
	if host != "" && user != "" && token != "" {
		return host, user, token
	} else if host != "" && token != "" {
		user = getUser(host, token)
		return host, user, token
	}

	// Attempt to auto-configure for GitLab CI
	host, user, token = config.CI()
	if host != "" && user != "" && token != "" {
		return host, user, token
	}

	if _, ok := viper.ReadInConfig().(viper.ConfigFileNotFoundError); ok {
		if err := config.New(path.Join(confpath, "lab.hcl"), os.Stdin); err != nil {
			log.Fatal(err)
		}

		if err := viper.ReadInConfig(); err != nil {
			log.Fatal(err)
		}
	}

	c := viper.AllSettings()["core"]
	var cfg map[string]interface{}
	switch v := c.(type) {
	// Most run this is the type
	case []map[string]interface{}:
		cfg = v[0]
	// On the first run when the cfg is created it comes in as this type
	// for whatever reason
	case map[string]interface{}:
		cfg = v
	}

	for _, v := range []string{"host", "token"} {
		if cv, ok := cfg[v]; !ok {
			log.Println(cv)
			log.Fatalf("missing config value core.%s in %s", v, viper.ConfigFileUsed())
		}
	}

	// Set environment overrides
	// Note: the code below that uses `cfg["host"]` to access these values
	// is tough to simplify since cfg["host"] is accessing the array "core"
	// and viper.GetString("core.host") is expecting a non-array so it
	// doens't match
	if v := viper.GetString("core.host"); v != "" {
		cfg["host"] = v
	}
	if v := viper.GetString("core.token"); v != "" {
		cfg["token"] = v
	}
	host = cfg["host"].(string)
	token = cfg["token"].(string)
	user = getUser(host, token)
	viper.Set("core.user", user)
	return host, user, token
}

func getUser(host, token string) string {
	lab := gitlab.NewClient(nil, token)
	lab.SetBaseURL(host + "/api/v4")
	u, _, err := lab.Users.CurrentUser()
	if err != nil {
		log.Fatal(err)
	}
	return u.Username
}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	cmd.Version = version
	lab.Init(loadConfig())
	cmd.Execute()
}
<|endoftext|>