<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"sort"
	"time"

	"github.com/go-errors/errors"
	isatty "github.com/mattn/go-isatty"
	lua "github.com/yuin/gopher-lua"
	"github.com/zyedidia/micro/internal/action"
	"github.com/zyedidia/micro/internal/buffer"
	"github.com/zyedidia/micro/internal/config"
	"github.com/zyedidia/micro/internal/screen"
	"github.com/zyedidia/micro/internal/shell"
	"github.com/zyedidia/micro/internal/util"
	"github.com/zyedidia/tcell"
)

var (
	// Event channel
	events   chan tcell.Event
	autosave chan bool

	// Command line flags
	flagVersion   = flag.Bool("version", false, "Show the version number and information")
	flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
	flagOptions   = flag.Bool("options", false, "Show all option help")
	flagDebug     = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
	flagPlugin    = flag.String("plugin", "", "Plugin command")
	flagClean     = flag.Bool("clean", false, "Clean configuration directory")
	optionFlags   map[string]*string
)

func InitFlags() {
	flag.Usage = func() {
		fmt.Println("Usage: micro [OPTIONS] [FILE]...")
		fmt.Println("-clean")
		fmt.Println("    \tCleans the configuration directory")
		fmt.Println("-config-dir dir")
		fmt.Println("    \tSpecify a custom location for the configuration directory")
		fmt.Println("[FILE]:LINE:COL")
		fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
		fmt.Println("-options")
		fmt.Println("    \tShow all option help")
		fmt.Println("-debug")
		fmt.Println("    \tEnable debug mode (enables logging to ./log.txt)")
		fmt.Println("-version")
		fmt.Println("    \tShow the version number and information")

		fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
		fmt.Println("-plugin install [PLUGIN]...")
		fmt.Println("    \tInstall plugin(s)")
		fmt.Println("-plugin remove [PLUGIN]...")
		fmt.Println("    \tRemove plugin(s)")
		fmt.Println("-plugin update [PLUGIN]...")
		fmt.Println("    \tUpdate plugin(s) (if no argument is given, updates all plugins)")
		fmt.Println("-plugin search [PLUGIN]...")
		fmt.Println("    \tSearch for a plugin")
		fmt.Println("-plugin list")
		fmt.Println("    \tList installed plugins")
		fmt.Println("-plugin available")
		fmt.Println("    \tList available plugins")

		fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
		fmt.Println("-option value")
		fmt.Println("    \tSet `option` to `value` for this session")
		fmt.Println("    \tFor example: `micro -syntax off file.c`")
		fmt.Println("\nUse `micro -options` to see the full list of configuration options")
	}

	optionFlags = make(map[string]*string)

	for k, v := range config.DefaultAllSettings() {
		optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
	}

	flag.Parse()

	if *flagVersion {
		// If -version was passed
		fmt.Println("Version:", util.Version)
		fmt.Println("Commit hash:", util.CommitHash)
		fmt.Println("Compiled on", util.CompileDate)
		os.Exit(0)
	}

	if *flagOptions {
		// If -options was passed
		var keys []string
		m := config.DefaultAllSettings()
		for k := range m {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, k := range keys {
			v := m[k]
			fmt.Printf("-%s value\n", k)
			fmt.Printf("    \tDefault value: '%v'\n", v)
		}
		os.Exit(0)
	}

	if util.Debug == "OFF" && *flagDebug {
		util.Debug = "ON"
	}
}

// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
func DoPluginFlags() {
	if *flagClean || *flagPlugin != "" {
		config.LoadAllPlugins()

		if *flagPlugin != "" {
			args := flag.Args()

			config.PluginCommand(os.Stdout, *flagPlugin, args)
		} else if *flagClean {
			CleanConfig()
		}

		os.Exit(0)
	}
}

// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
func LoadInput() []*buffer.Buffer {
	// There are a number of ways micro should start given its input

	// 1. If it is given a files in flag.Args(), it should open those

	// 2. If there is no input file and the input is not a terminal, that means
	// something is being piped in and the stdin should be opened in an
	// empty buffer

	// 3. If there is no input file and the input is a terminal, an empty buffer
	// should be opened

	var filename string
	var input []byte
	var err error
	args := flag.Args()
	buffers := make([]*buffer.Buffer, 0, len(args))

	btype := buffer.BTDefault
	if !isatty.IsTerminal(os.Stdout.Fd()) {
		btype = buffer.BTStdout
	}

	if len(args) > 0 {
		// Option 1
		// We go through each file and load it
		for i := 0; i < len(args); i++ {
			buf, err := buffer.NewBufferFromFile(args[i], btype)
			if err != nil {
				screen.TermMessage(err)
				continue
			}
			// If the file didn't exist, input will be empty, and we'll open an empty buffer
			buffers = append(buffers, buf)
		}
	} else if !isatty.IsTerminal(os.Stdin.Fd()) {
		// Option 2
		// The input is not a terminal, so something is being piped in
		// and we should read from stdin
		input, err = ioutil.ReadAll(os.Stdin)
		if err != nil {
			screen.TermMessage("Error reading from stdin: ", err)
			input = []byte{}
		}
		buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
	} else {
		// Option 3, just open an empty buffer
		buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
	}

	return buffers
}

func main() {
	defer func() {
		if util.Stdout.Len() > 0 {
			fmt.Fprint(os.Stdout, util.Stdout.String())
		}
		os.Exit(0)
	}()

	// runtime.SetCPUProfileRate(400)
	// f, _ := os.Create("micro.prof")
	// pprof.StartCPUProfile(f)
	// defer pprof.StopCPUProfile()

	var err error

	InitFlags()

	InitLog()

	err = config.InitConfigDir(*flagConfigDir)
	if err != nil {
		screen.TermMessage(err)
	}

	config.InitRuntimeFiles()
	err = config.ReadSettings()
	if err != nil {
		screen.TermMessage(err)
	}
	config.InitGlobalSettings()

	// flag options
	for k, v := range optionFlags {
		if *v != "" {
			nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
			if err != nil {
				screen.TermMessage(err)
				continue
			}
			config.GlobalSettings[k] = nativeValue
		}
	}

	DoPluginFlags()

	screen.Init()

	defer func() {
		if err := recover(); err != nil {
			screen.Screen.Fini()
			fmt.Println("Micro encountered an error:", err)
			// backup all open buffers
			for _, b := range buffer.OpenBuffers {
				b.Backup(false)
			}
			// Print the stack trace too
			fmt.Print(errors.Wrap(err, 2).ErrorStack())
			os.Exit(1)
		}
	}()

	err = config.LoadAllPlugins()
	if err != nil {
		screen.TermMessage(err)
	}

	action.InitBindings()
	action.InitCommands()

	err = config.InitColorscheme()
	if err != nil {
		screen.TermMessage(err)
	}

	b := LoadInput()

	if len(b) == 0 {
		// No buffers to open
		screen.Screen.Fini()
		runtime.Goexit()
	}

	action.InitTabs(b)
	action.InitGlobals()

	err = config.RunPluginFn("init")
	if err != nil {
		screen.TermMessage(err)
	}

	events = make(chan tcell.Event)

	// Here is the event loop which runs in a separate thread
	go func() {
		for {
			screen.Lock()
			e := screen.Screen.PollEvent()
			screen.Unlock()
			if e != nil {
				events <- e
			}
		}
	}()

	// clear the drawchan so we don't redraw excessively
	// if someone requested a redraw before we started displaying
	for len(screen.DrawChan()) > 0 {
		<-screen.DrawChan()
	}

	// wait for initial resize event
	select {
	case event := <-events:
		action.Tabs.HandleEvent(event)
	case <-time.After(10 * time.Millisecond):
		// time out after 10ms
	}

	// Since this loop is very slow (waits for user input every time) it's
	// okay to be inefficient and run it via a function every time
	// We do this so we can recover from panics without crashing the editor
	for {
		DoEvent()
	}
}

// DoEvent runs the main action loop of the editor
func DoEvent() {
	var event tcell.Event

	// recover from errors without crashing the editor
	defer func() {
		if err := recover(); err != nil {
			if e, ok := err.(*lua.ApiError); ok {
				screen.TermMessage("Lua API error:", e)
			} else {
				screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
			}
		}
	}()
	// Display everything
	screen.Screen.Fill(' ', config.DefStyle)
	screen.Screen.HideCursor()
	action.Tabs.Display()
	for _, ep := range action.MainTab().Panes {
		ep.Display()
	}
	action.MainTab().Display()
	action.InfoBar.Display()
	screen.Screen.Show()

	// Check for new events
	select {
	case f := <-shell.Jobs:
		// If a new job has finished while running in the background we should execute the callback
		f.Function(f.Output, f.Args)
	case <-config.Autosave:
		for _, b := range buffer.OpenBuffers {
			b.Save()
		}
	case <-shell.CloseTerms:
	case event = <-events:
	case <-screen.DrawChan():
	}

	if action.InfoBar.HasPrompt {
		action.InfoBar.HandleEvent(event)
	} else {
		action.Tabs.HandleEvent(event)
	}
}
<commit_msg>Support +LINE:COL flag syntax for cursor pos<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"runtime"
	"sort"
	"time"

	"github.com/go-errors/errors"
	isatty "github.com/mattn/go-isatty"
	lua "github.com/yuin/gopher-lua"
	"github.com/zyedidia/micro/internal/action"
	"github.com/zyedidia/micro/internal/buffer"
	"github.com/zyedidia/micro/internal/config"
	"github.com/zyedidia/micro/internal/screen"
	"github.com/zyedidia/micro/internal/shell"
	"github.com/zyedidia/micro/internal/util"
	"github.com/zyedidia/tcell"
)

var (
	// Event channel
	events   chan tcell.Event
	autosave chan bool

	// Command line flags
	flagVersion   = flag.Bool("version", false, "Show the version number and information")
	flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
	flagOptions   = flag.Bool("options", false, "Show all option help")
	flagDebug     = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
	flagPlugin    = flag.String("plugin", "", "Plugin command")
	flagClean     = flag.Bool("clean", false, "Clean configuration directory")
	optionFlags   map[string]*string
)

