<commit_before>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package commands

//
import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"

	api "github.com/pagodabox/nanobox-api-client"
	"github.com/pagodabox/nanobox-cli/auth"
	"github.com/pagodabox/nanobox-cli/config"
	"github.com/pagodabox/nanobox-cli/util"
	"github.com/pagodabox/nanobox-golang-stylish"
)

var tw *tar.Writer

//
var publishCmd = &cobra.Command{
	Use:   "publish",
	Short: "Publishes an engine to nanobox.io",
	Long: `
Description:
  Publishes an engine to nanobox.io`,

	Run: nanoPublish,
}

// nanoPublish
func nanoPublish(ccmd *cobra.Command, args []string) {
	//
	stylish.Header("publishing engine")

	//
	api.UserSlug, api.AuthToken = auth.Authenticate()

	// create a new release
	fmt.Printf(stylish.Bullet("Creating release..."))
	release := &api.EngineReleaseCreateOptions{}

	//
	if _, err := os.Stat("./Enginefile"); err != nil {
		fmt.Println("Enginefile not found. Be sure to publish from a project directory. Exiting... ")
		os.Exit(1)
	}

	if err := config.ParseConfig("./Enginefile", release); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	fmt.Printf(stylish.Bullet("Verifying engine is publishable..."))

	// determine if any required fields (name, version, language, summary) are missing,
	// if any are found to be missing exit 1
	// NOTE: I do this using fallthrough for asthetics onlye. The message is generic
	// enough that all cases will return the same message, and this looks better than
	// a single giant case (var == "" || var == "" || ...)
	switch {
	case release.Language == "":
		fallthrough
	case release.Name == "":
		fallthrough
	case release.Summary == "":
		fallthrough
	case release.Version == "":
		fmt.Printf(stylish.Error("required fields missing", `Your Enginefile is missing one or more of the following required fields for publishing:

  name:      # the name of your project
  version:   # the current version of the project
  language:  # the lanauge (ruby, golang, etc.) of the engine
  summary:   # a 140 character summary of the project

Please ensure all required fields are provided and try again.`))

		os.Exit(1)
	}

	// attempt to read a README.md file and add it to the release...
	b, err := ioutil.ReadFile("./README.md")
	if err != nil {

		// this only fails if the file is not found, EOF is not an error. If no Readme
		// is found exit 1
		fmt.Printf(stylish.Error("missing readme", "Your engine is missing a README.md file. This file is required for publishing, as it is the only way for you to communicate how to use your engine. Please add a README.md and try again."))
		os.Exit(1)
	}

	//
	release.Readme = string(b)

	// this is our predefined list of everything that gets archived as part of the
	// engine being published
	files := map[string][]string{
		"required": []string{"./bin", "./Enginefile"},
		"optional": []string{"./lib", "./templates", "./files"},
	}

	//
	for k, v := range files {
		if k == "required" {

			// check to ensure no required files are missing
			for _, f := range v {
				if fi, _ := os.Stat(f); fi == nil {
					fmt.Printf(stylish.Error("required files missing", "Your Engine is missing one or more required files for publishing. Please read the following documentation to ensure all required files are included and try again.:\n\ndocs.nanobox.io/engines/project-creation/#example-engine-file-structure\n"))
					os.Exit(1)
				}
			}
		}
	}

	//
	// GET to API to see if engine exists
	fmt.Printf(stylish.Bullet("Checking for existing engine on nanobox.io"))
	if _, err := api.GetEngine(api.UserSlug, release.Name); err != nil {

		// if no engine is found create one
		if apiErr, _ := err.(api.APIError); apiErr.Code == 404 {

			fmt.Printf(stylish.SubTaskStart("Creating new engine on nanobox.io"))

			//
			engineCreateOptions := &api.EngineCreateOptions{
				Generic:      release.Generic,
				LanguageName: release.Language,
				Name:         release.Name,
			}

			//
			if _, err := api.CreateEngine(engineCreateOptions); err != nil {
				fmt.Printf(stylish.ErrorHead("unable to create engine"))
				fmt.Printf(stylish.ErrorBody("nanobox was unable to create and engine for your release due to the following error from the API:\n%v", err))
				os.Exit(1)
			}

			// wait until engine has been successfuly created before uploading to s3
			for {
				fmt.Print(".")

				p, err := api.GetEngine(api.UserSlug, release.Name)
				if err != nil {
					util.LogFatal("[commands/publish] api.GetEngine failed", err)
				}

				// once the engine is "active", break
				if p.State == "active" {
					break
				}

				//
				time.Sleep(1000 * time.Millisecond)
			}

			// generically handle any other errors
		} else {
			util.LogFatal("[commands/publish] api.GetEngine failed", err)
		}

		stylish.Success()
	}

	// once the whole thing is working again, try swaping the go routine to be on
	// readers instead of the writer. the writer will block until readers are done
	// reading, so there may not be a need for the wait groups.

	// write the archive to a local file
	// archive, err := os.Create(fmt.Sprintf("%v-%v.release.tgz", release.Name, release.Version))
	// if err != nil {
	// 	util.LogFatal("[commands/publish] os.Create() failed", err)
	// }
	// defer archive.Close()

	// create an empty buffer for writing the file contents to for the subsequent
	// upload
	archive := bytes.NewBuffer(nil)

	//
	h := md5.New()

	//
	mw := io.MultiWriter(h, archive)

	//
	gzw := gzip.NewWriter(mw)

	//
	tw = tar.NewWriter(gzw)

	//
	wg := &sync.WaitGroup{}
	wg.Add(1)

	//
	go func() {

		defer gzw.Close()
		defer tw.Close()

		// range over each file type...
		for _, v := range files {

			// range over each file of each type...
			for _, f := range v {

				// required files have alrady been checked, so skip any remaining (optional)
				// files/folders that arent here
				if fi, _ := os.Stat(f); fi == nil {
					continue
				}

				// tarball any remaining files/folders that are found
				if err := filepath.Walk(f, tarFile); err != nil {
					util.LogFatal("[commands/publish] filepath.Walk() failed", err)
				}
			}
		}

		wg.Done()
	}()

	wg.Wait()

	// add the checksum for the new release once its finished being archived
	release.Checksum = fmt.Sprintf("%x", h.Sum(nil))

	//
	// attempt to upload the release to S3

	//
	fmt.Printf(stylish.Bullet("Uploading release to s3..."))

	v := url.Values{}
	v.Add("user_slug", api.UserSlug)
	v.Add("auth_token", api.AuthToken)
	v.Add("version", release.Version)

	//
	s3url, err := util.RequestS3URL(fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/request_upload?%v", release.Name, v.Encode()))
	if err != nil {
		util.LogFatal("[commands/publish] util.RequestS3URL failed", err)
	}

	//
	if err := util.S3Upload(s3url, archive); err != nil {
		util.LogFatal("[commands/publish] util.S3Upload failed", err)
	}

	//
	// if the release uploaded successfully to s3, created one on odin
	fmt.Printf(stylish.Bullet("Uploading release to nanobox.io"))
	if _, err := api.CreateEngineRelease(release.Name, release); err != nil {
		fmt.Printf(stylish.ErrorHead("unable to publish release"))
		fmt.Printf(stylish.ErrorBody("nanobox was unable to publish your release due to the following error from the API:\n%v", err))
		os.Exit(1)
	}
}

// tarFile
func tarFile(path string, fi os.FileInfo, err error) error {

	// only want to tar files...
	if !fi.Mode().IsDir() {

		// fmt.Println("TARING!", path)

		// create header for this file
		header := &tar.Header{
			Name:    path,
			Size:    fi.Size(),
			Mode:    int64(fi.Mode()),
			ModTime: fi.ModTime(),
		}

		// write the header to the tarball archive
		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// open the file for taring...
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()

		// copy the file data to the tarball
		if _, err := io.Copy(tw, f); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>updating to simply use Engine/Release in place of *CreateOptions<commit_after>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package commands

//
import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"

	api "github.com/pagodabox/nanobox-api-client"
	"github.com/pagodabox/nanobox-cli/auth"
	"github.com/pagodabox/nanobox-cli/config"
	"github.com/pagodabox/nanobox-cli/util"
	"github.com/pagodabox/nanobox-golang-stylish"
)

var tw *tar.Writer

//
var publishCmd = &cobra.Command{
	Use:   "publish",
	Short: "Publishes an engine to nanobox.io",
	Long: `
Description:
  Publishes an engine to nanobox.io`,

	Run: nanoPublish,
}

// nanoPublish
func nanoPublish(ccmd *cobra.Command, args []string) {
	//
	stylish.Header("publishing engine")

	//
	api.UserSlug, api.AuthToken = auth.Authenticate()

	// create a new release
	fmt.Printf(stylish.Bullet("Creating release..."))
	release := &api.EngineRelease{}

	//
	if _, err := os.Stat("./Enginefile"); err != nil {
		fmt.Println("Enginefile not found. Be sure to publish from a project directory. Exiting... ")
		os.Exit(1)
	}

	if err := config.ParseConfig("./Enginefile", release); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	fmt.Printf(stylish.Bullet("Verifying engine is publishable..."))

	// determine if any required fields (name, version, language, summary) are missing,
	// if any are found to be missing exit 1
	// NOTE: I do this using fallthrough for asthetics onlye. The message is generic
	// enough that all cases will return the same message, and this looks better than
	// a single giant case (var == "" || var == "" || ...)
	switch {
	case release.Language == "":
		fallthrough
	case release.Name == "":
		fallthrough
	case release.Summary == "":
		fallthrough
	case release.Version == "":
		fmt.Printf(stylish.Error("required fields missing", `Your Enginefile is missing one or more of the following required fields for publishing:

  name:      # the name of your project
  version:   # the current version of the project
  language:  # the lanauge (ruby, golang, etc.) of the engine
  summary:   # a 140 character summary of the project

Please ensure all required fields are provided and try again.`))

		os.Exit(1)
	}

	// attempt to read a README.md file and add it to the release...
	b, err := ioutil.ReadFile("./README.md")
	if err != nil {

		// this only fails if the file is not found, EOF is not an error. If no Readme
		// is found exit 1
		fmt.Printf(stylish.Error("missing readme", "Your engine is missing a README.md file. This file is required for publishing, as it is the only way for you to communicate how to use your engine. Please add a README.md and try again."))
		os.Exit(1)
	}

	//
	release.Readme = string(b)

	// this is our predefined list of everything that gets archived as part of the
	// engine being published
	files := map[string][]string{
		"required": []string{"./bin", "./Enginefile"},
		"optional": []string{"./lib", "./templates", "./files"},
	}

	//
	for k, v := range files {
		if k == "required" {

			// check to ensure no required files are missing
			for _, f := range v {
				if fi, _ := os.Stat(f); fi == nil {
					fmt.Printf(stylish.Error("required files missing", "Your Engine is missing one or more required files for publishing. Please read the following documentation to ensure all required files are included and try again.:\n\ndocs.nanobox.io/engines/project-creation/#example-engine-file-structure\n"))
					os.Exit(1)
				}
			}
		}
	}

	//
	// GET to API to see if engine exists
	fmt.Printf(stylish.Bullet("Checking for existing engine on nanobox.io"))
	if _, err := api.GetEngine(api.UserSlug, release.Name); err != nil {

		// if no engine is found create one
		if apiErr, _ := err.(api.APIError); apiErr.Code == 404 {

			fmt.Printf(stylish.SubTaskStart("Creating new engine on nanobox.io"))

			//
			engine := &api.Engine{
				Generic:      release.Generic,
				LanguageName: release.Language,
				Name:         release.Name,
			}

			//
			if _, err := api.CreateEngine(engine); err != nil {
				fmt.Printf(stylish.ErrorHead("unable to create engine"))
				fmt.Printf(stylish.ErrorBody("nanobox was unable to create and engine for your release due to the following error from the API:\n%v", err))
				os.Exit(1)
			}

			// wait until engine has been successfuly created before uploading to s3
			for {
				fmt.Print(".")

				p, err := api.GetEngine(api.UserSlug, release.Name)
				if err != nil {
					util.LogFatal("[commands/publish] api.GetEngine failed", err)
				}

				// once the engine is "active", break
				if p.State == "active" {
					break
				}

				//
				time.Sleep(1000 * time.Millisecond)
			}

			// generically handle any other errors
		} else {
			util.LogFatal("[commands/publish] api.GetEngine failed", err)
		}

		stylish.Success()
	}

	// once the whole thing is working again, try swaping the go routine to be on
	// readers instead of the writer. the writer will block until readers are done
	// reading, so there may not be a need for the wait groups.

	// write the archive to a local file
	// archive, err := os.Create(fmt.Sprintf("%v-%v.release.tgz", release.Name, release.Version))
	// if err != nil {
	// 	util.LogFatal("[commands/publish] os.Create() failed", err)
	// }
	// defer archive.Close()

	// create an empty buffer for writing the file contents to for the subsequent
	// upload
	archive := bytes.NewBuffer(nil)

	//
	h := md5.New()

	//
	mw := io.MultiWriter(h, archive)

	//
	gzw := gzip.NewWriter(mw)

	//
	tw = tar.NewWriter(gzw)

	//
	wg := &sync.WaitGroup{}
	wg.Add(1)

	//
	go func() {

		defer gzw.Close()
		defer tw.Close()

		// range over each file type...
		for _, v := range files {

			// range over each file of each type...
			for _, f := range v {

				// required files have alrady been checked, so skip any remaining (optional)
				// files/folders that arent here
				if fi, _ := os.Stat(f); fi == nil {
					continue
				}

				// tarball any remaining files/folders that are found
				if err := filepath.Walk(f, tarFile); err != nil {
					util.LogFatal("[commands/publish] filepath.Walk() failed", err)
				}
			}
		}

		wg.Done()
	}()

	wg.Wait()

	// add the checksum for the new release once its finished being archived
	release.Checksum = fmt.Sprintf("%x", h.Sum(nil))

	//
	// attempt to upload the release to S3

	//
	fmt.Printf(stylish.Bullet("Uploading release to s3..."))

	v := url.Values{}
	v.Add("user_slug", api.UserSlug)
	v.Add("auth_token", api.AuthToken)
	v.Add("version", release.Version)

	//
	s3url, err := util.RequestS3URL(fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/request_upload?%v", release.Name, v.Encode()))
	if err != nil {
		util.LogFatal("[commands/publish] util.RequestS3URL failed", err)
	}

	//
	if err := util.S3Upload(s3url, archive); err != nil {
		util.LogFatal("[commands/publish] util.S3Upload failed", err)
	}

	//
	// if the release uploaded successfully to s3, created one on odin
	fmt.Printf(stylish.Bullet("Uploading release to nanobox.io"))
	if _, err := api.CreateEngineRelease(release.Name, release); err != nil {
		fmt.Printf(stylish.ErrorHead("unable to publish release"))
		fmt.Printf(stylish.ErrorBody("nanobox was unable to publish your release due to the following error from the API:\n%v", err))
		os.Exit(1)
	}
}

// tarFile
func tarFile(path string, fi os.FileInfo, err error) error {

	// only want to tar files...
	if !fi.Mode().IsDir() {

		// fmt.Println("TARING!", path)

		// create header for this file
		header := &tar.Header{
			Name:    path,
			Size:    fi.Size(),
			Mode:    int64(fi.Mode()),
			ModTime: fi.ModTime(),
		}

		// write the header to the tarball archive
		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// open the file for taring...
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()

		// copy the file data to the tarball
		if _, err := io.Copy(tw, f); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*

=======================================================

gobatt - Lightweight battery tray icon for Linux.

Repository: https://github.com/solusipse/gobatt

=======================================================

The MIT License (MIT)

Copyright (c) 2013 solusipse

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

var acpiPaths = []string{}

const (
	ACPIROOT    = "/sys/class/power_supply/BAT"
	UPDATE_TIME = 1
)

var lastPercentage float64
var timeSlice [10]float64

func main() {
	if err := initAcpiPaths(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
	icon := trayIconInit()

	glib.TimeoutAdd(UPDATE_TIME*1000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		setTrayIcon(icon, batteryStatus, batteryPercentage)
		return true
	})

	glib.TimeoutAdd(10000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		getRemainingTime(icon, batteryStatus, batteryPercentage)
		return true
	})

	gtk.Main()
}

func initAcpiPaths() error {
	items, err := filepath.Glob(ACPIROOT + "*")
	if err != nil {
		return err
	}
	if len(items) == 0 {
		return errors.New("no batteries found")
	}
	for _, item := range items {
		stat, err := os.Stat(item)
		if err != nil {
			fmt.Println("Stat error with item:", item)
			continue
		}
		if stat.IsDir() {
			item += "/"
			fmt.Println("Found battery:", item)
			acpiPaths = append(acpiPaths, item)
		} else {
			fmt.Println("Skipping non-directory item:", item)
		}
	}
	return nil
}

func getFileContent(base, filename string) string {
	content, _ := ioutil.ReadFile(base + filename)
	return string(content)
}

func getBatteryState() string {
	return strings.TrimSuffix(getFileContent(acpiPaths[0], "status"), "\n")
}

func getBatteryPercentage() float64 {
	result := float64(0)
	for _, acpiPath := range acpiPaths {
		_fc := strings.TrimSuffix(getFileContent(acpiPath, "energy_full"), "\n")
		_nc := strings.TrimSuffix(getFileContent(acpiPath, "energy_now"), "\n")
		fullCap, _ := strconv.Atoi(_fc)
		nowCap, _ := strconv.Atoi(_nc)
		result += (float64(nowCap) / float64(fullCap))
		fmt.Println("Result for:", acpiPath, "is:", (float64(nowCap) / float64(fullCap)))
	}
	result /= float64(len(acpiPaths))
	fmt.Println("Result average:", result)
	return result
}

func updateData() (string, float64) {
	return getBatteryState(), getBatteryPercentage()
}

func trayIconInit() *gtk.StatusIcon {
	gtk.Init(nil)
	glib.SetApplicationName("gobatt")

	icon := gtk.NewStatusIcon()
	icon.SetTitle("gobatt")

	return icon
}

func getGtkIcon(percent float64, status string) string {
	percent = percent * 100
	if status == "Discharging" {
		if percent <= 10 {
			return "battery-caution-symbolic"
		} else if percent <= 20 {
			return "battery-empty-symbolic"
		} else if percent <= 45 {
			return "battery-low-symbolic"
		} else if percent <= 75 {
			return "battery-good-symbolic"
		} else if percent <= 100 {
			return "battery-full-symbolic"
		}
	}
	if status == "Charging" {
		if percent <= 10 {
			return "battery-caution-charging-symbolic"
		} else if percent <= 20 {
			return "battery-empty-charging-symbolic"
		} else if percent <= 45 {
			return "battery-low-charging-symbolic"
		} else if percent <= 75 {
			return "battery-good-charging-symbolic"
		} else if percent <= 99 {
			return "battery-full-charging-symbolic"
		} else if percent <= 100 {
			return "battery-full-charged-symbolic"
		}
	}
	if status == "Full" {
		return "battery-full-charged-symbolic"
	}

	return "battery-missing-symbolic"
}

func addTimeRecord(record float64) {
	if timeSlice[9] != 0 {
		var bufferSlice [10]float64
		for i := 0; i < 9; i++ {
			bufferSlice[i+1] = timeSlice[i]
		}
		timeSlice = bufferSlice
		timeSlice[0] = record
	} else {
		for i, j := range timeSlice {
			if j == 0 {
				timeSlice[i] = record
				break
			}
		}
	}
}

func getAverageTime() int {
	if timeSlice[9] != 0 {
		var buffer float64 = 0
		for _, j := range timeSlice {
			buffer += j
		}
		return int(buffer / 10)
	}
	return -1
}

func getRemainingTime(icon *gtk.StatusIcon, status string, percent float64) {
	if lastPercentage == 0 {
		lastPercentage = percent
	}

	if lastPercentage > percent {
		remaining := ((10 * percent) / (lastPercentage - percent)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

	if lastPercentage < percent {
		remaining := ((10 * (1 - percent)) / (percent - lastPercentage)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

}

func getTooltipString(percent float64, status string, time int) string {
	if percent*100 >= 99 {
		return "Battery is fully charged."
	}

	tooltipString := status
	tooltipString += ": " + strconv.Itoa(int(percent*100)) + "%\n"

	if time == -1 {
		tooltipString += "Remaining time: estimating."
	} else {
		hours := time / 60
		minutes := time - hours*60
		tooltipString += "Remaining time: " + strconv.Itoa(hours) + "h " +
			strconv.Itoa(minutes) + "m."
	}

	return tooltipString
}

func setToolTip(icon *gtk.StatusIcon, status string, percent float64, time int) {
	icon.SetTooltipMarkup(getTooltipString(percent, status, time))
}

func setTrayIcon(icon *gtk.StatusIcon, status string, percent float64) {
	iconName := getGtkIcon(percent, status)

	if icon.GetIconName() != iconName {
		icon.SetFromIconName(iconName)
	}
	setToolTip(icon, status, percent, getAverageTime())
}
<commit_msg>avoiding periodic logs<commit_after>/*

=======================================================

gobatt - Lightweight battery tray icon for Linux.

Repository: https://github.com/solusipse/gobatt

=======================================================

The MIT License (MIT)

Copyright (c) 2013 solusipse

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

var acpiPaths = []string{}

const (
	ACPIROOT    = "/sys/class/power_supply/BAT"
	UPDATE_TIME = 1
)

var lastPercentage float64
var timeSlice [10]float64

func main() {
	if err := initAcpiPaths(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
	icon := trayIconInit()

	glib.TimeoutAdd(UPDATE_TIME*1000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		setTrayIcon(icon, batteryStatus, batteryPercentage)
		return true
	})

	glib.TimeoutAdd(10000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		getRemainingTime(icon, batteryStatus, batteryPercentage)
		return true
	})

	gtk.Main()
}

func initAcpiPaths() error {
	items, err := filepath.Glob(ACPIROOT + "*")
	if err != nil {
		return err
	}
	if len(items) == 0 {
		return errors.New("no batteries found")
	}
	for _, item := range items {
		stat, err := os.Stat(item)
		if err != nil {
			fmt.Println("Stat error with item:", item)
			continue
		}
		if stat.IsDir() {
			item += "/"
			fmt.Println("Found battery:", item)
			acpiPaths = append(acpiPaths, item)
		} else {
			fmt.Println("Skipping non-directory item:", item)
		}
	}
	return nil
}

func getFileContent(base, filename string) string {
	content, _ := ioutil.ReadFile(base + filename)
	return string(content)
}

func getBatteryState() string {
	return strings.TrimSuffix(getFileContent(acpiPaths[0], "status"), "\n")
}

func getBatteryPercentage() float64 {
	result := float64(0)
	for _, acpiPath := range acpiPaths {
		_fc := strings.TrimSuffix(getFileContent(acpiPath, "energy_full"), "\n")
		_nc := strings.TrimSuffix(getFileContent(acpiPath, "energy_now"), "\n")
		fullCap, _ := strconv.Atoi(_fc)
		nowCap, _ := strconv.Atoi(_nc)
		result += (float64(nowCap) / float64(fullCap))
	}
	result /= float64(len(acpiPaths))
	return result
}

func updateData() (string, float64) {
	return getBatteryState(), getBatteryPercentage()
}

func trayIconInit() *gtk.StatusIcon {
	gtk.Init(nil)
	glib.SetApplicationName("gobatt")

	icon := gtk.NewStatusIcon()
	icon.SetTitle("gobatt")

	return icon
}

func getGtkIcon(percent float64, status string) string {
	percent = percent * 100
	if status == "Discharging" {
		if percent <= 10 {
			return "battery-caution-symbolic"
		} else if percent <= 20 {
			return "battery-empty-symbolic"
		} else if percent <= 45 {
			return "battery-low-symbolic"
		} else if percent <= 75 {
			return "battery-good-symbolic"
		} else if percent <= 100 {
			return "battery-full-symbolic"
		}
	}
	if status == "Charging" {
		if percent <= 10 {
			return "battery-caution-charging-symbolic"
		} else if percent <= 20 {
			return "battery-empty-charging-symbolic"
		} else if percent <= 45 {
			return "battery-low-charging-symbolic"
		} else if percent <= 75 {
			return "battery-good-charging-symbolic"
		} else if percent <= 99 {
			return "battery-full-charging-symbolic"
		} else if percent <= 100 {
			return "battery-full-charged-symbolic"
		}
	}
	if status == "Full" {
		return "battery-full-charged-symbolic"
	}

	return "battery-missing-symbolic"
}

func addTimeRecord(record float64) {
	if timeSlice[9] != 0 {
		var bufferSlice [10]float64
		for i := 0; i < 9; i++ {
			bufferSlice[i+1] = timeSlice[i]
		}
		timeSlice = bufferSlice
		timeSlice[0] = record
	} else {
		for i, j := range timeSlice {
			if j == 0 {
				timeSlice[i] = record
				break
			}
		}
	}
}

func getAverageTime() int {
	if timeSlice[9] != 0 {
		var buffer float64 = 0
		for _, j := range timeSlice {
			buffer += j
		}
		return int(buffer / 10)
	}
	return -1
}

func getRemainingTime(icon *gtk.StatusIcon, status string, percent float64) {
	if lastPercentage == 0 {
		lastPercentage = percent
	}

	if lastPercentage > percent {
		remaining := ((10 * percent) / (lastPercentage - percent)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

	if lastPercentage < percent {
		remaining := ((10 * (1 - percent)) / (percent - lastPercentage)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

}

func getTooltipString(percent float64, status string, time int) string {
	if percent*100 >= 99 {
		return "Battery is fully charged."
	}

	tooltipString := status
	tooltipString += ": " + strconv.Itoa(int(percent*100)) + "%\n"

	if time == -1 {
		tooltipString += "Remaining time: estimating."
	} else {
		hours := time / 60
		minutes := time - hours*60
		tooltipString += "Remaining time: " + strconv.Itoa(hours) + "h " +
			strconv.Itoa(minutes) + "m."
	}

	return tooltipString
}

func setToolTip(icon *gtk.StatusIcon, status string, percent float64, time int) {
	icon.SetTooltipMarkup(getTooltipString(percent, status, time))
}

func setTrayIcon(icon *gtk.StatusIcon, status string, percent float64) {
	iconName := getGtkIcon(percent, status)

	if icon.GetIconName() != iconName {
		icon.SetFromIconName(iconName)
	}
	setToolTip(icon, status, percent, getAverageTime())
}
<|endoftext|>


<commit_before>/*

=======================================================

gobatt - Lightweight battery tray icon for Linux.

Repository: https://github.com/solusipse/gobatt

=======================================================

The MIT License (MIT)

Copyright (c) 2013 solusipse

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

const (
	// ACPIROOT constant is the common part of the battery sysfs directories
	ACPIROOT = "/sys/class/power_supply/BAT"
	// UPDATETIME constant is the timeout (in seconds) which will trigger new measurements.
	UPDATETIME = 1
)

var acpiPaths = []string{}
var lastPercentage float64
var timeSlice [10]float64

func main() {
	if err := initAcpiPaths(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
	icon := trayIconInit()

	glib.TimeoutAdd(UPDATETIME*1000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		setTrayIcon(icon, batteryStatus, batteryPercentage)
		return true
	})

	glib.TimeoutAdd(10000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		getRemainingTime(icon, batteryStatus, batteryPercentage)
		return true
	})

	gtk.Main()
}

func initAcpiPaths() error {
	items, err := filepath.Glob(ACPIROOT + "*")
	if err != nil {
		return err
	}
	if len(items) == 0 {
		return errors.New("no batteries found")
	}
	for _, item := range items {
		stat, err := os.Stat(item)
		if err != nil {
			fmt.Println("Stat error with item:", item)
			continue
		}
		if stat.IsDir() {
			item += "/"
			fmt.Println("Found battery:", item)
			acpiPaths = append(acpiPaths, item)
		} else {
			fmt.Println("Skipping non-directory item:", item)
		}
	}
	return nil
}

func getFileContent(base, filename string) string {
	content, _ := ioutil.ReadFile(base + filename)
	return string(content)
}

func getBatteryState() string {
	return strings.TrimSuffix(getFileContent(acpiPaths[0], "status"), "\n")
}

func getBatteryPercentage() float64 {
	result := float64(0)
	for _, acpiPath := range acpiPaths {
		_fc := strings.TrimSuffix(getFileContent(acpiPath, "energy_full"), "\n")
		_nc := strings.TrimSuffix(getFileContent(acpiPath, "energy_now"), "\n")
		fullCap, _ := strconv.Atoi(_fc)
		nowCap, _ := strconv.Atoi(_nc)
		result += (float64(nowCap) / float64(fullCap))
	}
	result /= float64(len(acpiPaths))
	return result
}

func updateData() (string, float64) {
	return getBatteryState(), getBatteryPercentage()
}

func trayIconInit() *gtk.StatusIcon {
	gtk.Init(nil)
	glib.SetApplicationName("gobatt")

	icon := gtk.NewStatusIcon()
	icon.SetTitle("gobatt")

	return icon
}

func getGtkIcon(percent float64, status string) string {
	percent = percent * 100
	if status == "Discharging" {
		if percent <= 10 {
			return "battery-caution-symbolic"
		} else if percent <= 20 {
			return "battery-empty-symbolic"
		} else if percent <= 45 {
			return "battery-low-symbolic"
		} else if percent <= 75 {
			return "battery-good-symbolic"
		} else if percent <= 100 {
			return "battery-full-symbolic"
		}
	}
	if status == "Charging" {
		if percent <= 10 {
			return "battery-caution-charging-symbolic"
		} else if percent <= 20 {
			return "battery-empty-charging-symbolic"
		} else if percent <= 45 {
			return "battery-low-charging-symbolic"
		} else if percent <= 75 {
			return "battery-good-charging-symbolic"
		} else if percent <= 99 {
			return "battery-full-charging-symbolic"
		} else if percent <= 100 {
			return "battery-full-charged-symbolic"
		}
	}
	if status == "Full" {
		return "battery-full-charged-symbolic"
	}

	return "battery-missing-symbolic"
}

func addTimeRecord(record float64) {
	if timeSlice[9] != 0 {
		var bufferSlice [10]float64
		for i := 0; i < 9; i++ {
			bufferSlice[i+1] = timeSlice[i]
		}
		timeSlice = bufferSlice
		timeSlice[0] = record
	} else {
		for i, j := range timeSlice {
			if j == 0 {
				timeSlice[i] = record
				break
			}
		}
	}
}

func getAverageTime() int {
	if timeSlice[9] != 0 {
		var buffer float64 = 0
		for _, j := range timeSlice {
			buffer += j
		}
		return int(buffer / 10)
	}
	return -1
}

func getRemainingTime(icon *gtk.StatusIcon, status string, percent float64) {
	if lastPercentage == 0 {
		lastPercentage = percent
	}

	if lastPercentage > percent {
		remaining := ((10 * percent) / (lastPercentage - percent)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

	if lastPercentage < percent {
		remaining := ((10 * (1 - percent)) / (percent - lastPercentage)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

}

func getTooltipString(percent float64, status string, time int) string {
	if percent*100 >= 99 {
		return "Battery is fully charged."
	}

	tooltipString := status
	tooltipString += ": " + strconv.Itoa(int(percent*100)) + "%\n"

	if time == -1 {
		tooltipString += "Remaining time: estimating."
	} else {
		hours := time / 60
		minutes := time - hours*60
		tooltipString += "Remaining time: " + strconv.Itoa(hours) + "h " +
			strconv.Itoa(minutes) + "m."
	}

	return tooltipString
}

func setToolTip(icon *gtk.StatusIcon, status string, percent float64, time int) {
	icon.SetTooltipMarkup(getTooltipString(percent, status, time))
}

func setTrayIcon(icon *gtk.StatusIcon, status string, percent float64) {
	iconName := getGtkIcon(percent, status)

	if icon.GetIconName() != iconName {
		icon.SetFromIconName(iconName)
	}
	setToolTip(icon, status, percent, getAverageTime())
}
<commit_msg>immediate measurement and display for the icon on startup<commit_after>/*

=======================================================

gobatt - Lightweight battery tray icon for Linux.

Repository: https://github.com/solusipse/gobatt

=======================================================

The MIT License (MIT)

Copyright (c) 2013 solusipse

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

const (
	// ACPIROOT constant is the common part of the battery sysfs directories
	ACPIROOT = "/sys/class/power_supply/BAT"
	// UPDATETIME constant is the timeout (in seconds) which will trigger new measurements.
	UPDATETIME = 1
)

var acpiPaths = []string{}
var lastPercentage float64
var timeSlice [10]float64

func main() {
	if err := initAcpiPaths(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
	icon := trayIconInit()

	batteryStatus, batteryPercentage := updateData()
	setTrayIcon(icon, batteryStatus, batteryPercentage)

	glib.TimeoutAdd(UPDATETIME*1000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		setTrayIcon(icon, batteryStatus, batteryPercentage)
		return true
	})

	glib.TimeoutAdd(10000, func() bool {
		batteryStatus, batteryPercentage := updateData()
		getRemainingTime(icon, batteryStatus, batteryPercentage)
		return true
	})

	gtk.Main()
}

func initAcpiPaths() error {
	items, err := filepath.Glob(ACPIROOT + "*")
	if err != nil {
		return err
	}
	if len(items) == 0 {
		return errors.New("no batteries found")
	}
	for _, item := range items {
		stat, err := os.Stat(item)
		if err != nil {
			fmt.Println("Stat error with item:", item)
			continue
		}
		if stat.IsDir() {
			item += "/"
			fmt.Println("Found battery:", item)
			acpiPaths = append(acpiPaths, item)
		} else {
			fmt.Println("Skipping non-directory item:", item)
		}
	}
	return nil
}

func getFileContent(base, filename string) string {
	content, _ := ioutil.ReadFile(base + filename)
	return string(content)
}

func getBatteryState() string {
	return strings.TrimSuffix(getFileContent(acpiPaths[0], "status"), "\n")
}

func getBatteryPercentage() float64 {
	result := float64(0)
	for _, acpiPath := range acpiPaths {
		_fc := strings.TrimSuffix(getFileContent(acpiPath, "energy_full"), "\n")
		_nc := strings.TrimSuffix(getFileContent(acpiPath, "energy_now"), "\n")
		fullCap, _ := strconv.Atoi(_fc)
		nowCap, _ := strconv.Atoi(_nc)
		result += (float64(nowCap) / float64(fullCap))
	}
	result /= float64(len(acpiPaths))
	return result
}

func updateData() (string, float64) {
	return getBatteryState(), getBatteryPercentage()
}

func trayIconInit() *gtk.StatusIcon {
	gtk.Init(nil)
	glib.SetApplicationName("gobatt")

	icon := gtk.NewStatusIcon()
	icon.SetTitle("gobatt")

	return icon
}

func getGtkIcon(percent float64, status string) string {
	percent = percent * 100
	if status == "Discharging" {
		if percent <= 10 {
			return "battery-caution-symbolic"
		} else if percent <= 20 {
			return "battery-empty-symbolic"
		} else if percent <= 45 {
			return "battery-low-symbolic"
		} else if percent <= 75 {
			return "battery-good-symbolic"
		} else if percent <= 100 {
			return "battery-full-symbolic"
		}
	}
	if status == "Charging" {
		if percent <= 10 {
			return "battery-caution-charging-symbolic"
		} else if percent <= 20 {
			return "battery-empty-charging-symbolic"
		} else if percent <= 45 {
			return "battery-low-charging-symbolic"
		} else if percent <= 75 {
			return "battery-good-charging-symbolic"
		} else if percent <= 99 {
			return "battery-full-charging-symbolic"
		} else if percent <= 100 {
			return "battery-full-charged-symbolic"
		}
	}
	if status == "Full" {
		return "battery-full-charged-symbolic"
	}

	return "battery-missing-symbolic"
}

func addTimeRecord(record float64) {
	if timeSlice[9] != 0 {
		var bufferSlice [10]float64
		for i := 0; i < 9; i++ {
			bufferSlice[i+1] = timeSlice[i]
		}
		timeSlice = bufferSlice
		timeSlice[0] = record
	} else {
		for i, j := range timeSlice {
			if j == 0 {
				timeSlice[i] = record
				break
			}
		}
	}
}

func getAverageTime() int {
	if timeSlice[9] != 0 {
		var buffer float64 = 0
		for _, j := range timeSlice {
			buffer += j
		}
		return int(buffer / 10)
	}
	return -1
}

func getRemainingTime(icon *gtk.StatusIcon, status string, percent float64) {
	if lastPercentage == 0 {
		lastPercentage = percent
	}

	if lastPercentage > percent {
		remaining := ((10 * percent) / (lastPercentage - percent)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

	if lastPercentage < percent {
		remaining := ((10 * (1 - percent)) / (percent - lastPercentage)) / 60

		addTimeRecord(remaining)
		lastPercentage = percent
	}

}

func getTooltipString(percent float64, status string, time int) string {
	if percent*100 >= 99 {
		return "Battery is fully charged."
	}

	tooltipString := status
	tooltipString += ": " + strconv.Itoa(int(percent*100)) + "%\n"

	if time == -1 {
		tooltipString += "Remaining time: estimating."
	} else {
		hours := time / 60
		minutes := time - hours*60
		tooltipString += "Remaining time: " + strconv.Itoa(hours) + "h " +
			strconv.Itoa(minutes) + "m."
	}

	return tooltipString
}

func setToolTip(icon *gtk.StatusIcon, status string, percent float64, time int) {
	icon.SetTooltipMarkup(getTooltipString(percent, status, time))
}

func setTrayIcon(icon *gtk.StatusIcon, status string, percent float64) {
	iconName := getGtkIcon(percent, status)

	if icon.GetIconName() != iconName {
		icon.SetFromIconName(iconName)
	}
	setToolTip(icon, status, percent, getAverageTime())
}
<|endoftext|>


<commit_before>package main
// Author: Robert B Frangioso

import (
	"path/filepath"
	"os"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"
	"os/exec"
	"bytes"
)

var wg sync.WaitGroup
var exp string

func visit(path string, f os.FileInfo, err error) error {

	b, err := filepath.Match(exp, f.Name())
	if b == true {
		fmt.Printf("%s\n", path)
	}

	return err
}

func startWalk(root string) error {

	defer wg.Done()
	var o bytes.Buffer

	cmd_out := &o

	cmd := exec.Command("find", root, "-name", exp, "-print")
	cmd.Stdout = cmd_out

	err := cmd.Run()

	if(cmd_out.Len() > 0) {
		fmt.Printf("%s\n", cmd_out.String())
	}
	// SLOWWWWW
	// err := filepath.Walk(root, visit)

	return err
}

func main() {

	flag.Parse()
	root := flag.Arg(0)
	exp = flag.Arg(1)

	basedirs, direrr := ioutil.ReadDir(root)

	if(direrr != nil) {
		fmt.Printf("ReadDir err %v \n", direrr)
	}

	for  dir := range basedirs {
		if basedirs[dir].IsDir() {
			wg.Add(1)
			go startWalk(filepath.Join(root, basedirs[dir].Name()))
		}
	}

	wg.Wait()
}

<commit_msg>startwalk -> find<commit_after>package main
// Author: Robert B Frangioso

import (
	"path/filepath"
	"os"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"
	"os/exec"
	"bytes"
)

var wg sync.WaitGroup
var exp string

func visit(path string, f os.FileInfo, err error) error {

	b, err := filepath.Match(exp, f.Name())
	if b == true {
		fmt.Printf("%s\n", path)
	}

	return err
}

func find(root string) error {

	defer wg.Done()
	var o bytes.Buffer

	cmd_out := &o

	cmd := exec.Command("find", root, "-name", exp, "-print")
	cmd.Stdout = cmd_out

	err := cmd.Run()

	if(cmd_out.Len() > 0) {
		fmt.Printf("%s\n", cmd_out.String())
	}
	// SLOWWWWW
	// err := filepath.Walk(root, visit)

	return err
}

func main() {

	flag.Parse()
	root := flag.Arg(0)
	exp = flag.Arg(1)

	basedirs, direrr := ioutil.ReadDir(root)

	if(direrr != nil) {
		fmt.Printf("ReadDir err %v \n", direrr)
	}

	for  dir := range basedirs {
		if basedirs[dir].IsDir() {
			wg.Add(1)
			go find(filepath.Join(root, basedirs[dir].Name()))
		}
	}

	wg.Wait()
}

<|endoftext|>


<commit_before>package gomapr

import (
	"errors"
	"log"
	"sync"
)

var (
	EndOfEmit = errors.New("Nothing left to emit")
)

type MapReduce interface {
	Emit() (interface{}, error)
	Map(interface{}) (interface{}, interface{})
	Reduce(interface{}, []interface{}) (interface{}, interface{})
}

type Partials struct {
	Partials []interface{}
	mutex    *sync.Mutex
}

func NewPartials() *Partials {
	return &Partials{
		Partials: make([]interface{}, 0),
		mutex:    &sync.Mutex{},
	}
}

func (p *Partials) Add(v interface{}) {
	p.mutex.Lock()
	p.Partials = append(p.Partials, v)
	p.mutex.Unlock()
}

type Reduced struct {
	Partials map[interface{}]*Partials
	mutex    *sync.Mutex
}

func NewReduced() *Reduced {
	return &Reduced{
		make(map[interface{}]*Partials),
		&sync.Mutex{},
	}
}

func (r *Reduced) GetPartials(key interface{}) *Partials {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	partials, ok := r.Partials[key]
	if !ok {
		partials = NewPartials()
		r.Partials[key] = partials
	}
	return partials
}

func (r *Reduced) Add(key interface{}, value interface{}) {
	partials := r.GetPartials(key)
	partials.Add(value)
}

type Runner struct {
	Reduced *Reduced
	MR      MapReduce
}

func NewRunner(m MapReduce) *Runner {
	return &Runner{
		Reduced: NewReduced(),
		MR:      m,
	}
}

func (r *Runner) MapWorker(emitted chan interface{}, response chan interface{}, status chan struct{}) {
	for val := range emitted {
		key, mapped := r.MR.Map(val)
		r.Reduced.Add(key, mapped)
		response <- key
	}
	status <- struct{}{}
}

func (r *Runner) Reduce(key interface{}) {
	partials := r.Reduced.Partials[key]
	partials.mutex.Lock()
	defer partials.mutex.Unlock()
	if len(partials.Partials) > 1 {
		key, partial := r.MR.Reduce(key, partials.Partials)
		r.Reduced.Partials[key].Partials = []interface{}{partial}
	}
}

func (r *Runner) Run(mappers int) {
	emit := make(chan interface{}, mappers)
	responses := make(chan interface{}, mappers)
	status := make(chan struct{})

	// Create background mapping workers.
	for i := 0; i < mappers; i++ {
		go r.MapWorker(emit, responses, status)
	}

	// Emit all events for mapping.
	go func() {
		for {
			emitted, err := r.MR.Emit()
			if err == EndOfEmit {
				break
			} else if err != nil {
				log.Printf("Error emitting: %v", err)
				break
			}
			emit <- emitted
		}
		log.Print("Closing channels")
		close(emit)
	}()

	// Wait for all mapping workers to finish.
	wg := sync.WaitGroup{}
	workersFinished := 0
	for {
		select {
		case <-status:
			workersFinished += 1
			if workersFinished == mappers {
				return
			}
		case key := <-responses:
			log.Printf("Reducing: %v", key)
			go r.Reduce(key)
			wg.Add(1)
		}
	}
	wg.Wait()
}
<commit_msg>Eliminated race conditions<commit_after>package gomapr

import (
	"errors"
	"log"
	"sync"
)

var (
	EndOfEmit = errors.New("Nothing left to emit")
)

type MapReduce interface {
	Emit() (interface{}, error)
	Map(interface{}) (interface{}, interface{})
	Reduce(interface{}, []interface{}) (interface{}, interface{})
}

type Partials struct {
	Partials []interface{}
	mutex    *sync.Mutex
}

func NewPartials() *Partials {
	return &Partials{
		Partials: make([]interface{}, 0),
		mutex:    &sync.Mutex{},
	}
}

func (p *Partials) Add(v interface{}) {
	p.mutex.Lock()
	p.Partials = append(p.Partials, v)
	p.mutex.Unlock()
}

type Reduced struct {
	Partials map[interface{}]*Partials
	mutex    *sync.Mutex
}

func NewReduced() *Reduced {
	return &Reduced{
		make(map[interface{}]*Partials),
		&sync.Mutex{},
	}
}

func (r *Reduced) GetPartials(key interface{}) *Partials {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	partials, ok := r.Partials[key]
	if !ok {
		partials = NewPartials()
		r.Partials[key] = partials
	}
	return partials
}

func (r *Reduced) Add(key interface{}, value interface{}) {
	partials := r.GetPartials(key)
	partials.Add(value)
}

type Runner struct {
	Reduced *Reduced
	MR      MapReduce
}

func NewRunner(m MapReduce) *Runner {
	return &Runner{
		Reduced: NewReduced(),
		MR:      m,
	}
}

func (r *Runner) MapWorker(emitted chan interface{}, w *sync.WaitGroup) {
	for val := range emitted {
		key, mapped := r.MR.Map(val)
		r.Reduced.Add(key, mapped)
		w.Add(1)
		go r.Reduce(key, w)
	}
	w.Done()
}

func (r *Runner) Reduce(key interface{}, w *sync.WaitGroup) {
	r.Reduced.mutex.Lock()
	partials := r.Reduced.Partials[key]
	r.Reduced.mutex.Unlock()

	partials.mutex.Lock()
	defer partials.mutex.Unlock()
	if len(partials.Partials) > 1 {
		key, partial := r.MR.Reduce(key, partials.Partials)
		r.Reduced.Partials[key].Partials = []interface{}{partial}
	}
	w.Done()
}

func (r *Runner) Run(mappers int) {
	emit := make(chan interface{}, mappers)
	wg := sync.WaitGroup{}

	// Create background mapping workers.
	for i := 0; i < mappers; i++ {
		wg.Add(1)
		go r.MapWorker(emit, &wg)
	}

	// Emit all events for mapping.
	go func() {
		for {
			emitted, err := r.MR.Emit()
			if err == EndOfEmit {
				break
			} else if err != nil {
				log.Printf("Error emitting: %v", err)
				break
			}
			emit <- emitted
		}
		close(emit)
	}()

	// Wait for all mapping workers to finish.
	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/terminal"
	"github.com/ongaeshi/gomilk/search/option"
	"github.com/ongaeshi/gomilk/search"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

const version = "0.1.0"

var opts option.Option

func init() {
	if cpu := runtime.NumCPU(); cpu == 1 {
		runtime.GOMAXPROCS(2)
	} else {
		runtime.GOMAXPROCS(cpu)
	}
}

func main() {

	parser := flags.NewParser(&opts, flags.Default)
	parser.Name = "gomilk"
	parser.Usage = "[OPTIONS] PATTERN [PATH]"

	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	if opts.Version {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	if len(args) == 0 && opts.FilesWithRegexp == "" {
		parser.WriteHelp(os.Stdout)
		os.Exit(1)
	}

	var root = "."
	if len(args) == 2 {
		root = strings.TrimRight(args[1], "\"")
		_, err := os.Lstat(root)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s\n", err)
			os.Exit(1)
		}
	}

	opts.Proc = runtime.NumCPU()

	if !terminal.IsTerminal(os.Stdout) {
		opts.NoColor = true
		opts.NoGroup = true
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	pattern := ""
	if len(args) > 0 {
		pattern = args[0]
	}

	if opts.Update {
		cmd := exec.Command("milk", "update")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		err := cmd.Run()

		if err != nil {
			log.Fatal(err)
		}
	}

	searcher := search.Searcher{root, pattern, &opts}
	err = searcher.Search()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}

}
<commit_msg>Change current directory to "root" at "milk update"<commit_after>package main

import (
	"fmt"
	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/terminal"
	"github.com/ongaeshi/gomilk/search/option"
	"github.com/ongaeshi/gomilk/search"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

const version = "0.1.0"

var opts option.Option

func init() {
	if cpu := runtime.NumCPU(); cpu == 1 {
		runtime.GOMAXPROCS(2)
	} else {
		runtime.GOMAXPROCS(cpu)
	}
}

func main() {

	parser := flags.NewParser(&opts, flags.Default)
	parser.Name = "gomilk"
	parser.Usage = "[OPTIONS] PATTERN [PATH]"

	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	if opts.Version {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	if len(args) == 0 && opts.FilesWithRegexp == "" {
		parser.WriteHelp(os.Stdout)
		os.Exit(1)
	}

	var root = "."
	if len(args) == 2 {
		root = strings.TrimRight(args[1], "\"")
		_, err := os.Lstat(root)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s\n", err)
			os.Exit(1)
		}
	}

	opts.Proc = runtime.NumCPU()

	if !terminal.IsTerminal(os.Stdout) {
		opts.NoColor = true
		opts.NoGroup = true
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	pattern := ""
	if len(args) > 0 {
		pattern = args[0]
	}

	if opts.Update {
		prevDir, _ := filepath.Abs(".")
		os.Chdir(root)

		cmd := exec.Command("milk", "update")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		err := cmd.Run()

		if err != nil {
			log.Fatal(err)
		}

		os.Chdir(prevDir)
	}

	searcher := search.Searcher{root, pattern, &opts}
	err = searcher.Search()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}

}
<|endoftext|>


<commit_before>// Copyright 2015 Reborndb Org. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/juju/errors"
	"github.com/mitchellh/go-ps"
	log "github.com/ngaut/logging"
	"github.com/nu7hatch/gouuid"
	"github.com/reborndb/go/io/ioutils"
)

func genProcID() string {
	u, err := uuid.NewV4()
	if err != nil {
		log.Fatalf("gen uuid err: %v", err)
	}

	return strings.ToLower(hex.EncodeToString(u[0:16]))
}

type process struct {
	// uuid for a process in agent use
	ID string `json:"id"`

	// process type, like proxy, redis, qdb, dashboard
	Type string `json:"type"`

	// Current pid, every process will save it in its own pid file
	// so we don't save it in data file.
	Pid int `json:"-"`

	// for start process, use cmd and args
	Cmd  string   `json:"name"`
	Args []string `json:"args"`

	// for specail use
	Ctx map[string]string `json:"ctx"`

	postStartFunc func(p *process) error

	// if not nil, we will use this func to stop process
	stopFunc func(p *process) error
}

func newDefaultProcess(cmd string, tp string) *process {
	id := genProcID()
	p := new(process)

	p.ID = id
	p.Cmd = cmd
	p.Type = tp
	p.Ctx = make(map[string]string)

	return p
}

func loadProcess(dataPath string) (*process, error) {
	p := new(process)

	data, err := ioutil.ReadFile(dataPath)
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err = json.Unmarshal(data, &p); err != nil {
		return nil, errors.Trace(err)
	}

	if !isFileExist(p.pidPath()) {
		// pid file is not exists, we should not handle this id anymore
		os.Remove(dataPath)
		log.Infof("pid file %s is not exist, skip", p.pidPath())
		return nil, nil
	}

	data, err = ioutil.ReadFile(p.pidPath())
	if err != nil {
		return nil, errors.Trace(err)
	}

	if p.Pid, err = p.readPid(); err != nil {
		return nil, errors.Trace(err)
	}

	return p, nil
}

func (p *process) readPid() (int, error) {
	data, err := ioutil.ReadFile(p.pidPath())
	if err != nil {
		return 0, errors.Trace(err)
	}

	return strconv.Atoi(strings.TrimSpace(string(data)))
}

func (p *process) addCmdArgs(args ...string) {
	p.Args = append(p.Args, args...)
}

func (p *process) start() error {
	os.MkdirAll(p.baseLogDir(), 0755)
	os.MkdirAll(p.baseDataDir(), 0755)

	c := exec.Command(p.Cmd, p.Args...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr

	if err := c.Start(); err != nil {
		return errors.Trace(err)
	}

	ch := make(chan error, 1)
	go func() {
		// use another goroutine to wait process over
		// we don't handle anything here, because we will
		// check process alive in a checker totally.
		err := c.Wait()
		ch <- err
	}()

	// wait some time
	log.Infof("wait 3 seonds for %s starts ok", p.Type)
	select {
	case err := <-ch:
		return errors.Errorf("start %s but proc exited unexpectedly, err: %v", p.Cmd, err)
	case <-time.After(3 * time.Second):
	}

	// we must read pid from pid file
	var err error
	if p.Pid, err = p.readPid(); err != nil {
		return errors.Trace(err)
	}

	if b, err := p.checkAlive(); err != nil {
		return errors.Trace(err)
	} else if !b {
		return errors.Errorf("start %d (%s) but it's not alive", p.Pid, p.Type)
	}

	if p.postStartFunc != nil {
		if err := p.postStartFunc(p); err != nil {
			log.Errorf("post start %d (%s) err %v", p.Pid, p.Type, err)
			return errors.Trace(err)
		}
	}

	return errors.Trace(p.save())
}

func (p *process) save() error {
	// we only handle data file here, because process itself will handle pid file
	data, err := json.Marshal(p)
	if err != nil {
		return errors.Trace(err)
	}

	err = ioutils.WriteFileAtomic(p.dataPath(), data, 0644)
	return errors.Trace(err)
}

func (p *process) pidPath() string {
	return path.Join(p.baseDataDir(), fmt.Sprintf("%s.pid", p.Type))
}

func (p *process) dataPath() string {
	return path.Join(p.baseDataDir(), fmt.Sprintf("%s.dat", p.Type))
}

func (p *process) baseDataDir() string {
	return path.Join(dataDir, fmt.Sprintf("%s_%s", p.Type, p.ID))
}

func (p *process) baseLogDir() string {
	return path.Join(logDir, fmt.Sprintf("%s_%s", p.Type, p.ID))
}

func (p *process) checkAlive() (bool, error) {
	proc, err := ps.FindProcess(p.Pid)
	if err != nil {
		return false, errors.Trace(err)
	} else if proc == nil {
		// proc is not alive
		return false, nil
	} else {
		if strings.Contains(proc.Executable(), p.Cmd) {
			return true, nil
		} else {
			log.Warningf("pid %d exits, but exeutable name is %s, not %s", p.Pid, proc.Executable(), p.Cmd)
			return false, nil
		}
	}
}

func isFileExist(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func (p *process) needRestart() bool {
	// if the process exited but the pid file exists,
	// we may think the process is closed unpredictably,
	// so we need restart it

	return isFileExist(p.pidPath())
}

func (p *process) clear() {
	os.Remove(p.pidPath())
	os.Remove(p.dataPath())
}

func (p *process) stop() error {
	b, err := p.checkAlive()
	if err != nil {
		return errors.Trace(err)
	}

	defer p.clear()

	if !b {
		return nil
	} else {
		if proc, err := os.FindProcess(p.Pid); err != nil {
			return errors.Trace(err)
		} else {
			if p.stopFunc != nil {
				if err := p.stopFunc(p); err != nil {
					log.Errorf("stop %d (%s) err %v, send kill signal", p.Pid, p.Type, err)
					proc.Signal(syscall.SIGTERM)
					proc.Signal(os.Kill)
				}
			} else {
				proc.Signal(syscall.SIGTERM)
				proc.Signal(os.Kill)
			}

			ch := make(chan struct{}, 1)
			go func(ch chan struct{}) {
				proc.Wait()
				ch <- struct{}{}
			}(ch)

			select {
			case <-ch:
			case <-time.After(5 * time.Minute):
				proc.Kill()
				log.Errorf("wait %d (%s)stopped timeout, force kill", p.Pid, p.Type)
			}

			return nil
		}
	}
}
<commit_msg>still use wait + read pid to check alive<commit_after>// Copyright 2015 Reborndb Org. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/juju/errors"
	"github.com/mitchellh/go-ps"
	log "github.com/ngaut/logging"
	"github.com/nu7hatch/gouuid"
	"github.com/reborndb/go/io/ioutils"
)

func genProcID() string {
	u, err := uuid.NewV4()
	if err != nil {
		log.Fatalf("gen uuid err: %v", err)
	}

	return strings.ToLower(hex.EncodeToString(u[0:16]))
}

type process struct {
	// uuid for a process in agent use
	ID string `json:"id"`

	// process type, like proxy, redis, qdb, dashboard
	Type string `json:"type"`

	// Current pid, every process will save it in its own pid file
	// so we don't save it in data file.
	Pid int `json:"-"`

	// for start process, use cmd and args
	Cmd  string   `json:"name"`
	Args []string `json:"args"`

	// for specail use
	Ctx map[string]string `json:"ctx"`

	postStartFunc func(p *process) error

	// if not nil, we will use this func to stop process
	stopFunc func(p *process) error
}

func newDefaultProcess(cmd string, tp string) *process {
	id := genProcID()
	p := new(process)

	p.ID = id
	p.Cmd = cmd
	p.Type = tp
	p.Ctx = make(map[string]string)

	return p
}

func loadProcess(dataPath string) (*process, error) {
	p := new(process)

	data, err := ioutil.ReadFile(dataPath)
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err = json.Unmarshal(data, &p); err != nil {
		return nil, errors.Trace(err)
	}

	if !isFileExist(p.pidPath()) {
		// pid file is not exists, we should not handle this id anymore
		os.Remove(dataPath)
		log.Infof("pid file %s is not exist, skip", p.pidPath())
		return nil, nil
	}

	data, err = ioutil.ReadFile(p.pidPath())
	if err != nil {
		return nil, errors.Trace(err)
	}

	if p.Pid, err = p.readPid(); err != nil {
		return nil, errors.Trace(err)
	}

	return p, nil
}

func (p *process) readPid() (int, error) {
	data, err := ioutil.ReadFile(p.pidPath())
	if err != nil {
		return 0, errors.Trace(err)
	}

	return strconv.Atoi(strings.TrimSpace(string(data)))
}

func (p *process) addCmdArgs(args ...string) {
	p.Args = append(p.Args, args...)
}

func (p *process) start() error {
	os.MkdirAll(p.baseLogDir(), 0755)
	os.MkdirAll(p.baseDataDir(), 0755)

	c := exec.Command(p.Cmd, p.Args...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr

	if err := c.Start(); err != nil {
		return errors.Trace(err)
	}

	go func() {
		// use another goroutine to wait process over
		// we don't handle anything here, because we will
		// check process alive in a checker totally.
		c.Wait()
	}()

	var err error
	for i := 0; i < 5; i++ {
		// we must read pid from pid file
		time.Sleep(1 * time.Second)

		if p.Pid, err = p.readPid(); err != nil {
			log.Errorf("read pid failed, err %v, wait 1s and retry", err)
			err = errors.Trace(err)
		} else {
			break
		}
	}

	if err != nil {
		return errors.Trace(err)
	}

	if b, err := p.checkAlive(); err != nil {
		return errors.Trace(err)
	} else if !b {
		return errors.Errorf("start %d (%s) but it's not alive", p.Pid, p.Type)
	}

	if p.postStartFunc != nil {
		if err := p.postStartFunc(p); err != nil {
			log.Errorf("post start %d (%s) err %v", p.Pid, p.Type, err)
			return errors.Trace(err)
		}
	}

	return errors.Trace(p.save())
}

func (p *process) save() error {
	// we only handle data file here, because process itself will handle pid file
	data, err := json.Marshal(p)
	if err != nil {
		return errors.Trace(err)
	}

	err = ioutils.WriteFileAtomic(p.dataPath(), data, 0644)
	return errors.Trace(err)
}

func (p *process) pidPath() string {
	return path.Join(p.baseDataDir(), fmt.Sprintf("%s.pid", p.Type))
}

func (p *process) dataPath() string {
	return path.Join(p.baseDataDir(), fmt.Sprintf("%s.dat", p.Type))
}

func (p *process) baseDataDir() string {
	return path.Join(dataDir, fmt.Sprintf("%s_%s", p.Type, p.ID))
}

func (p *process) baseLogDir() string {
	return path.Join(logDir, fmt.Sprintf("%s_%s", p.Type, p.ID))
}

func (p *process) checkAlive() (bool, error) {
	proc, err := ps.FindProcess(p.Pid)
	if err != nil {
		return false, errors.Trace(err)
	} else if proc == nil {
		// proc is not alive
		return false, nil
	} else {
		if strings.Contains(proc.Executable(), p.Cmd) {
			return true, nil
		} else {
			log.Warningf("pid %d exits, but exeutable name is %s, not %s", p.Pid, proc.Executable(), p.Cmd)
			return false, nil
		}
	}
}

func isFileExist(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func (p *process) needRestart() bool {
	// if the process exited but the pid file exists,
	// we may think the process is closed unpredictably,
	// so we need restart it

	return isFileExist(p.pidPath())
}

func (p *process) clear() {
	os.RemoveAll(p.baseDataDir())
	os.RemoveAll(p.baseLogDir())
}

func (p *process) stop() error {
	b, err := p.checkAlive()
	if err != nil {
		return errors.Trace(err)
	}

	defer p.clear()

	if !b {
		return nil
	} else {
		if proc, err := os.FindProcess(p.Pid); err != nil {
			return errors.Trace(err)
		} else {
			if p.stopFunc != nil {
				if err := p.stopFunc(p); err != nil {
					log.Errorf("stop %d (%s) err %v, send kill signal", p.Pid, p.Type, err)
					proc.Signal(syscall.SIGTERM)
					proc.Signal(os.Kill)
				}
			} else {
				proc.Signal(syscall.SIGTERM)
				proc.Signal(os.Kill)
			}

			ch := make(chan struct{}, 1)
			go func(ch chan struct{}) {
				proc.Wait()
				ch <- struct{}{}
			}(ch)

			select {
			case <-ch:
			case <-time.After(5 * time.Minute):
				proc.Kill()
				log.Errorf("wait %d (%s)stopped timeout, force kill", p.Pid, p.Type)
			}

			return nil
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"sort"

	"github.com/rjw57/aonui"
)

var cmdInfo = &Command{
	Run:       runInfo,
	UsageLine: "info gribfile",
	Short:     "print information on GRIB2 files",
	Long: `
Info prints information on the shape of data in a GRIB2 file to standard
output. Gribfile specifies which GRIB2 file is parsed. Output has the following
form:

	NX=720
	NY=361
	NPARAM=3
	NPRESSURE=47
	NFCSTHOUR=65
	PRESSURES=1000,975,950,925,900,875,850,... # etc
	FCSTHOURS=0,3,6,9,12,15,18,21,24,27,30,... # etc

NX, NY, NPARAM, NPRESSURE and NFCSTHOUR give the sizes of each dimension of the
data. PRESSURES and FCSTHOURS are comma-separated integers giving the
particular pressures and forecast hours which correspondt to each point along
the respective axes.

Note that this command may take some time to complete the first time it is run
on a file since collating the pressures and forecast hours requires scanning
through the entire GRIB2 message.
`,
}

func runInfo(cmd *Command, args []string) {
	if len(args) != 1 {
		log.Print("error: no GRIB file specified")
		setExitStatus(1)
		return
	}

	gribFn := args[0]

	// Get inventory from grib
	inv, err := aonui.TawhiriOrderedInventory(gribFn)
	if err != nil {
		log.Print(err)
		setExitStatus(1)
		return
	}

	// Check for empty file
	if len(inv) == 0 {
		log.Print("error: empty GRIB")
		setExitStatus(1)
		return
	}

	// Form a map of parameters, forecast hours and pressures.
	fcstHourMap := make(map[int]bool)
	pressureMap := make(map[int]bool)
	paramMap := make(map[string]bool)

	// For each tawhiri item in the inventory...
	for _, twItem := range aonui.ToTawhiris(inv) {
		// skip invalid items
		if !twItem.IsValid {
			continue
		}

		// set pressure and forecast hour flag
		fcstHourMap[twItem.ForecastHour] = true
		pressureMap[twItem.Pressure] = true

		// set parameter flag for each parameter
		for _, p := range twItem.Item.Parameters {
			paramMap[p] = true
		}
	}

	// Form a list of parameters, forecast hours and pressures
	var (
		fcstHours, pressures []int
		parameters           []string
	)
	for k := range fcstHourMap {
		fcstHours = append(fcstHours, k)
	}
	for k := range pressureMap {
		pressures = append(pressures, k)
	}
	for k := range paramMap {
		parameters = append(parameters, k)
	}

	// Sort forecast hours and pressures
	sort.Ints(fcstHours)
	sort.Sort(sort.Reverse(sort.IntSlice(pressures)))

	// Get shapes from grib
	// HACK: only look at first item
	shapes, err := aonui.Wgrib2GridShapes(inv[:1], gribFn)
	if err != nil {
		log.Print(err)
		setExitStatus(1)
		return
	}
	if len(shapes) < 1 {
		log.Print("error: no grids in GRIB?!")
		setExitStatus(1)
		return
	}

	fmt.Printf("NX=%d\n", shapes[0].Columns)
	fmt.Printf("NY=%d\n", shapes[0].Rows)
	fmt.Printf("NPARAM=%d\n", len(parameters))
	fmt.Printf("NPRESSURE=%d\n", len(pressures))
	fmt.Printf("NFCSTHOUR=%d\n", len(fcstHours))

	fmt.Print("PRESSURES=")
	for idx, p := range pressures {
		if idx != 0 {
			fmt.Print(",")
		}
		fmt.Print(p)
	}
	fmt.Print("\n")

	fmt.Print("FCSTHOURS=")
	for idx, fh := range fcstHours {
		if idx != 0 {
			fmt.Print(",")
		}
		fmt.Print(fh)
	}
	fmt.Print("\n")
}
<commit_msg>info: add run date to information dumped out<commit_after>package main

import (
	"fmt"
	"log"
	"sort"

	"github.com/rjw57/aonui"
)

var cmdInfo = &Command{
	Run:       runInfo,
	UsageLine: "info gribfile",
	Short:     "print information on GRIB2 files",
	Long: `
Info prints information on the shape of data in a GRIB2 file to standard
output. Gribfile specifies which GRIB2 file is parsed. Output has the following
form:

	NX=720
	NY=361
	NPARAM=3
	NPRESSURE=47
	NFCSTHOUR=65
	PRESSURES=1000,975,950,925,900,875,850,... # etc
	FCSTHOURS=0,3,6,9,12,15,18,21,24,27,30,... # etc
	RUNTIME=2014102106

NX, NY, NPARAM, NPRESSURE and NFCSTHOUR give the sizes of each dimension of the
data. PRESSURES and FCSTHOURS are comma-separated integers giving the
particular pressures and forecast hours which correspondt to each point along
the respective axes. The RUNTIME is the date and time the forecast was run on
formatted as YYYYMMDDHH.

Note that this command may take some time to complete the first time it is run
on a file since collating the pressures and forecast hours requires scanning
through the entire GRIB2 message.
`,
}

func runInfo(cmd *Command, args []string) {
	if len(args) != 1 {
		log.Print("error: no GRIB file specified")
		setExitStatus(1)
		return
	}

	gribFn := args[0]

	// Get inventory from grib
	inv, err := aonui.TawhiriOrderedInventory(gribFn)
	if err != nil {
		log.Print(err)
		setExitStatus(1)
		return
	}

	// Check for empty file
	if len(inv) == 0 {
		log.Print("error: empty GRIB")
		setExitStatus(1)
		return
	}

	// HACK: Assume the date of the first InventoryItem holds for the rest.
	runTime := inv[0].When

	// Form a map of parameters, forecast hours and pressures.
	fcstHourMap := make(map[int]bool)
	pressureMap := make(map[int]bool)
	paramMap := make(map[string]bool)

	// For each tawhiri item in the inventory...
	for _, twItem := range aonui.ToTawhiris(inv) {
		// skip invalid items
		if !twItem.IsValid {
			continue
		}

		// set pressure and forecast hour flag
		fcstHourMap[twItem.ForecastHour] = true
		pressureMap[twItem.Pressure] = true

		// set parameter flag for each parameter
		for _, p := range twItem.Item.Parameters {
			paramMap[p] = true
		}
	}

	// Form a list of parameters, forecast hours and pressures
	var (
		fcstHours, pressures []int
		parameters           []string
	)
	for k := range fcstHourMap {
		fcstHours = append(fcstHours, k)
	}
	for k := range pressureMap {
		pressures = append(pressures, k)
	}
	for k := range paramMap {
		parameters = append(parameters, k)
	}

	// Sort forecast hours and pressures
	sort.Ints(fcstHours)
	sort.Sort(sort.Reverse(sort.IntSlice(pressures)))

	// Get shapes from grib
	// HACK: only look at first item
	shapes, err := aonui.Wgrib2GridShapes(inv[:1], gribFn)
	if err != nil {
		log.Print(err)
		setExitStatus(1)
		return
	}
	if len(shapes) < 1 {
		log.Print("error: no grids in GRIB?!")
		setExitStatus(1)
		return
	}

	fmt.Printf("NX=%d\n", shapes[0].Columns)
	fmt.Printf("NY=%d\n", shapes[0].Rows)
	fmt.Printf("NPARAM=%d\n", len(parameters))
	fmt.Printf("NPRESSURE=%d\n", len(pressures))
	fmt.Printf("NFCSTHOUR=%d\n", len(fcstHours))

	fmt.Print("PRESSURES=")
	for idx, p := range pressures {
		if idx != 0 {
			fmt.Print(",")
		}
		fmt.Print(p)
	}
	fmt.Print("\n")

	fmt.Print("FCSTHOURS=")
	for idx, fh := range fcstHours {
		if idx != 0 {
			fmt.Print(",")
		}
		fmt.Print(fh)
	}
	fmt.Print("\n")

	fmt.Printf("RUNTIME=%v\n", runTime.Format("2006010215"))
}
<|endoftext|>


<commit_before>// dbusd is the dbus daemon.
package main

import (
	"fmt"
	"os"
	"runtime/debug"
	"time"

	"github.com/funkygao/dbus"
	"github.com/funkygao/dbus/engine"
	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/diagnostics/agent"
	"github.com/funkygao/log4go"

	// bootstrap plugins
	_ "github.com/funkygao/dbus/plugins"
	_ "github.com/funkygao/dbus/plugins/filter"
	_ "github.com/funkygao/dbus/plugins/input"
	_ "github.com/funkygao/dbus/plugins/output"
)

func init() {
	parseFlags()

	if options.showversion {
		showVersionAndExit()
	}

	setupLogging()

	ctx.LoadFromHome()
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			debug.PrintStack()
		}
	}()

	fmt.Fprint(os.Stderr, logo[1:])

	globals := engine.DefaultGlobals()
	globals.Debug = options.debug
	globals.RPCPort = options.rpcPort
	globals.APIPort = options.apiPort
	globals.RouterTrack = options.routerTrack
	globals.InputRecyclePoolSize = options.inputPoolSize
	globals.FilterRecyclePoolSize = options.filterPoolSize
	globals.HubChanSize = options.hubPoolSize
	globals.PluginChanSize = options.pluginPoolSize
	globals.ClusterEnabled = options.clusterEnable
	globals.Zone = options.zone
	globals.Cluster = options.cluster

	if !options.validateConf && len(options.visualizeFile) == 0 {
		// daemon mode
		log4go.Info("dbus[%s@%s] starting", dbus.Revision, dbus.Version)

		agent.HttpAddr = options.pprofAddr
		log4go.Info("pprof agent ready on %s", agent.Start())
		go func() {
			log4go.Error("%s", <-agent.Errors)
		}()
	}

	t0 := time.Now()
	var err error
	for {
		e := engine.New(globals).LoadFrom(options.configPath)

		if options.visualizeFile != "" {
			e.ExportDiagram(options.visualizeFile)
			return
		}

		if options.validateConf {
			fmt.Println("ok")
			return
		}

		if err = e.ServeForever(); err != nil {
			// e,g. SIGTERM received
			break
		}
	}

	log4go.Info("dbus[%s@%s] %s, bye!", dbus.Revision, dbus.Version, time.Since(t0))
	log4go.Close()
}
<commit_msg>log the zone/cluster on boot<commit_after>// dbusd is the dbus daemon.
package main

import (
	"fmt"
	"os"
	"runtime/debug"
	"time"

	"github.com/funkygao/dbus"
	"github.com/funkygao/dbus/engine"
	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/diagnostics/agent"
	"github.com/funkygao/log4go"

	// bootstrap plugins
	_ "github.com/funkygao/dbus/plugins"
	_ "github.com/funkygao/dbus/plugins/filter"
	_ "github.com/funkygao/dbus/plugins/input"
	_ "github.com/funkygao/dbus/plugins/output"
)

func init() {
	parseFlags()

	if options.showversion {
		showVersionAndExit()
	}

	setupLogging()

	ctx.LoadFromHome()
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			debug.PrintStack()
		}
	}()

	fmt.Fprint(os.Stderr, logo[1:])

	globals := engine.DefaultGlobals()
	globals.Debug = options.debug
	globals.RPCPort = options.rpcPort
	globals.APIPort = options.apiPort
	globals.RouterTrack = options.routerTrack
	globals.InputRecyclePoolSize = options.inputPoolSize
	globals.FilterRecyclePoolSize = options.filterPoolSize
	globals.HubChanSize = options.hubPoolSize
	globals.PluginChanSize = options.pluginPoolSize
	globals.ClusterEnabled = options.clusterEnable
	globals.Zone = options.zone
	globals.Cluster = options.cluster

	if !options.validateConf && len(options.visualizeFile) == 0 {
		// daemon mode
		log4go.Info("dbus[%s@%s] starting for {zone:%s cluster:%s}",
			dbus.Revision, dbus.Version, options.zone, options.cluster)

		agent.HttpAddr = options.pprofAddr
		log4go.Info("pprof agent ready on %s", agent.Start())
		go func() {
			log4go.Error("%s", <-agent.Errors)
		}()
	}

	t0 := time.Now()
	var err error
	for {
		e := engine.New(globals).LoadFrom(options.configPath)

		if options.visualizeFile != "" {
			e.ExportDiagram(options.visualizeFile)
			return
		}

		if options.validateConf {
			fmt.Println("ok")
			return
		}

		if err = e.ServeForever(); err != nil {
			// e,g. SIGTERM received
			break
		}
	}

	log4go.Info("dbus[%s@%s] %s, bye!", dbus.Revision, dbus.Version, time.Since(t0))
	log4go.Close()
}
<|endoftext|>


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

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"text/tabwriter"

	"github.com/golang/dep"
	"github.com/sdboyer/gps"
)

const statusShortHelp = `Report the status of the project's dependencies`
const statusLongHelp = `
With no arguments, print the status of each dependency of the project.

  PROJECT     Import path
  CONSTRAINT  Version constraint, from the manifest
  VERSION     Version chosen, from the lock
  REVISION    VCS revision of the chosen version
  LATEST      Latest VCS revision available
  PKGS USED   Number of packages from this project that are actually used

With one or more explicitly specified packages, or with the -detailed flag,
print an extended status output for each dependency of the project.

  TODO    Another column description
  FOOBAR  Another column description

Status returns exit code zero if all dependencies are in a "good state".
`

func (cmd *statusCommand) Name() string      { return "status" }
func (cmd *statusCommand) Args() string      { return "[package...]" }
func (cmd *statusCommand) ShortHelp() string { return statusShortHelp }
func (cmd *statusCommand) LongHelp() string  { return statusLongHelp }
func (cmd *statusCommand) Hidden() bool      { return false }

func (cmd *statusCommand) Register(fs *flag.FlagSet) {
	fs.BoolVar(&cmd.detailed, "detailed", false, "report more detailed status")
	fs.BoolVar(&cmd.json, "json", false, "output in JSON format")
	fs.StringVar(&cmd.template, "f", "", "output in text/template format")
	fs.BoolVar(&cmd.dot, "dot", false, "output the dependency graph in GraphViz format")
	fs.BoolVar(&cmd.old, "old", false, "only show out-of-date dependencies")
	fs.BoolVar(&cmd.missing, "missing", false, "only show missing dependencies")
	fs.BoolVar(&cmd.unused, "unused", false, "only show unused dependencies")
	fs.BoolVar(&cmd.modified, "modified", false, "only show modified dependencies")
}

type statusCommand struct {
	detailed bool
	json     bool
	template string
	dot      bool
	old      bool
	missing  bool
	unused   bool
	modified bool
}

type outputter interface {
	BasicHeader()
	BasicLine(*BasicStatus)
	BasicFooter()
	MissingHeader()
	MissingLine(*MissingStatus)
	MissingFooter()
}

type tableOutput struct{ w *tabwriter.Writer }

func (out *tableOutput) BasicHeader() {
	fmt.Fprintf(out.w, "PROJECT\tCONSTRAINT\tVERSION\tREVISION\tLATEST\tPKGS USED\n")
}

func (out *tableOutput) BasicFooter() {
	out.w.Flush()
}

func (out *tableOutput) BasicLine(bs *BasicStatus) {
	var constraint string
	if v, ok := bs.Constraint.(gps.Version); ok {
		constraint = formatVersion(v)
	} else {
		constraint = bs.Constraint.String()
	}
	fmt.Fprintf(out.w,
		"%s\t%s\t%s\t%s\t%s\t%d\t\n",
		bs.ProjectRoot,
		constraint,
		formatVersion(bs.Version),
		formatVersion(bs.Revision),
		formatVersion(bs.Latest),
		bs.PackageCount,
	)
}

func (out *tableOutput) MissingHeader() {
	fmt.Fprintln(out.w, "PROJECT\tMISSING PACKAGES")
}

func (out *tableOutput) MissingLine(ms *MissingStatus) {
	fmt.Fprintf(out.w,
		"%s\t%s\t\n",
		ms.ProjectRoot,
		ms.MissingPackages,
	)
}

func (out *tableOutput) MissingFooter() {
	out.w.Flush()
}

type jsonOutput struct {
	w       io.Writer
	basic   []*BasicStatus
	missing []*MissingStatus
}

func (out *jsonOutput) BasicHeader() {
	out.basic = []*BasicStatus{}
}

func (out *jsonOutput) BasicFooter() {
	json.NewEncoder(out.w).Encode(out.basic)
}

func (out *jsonOutput) BasicLine(bs *BasicStatus) {
	out.basic = append(out.basic, bs)
}

func (out *jsonOutput) MissingHeader() {
	out.missing = []*MissingStatus{}
}

func (out *jsonOutput) MissingLine(ms *MissingStatus) {
	out.missing = append(out.missing, ms)
}

func (out *jsonOutput) MissingFooter() {
	json.NewEncoder(out.w).Encode(out.missing)
}

func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error {
	p, err := ctx.LoadProject("")
	if err != nil {
		return err
	}

	sm, err := ctx.SourceManager()
	if err != nil {
		return err
	}
	sm.UseDefaultSignalHandling()
	defer sm.Release()

	var out outputter
	switch {
	case cmd.detailed:
		return fmt.Errorf("not implemented")
	case cmd.json:
		out = &jsonOutput{
			w: os.Stdout,
		}
	default:
		out = &tableOutput{
			w: tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0),
		}
	}
	return runStatusAll(out, p, sm)
}

// BasicStatus contains all the information reported about a single dependency
// in the summary/list status output mode.
type BasicStatus struct {
	ProjectRoot  string
	Constraint   gps.Constraint
	Version      gps.UnpairedVersion
	Revision     gps.Revision
	Latest       gps.Version
	PackageCount int
}

type MissingStatus struct {
	ProjectRoot     string
	MissingPackages []string
}

func runStatusAll(out outputter, p *dep.Project, sm *gps.SourceMgr) error {
	if p.Lock == nil {
		// TODO if we have no lock file, do...other stuff
		return nil
	}

	// While the network churns on ListVersions() requests, statically analyze
	// code from the current project.
	ptree, err := gps.ListPackages(p.AbsRoot, string(p.ImportRoot))
	if err != nil {
		return fmt.Errorf("analysis of local packages failed: %v", err)
	}

	// Set up a solver in order to check the InputHash.
	params := gps.SolveParameters{
		RootDir:         p.AbsRoot,
		RootPackageTree: ptree,
		Manifest:        p.Manifest,
		// Locks aren't a part of the input hash check, so we can omit it.
	}
	if *verbose {
		params.Trace = true
		params.TraceLogger = log.New(os.Stderr, "", 0)
	}

	s, err := gps.Prepare(params, sm)
	if err != nil {
		return fmt.Errorf("could not set up solver for input hashing: %s", err)
	}

	cm := collectConstraints(ptree, p, sm)

	// Get the project list and sort it so that the printed output users see is
	// deterministically ordered. (This may be superfluous if the lock is always
	// written in alpha order, but it doesn't hurt to double down.)
	slp := p.Lock.Projects()
	sort.Sort(dep.SortedLockedProjects(slp))

	if bytes.Equal(s.HashInputs(), p.Lock.Memo) {
		// If these are equal, we're guaranteed that the lock is a transitively
		// complete picture of all deps. That eliminates the need for at least
		// some checks.

		out.BasicHeader()

		for _, proj := range slp {
			bs := BasicStatus{
				ProjectRoot:  string(proj.Ident().ProjectRoot),
				PackageCount: len(proj.Packages()),
			}

			// Split apart the version from the lock into its constituent parts
			switch tv := proj.Version().(type) {
			case gps.UnpairedVersion:
				bs.Version = tv
			case gps.Revision:
				bs.Revision = tv
			case gps.PairedVersion:
				bs.Version = tv.Unpair()
				bs.Revision = tv.Underlying()
			}

			// Check if the manifest has an override for this project. If so,
			// set that as the constraint.
			if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
				// TODO note somehow that it's overridden
				bs.Constraint = pp.Constraint
			} else {
				bs.Constraint = gps.Any()
				for _, c := range cm[bs.ProjectRoot] {
					bs.Constraint = c.Intersect(bs.Constraint)
				}
			}

			// Only if we have a non-rev and non-plain version do/can we display
			// anything wrt the version's updateability.
			if bs.Version != nil && bs.Version.Type() != gps.IsVersion {
				c, has := p.Manifest.Dependencies[proj.Ident().ProjectRoot]
				if !has {
					c.Constraint = gps.Any()
				}
				// TODO: This constraint is only the constraint imposed by the
				// current project, not by any transitive deps. As a result,
				// transitive project deps will always show "any" here.
				bs.Constraint = c.Constraint

				vl, err := sm.ListVersions(proj.Ident())
				if err == nil {
					gps.SortForUpgrade(vl)

					for _, v := range vl {
						// Because we've sorted the version list for
						// upgrade, the first version we encounter that
						// matches our constraint will be what we want.
						if c.Constraint.Matches(v) {
							// For branch constraints this should be the
							// most recent revision on the selected
							// branch.
							if tv, ok := v.(gps.PairedVersion); ok && v.Type() == gps.IsBranch {
								bs.Latest = tv.Underlying()
							} else {
								bs.Latest = v
							}
							break
						}
					}
				}
			}

			out.BasicLine(&bs)
		}
		out.BasicFooter()

		return nil
	}

	// Hash digest mismatch may indicate that some deps are no longer
	// needed, some are missing, or that some constraints or source
	// locations have changed.
	//
	// It's possible for digests to not match, but still have a correct
	// lock.
	out.MissingHeader()

	rm, _ := ptree.ToReachMap(true, true, false, nil)

	external := rm.Flatten(false)
	roots := make(map[gps.ProjectRoot][]string)
	var errs []string
	for _, e := range external {
		root, err := sm.DeduceProjectRoot(e)
		if err != nil {
			errs = append(errs, string(root))
			continue
		}

		roots[root] = append(roots[root], e)
	}

outer:
	for root, pkgs := range roots {
		// TODO also handle the case where the project is present, but there
		// are items missing from just the package list
		for _, lp := range slp {
			if lp.Ident().ProjectRoot == root {
				continue outer
			}
		}

		out.MissingLine(&MissingStatus{ProjectRoot: string(root), MissingPackages: pkgs})
	}
	out.MissingFooter()

	return nil
}

func formatVersion(v gps.Version) string {
	if v == nil {
		return ""
	}
	switch v.Type() {
	case gps.IsBranch:
		return "branch " + v.String()
	case gps.IsRevision:
		r := v.String()
		if len(r) > 7 {
			r = r[:7]
		}
		return r
	}
	return v.String()
}

func collectConstraints(ptree gps.PackageTree, p *dep.Project, sm *gps.SourceMgr) map[string][]gps.Constraint {
	// TODO
	return map[string][]gps.Constraint{}
}
<commit_msg>Children field in BasicStatus struct to store child data for tree/graph statuses<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"text/tabwriter"

	"github.com/golang/dep"
	"github.com/sdboyer/gps"
	"path/filepath"
)

const statusShortHelp = `Report the status of the project's dependencies`
const statusLongHelp = `
With no arguments, print the status of each dependency of the project.

  PROJECT     Import path
  CONSTRAINT  Version constraint, from the manifest
  VERSION     Version chosen, from the lock
  REVISION    VCS revision of the chosen version
  LATEST      Latest VCS revision available
  PKGS USED   Number of packages from this project that are actually used

With one or more explicitly specified packages, or with the -detailed flag,
print an extended status output for each dependency of the project.

  TODO    Another column description
  FOOBAR  Another column description

Status returns exit code zero if all dependencies are in a "good state".
`

func (cmd *statusCommand) Name() string      { return "status" }
func (cmd *statusCommand) Args() string      { return "[package...]" }
func (cmd *statusCommand) ShortHelp() string { return statusShortHelp }
func (cmd *statusCommand) LongHelp() string  { return statusLongHelp }
func (cmd *statusCommand) Hidden() bool      { return false }

func (cmd *statusCommand) Register(fs *flag.FlagSet) {
	fs.BoolVar(&cmd.detailed, "detailed", false, "report more detailed status")
	fs.BoolVar(&cmd.json, "json", false, "output in JSON format")
	fs.StringVar(&cmd.template, "f", "", "output in text/template format")
	fs.BoolVar(&cmd.dot, "dot", false, "output the dependency graph in GraphViz format")
	fs.BoolVar(&cmd.old, "old", false, "only show out-of-date dependencies")
	fs.BoolVar(&cmd.missing, "missing", false, "only show missing dependencies")
	fs.BoolVar(&cmd.unused, "unused", false, "only show unused dependencies")
	fs.BoolVar(&cmd.modified, "modified", false, "only show modified dependencies")
}

type statusCommand struct {
	detailed bool
	json     bool
	template string
	dot      bool
	old      bool
	missing  bool
	unused   bool
	modified bool
}

type outputter interface {
	BasicHeader()
	BasicLine(*BasicStatus)
	BasicFooter()
	MissingHeader()
	MissingLine(*MissingStatus)
	MissingFooter()
}

type tableOutput struct{ w *tabwriter.Writer }

func (out *tableOutput) BasicHeader() {
	fmt.Fprintf(out.w, "PROJECT\tCONSTRAINT\tVERSION\tREVISION\tLATEST\tPKGS USED\n")
}

func (out *tableOutput) BasicFooter() {
	out.w.Flush()
}

func (out *tableOutput) BasicLine(bs *BasicStatus) {
	var constraint string
	if v, ok := bs.Constraint.(gps.Version); ok {
		constraint = formatVersion(v)
	} else {
		constraint = bs.Constraint.String()
	}
	fmt.Fprintf(out.w,
		"%s\t%s\t%s\t%s\t%s\t%d\t\n",
		bs.ProjectRoot,
		constraint,
		formatVersion(bs.Version),
		formatVersion(bs.Revision),
		formatVersion(bs.Latest),
		bs.PackageCount,
	)
}

func (out *tableOutput) MissingHeader() {
	fmt.Fprintln(out.w, "PROJECT\tMISSING PACKAGES")
}

func (out *tableOutput) MissingLine(ms *MissingStatus) {
	fmt.Fprintf(out.w,
		"%s\t%s\t\n",
		ms.ProjectRoot,
		ms.MissingPackages,
	)
}

func (out *tableOutput) MissingFooter() {
	out.w.Flush()
}

type jsonOutput struct {
	w       io.Writer
	basic   []*BasicStatus
	missing []*MissingStatus
}

func (out *jsonOutput) BasicHeader() {
	out.basic = []*BasicStatus{}
}

func (out *jsonOutput) BasicFooter() {
	json.NewEncoder(out.w).Encode(out.basic)
}

func (out *jsonOutput) BasicLine(bs *BasicStatus) {
	out.basic = append(out.basic, bs)
}

func (out *jsonOutput) MissingHeader() {
	out.missing = []*MissingStatus{}
}

func (out *jsonOutput) MissingLine(ms *MissingStatus) {
	out.missing = append(out.missing, ms)
}

func (out *jsonOutput) MissingFooter() {
	json.NewEncoder(out.w).Encode(out.missing)
}

func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error {
	p, err := ctx.LoadProject("")
	if err != nil {
		return err
	}

	sm, err := ctx.SourceManager()
	if err != nil {
		return err
	}
	sm.UseDefaultSignalHandling()
	defer sm.Release()

	var out outputter

	// Require of children should be useful for tree/graph operations.
	// By default is set to false in order to avoid slower status process
	var rch bool = false
	switch {
	case cmd.detailed:
		return fmt.Errorf("not implemented")
	case cmd.json:
		out = &jsonOutput{
			w: os.Stdout,
		}
	default:
		out = &tableOutput{
			w: tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0),
		}
	}
	return runStatusAll(out, p, sm, rch)
}

// BasicStatus contains all the information reported about a single dependency
// in the summary/list status output mode.
type BasicStatus struct {
	ProjectRoot  string
	children     []string
	Constraint   gps.Constraint
	Version      gps.UnpairedVersion
	Revision     gps.Revision
	Latest       gps.Version
	PackageCount int
}

type MissingStatus struct {
	ProjectRoot     string
	MissingPackages []string
}

func runStatusAll(out outputter, p *dep.Project, sm *gps.SourceMgr, rch bool) error {
	if p.Lock == nil {
		// TODO if we have no lock file, do...other stuff
		return nil
	}

	// While the network churns on ListVersions() requests, statically analyze
	// code from the current project.
	ptree, err := gps.ListPackages(p.AbsRoot, string(p.ImportRoot))
	if err != nil {
		return fmt.Errorf("analysis of local packages failed: %v", err)
	}

	// Set up a solver in order to check the InputHash.
	params := gps.SolveParameters{
		RootDir:         p.AbsRoot,
		RootPackageTree: ptree,
		Manifest:        p.Manifest,
		// Locks aren't a part of the input hash check, so we can omit it.
	}
	if *verbose {
		params.Trace = true
		params.TraceLogger = log.New(os.Stderr, "", 0)
	}

	s, err := gps.Prepare(params, sm)
	if err != nil {
		return fmt.Errorf("could not set up solver for input hashing: %s", err)
	}

	cm := collectConstraints(ptree, p, sm)

	// Get the project list and sort it so that the printed output users see is
	// deterministically ordered. (This may be superfluous if the lock is always
	// written in alpha order, but it doesn't hurt to double down.)
	slp := p.Lock.Projects()
	sort.Sort(dep.SortedLockedProjects(slp))

	if bytes.Equal(s.HashInputs(), p.Lock.Memo) {
		// If these are equal, we're guaranteed that the lock is a transitively
		// complete picture of all deps. That eliminates the need for at least
		// some checks.

		out.BasicHeader()

		for _, proj := range slp {
			bs := BasicStatus{
				ProjectRoot:  string(proj.Ident().ProjectRoot),
				PackageCount: len(proj.Packages()),
			}

			// List project child packages if required
			if rch == true {
				r := filepath.Join(p.AbsRoot, "vendor", string(proj.Ident().ProjectRoot))
				ptr, err := gps.ListPackages(r, string(proj.Ident().ProjectRoot))

				if err != nil {
					return fmt.Errorf("analysis of %s package failed: %v", proj.Ident().ProjectRoot, err)
				}

				prm, _ := ptr.ToReachMap(false, false, false, nil)
				bs.children = prm.Flatten(false)
			}

			// Split apart the version from the lock into its constituent parts
			switch tv := proj.Version().(type) {
			case gps.UnpairedVersion:
				bs.Version = tv
			case gps.Revision:
				bs.Revision = tv
			case gps.PairedVersion:
				bs.Version = tv.Unpair()
				bs.Revision = tv.Underlying()
			}

			// Check if the manifest has an override for this project. If so,
			// set that as the constraint.
			if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
				// TODO note somehow that it's overridden
				bs.Constraint = pp.Constraint
			} else {
				bs.Constraint = gps.Any()
				for _, c := range cm[bs.ProjectRoot] {
					bs.Constraint = c.Intersect(bs.Constraint)
				}
			}

			// Only if we have a non-rev and non-plain version do/can we display
			// anything wrt the version's updateability.
			if bs.Version != nil && bs.Version.Type() != gps.IsVersion {
				c, has := p.Manifest.Dependencies[proj.Ident().ProjectRoot]
				if !has {
					c.Constraint = gps.Any()
				}
				// TODO: This constraint is only the constraint imposed by the
				// current project, not by any transitive deps. As a result,
				// transitive project deps will always show "any" here.
				bs.Constraint = c.Constraint

				vl, err := sm.ListVersions(proj.Ident())
				if err == nil {
					gps.SortForUpgrade(vl)

					for _, v := range vl {
						// Because we've sorted the version list for
						// upgrade, the first version we encounter that
						// matches our constraint will be what we want.
						if c.Constraint.Matches(v) {
							// For branch constraints this should be the
							// most recent revision on the selected
							// branch.
							if tv, ok := v.(gps.PairedVersion); ok && v.Type() == gps.IsBranch {
								bs.Latest = tv.Underlying()
							} else {
								bs.Latest = v
							}
							break
						}
					}
				}
			}

			out.BasicLine(&bs)
		}
		out.BasicFooter()

		return nil
	}

	// Hash digest mismatch may indicate that some deps are no longer
	// needed, some are missing, or that some constraints or source
	// locations have changed.
	//
	// It's possible for digests to not match, but still have a correct
	// lock.
	out.MissingHeader()

	rm, _ := ptree.ToReachMap(true, true, false, nil)

	external := rm.Flatten(false)
	roots := make(map[gps.ProjectRoot][]string)
	var errs []string
	for _, e := range external {
		root, err := sm.DeduceProjectRoot(e)
		if err != nil {
			errs = append(errs, string(root))
			continue
		}

		roots[root] = append(roots[root], e)
	}

outer:
	for root, pkgs := range roots {
		// TODO also handle the case where the project is present, but there
		// are items missing from just the package list
		for _, lp := range slp {
			if lp.Ident().ProjectRoot == root {
				continue outer
			}
		}

		out.MissingLine(&MissingStatus{ProjectRoot: string(root), MissingPackages: pkgs})
	}
	out.MissingFooter()

	return nil
}

func formatVersion(v gps.Version) string {
	if v == nil {
		return ""
	}
	switch v.Type() {
	case gps.IsBranch:
		return "branch " + v.String()
	case gps.IsRevision:
		r := v.String()
		if len(r) > 7 {
			r = r[:7]
		}
		return r
	}
	return v.String()
}

func collectConstraints(ptree gps.PackageTree, p *dep.Project, sm *gps.SourceMgr) map[string][]gps.Constraint {
	// TODO
	return map[string][]gps.Constraint{}
}
<|endoftext|>