func InitFlags() {
	flag.Usage = func() {
		fmt.Println("Usage: micro [OPTIONS] [FILE]...")
		fmt.Println("-clean")
		fmt.Println("    \tCleans the configuration directory")
		fmt.Println("-config-dir dir")
		fmt.Println("    \tSpecify a custom location for the configuration directory")
		fmt.Println("[FILE]:LINE:COL")
		fmt.Println("+LINE:COL")
		fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
		fmt.Println("-options")
		fmt.Println("    \tShow all option help")
		fmt.Println("-debug")
		fmt.Println("    \tEnable debug mode (enables logging to ./log.txt)")
		fmt.Println("-version")
		fmt.Println("    \tShow the version number and information")

		fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
		fmt.Println("-plugin install [PLUGIN]...")
		fmt.Println("    \tInstall plugin(s)")
		fmt.Println("-plugin remove [PLUGIN]...")
		fmt.Println("    \tRemove plugin(s)")
		fmt.Println("-plugin update [PLUGIN]...")
		fmt.Println("    \tUpdate plugin(s) (if no argument is given, updates all plugins)")
		fmt.Println("-plugin search [PLUGIN]...")
		fmt.Println("    \tSearch for a plugin")
		fmt.Println("-plugin list")
		fmt.Println("    \tList installed plugins")
		fmt.Println("-plugin available")
		fmt.Println("    \tList available plugins")

		fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
		fmt.Println("-option value")
		fmt.Println("    \tSet `option` to `value` for this session")
		fmt.Println("    \tFor example: `micro -syntax off file.c`")
		fmt.Println("\nUse `micro -options` to see the full list of configuration options")
	}

	optionFlags = make(map[string]*string)

	for k, v := range config.DefaultAllSettings() {
		optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
	}

	flag.Parse()

	if *flagVersion {
		// If -version was passed
		fmt.Println("Version:", util.Version)
		fmt.Println("Commit hash:", util.CommitHash)
		fmt.Println("Compiled on", util.CompileDate)
		os.Exit(0)
	}

	if *flagOptions {
		// If -options was passed
		var keys []string
		m := config.DefaultAllSettings()
		for k := range m {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, k := range keys {
			v := m[k]
			fmt.Printf("-%s value\n", k)
			fmt.Printf("    \tDefault value: '%v'\n", v)
		}
		os.Exit(0)
	}

	if util.Debug == "OFF" && *flagDebug {
		util.Debug = "ON"
	}
}

// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
func DoPluginFlags() {
	if *flagClean || *flagPlugin != "" {
		config.LoadAllPlugins()

		if *flagPlugin != "" {
			args := flag.Args()

			config.PluginCommand(os.Stdout, *flagPlugin, args)
		} else if *flagClean {
			CleanConfig()
		}

		os.Exit(0)
	}
}

// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
func LoadInput() []*buffer.Buffer {
	// There are a number of ways micro should start given its input

	// 1. If it is given a files in flag.Args(), it should open those

	// 2. If there is no input file and the input is not a terminal, that means
	// something is being piped in and the stdin should be opened in an
	// empty buffer

	// 3. If there is no input file and the input is a terminal, an empty buffer
	// should be opened

	var filename string
	var input []byte
	var err error
	args := flag.Args()
	buffers := make([]*buffer.Buffer, 0, len(args))

	btype := buffer.BTDefault
	if !isatty.IsTerminal(os.Stdout.Fd()) {
		btype = buffer.BTStdout
	}

	files := make([]string, 0, len(args))
	flagStartPos := ""
	flagr := regexp.MustCompile(`^\+\d+(:\d+)?$`)
	for _, a := range args {
		if flagr.MatchString(a) {
			flagStartPos = a[1:]
		} else {
			if flagStartPos != "" {
				files = append(files, a+":"+flagStartPos)
				flagStartPos = ""
			} else {
				files = append(files, a)
			}
		}
	}

	if len(files) > 0 {
		// Option 1
		// We go through each file and load it
		for i := 0; i < len(files); i++ {
			buf, err := buffer.NewBufferFromFile(files[i], btype)
			if err != nil {
				screen.TermMessage(err)
				continue
			}
			// If the file didn't exist, input will be empty, and we'll open an empty buffer
			buffers = append(buffers, buf)
		}
	} else if !isatty.IsTerminal(os.Stdin.Fd()) {
		// Option 2
		// The input is not a terminal, so something is being piped in
		// and we should read from stdin
		input, err = ioutil.ReadAll(os.Stdin)
		if err != nil {
			screen.TermMessage("Error reading from stdin: ", err)
			input = []byte{}
		}
		buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
	} else {
		// Option 3, just open an empty buffer
		buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
	}

	return buffers
}

func main() {
	defer func() {
		if util.Stdout.Len() > 0 {
			fmt.Fprint(os.Stdout, util.Stdout.String())
		}
		os.Exit(0)
	}()

	// runtime.SetCPUProfileRate(400)
	// f, _ := os.Create("micro.prof")
	// pprof.StartCPUProfile(f)
	// defer pprof.StopCPUProfile()

	var err error

	InitFlags()

	InitLog()

	err = config.InitConfigDir(*flagConfigDir)
	if err != nil {
		screen.TermMessage(err)
	}

	config.InitRuntimeFiles()
	err = config.ReadSettings()
	if err != nil {
		screen.TermMessage(err)
	}
	config.InitGlobalSettings()

	// flag options
	for k, v := range optionFlags {
		if *v != "" {
			nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
			if err != nil {
				screen.TermMessage(err)
				continue
			}
			config.GlobalSettings[k] = nativeValue
		}
	}

	DoPluginFlags()

	screen.Init()

	defer func() {
		if err := recover(); err != nil {
			screen.Screen.Fini()
			fmt.Println("Micro encountered an error:", err)
			// backup all open buffers
			for _, b := range buffer.OpenBuffers {
				b.Backup(false)
			}
			// Print the stack trace too
			fmt.Print(errors.Wrap(err, 2).ErrorStack())
			os.Exit(1)
		}
	}()

	err = config.LoadAllPlugins()
	if err != nil {
		screen.TermMessage(err)
	}

	action.InitBindings()
	action.InitCommands()

	err = config.InitColorscheme()
	if err != nil {
		screen.TermMessage(err)
	}

	b := LoadInput()

	if len(b) == 0 {
		// No buffers to open
		screen.Screen.Fini()
		runtime.Goexit()
	}

	action.InitTabs(b)
	action.InitGlobals()

	err = config.RunPluginFn("init")
	if err != nil {
		screen.TermMessage(err)
	}

	events = make(chan tcell.Event)

	// Here is the event loop which runs in a separate thread
	go func() {
		for {
			screen.Lock()
			e := screen.Screen.PollEvent()
			screen.Unlock()
			if e != nil {
				events <- e
			}
		}
	}()

	// clear the drawchan so we don't redraw excessively
	// if someone requested a redraw before we started displaying
	for len(screen.DrawChan()) > 0 {
		<-screen.DrawChan()
	}

	// wait for initial resize event
	select {
	case event := <-events:
		action.Tabs.HandleEvent(event)
	case <-time.After(10 * time.Millisecond):
		// time out after 10ms
	}

	// Since this loop is very slow (waits for user input every time) it's
	// okay to be inefficient and run it via a function every time
	// We do this so we can recover from panics without crashing the editor
	for {
		DoEvent()
	}
}

// DoEvent runs the main action loop of the editor
func DoEvent() {
	var event tcell.Event

	// recover from errors without crashing the editor
	defer func() {
		if err := recover(); err != nil {
			if e, ok := err.(*lua.ApiError); ok {
				screen.TermMessage("Lua API error:", e)
			} else {
				screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
			}
		}
	}()
	// Display everything
	screen.Screen.Fill(' ', config.DefStyle)
	screen.Screen.HideCursor()
	action.Tabs.Display()
	for _, ep := range action.MainTab().Panes {
		ep.Display()
	}
	action.MainTab().Display()
	action.InfoBar.Display()
	screen.Screen.Show()

	// Check for new events
	select {
	case f := <-shell.Jobs:
		// If a new job has finished while running in the background we should execute the callback
		f.Function(f.Output, f.Args)
	case <-config.Autosave:
		for _, b := range buffer.OpenBuffers {
			b.Save()
		}
	case <-shell.CloseTerms:
	case event = <-events:
	case <-screen.DrawChan():
	}

	if action.InfoBar.HasPrompt {
		action.InfoBar.HandleEvent(event)
	} else {
		action.Tabs.HandleEvent(event)
	}
}
<|endoftext|>


<commit_before>// Copyright © 2017 Michael Lihs
//
// 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 cmd

import (
	"github.com/spf13/cobra"
	"github.com/michaellihs/golab/cmd/mapper"
	"github.com/pkg/errors"
	"github.com/xanzy/go-gitlab"
)

var listMergeRequestFlagsMapper mapper.FlagMapper

var mergeRequestsCmd = &cobra.Command{
	Use:   "merge-requests",
	Short: "Manage Merge Requests",
	Long:  `Show, create, edit and delte Merge Requests`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.New("this command cannot be run without a sub-command")
	},
}

type listMergeRequestsFlags struct {
	State           *string `flag_name:"state" type:"string" required:"no" description:"Return all merge requests or just those that are opened, closed, or merged"`
	OrderBy         *string `flag_name:"order_by" type:"string" required:"no" description:"Return requests ordered by created_at or updated_at fields. Default is created_at"`
	Sort            *string `flag_name:"sort" type:"string" required:"no" description:"Return requests sorted in asc or desc order. Default is desc"`
	Milestone       *string `flag_name:"milestone" type:"string" required:"no" description:"Return merge requests for a specific milestone"`
	View            *string `flag_name:"view" type:"string" required:"no" description:"If simple, returns the iid, URL, title, description, and basic state of merge request"`
	Labels          *string `flag_name:"labels" type:"string" required:"no" description:"Return merge requests matching a comma separated list of labels"`
	CreatedAfter    *string `flag_name:"created_after" type:"datetime" required:"no" description:"Return merge requests created after the given time (inclusive)"`
	CreatedBefore   *string `flag_name:"created_before" type:"datetime" required:"no" description:"Return merge requests created before the given time (inclusive)"`
	Scope           *string `flag_name:"scope" type:"string" required:"no" description:"Return merge requests for the given scope: created-by-me, assigned-to-me or all. Defaults to created-by-me"`
	AuthorId        *int    `flag_name:"author_id" type:"integer" required:"no" description:"Returns merge requests created by the given user id. Combine with scope=all or scope=assigned-to-me"`
	AssigneeId      *int    `flag_name:"assignee_id" type:"integer" required:"no" description:"Returns merge requests assigned to the given user id"`
	MyReactionEmoji *string `flag_name:"my_reaction_emoji" type:"string" required:"no" description:"Return merge requests reacted by the authenticated user by the given emoji (Introduced in GitLab 10.0)"`
}

var mergeRequestListCmd = &cobra.Command{
	Use:   "ls",
	Short: "List merge requests",
	Long: `Get all merge requests the authenticated user has access to. By default it returns only merge requests created by the current user. To get all merge requests, use parameter scope=all.

The state parameter can be used to get only merge requests with a given state (opened, closed, or merged) or all of them (all). The pagination parameters page and per_page can be used to restrict the list of merge requests.

Note: the changes_count value in the response is a string, not an integer. This is because when an MR has too many changes to display and store, it will be capped at 1,000. In that case, the API will return the string "1000+" for the changes count.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		listMergeRequestFlagsMapper.AutoMap()
		opts := listMergeRequestFlagsMapper.MappedOpts().(*gitlab.ListMergeRequestsOptions)
		mergeRequests, _, err := gitlabClient.MergeRequests.ListMergeRequests(opts)
		if err != nil {
			return err
		}
		return OutputJson(mergeRequests)
	},
}

func init() {
	initListMergeRequestCmd()
	RootCmd.AddCommand(mergeRequestsCmd)
}

func initListMergeRequestCmd() {
	listMergeRequestFlagsMapper = mapper.InitializedMapper(mergeRequestListCmd, &listMergeRequestsFlags{}, &gitlab.ListMergeRequestsOptions{})
	mergeRequestsCmd.AddCommand(mergeRequestListCmd)
}
<commit_msg>fix import in merge request<commit_after>// Copyright © 2017 Michael Lihs
//
// 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 cmd

import (
	"errors"

	"github.com/spf13/cobra"
	"github.com/michaellihs/golab/cmd/mapper"
	"github.com/xanzy/go-gitlab"
)

var listMergeRequestFlagsMapper mapper.FlagMapper

var mergeRequestsCmd = &cobra.Command{
	Use:   "merge-requests",
	Short: "Manage Merge Requests",
	Long:  `Show, create, edit and delte Merge Requests`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.New("this command cannot be run without a sub-command")
	},
}

type listMergeRequestsFlags struct {
	State           *string `flag_name:"state" type:"string" required:"no" description:"Return all merge requests or just those that are opened, closed, or merged"`
	OrderBy         *string `flag_name:"order_by" type:"string" required:"no" description:"Return requests ordered by created_at or updated_at fields. Default is created_at"`
	Sort            *string `flag_name:"sort" type:"string" required:"no" description:"Return requests sorted in asc or desc order. Default is desc"`
	Milestone       *string `flag_name:"milestone" type:"string" required:"no" description:"Return merge requests for a specific milestone"`
	View            *string `flag_name:"view" type:"string" required:"no" description:"If simple, returns the iid, URL, title, description, and basic state of merge request"`
	Labels          *string `flag_name:"labels" type:"string" required:"no" description:"Return merge requests matching a comma separated list of labels"`
	CreatedAfter    *string `flag_name:"created_after" type:"datetime" required:"no" description:"Return merge requests created after the given time (inclusive)"`
	CreatedBefore   *string `flag_name:"created_before" type:"datetime" required:"no" description:"Return merge requests created before the given time (inclusive)"`
	Scope           *string `flag_name:"scope" type:"string" required:"no" description:"Return merge requests for the given scope: created-by-me, assigned-to-me or all. Defaults to created-by-me"`
	AuthorId        *int    `flag_name:"author_id" type:"integer" required:"no" description:"Returns merge requests created by the given user id. Combine with scope=all or scope=assigned-to-me"`
	AssigneeId      *int    `flag_name:"assignee_id" type:"integer" required:"no" description:"Returns merge requests assigned to the given user id"`
	MyReactionEmoji *string `flag_name:"my_reaction_emoji" type:"string" required:"no" description:"Return merge requests reacted by the authenticated user by the given emoji (Introduced in GitLab 10.0)"`
}

var mergeRequestListCmd = &cobra.Command{
	Use:   "ls",
	Short: "List merge requests",
	Long: `Get all merge requests the authenticated user has access to. By default it returns only merge requests created by the current user. To get all merge requests, use parameter scope=all.

The state parameter can be used to get only merge requests with a given state (opened, closed, or merged) or all of them (all). The pagination parameters page and per_page can be used to restrict the list of merge requests.

Note: the changes_count value in the response is a string, not an integer. This is because when an MR has too many changes to display and store, it will be capped at 1,000. In that case, the API will return the string "1000+" for the changes count.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		listMergeRequestFlagsMapper.AutoMap()
		opts := listMergeRequestFlagsMapper.MappedOpts().(*gitlab.ListMergeRequestsOptions)
		mergeRequests, _, err := gitlabClient.MergeRequests.ListMergeRequests(opts)
		if err != nil {
			return err
		}
		return OutputJson(mergeRequests)
	},
}

func init() {
	initListMergeRequestCmd()
	RootCmd.AddCommand(mergeRequestsCmd)
}

func initListMergeRequestCmd() {
	listMergeRequestFlagsMapper = mapper.InitializedMapper(mergeRequestListCmd, &listMergeRequestsFlags{}, &gitlab.ListMergeRequestsOptions{})
	mergeRequestsCmd.AddCommand(mergeRequestListCmd)
}
<|endoftext|>


<commit_before>package main

const legal = `
Execept where noted below, the source code for misspell is
copyright Nick Galbreath and distribution is allowed under a
MIT license.  See the following for details:

* https://github.com/client9/misspell/blob/master/LICENSE
* https://tldrlegal.com/license/mit-license

Misspell contains a modified version of Golang's strings.Replacer
which is covered under a BSD License

* https://golang.org/pkg/strings/#Replacer
* https://golang.org/src/strings/replace.go
* https://github.com/golang/go/blob/master/LICENSE

Copyright (c) 2009 The Go Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
`
<commit_msg>more legal<commit_after>package main

const legal = `
Execept where noted below, the source code for misspell is
copyright Nick Galbreath and distribution is allowed under a
MIT license.  See the following for details:

* https://github.com/client9/misspell/blob/master/LICENSE
* https://tldrlegal.com/license/mit-license

Misspell makes uses of the Golang standard library and
contains a modified version of Golang's strings.Replacer
which are covered under a BSD License.

* https://golang.org/pkg/strings/#Replacer
* https://golang.org/src/strings/replace.go
* https://github.com/golang/go/blob/master/LICENSE

Copyright (c) 2009 The Go Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
`
<|endoftext|>


<commit_before>package main

import (
	"flag"

	log "github.com/Sirupsen/logrus"
	"github.com/dailymotion/oplog"
)

var (
	listenAddr           = flag.String("listen", ":8042", "The address to listen on. Same address is used for both SSE(HTTP) and UDP APIs.")
	mongoURL             = flag.String("mongo-url", "", "MongoDB URL to connect to.")
	cappedCollectionSize = flag.Int("capped-collection-size", 104857600, "Size of the created MongoDB capped collection size in bytes (default 100MB).")
	maxQueuedEvents      = flag.Int("max-queued-events", 100000, "Number of events to queue before starting throwing UDP messages.")
	password             = flag.String("password", "", "Password protecting the global SSE stream.")
)

func main() {
	flag.Parse()
	log.SetLevel(log.DebugLevel)

	ol, err := oplog.NewOpLog(*mongoURL, *cappedCollectionSize)
	if err != nil {
		log.Fatal(err)
	}

	udpd := oplog.NewUDPDaemon(*listenAddr, ol)
	go func() {
		log.Fatal(udpd.Run(*maxQueuedEvents))
	}()

	ssed := oplog.NewSSEDaemon(*listenAddr, ol)
	ssed.Password = *password
	log.Fatal(ssed.Run())
}
<commit_msg>Add --debug option<commit_after>package main

import (
	"flag"

	log "github.com/Sirupsen/logrus"
	"github.com/dailymotion/oplog"
)

var (
	debug                = flag.Bool("debug", false, "Show debug log messages.")
	listenAddr           = flag.String("listen", ":8042", "The address to listen on. Same address is used for both SSE(HTTP) and UDP APIs.")
	mongoURL             = flag.String("mongo-url", "", "MongoDB URL to connect to.")
	cappedCollectionSize = flag.Int("capped-collection-size", 104857600, "Size of the created MongoDB capped collection size in bytes (default 100MB).")
	maxQueuedEvents      = flag.Int("max-queued-events", 100000, "Number of events to queue before starting throwing UDP messages.")
	password             = flag.String("password", "", "Password protecting the global SSE stream.")
)

func main() {
	flag.Parse()

	if *debug {
		log.SetLevel(log.DebugLevel)
	}

	ol, err := oplog.NewOpLog(*mongoURL, *cappedCollectionSize)
	if err != nil {
		log.Fatal(err)
	}

	log.Infof("Listening on %s (UDP/TCP)", *listenAddr)

	udpd := oplog.NewUDPDaemon(*listenAddr, ol)
	go func() {
		log.Fatal(udpd.Run(*maxQueuedEvents))
	}()

	ssed := oplog.NewSSEDaemon(*listenAddr, ol)
	ssed.Password = *password
	log.Fatal(ssed.Run())
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/vially/seomoz"
	"log"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: seomoz URL [COLS]")
		os.Exit(1)
	}

	queryURL := os.Args[1]
	cols := 103079217156
	if len(os.Args) > 2 {
		if columns, err := strconv.Atoi(os.Args[2]); err != nil {
			log.Fatalln("Invalid COLS value: " + os.Args[2])
		} else {
			cols = columns
		}
	}

	seomoz := seomoz.NewEnvClient()
	m, err := seomoz.GetURLMetrics(queryURL, cols)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("%s\t%.0f\t%.0f\t%.0f\n", m.URL, m.Links, m.PageAuthority, m.DomainAuthority)
}
<commit_msg>Improve seomoz command output<commit_after>package main

import (
	"fmt"
	"github.com/vially/seomoz"
	"log"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: seomoz URL [COLS]")
		os.Exit(1)
	}

	queryURL := os.Args[1]
	cols := 103079217156
	if len(os.Args) > 2 {
		if columns, err := strconv.Atoi(os.Args[2]); err != nil {
			log.Fatalln("Invalid COLS value: " + os.Args[2])
		} else {
			cols = columns
		}
	}

	seomoz := seomoz.NewEnvClient()
	metrics, err := seomoz.GetURLMetrics(queryURL, cols)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("URL: %s\nLinks: %.0f\nPage Authority: %.0f\nDomain Authority: %.0f\n", metrics.URL, metrics.Links, metrics.PageAuthority, metrics.DomainAuthority)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"strconv"
	"strings"

	"nelhage.com/tak/ai"
	"nelhage.com/tak/playtak"
	"nelhage.com/tak/ptn"
	"nelhage.com/tak/tak"
)

var (
	server = flag.String("server", "playtak.com:10000", "playtak.com server to connect to")
	depth  = flag.Int("depth", 5, "minimax depth")
	user   = flag.String("user", "", "username for login")
	pass   = flag.String("pass", "", "password for login")
	accept = flag.String("accept", "", "accept a game from specified user")
	once   = flag.Bool("once", false, "play a single game and exit")
)

const Client = "Takker AI"

func main() {
	flag.Parse()
	client := &client{
		debug: true,
	}
	err := client.Connect(*server)
	if err != nil {
		log.Fatal(err)
	}
	client.SendClient(Client)
	if *user != "" {
		err = client.Login(*user, *pass)
	} else {
		err = client.LoginGuest()
	}
	if err != nil {
		log.Fatal("login: ", err)
	}
	for {
		if *accept != "" {
			for line := range client.recv {
				if strings.HasPrefix(line, "Seek new") {
					bits := strings.Split(line, " ")
					if bits[3] == *accept {
						log.Printf("accepting game %s from %s", bits[2], bits[3])
						client.sendCommand("Accept", bits[2])
						break
					}
				}
			}
		} else {
			client.sendCommand("Seek", "5", "1200")
		}
		for line := range client.recv {
			if strings.HasPrefix(line, "Game Start") {
				playGame(client, line)
				break
			}
		}
		if *once || *accept != "" {
			return
		}
	}
}

func playGame(c *client, line string) {
	log.Println("New Game", line)
	ai := ai.NewMinimax(*depth)
	ai.Debug = true
	bits := strings.Split(line, " ")
	size, _ := strconv.Atoi(bits[3])
	p := tak.New(tak.Config{Size: size})
	gameStr := fmt.Sprintf("Game#%s", bits[2])
	var color tak.Color
	switch bits[7] {
	case "white":
		color = tak.White
	case "black":
		color = tak.Black
	default:
		panic(fmt.Sprintf("bad color: %s", bits[7]))
	}
	for {
		over, _ := p.GameOver()
		if color == p.ToMove() && !over {
			move := ai.GetMove(p)
			next, err := p.Move(&move)
			if err != nil {
				log.Printf("ai returned bad move: %s: %s",
					ptn.FormatMove(&move), err)
				continue
			}
			p = next
			c.sendCommand(gameStr, playtak.FormatServer(&move))
		} else {
		theirMove:
			for line := range c.recv {
				if !strings.HasPrefix(line, gameStr) {
					continue
				}
				bits = strings.Split(line, " ")
				switch bits[1] {
				case "P", "M":
					move, err := playtak.ParseServer(strings.Join(bits[1:], " "))
					if err != nil {
						panic(err)
					}
					p, err = p.Move(&move)
					if err != nil {
						panic(err)
					}
					break theirMove
				case "Abandoned.", "Over":
					return
				}
			}
		}
	}
}
<commit_msg>Add a mode to challenge takbot<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"strconv"
	"strings"

	"nelhage.com/tak/ai"
	"nelhage.com/tak/playtak"
	"nelhage.com/tak/ptn"
	"nelhage.com/tak/tak"
)

var (
	server = flag.String("server", "playtak.com:10000", "playtak.com server to connect to")
	depth  = flag.Int("depth", 5, "minimax depth")
	user   = flag.String("user", "", "username for login")
	pass   = flag.String("pass", "", "password for login")
	accept = flag.String("accept", "", "accept a game from specified user")
	once   = flag.Bool("once", false, "play a single game and exit")
	takbot = flag.Bool("takbot", true, "challenge TakBot")
)

const Client = "Takker AI"

func main() {
	flag.Parse()
	client := &client{
		debug: true,
	}
	err := client.Connect(*server)
	if err != nil {
		log.Fatal(err)
	}
	client.SendClient(Client)
	if *user != "" {
		err = client.Login(*user, *pass)
	} else {
		err = client.LoginGuest()
	}
	if err != nil {
		log.Fatal("login: ", err)
	}
	if *accept != "" || *takbot {
		*once = true
	}
	for {
		if *accept != "" {
			for line := range client.recv {
				if strings.HasPrefix(line, "Seek new") {
					bits := strings.Split(line, " ")
					if bits[3] == *accept {
						log.Printf("accepting game %s from %s", bits[2], bits[3])
						client.sendCommand("Accept", bits[2])
						break
					}
				}
			}
		} else {
			client.sendCommand("Seek", "5", "1200")
			if *takbot {
				client.sendCommand("Shout", "takbot: play")
			}
		}
		for line := range client.recv {
			if strings.HasPrefix(line, "Game Start") {
				playGame(client, line)
				break
			}
		}
		if *once {
			return
		}
	}
}

func playGame(c *client, line string) {
	log.Println("New Game", line)
	ai := ai.NewMinimax(*depth)
	ai.Debug = true
	bits := strings.Split(line, " ")
	size, _ := strconv.Atoi(bits[3])
	p := tak.New(tak.Config{Size: size})
	gameStr := fmt.Sprintf("Game#%s", bits[2])
	var color tak.Color
	switch bits[7] {
	case "white":
		color = tak.White
	case "black":
		color = tak.Black
	default:
		panic(fmt.Sprintf("bad color: %s", bits[7]))
	}
	for {
		over, _ := p.GameOver()
		if color == p.ToMove() && !over {
			move := ai.GetMove(p)
			next, err := p.Move(&move)
			if err != nil {
				log.Printf("ai returned bad move: %s: %s",
					ptn.FormatMove(&move), err)
				continue
			}
			p = next
			c.sendCommand(gameStr, playtak.FormatServer(&move))
		} else {
		theirMove:
			for line := range c.recv {
				if !strings.HasPrefix(line, gameStr) {
					continue
				}
				bits = strings.Split(line, " ")
				switch bits[1] {
				case "P", "M":
					move, err := playtak.ParseServer(strings.Join(bits[1:], " "))
					if err != nil {
						panic(err)
					}
					p, err = p.Move(&move)
					if err != nil {
						panic(err)
					}
					break theirMove
				case "Abandoned.", "Over":
					return
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"time"
)

// Configure the global logger.
func configureLogging(c *cli.Context) {
	log.SetLevel(log.InfoLevel)
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func action(cc *cli.Context) error {
	conf := Config{}

	switch len(cc.Args()) {
	case 0:
		if !cc.IsSet("script") && !cc.IsSet("url") {
			log.Fatal("No config file, script or URL provided; see --help for usage")
		}
	case 1:
		bytes, err := ioutil.ReadFile(cc.Args()[0])
		if err != nil {
			log.WithError(err).Fatal("Couldn't read config file")
		}
		if err := yaml.Unmarshal(bytes, &conf); err != nil {
			log.WithError(err).Fatal("Couldn't parse config file")
		}
	default:
		log.Fatal("Too many arguments!")
	}

	_, err := conf.MakeTest()
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	return nil
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
		cli.StringFlag{
			Name:  "out-file, o",
			Usage: "Output raw metrics to a file",
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<commit_msg>[feat] Dump test configuration to stderr<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"time"
)

// Configure the global logger.
func configureLogging(c *cli.Context) {
	log.SetLevel(log.InfoLevel)
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func action(cc *cli.Context) error {
	conf := Config{}

	switch len(cc.Args()) {
	case 0:
		if !cc.IsSet("script") && !cc.IsSet("url") {
			log.Fatal("No config file, script or URL provided; see --help for usage")
		}
	case 1:
		bytes, err := ioutil.ReadFile(cc.Args()[0])
		if err != nil {
			log.WithError(err).Fatal("Couldn't read config file")
		}
		if err := yaml.Unmarshal(bytes, &conf); err != nil {
			log.WithError(err).Fatal("Couldn't parse config file")
		}
	default:
		log.Fatal("Too many arguments!")
	}

	t, err := conf.MakeTest()
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	if cc.Bool("dump") {
		log.WithFields(log.Fields{
			"script": t.Script,
			"url":    t.URL,
		}).Info("General")
		for i, stage := range t.Stages {
			log.WithFields(log.Fields{
				"#":        i,
				"duration": stage.Duration,
				"start":    stage.StartVUs,
				"end":      stage.EndVUs,
			}).Info("Stage")
		}
	}

	return nil
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
		cli.StringFlag{
			Name:  "out-file, o",
			Usage: "Output raw metrics to a file",
		},
		cli.BoolFlag{
			Name:  "dump",
			Usage: "Dump parsed test and exit",
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"runtime"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

// command specific flags.
var (
	updateFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "experimental, E",
			Usage: "Check experimental update.",
		},
	}
)

// Check for new software updates.
var updateCmd = cli.Command{
	Name:   "update",
	Usage:  "Check for a new software update.",
	Action: mainUpdate,
	Flags:  append(updateFlags, globalFlags...),
	CustomHelpTemplate: `Name:
   minio {{.Name}} - {{.Usage}}

USAGE:
   minio {{.Name}} [FLAGS]

FLAGS:
  {{range .Flags}}{{.}}
  {{end}}
EXAMPLES:
   1. Check for any new official release.
      $ minio {{.Name}}

   2. Check for any new experimental release.
      $ minio {{.Name}} --experimental
`,
}

// update URL endpoints.
const (
	minioUpdateStableURL       = "https://dl.minio.io/server/minio/release"
	minioUpdateExperimentalURL = "https://dl.minio.io/server/minio/experimental"
)

// updateMessage container to hold update messages.
type updateMessage struct {
	Status   string `json:"status"`
	Update   bool   `json:"update"`
	Download string `json:"downloadURL"`
	Version  string `json:"version"`
}

// String colorized update message.
func (u updateMessage) String() string {
	if !u.Update {
		updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
		return updateMessage("You are already running the most recent version of ‘minio’.")
	}
	msg := colorizeUpdateMessage(u.Download)
	return msg
}

// JSON jsonified update message.
func (u updateMessage) JSON() string {
	u.Status = "success"
	updateMessageJSONBytes, err := json.Marshal(u)
	fatalIf((err), "Unable to marshal into JSON.")

	return string(updateMessageJSONBytes)
}

func parseReleaseData(data string) (time.Time, error) {
	releaseStr := strings.Fields(data)
	if len(releaseStr) < 2 {
		return time.Time{}, errors.New("Update data malformed")
	}
	releaseDate := releaseStr[1]
	releaseDateSplits := strings.SplitN(releaseDate, ".", 3)
	if len(releaseDateSplits) < 3 {
		return time.Time{}, (errors.New("Update data malformed"))
	}
	if releaseDateSplits[0] != "minio" {
		return time.Time{}, (errors.New("Update data malformed, missing minio tag"))
	}
	// "OFFICIAL" tag is still kept for backward compatibility, we should remove this for the next release.
	if releaseDateSplits[1] != "RELEASE" && releaseDateSplits[1] != "OFFICIAL" {
		return time.Time{}, (errors.New("Update data malformed, missing RELEASE tag"))
	}
	dateSplits := strings.SplitN(releaseDateSplits[2], "T", 2)
	if len(dateSplits) < 2 {
		return time.Time{}, (errors.New("Update data malformed, not in modified RFC3359 form"))
	}
	dateSplits[1] = strings.Replace(dateSplits[1], "-", ":", -1)
	date := strings.Join(dateSplits, "T")

	parsedDate, err := time.Parse(time.RFC3339, date)
	if err != nil {
		return time.Time{}, err
	}
	return parsedDate, nil
}

// verify updates for releases.
func getReleaseUpdate(updateURL string) (updateMsg updateMessage, errMsg string, err error) {
	// Construct a new update url.
	newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH
	newUpdateURL := newUpdateURLPrefix + "/minio.shasum"

	// Get the downloadURL.
	var downloadURL string
	switch runtime.GOOS {
	case "windows":
		// For windows.
		downloadURL = newUpdateURLPrefix + "/minio.exe?update=yes"
	default:
		// For all other operating systems.
		downloadURL = newUpdateURLPrefix + "/minio?update=yes"
	}

	// Initialize update message.
	updateMsg = updateMessage{
		Download: downloadURL,
		Version:  Version,
	}

	// Instantiate a new client with 3 sec timeout.
	client := &http.Client{
		Timeout: 3 * time.Second,
	}

	// Parse current minio version into RFC3339.
	current, err := time.Parse(time.RFC3339, Version)
	if err != nil {
		errMsg = "Unable to parse version string as time."
		return
	}

	// Verify if current minio version is zero.
	if current.IsZero() {
		err = errors.New("date should not be zero")
		errMsg = "Updates mechanism is not supported for custom builds. Please download official releases from https://minio.io/#minio"
		return
	}

	// Fetch new update.
	data, err := client.Get(newUpdateURL)
	if err != nil {
		return
	}

	// Verify if we have a valid http response i.e http.StatusOK.
	if data != nil {
		if data.StatusCode != http.StatusOK {
			errMsg = "Failed to retrieve update notice."
			err = errors.New("http status : " + data.Status)
			return
		}
	}

	// Read the response body.
	updateBody, err := ioutil.ReadAll(data.Body)
	if err != nil {
		errMsg = "Failed to retrieve update notice. Please try again later."
		return
	}

	errMsg = "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues"

	// Parse the date if its valid.
	latest, err := parseReleaseData(string(updateBody))
	if err != nil {
		return
	}

	// Verify if the date is not zero.
	if latest.IsZero() {
		err = errors.New("date should not be zero")
		return
	}

	// Is the update latest?.
	if latest.After(current) {
		updateMsg.Update = true
	}

	// Return update message.
	return updateMsg, "", nil
}

// main entry point for update command.
func mainUpdate(ctx *cli.Context) {
	// Error out if 'update' command is issued for development based builds.
	if Version == "DEVELOPMENT.GOGET" {
		fatalIf(errors.New(""), "Update mechanism is not supported for ‘go get’ based binary builds. Please download official releases from https://minio.io/#minio")
	}

	// Check for update.
	var updateMsg updateMessage
	var errMsg string
	var err error
	if ctx.Bool("experimental") {
		updateMsg, errMsg, err = getReleaseUpdate(minioUpdateExperimentalURL)
	} else {
		updateMsg, errMsg, err = getReleaseUpdate(minioUpdateStableURL)
	}
	fatalIf(err, errMsg)
	console.Println(updateMsg)
}
<commit_msg>update: Deprecate the usage of update=yes query param. (#2801)<commit_after>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"runtime"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

// command specific flags.
var (
	updateFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "experimental, E",
			Usage: "Check experimental update.",
		},
	}
)

// Check for new software updates.
var updateCmd = cli.Command{
	Name:   "update",
	Usage:  "Check for a new software update.",
	Action: mainUpdate,
	Flags:  append(updateFlags, globalFlags...),
	CustomHelpTemplate: `Name:
   minio {{.Name}} - {{.Usage}}

USAGE:
   minio {{.Name}} [FLAGS]

FLAGS:
  {{range .Flags}}{{.}}
  {{end}}
EXAMPLES:
   1. Check for any new official release.
      $ minio {{.Name}}

   2. Check for any new experimental release.
      $ minio {{.Name}} --experimental
`,
}

// update URL endpoints.
const (
	minioUpdateStableURL       = "https://dl.minio.io/server/minio/release"
	minioUpdateExperimentalURL = "https://dl.minio.io/server/minio/experimental"
)

// updateMessage container to hold update messages.
type updateMessage struct {
	Status   string `json:"status"`
	Update   bool   `json:"update"`
	Download string `json:"downloadURL"`
	Version  string `json:"version"`
}

// String colorized update message.
func (u updateMessage) String() string {
	if !u.Update {
		updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
		return updateMessage("You are already running the most recent version of ‘minio’.")
	}
	msg := colorizeUpdateMessage(u.Download)
	return msg
}

// JSON jsonified update message.
func (u updateMessage) JSON() string {
	u.Status = "success"
	updateMessageJSONBytes, err := json.Marshal(u)
	fatalIf((err), "Unable to marshal into JSON.")

	return string(updateMessageJSONBytes)
}

func parseReleaseData(data string) (time.Time, error) {
	releaseStr := strings.Fields(data)
	if len(releaseStr) < 2 {
		return time.Time{}, errors.New("Update data malformed")
	}
	releaseDate := releaseStr[1]
	releaseDateSplits := strings.SplitN(releaseDate, ".", 3)
	if len(releaseDateSplits) < 3 {
		return time.Time{}, (errors.New("Update data malformed"))
	}
	if releaseDateSplits[0] != "minio" {
		return time.Time{}, (errors.New("Update data malformed, missing minio tag"))
	}
	// "OFFICIAL" tag is still kept for backward compatibility, we should remove this for the next release.
	if releaseDateSplits[1] != "RELEASE" && releaseDateSplits[1] != "OFFICIAL" {
		return time.Time{}, (errors.New("Update data malformed, missing RELEASE tag"))
	}
	dateSplits := strings.SplitN(releaseDateSplits[2], "T", 2)
	if len(dateSplits) < 2 {
		return time.Time{}, (errors.New("Update data malformed, not in modified RFC3359 form"))
	}
	dateSplits[1] = strings.Replace(dateSplits[1], "-", ":", -1)
	date := strings.Join(dateSplits, "T")

	parsedDate, err := time.Parse(time.RFC3339, date)
	if err != nil {
		return time.Time{}, err
	}
	return parsedDate, nil
}

// verify updates for releases.
func getReleaseUpdate(updateURL string) (updateMsg updateMessage, errMsg string, err error) {
	// Construct a new update url.
	newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH
	newUpdateURL := newUpdateURLPrefix + "/minio.shasum"

	// Get the downloadURL.
	var downloadURL string
	switch runtime.GOOS {
	case "windows":
		// For windows.
		downloadURL = newUpdateURLPrefix + "/minio.exe"
	default:
		// For all other operating systems.
		downloadURL = newUpdateURLPrefix + "/minio"
	}

	// Initialize update message.
	updateMsg = updateMessage{
		Download: downloadURL,
		Version:  Version,
	}

	// Instantiate a new client with 3 sec timeout.
	client := &http.Client{
		Timeout: 3 * time.Second,
	}

	// Parse current minio version into RFC3339.
	current, err := time.Parse(time.RFC3339, Version)
	if err != nil {
		errMsg = "Unable to parse version string as time."
		return
	}

	// Verify if current minio version is zero.
	if current.IsZero() {
		err = errors.New("date should not be zero")
		errMsg = "Updates mechanism is not supported for custom builds. Please download official releases from https://minio.io/#minio"
		return
	}

	// Fetch new update.
	data, err := client.Get(newUpdateURL)
	if err != nil {
		return
	}

	// Verify if we have a valid http response i.e http.StatusOK.
	if data != nil {
		if data.StatusCode != http.StatusOK {
			errMsg = "Failed to retrieve update notice."
			err = errors.New("http status : " + data.Status)
			return
		}
	}

	// Read the response body.
	updateBody, err := ioutil.ReadAll(data.Body)
	if err != nil {
		errMsg = "Failed to retrieve update notice. Please try again later."
		return
	}

	errMsg = "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues"

	// Parse the date if its valid.
	latest, err := parseReleaseData(string(updateBody))
	if err != nil {
		return
	}

	// Verify if the date is not zero.
	if latest.IsZero() {
		err = errors.New("date should not be zero")
		return
	}

	// Is the update latest?.
	if latest.After(current) {
		updateMsg.Update = true
	}

	// Return update message.
	return updateMsg, "", nil
}

// main entry point for update command.
func mainUpdate(ctx *cli.Context) {
	// Error out if 'update' command is issued for development based builds.
	if Version == "DEVELOPMENT.GOGET" {
		fatalIf(errors.New(""), "Update mechanism is not supported for ‘go get’ based binary builds. Please download official releases from https://minio.io/#minio")
	}

	// Check for update.
	var updateMsg updateMessage
	var errMsg string
	var err error
	if ctx.Bool("experimental") {
		updateMsg, errMsg, err = getReleaseUpdate(minioUpdateExperimentalURL)
	} else {
		updateMsg, errMsg, err = getReleaseUpdate(minioUpdateStableURL)
	}
	fatalIf(err, errMsg)
	console.Println(updateMsg)
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin 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 (
	"flag"
	"fmt"
	"os"
	"text/tabwriter"
	"text/template"
	"time"

	"upspin.io/access"
	"upspin.io/pack"
	"upspin.io/path"
	"upspin.io/upspin"
)

func (s *State) info(args ...string) {
	const help = `
Info prints to standard output a thorough description of all the
information about named paths, including information provided by
ls but also storage references, sizes, and other metadata.

If the path names an Access or Group file, it is also checked for
validity. If it is a link, the command attempts to access the target
of the link.
`
	fs := flag.NewFlagSet("info", flag.ExitOnError)
	s.ParseFlags(fs, args, help, "info path...")

	if fs.NArg() == 0 {
		fs.Usage()
	}
	for _, name := range fs.Args() {
		entries, err := s.DirServer(upspin.PathName(name)).Glob(name)
		// ErrFollowLink is OK; we still get the relevant entry.
		if err != nil && err != upspin.ErrFollowLink {
			s.Exit(err)
		}
		for _, entry := range entries {
			s.printInfo(entry)
			switch {
			case access.IsAccessFile(entry.Name):
				s.checkAccessFile(entry.Name)
			case access.IsGroupFile(entry.Name):
				s.checkGroupFile(entry.Name)
			}
		}
	}
}

// infoDirEntry wraps a DirEntry to allow new methods for easy formatting.
// It also has fields that hold relevant information as we acquire it.
type infoDirEntry struct {
	*upspin.DirEntry
	state *State
	// The following fields are computed as we run.
	access    *access.Access
	lastUsers string
}

func (d *infoDirEntry) TimeString() string {
	return d.Time.Go().In(time.Local).Format("Mon Jan 2 15:04:05 MST 2006")
}

func (d *infoDirEntry) AttrString() string {
	return attrFormat(d.Attr)
}

func (d *infoDirEntry) Rights() []access.Right {
	return []access.Right{access.Read, access.Write, access.List, access.Create, access.Delete}
}

func (d *infoDirEntry) Readers() string {
	d.state.sharer.addAccess(d.DirEntry)
	d.lastUsers = "<nobody>"
	if d.IsDir() {
		return "is a directory"
	}
	_, users, _, err := d.state.sharer.readers(d.DirEntry)
	if err != nil {
		return err.Error()
	}
	d.lastUsers = users
	return users
}

func (d *infoDirEntry) Sequence() int64 {
	return upspin.SeqVersion(d.DirEntry.Sequence)
}

func (d *infoDirEntry) Hashes() string {
	h := ""
	if d.IsDir() || d.Packing != upspin.EEPack {
		return h
	}
	packer := pack.Lookup(d.Packing)
	hashes, err := packer.ReaderHashes(d.Packdata)
	if err != nil {
		return h
	}
	for _, r := range hashes {
		if h == "" {
			h += " "
		}
		h += fmt.Sprintf("%x...", r[:4])
	}
	return h
}

func (d *infoDirEntry) Users(right access.Right) string {
	users := userListToString(d.state.usersWithAccess(d.state.Client, d.access, right))
	if users == d.lastUsers {
		return "(same)"
	}
	d.lastUsers = users
	return users
}

func (d *infoDirEntry) WhichAccess() string {
	var acc *access.Access
	accEntry, err := d.state.whichAccessFollowLinks(d.Name)
	if err != nil {
		return err.Error()
	}
	accFile := "owner only"
	if accEntry == nil {
		// No access file applies.
		acc, err = access.New(d.Name)
		if err != nil {
			// Can't happen, since the name must be valid.
			d.state.Exitf("%q: %s", d.Name, err)
		}
	} else {
		accFile = string(accEntry.Name)
		data, err := read(d.state.Client, accEntry.Name)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot open access file %q: %s\n", accFile, err)
		}
		acc, err = access.Parse(accEntry.Name, data)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot parse access file %q: %s\n", accFile, err)
		}
	}
	d.access = acc
	return accFile
}

// printInfo prints, in human-readable form, most of the information about
// the entry, including the users that have permission to access it.
// TODO: Present this more neatly.
// TODO: Present group information.
func (s *State) printInfo(entry *upspin.DirEntry) {
	infoDir := &infoDirEntry{
		state:    s,
		DirEntry: entry,
	}
	writer := tabwriter.NewWriter(os.Stdout, 4, 4, 1, ' ', 0)
	err := infoTmpl.Execute(writer, infoDir)
	if err != nil {
		s.Exitf("executing info template: %v", err)
	}
	err = writer.Flush()
	if err != nil {
		s.Exitf("flushing template output: %v", err)
	}
	if !entry.IsLink() {
		return
	}
	// Check and print information about the link target.
	target, err := s.Client.Lookup(entry.Link, true)
	if err != nil {
		// Print the whole error indented, starting on the next line. This helps it stand out.
		s.Exitf("Error: link %s has invalid target %s:\n\t%v", entry.Name, entry.Link, err)
	}
	fmt.Printf("Target of link %s:\n", entry.Name)
	s.printInfo(target)
}

func attrFormat(attr upspin.Attribute) string {
	a := attr
	tail := ""
	if a&upspin.AttrIncomplete > 0 {
		tail = " (incomplete)"
		a ^= upspin.AttrIncomplete
	}
	switch a {
	case upspin.AttrNone:
		return "none (plain file)" + tail
	case upspin.AttrDirectory:
		return "directory" + tail
	case upspin.AttrLink:
		return "link" + tail
	}
	return fmt.Sprintf("attribute(%#x)", attr)
}

var infoTmpl = template.Must(template.New("info").Parse(infoText))

const infoText = `{{.Name}}
	packing:	{{.Packing}}
	size:	{{.Size}}
	time:	{{.TimeString}}
	writer:	{{.Writer}}
	attributes:	{{.AttrString}}
	sequence:	{{.Sequence}}
	access file:	{{.WhichAccess}}
	key holders: 	{{.Readers}}
	key hashes:     {{.Hashes}}
	{{range $right := .Rights -}}
	can {{$right}}:	{{$.Users $right}}
	{{end -}}
	Block#	Offset	Size	Location
	{{range $index, $block := .Blocks -}}
	{{$index}}	{{.Offset}}	{{.Size}}	{{.Location}}
	{{end}}`

// checkGroupFile diagnoses likely problems with the contents and rights
// of the Group file.
// TODO: We could check that packing is Plain but that should never be a problem.
func (s *State) checkGroupFile(name upspin.PathName) {
	parsed, err := path.Parse(name)
	if err != nil {
		s.Exit(err) // Should never happen.
	}
	groupSeen := make(map[upspin.PathName]bool)
	userSeen := make(map[upspin.UserName]bool)
	s.doCheckGroupFile(parsed, groupSeen, userSeen)
}

// doCheckGroupFile is the inner, recursive implementation of checkGroupFile.
func (s *State) doCheckGroupFile(parsed path.Parsed, groupSeen map[upspin.PathName]bool, userSeen map[upspin.UserName]bool) {
	group := parsed.Path()
	if groupSeen[group] {
		return
	}
	groupSeen[group] = true
	data, err := s.Client.Get(group)
	if err != nil {
		s.Exitf("cannot read Group file: %v", err)
	}

	// Get the Access file, if any, that applies.
	// TODO: We've already got it in earlier code, so could save it.
	whichAccess, err := s.DirServer(group).WhichAccess(group)
	if err != nil {
		s.Exitf("unexpected error finding Access file for Group file %s: %v", group, err)
	}
	var accessFile *access.Access
	if whichAccess == nil {
		accessFile, err = access.New(group)
		if err != nil {
			s.Exitf("cannot create default Access file: %v", err)
		}
	} else {
		data, err := s.Client.Get(whichAccess.Name)
		if err != nil {
			s.Exitf("cannot get Access file: %v", err)
		}
		accessFile, err = access.Parse(whichAccess.Name, data)
		if err != nil {
			s.Exitf("cannot parse Access file: %v", err)
		}
	}

	// Each member should be either a plain user or a group and be able to access the Group file.
	members, err := access.ParseGroup(parsed, data)
	if err != nil {
		s.Exitf("error parsing Group file %s: %v", group, err)
	}
	for _, member := range members {
		if member.IsRoot() {
			// Normal user.
			user := member.User()
			if !s.userExists(user, userSeen) {
				s.Failf("user %s in Group file %s not found in key server", user, group)
				continue
			}
			// Member must be able to read the Group file.
			canRead, err := accessFile.Can(user, access.Read, group, s.Client.Get)
			if err != nil {
				s.Exitf("error checking permissions in Group file %s for user %s: %v", group, user, err)
				continue
			}
			if !canRead {
				s.Failf("user %s is missing read access for group %s", user, group)
			}
			continue
		}
		if !access.IsGroupFile(member.Path()) {
			s.Failf("do not understand member %s of Group file %s", member, parsed) // Should never happen.
			continue
		}
		// Member is a group. Recur using Group file.
		s.doCheckGroupFile(member, groupSeen, userSeen)
	}
}

func (s *State) checkAccessFile(name upspin.PathName) {
	data, err := s.Client.Get(name)
	if err != nil {
		s.Exitf("cannot get Access file: %v", err)
	}
	accessFile, err := access.Parse(name, data)
	if err != nil {
		s.Exitf("cannot parse Access file: %v", err)
	}
	users := accessFile.List(access.AnyRight)

	groupSeen := make(map[upspin.PathName]bool)
	userSeen := make(map[upspin.UserName]bool)
	for _, user := range users {
		if user.IsRoot() {
			// Normal user.
			if !s.userExists(user.User(), userSeen) {
				s.Failf("user %s in Access file %s not found in key server", user.User(), name)
			}
			continue
		}
		// Member is a group.
		s.doCheckGroupFile(user, groupSeen, userSeen)
	}
}

func (s *State) userExists(user upspin.UserName, userSeen map[upspin.UserName]bool) bool {
	if userSeen[user] || user == access.AllUsers { // all@upspin.io is baked in.
		return true // Previous answer will do.
	}
	// Ignore wildcards.
	if isWildcardUser(user) {
		return true
	}
	userSeen[user] = true
	_, err := s.KeyServer().Lookup(user)
	return err == nil
}
<commit_msg>cmd/upspin: substitute "All" for "all@upspin.io" in rights lists<commit_after>// Copyright 2016 The Upspin 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 (
	"flag"
	"fmt"
	"os"
	"text/tabwriter"
	"text/template"
	"time"

	"upspin.io/access"
	"upspin.io/pack"
	"upspin.io/path"
	"upspin.io/upspin"
)

func (s *State) info(args ...string) {
	const help = `
Info prints to standard output a thorough description of all the
information about named paths, including information provided by
ls but also storage references, sizes, and other metadata.

If the path names an Access or Group file, it is also checked for
validity. If it is a link, the command attempts to access the target
of the link.
`
	fs := flag.NewFlagSet("info", flag.ExitOnError)
	s.ParseFlags(fs, args, help, "info path...")

	if fs.NArg() == 0 {
		fs.Usage()
	}
	for _, name := range fs.Args() {
		entries, err := s.DirServer(upspin.PathName(name)).Glob(name)
		// ErrFollowLink is OK; we still get the relevant entry.
		if err != nil && err != upspin.ErrFollowLink {
			s.Exit(err)
		}
		for _, entry := range entries {
			s.printInfo(entry)
			switch {
			case access.IsAccessFile(entry.Name):
				s.checkAccessFile(entry.Name)
			case access.IsGroupFile(entry.Name):
				s.checkGroupFile(entry.Name)
			}
		}
	}
}

// infoDirEntry wraps a DirEntry to allow new methods for easy formatting.
// It also has fields that hold relevant information as we acquire it.
type infoDirEntry struct {
	*upspin.DirEntry
	state *State
	// The following fields are computed as we run.
	access    *access.Access
	lastUsers string
}

func (d *infoDirEntry) TimeString() string {
	return d.Time.Go().In(time.Local).Format("Mon Jan 2 15:04:05 MST 2006")
}

func (d *infoDirEntry) AttrString() string {
	return attrFormat(d.Attr)
}

func (d *infoDirEntry) Rights() []access.Right {
	return []access.Right{access.Read, access.Write, access.List, access.Create, access.Delete}
}

func (d *infoDirEntry) Readers() string {
	d.state.sharer.addAccess(d.DirEntry)
	d.lastUsers = "<nobody>"
	if d.IsDir() {
		return "is a directory"
	}
	_, users, _, err := d.state.sharer.readers(d.DirEntry)
	if err != nil {
		return err.Error()
	}
	d.lastUsers = users
	return users
}

func (d *infoDirEntry) Sequence() int64 {
	return upspin.SeqVersion(d.DirEntry.Sequence)
}

func (d *infoDirEntry) Hashes() string {
	h := ""
	if d.IsDir() || d.Packing != upspin.EEPack {
		return h
	}
	packer := pack.Lookup(d.Packing)
	hashes, err := packer.ReaderHashes(d.Packdata)
	if err != nil {
		return h
	}
	for _, r := range hashes {
		if h == "" {
			h += " "
		}
		h += fmt.Sprintf("%x...", r[:4])
	}
	return h
}

func (d *infoDirEntry) Users(right access.Right) string {
	userList := d.state.usersWithAccess(d.state.Client, d.access, right)
	// Change "all@upspin.io" back to "All".
	for i, user := range userList {
		if user == access.AllUsers {
			userList[i] = "All" // Capitalize it here for clarity.
		}
	}
	users := userListToString(userList)
	if users == d.lastUsers {
		return "(same)"
	}
	d.lastUsers = users
	return users
}

func (d *infoDirEntry) WhichAccess() string {
	var acc *access.Access
	accEntry, err := d.state.whichAccessFollowLinks(d.Name)
	if err != nil {
		return err.Error()
	}
	accFile := "owner only"
	if accEntry == nil {
		// No access file applies.
		acc, err = access.New(d.Name)
		if err != nil {
			// Can't happen, since the name must be valid.
			d.state.Exitf("%q: %s", d.Name, err)
		}
	} else {
		accFile = string(accEntry.Name)
		data, err := read(d.state.Client, accEntry.Name)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot open access file %q: %s\n", accFile, err)
		}
		acc, err = access.Parse(accEntry.Name, data)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot parse access file %q: %s\n", accFile, err)
		}
	}
	d.access = acc
	return accFile
}

// printInfo prints, in human-readable form, most of the information about
// the entry, including the users that have permission to access it.
// TODO: Present this more neatly.
// TODO: Present group information.
func (s *State) printInfo(entry *upspin.DirEntry) {
	infoDir := &infoDirEntry{
		state:    s,
		DirEntry: entry,
	}
	writer := tabwriter.NewWriter(os.Stdout, 4, 4, 1, ' ', 0)
	err := infoTmpl.Execute(writer, infoDir)
	if err != nil {
		s.Exitf("executing info template: %v", err)
	}
	err = writer.Flush()
	if err != nil {
		s.Exitf("flushing template output: %v", err)
	}
	if !entry.IsLink() {
		return
	}
	// Check and print information about the link target.
	target, err := s.Client.Lookup(entry.Link, true)
	if err != nil {
		// Print the whole error indented, starting on the next line. This helps it stand out.
		s.Exitf("Error: link %s has invalid target %s:\n\t%v", entry.Name, entry.Link, err)
	}
	fmt.Printf("Target of link %s:\n", entry.Name)
	s.printInfo(target)
}

func attrFormat(attr upspin.Attribute) string {
	a := attr
	tail := ""
	if a&upspin.AttrIncomplete > 0 {
		tail = " (incomplete)"
		a ^= upspin.AttrIncomplete
	}
	switch a {
	case upspin.AttrNone:
		return "none (plain file)" + tail
	case upspin.AttrDirectory:
		return "directory" + tail
	case upspin.AttrLink:
		return "link" + tail
	}
	return fmt.Sprintf("attribute(%#x)", attr)
}

var infoTmpl = template.Must(template.New("info").Parse(infoText))

const infoText = `{{.Name}}
	packing:	{{.Packing}}
	size:	{{.Size}}
	time:	{{.TimeString}}
	writer:	{{.Writer}}
	attributes:	{{.AttrString}}
	sequence:	{{.Sequence}}
	access file:	{{.WhichAccess}}
	key holders: 	{{.Readers}}
	key hashes:     {{.Hashes}}
	{{range $right := .Rights -}}
	can {{$right}}:	{{$.Users $right}}
	{{end -}}
	Block#	Offset	Size	Location
	{{range $index, $block := .Blocks -}}
	{{$index}}	{{.Offset}}	{{.Size}}	{{.Location}}
	{{end}}`

// checkGroupFile diagnoses likely problems with the contents and rights
// of the Group file.
// TODO: We could check that packing is Plain but that should never be a problem.
func (s *State) checkGroupFile(name upspin.PathName) {
	parsed, err := path.Parse(name)
	if err != nil {
		s.Exit(err) // Should never happen.
	}
	groupSeen := make(map[upspin.PathName]bool)
	userSeen := make(map[upspin.UserName]bool)
	s.doCheckGroupFile(parsed, groupSeen, userSeen)
}

// doCheckGroupFile is the inner, recursive implementation of checkGroupFile.
func (s *State) doCheckGroupFile(parsed path.Parsed, groupSeen map[upspin.PathName]bool, userSeen map[upspin.UserName]bool) {
	group := parsed.Path()
	if groupSeen[group] {
		return
	}
	groupSeen[group] = true
	data, err := s.Client.Get(group)
	if err != nil {
		s.Exitf("cannot read Group file: %v", err)
	}

	// Get the Access file, if any, that applies.
	// TODO: We've already got it in earlier code, so could save it.
	whichAccess, err := s.DirServer(group).WhichAccess(group)
	if err != nil {
		s.Exitf("unexpected error finding Access file for Group file %s: %v", group, err)
	}
	var accessFile *access.Access
	if whichAccess == nil {
		accessFile, err = access.New(group)
		if err != nil {
			s.Exitf("cannot create default Access file: %v", err)
		}
	} else {
		data, err := s.Client.Get(whichAccess.Name)
		if err != nil {
			s.Exitf("cannot get Access file: %v", err)
		}
		accessFile, err = access.Parse(whichAccess.Name, data)
		if err != nil {
			s.Exitf("cannot parse Access file: %v", err)
		}
	}

	// Each member should be either a plain user or a group and be able to access the Group file.
	members, err := access.ParseGroup(parsed, data)
	if err != nil {
		s.Exitf("error parsing Group file %s: %v", group, err)
	}
	for _, member := range members {
		if member.IsRoot() {
			// Normal user.
			user := member.User()
			if !s.userExists(user, userSeen) {
				s.Failf("user %s in Group file %s not found in key server", user, group)
				continue
			}
			// Member must be able to read the Group file.
			canRead, err := accessFile.Can(user, access.Read, group, s.Client.Get)
			if err != nil {
				s.Exitf("error checking permissions in Group file %s for user %s: %v", group, user, err)
				continue
			}
			if !canRead {
				s.Failf("user %s is missing read access for group %s", user, group)
			}
			continue
		}
		if !access.IsGroupFile(member.Path()) {
			s.Failf("do not understand member %s of Group file %s", member, parsed) // Should never happen.
			continue
		}
		// Member is a group. Recur using Group file.
		s.doCheckGroupFile(member, groupSeen, userSeen)
	}
}

func (s *State) checkAccessFile(name upspin.PathName) {
	data, err := s.Client.Get(name)
	if err != nil {
		s.Exitf("cannot get Access file: %v", err)
	}
	accessFile, err := access.Parse(name, data)
	if err != nil {
		s.Exitf("cannot parse Access file: %v", err)
	}
	users := accessFile.List(access.AnyRight)

	groupSeen := make(map[upspin.PathName]bool)
	userSeen := make(map[upspin.UserName]bool)
	for _, user := range users {
		if user.IsRoot() {
			// Normal user.
			if !s.userExists(user.User(), userSeen) {
				s.Failf("user %s in Access file %s not found in key server", user.User(), name)
			}
			continue
		}
		// Member is a group.
		s.doCheckGroupFile(user, groupSeen, userSeen)
	}
}

func (s *State) userExists(user upspin.UserName, userSeen map[upspin.UserName]bool) bool {
	if userSeen[user] || user == access.AllUsers { // all@upspin.io is baked in.
		return true // Previous answer will do.
	}
	// Ignore wildcards.
	if isWildcardUser(user) {
		return true
	}
	userSeen[user] = true
	_, err := s.KeyServer().Lookup(user)
	return err == nil
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"

	"tchaik.com/index"
	"tchaik.com/index/attr"

	"tchaik.com/store"
)

// Library is a type which encompases the components which form a full library.
type Library struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
}

type libraryFileSystem struct {
	store.FileSystem
	index.Library
}

// Open implements store.FileSystem and rewrites ID values to their corresponding Location
// values using the index.Library.
func (l *libraryFileSystem) Open(ctx context.Context, path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // IDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	loc = filepath.ToSlash(loc)
	return l.FileSystem.Open(ctx, loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	ID          interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
	Favourite   bool        `json:",omitempty"`
	Checklist   bool        `json:",omitempty"`
}

type track struct {
	ID          string   `json:",omitempty"`
	Name        string   `json:",omitempty"`
	Album       string   `json:",omitempty"`
	Artist      []string `json:",omitempty"`
	AlbumArtist []string `json:",omitempty"`
	Composer    []string `json:",omitempty"`
	Year        int      `json:",omitempty"`
	DiscNumber  int      `json:",omitempty"`
	TotalTime   int      `json:",omitempty"`
	BitRate     int      `json:",omitempty"`
	Favourite   bool     `json:",omitempty"`
	Checklist   bool     `json:",omitempty"`
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

func buildCollection(h group, c index.Collection) group {
	getField := func(f string, g index.Group, c index.Collection) interface{} {
		if StringSliceEqual(g.Field(f), c.Field(f)) {
			return nil
		}
		return g.Field(f)
	}

	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(attr.Strings("AlbumArtist"), g)
		g = index.CommonGroupAttr([]attr.Interface{attr.Strings("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: getField("AlbumArtist", g, c),
			Artist:      getField("Artist", g, c),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		ID:          g.Field("ID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != nil {
			return ""
		}
		return t.GetString(field)
	}

	getStrings := func(t index.Track, field string) []string {
		if g.Field(field) != nil {
			return nil
		}
		return t.GetStrings(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != nil {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			ID:        t.GetString("ID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Artist:      getStrings(t, "Artist"),
			AlbumArtist: getStrings(t, "AlbumArtist"),
			Composer:    getStrings(t, "Composer"),
			Album:       getString(t, "Album"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

type rootCollection struct {
	index.Collection
}

func (r *rootCollection) Get(k index.Key) index.Group {
	g := r.Collection.Get(k)
	if g == nil {
		return g
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	g = index.Transform(g, index.SplitList("Artist", "AlbumArtist", "Composer"))
	g = index.Transform(g, index.TrimTrackNumPrefix)
	c := index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []attr.Interface{
		attr.String("Album"),
		attr.Strings("Artist"),
		attr.Strings("AlbumArtist"),
		attr.Strings("Composer"),
		attr.Int("Year"),
		attr.Int("BitRate"),
		attr.Int("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)
	return g
}

func (l *Library) Build(c index.Collection, p index.Path) (index.Group, error) {
	if len(p) == 0 {
		return c, nil
	}

	c = &rootCollection{c}
	var g index.Group = c
	k := index.Key(p[0])
	g = c.Get(k)

	if g == nil {
		return g, fmt.Errorf("invalid path: near '%v'", p[0])
	}

	for i, k := range p[1:] {
		var ok bool
		c, ok = g.(index.Collection)
		if !ok {
			return nil, fmt.Errorf("retrieved Group is not a Collection")
		}

		g = c.Get(k)
		if g == nil {
			return g, fmt.Errorf("invalid path near '%v'", p[1:][i])
		}

		if _, ok = g.(index.Collection); !ok {
			if i == len(p[1:])-1 {
				break
			}
			return nil, fmt.Errorf("retrieved Group isn't a Collection: %v", p)
		}
	}
	if g == nil {
		return g, fmt.Errorf("could not find group")
	}
	g = index.FirstTrackAttr(attr.String("ID"), g)
	return g, nil
}

// Fetch returns a group from the collection with the given path.
func (l *Library) Fetch(c index.Collection, p index.Path) (group, error) {
	if len(p) == 0 {
		return build(c, index.Key("Root")), nil
	}

	k := index.Key(p[0])
	g, err := l.Build(c, p)
	if err != nil {
		return group{}, err
	}
	return build(g, k), nil
}

// FileSystem wraps the http.FileSystem in a library lookup which will translate /ID
// requests into their corresponding track paths.
func (l *Library) FileSystem(fs store.FileSystem) store.FileSystem {
	return store.Trace(&libraryFileSystem{fs, l.Library}, "libraryFileSystem")
}

// ExpandPaths constructs a collection (group) whose sub-groups are taken from the "Root"
// collection.
func (l *Library) ExpandPaths(paths []index.Path) group {
	return build(index.NewPathsCollection(l.collections["Root"], paths), index.Key("Root"))
}
<commit_msg>Use GroupFromPath in Library.Build.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"

	"tchaik.com/index"
	"tchaik.com/index/attr"

	"tchaik.com/store"
)

// Library is a type which encompases the components which form a full library.
type Library struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
}

type libraryFileSystem struct {
	store.FileSystem
	index.Library
}

// Open implements store.FileSystem and rewrites ID values to their corresponding Location
// values using the index.Library.
func (l *libraryFileSystem) Open(ctx context.Context, path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // IDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	loc = filepath.ToSlash(loc)
	return l.FileSystem.Open(ctx, loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	ID          interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
	Favourite   bool        `json:",omitempty"`
	Checklist   bool        `json:",omitempty"`
}

type track struct {
	ID          string   `json:",omitempty"`
	Name        string   `json:",omitempty"`
	Album       string   `json:",omitempty"`
	Artist      []string `json:",omitempty"`
	AlbumArtist []string `json:",omitempty"`
	Composer    []string `json:",omitempty"`
	Year        int      `json:",omitempty"`
	DiscNumber  int      `json:",omitempty"`
	TotalTime   int      `json:",omitempty"`
	BitRate     int      `json:",omitempty"`
	Favourite   bool     `json:",omitempty"`
	Checklist   bool     `json:",omitempty"`
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

func buildCollection(h group, c index.Collection) group {
	getField := func(f string, g index.Group, c index.Collection) interface{} {
		if StringSliceEqual(g.Field(f), c.Field(f)) {
			return nil
		}
		return g.Field(f)
	}

	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(attr.Strings("AlbumArtist"), g)
		g = index.CommonGroupAttr([]attr.Interface{attr.Strings("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: getField("AlbumArtist", g, c),
			Artist:      getField("Artist", g, c),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		ID:          g.Field("ID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != nil {
			return ""
		}
		return t.GetString(field)
	}

	getStrings := func(t index.Track, field string) []string {
		if g.Field(field) != nil {
			return nil
		}
		return t.GetStrings(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != nil {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			ID:        t.GetString("ID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Artist:      getStrings(t, "Artist"),
			AlbumArtist: getStrings(t, "AlbumArtist"),
			Composer:    getStrings(t, "Composer"),
			Album:       getString(t, "Album"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

type rootCollection struct {
	index.Collection
}

func (r *rootCollection) Get(k index.Key) index.Group {
	g := r.Collection.Get(k)
	if g == nil {
		return g
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	g = index.Transform(g, index.SplitList("Artist", "AlbumArtist", "Composer"))
	g = index.Transform(g, index.TrimTrackNumPrefix)
	c := index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []attr.Interface{
		attr.String("Album"),
		attr.Strings("Artist"),
		attr.Strings("AlbumArtist"),
		attr.Strings("Composer"),
		attr.Int("Year"),
		attr.Int("BitRate"),
		attr.Int("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)
	return g
}

func (l *Library) Build(c index.Collection, p index.Path) (index.Group, error) {
	if len(p) == 0 {
		return c, nil
	}

	g, err := index.GroupFromPath(&rootCollection{c}, p)
	if err != nil {
		return nil, err
	}
	g = index.FirstTrackAttr(attr.String("ID"), g)
	return g, nil
}

// Fetch returns a group from the collection with the given path.
func (l *Library) Fetch(c index.Collection, p index.Path) (group, error) {
	if len(p) == 0 {
		return build(c, index.Key("Root")), nil
	}

	k := index.Key(p[0])
	g, err := l.Build(c, p)
	if err != nil {
		return group{}, err
	}
	return build(g, k), nil
}

// FileSystem wraps the http.FileSystem in a library lookup which will translate /ID
// requests into their corresponding track paths.
func (l *Library) FileSystem(fs store.FileSystem) store.FileSystem {
	return store.Trace(&libraryFileSystem{fs, l.Library}, "libraryFileSystem")
}

// ExpandPaths constructs a collection (group) whose sub-groups are taken from the "Root"
// collection.
func (l *Library) ExpandPaths(paths []index.Path) group {
	return build(index.NewPathsCollection(l.collections["Root"], paths), index.Key("Root"))
}
<|endoftext|>


<commit_before>// Copyright 2013 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 crc16

// Predefined polynomials.
const (
	// Used by Bisync, Modbus, USB, ANSI X3.28, SIA DC-07, ...
	IBM = 0xA001

	// Used by X.25, V.41, HDLC FCS, XMODEM, Bluetooth, PACTOR, SD, ...
	CCITT = 0x8408

	// Used by SCSI
	SCSI = 0xEDD1
)

// Table is a 256-word table representing the polynomial for efficient processing.
type Table [256]uint16

// IBMTable is the table for the IBM polynomial.
var IBMTable = makeTable(IBM)

// CCITTTable is the table for the CCITT polynomial.
var CCITTTable = makeTable(CCITT)

// SCSITable is the table for the SCSI polynomial.
var SCSITable = makeTable(SCSI)

// MakeTable returns the Table constructed from the specified polynomial.
func MakeTable(poly uint16) *Table {
	return makeTable(poly)
}

// makeTable returns the Table constructed from the specified polynomial.
func makeTable(poly uint16) *Table {
	t := new(Table)
	for i := 0; i < 256; i++ {
		crc := uint16(i)
		for j := 0; j < 8; j++ {
			if crc&1 == 1 {
				crc = (crc >> 1) ^ poly
			} else {
				crc >>= 1
			}
		}
		t[i] = crc
	}
	return t
}

func update(crc uint16, tab *Table, p []byte) uint16 {
	crc = ^crc
	for _, v := range p {
		crc = tab[byte(crc)^v] ^ (crc >> 8)
	}
	return ^crc
}

// Update returns the result of adding the bytes in p to the crc.
func Update(crc uint16, tab *Table, p []byte) uint16 {
	return update(crc, tab, p)
}

// Checksum returns the CRC-16 checksum of data
// using the polynomial represented by the Table.
func Checksum(data []byte, tab *Table) uint16 { return Update(0, tab, data) }

// ChecksumIBM returns the CRC-16 checksum of data
// using the IBM polynomial.
func ChecksumIBM(data []byte) uint16 { return update(0, IBMTable, data) }

// ChecksumCCITT returns the CRC-16 checksum of data
// using the CCITT polynomial.
func ChecksumCCITT(data []byte) uint16 { return update(0, CCITTTable, data) }

// ChecksumSCSI returns the CRC-16 checksum of data
// using the SCSI polynomial.
func ChecksumSCSI(data []byte) uint16 { return update(0, SCSITable, data) }
<commit_msg>Update comments to fix go vet complaints.<commit_after>// Copyright 2013 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 crc16

// Predefined polynomials.
const (
	// IBM is used by Bisync, Modbus, USB, ANSI X3.28, SIA DC-07, ...
	IBM = 0xA001

	// CCITT is used by X.25, V.41, HDLC FCS, XMODEM, Bluetooth, PACTOR, SD, ...
	// CCITT forward is 0x8408. Reverse is 0x1021. And we do CCITT in reverse.
	CCITT = 0x1021

	// SCSI is used by SCSI
	SCSI = 0xEDD1
)

// Table is a 256-word table representing the polynomial for efficient processing.
type Table [256]uint16

// IBMTable is the table for the IBM polynomial.
var IBMTable = makeTable(IBM)

// CCITTTable is the table for the CCITT polynomial.
var CCITTTable = makeTable(CCITT)

// SCSITable is the table for the SCSI polynomial.
var SCSITable = makeTable(SCSI)

// MakeTable returns the Table constructed from the specified polynomial.
func MakeTable(poly uint16) *Table {
	return makeTable(poly)
}

// makeTable returns the Table constructed from the specified polynomial.
func makeTable(poly uint16) *Table {
	t := new(Table)
	for i := 0; i < 256; i++ {
		crc := uint16(i)
		for j := 0; j < 8; j++ {
			if crc&1 == 1 {
				crc = (crc >> 1) ^ poly
			} else {
				crc >>= 1
			}
		}
		t[i] = crc
	}
	return t
}

func update(crc uint16, tab *Table, p []byte) uint16 {
	crc = ^crc
	for _, v := range p {
		crc = tab[byte(crc)^v] ^ (crc >> 8)
	}
	return ^crc
}

// Update returns the result of adding the bytes in p to the crc.
func Update(crc uint16, tab *Table, p []byte) uint16 {
	return update(crc, tab, p)
}

// Checksum returns the CRC-16 checksum of data
// using the polynomial represented by the Table.
func Checksum(data []byte, tab *Table) uint16 { return Update(0, tab, data) }

// ChecksumIBM returns the CRC-16 checksum of data
// using the IBM polynomial.
func ChecksumIBM(data []byte) uint16 { return update(0, IBMTable, data) }

// ChecksumCCITT returns the CRC-16 checksum of data
// using the CCITT polynomial.
func ChecksumCCITT(data []byte) uint16 { return update(0, CCITTTable, data) }

// ChecksumSCSI returns the CRC-16 checksum of data
// using the SCSI polynomial.
func ChecksumSCSI(data []byte) uint16 { return update(0, SCSITable, data) }
<|endoftext|>