<commit_before>package cmd

// filesCmdGroup represents the instances command
import (
	"encoding/json"
	"errors"
	"fmt"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errAppsMissingDomain = errors.New("Missing --domain flag")

var flagAppsDomain string
var flagAllDomains bool
var flagAppsDeactivated bool

var webappsCmdGroup = &cobra.Command{
	Use:   "apps [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack apps allows to interact with the cozy applications.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installWebappCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an application with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack apps install --domain cozy.tools:8080 drive 'git://github.com/cozy/cozy-drive.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Apps)
	},
}

var updateWebappCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the application with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Apps)
	},
}

var uninstallWebappCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the application with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Apps)
	},
}

var lsWebappsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed applications.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Apps)
	},
}

var konnectorsCmdGroup = &cobra.Command{
	Use:   "konnectors [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack konnectors allows to interact with the cozy konnectors.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installKonnectorCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an konnector with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack konnectors install --domain cozy.tools:8080 trainline 'git://github.com/cozy/cozy-konnector-trainline.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Konnectors)
	},
}

var updateKonnectorCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the konnector with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Konnectors)
	},
}

var uninstallKonnectorCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the konnector with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Konnectors)
	},
}

var lsKonnectorsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed konnectors.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Konnectors)
	},
}

func installApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) < 1 {
		return cmd.Help()
	}
	slug := args[0]
	var source string
	if len(args) == 1 {
		s, ok := consts.AppsRegistry[slug]
		if !ok {
			return cmd.Help()
		}
		source = s
	} else {
		source = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.InstallApp(&client.AppOptions{
				AppType:     appType,
				Slug:        slug,
				SourceURL:   source,
				Deactivated: flagAppsDeactivated,
			})
			if err != nil {
				if err.Error() == "Application with same slug already exists" {
					return nil
				}
				return err
			}
			fmt.Printf("Application installed successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.InstallApp(&client.AppOptions{
		AppType:     appType,
		Slug:        slug,
		SourceURL:   source,
		Deactivated: flagAppsDeactivated,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func updateApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) == 0 || len(args) > 2 {
		return cmd.Help()
	}
	var src string
	if len(args) > 1 {
		src = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.UpdateApp(&client.AppOptions{
				AppType:   appType,
				Slug:      args[0],
				SourceURL: src,
			})
			if err != nil {
				if err.Error() == "Application is not installed" {
					return nil
				}
				return err
			}
			fmt.Printf("Application updated successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UpdateApp(&client.AppOptions{
		AppType:   appType,
		Slug:      args[0],
		SourceURL: src,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func uninstallApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UninstallApp(&client.AppOptions{
		AppType: appType,
		Slug:    args[0],
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func lsApps(cmd *cobra.Command, args []string, appType string) error {
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	// TODO(pagination)
	apps, err := c.ListApps(appType)
	if err != nil {
		return err
	}
	for _, app := range apps {
		fmt.Printf("%s\t%s\t%s\n",
			app.Attrs.Slug, app.Attrs.Source, app.Attrs.State)
	}
	return nil
}

func foreachDomains(predicate func(*client.Instance) error) error {
	c := newAdminClient()
	// TODO(pagination): Make this iteration more robust
	list, err := c.ListInstances()
	if err != nil {
		return nil
	}
	var hasErr bool
	for _, i := range list {
		if err = predicate(i); err != nil {
			errPrintfln("%s: %s", i.Attrs.Domain, err)
			hasErr = true
		}
	}
	if hasErr {
		return errors.New("At least one error occured while executing this command")
	}
	return nil
}

func init() {
	webappsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	webappsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")
	installWebappCmd.PersistentFlags().BoolVar(&flagAppsDeactivated, "ask-permissions", false, "specify that the application should not be activated after installation")

	webappsCmdGroup.AddCommand(lsWebappsCmd)
	webappsCmdGroup.AddCommand(installWebappCmd)
	webappsCmdGroup.AddCommand(updateWebappCmd)
	webappsCmdGroup.AddCommand(uninstallWebappCmd)

	konnectorsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	konnectorsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")

	konnectorsCmdGroup.AddCommand(lsKonnectorsCmd)
	konnectorsCmdGroup.AddCommand(installKonnectorCmd)
	konnectorsCmdGroup.AddCommand(updateKonnectorCmd)
	konnectorsCmdGroup.AddCommand(uninstallKonnectorCmd)

	RootCmd.AddCommand(webappsCmdGroup)
	RootCmd.AddCommand(konnectorsCmdGroup)
}
<commit_msg>Show versions on cozy-stack apps ls<commit_after>package cmd

// filesCmdGroup represents the instances command
import (
	"encoding/json"
	"errors"
	"fmt"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errAppsMissingDomain = errors.New("Missing --domain flag")

var flagAppsDomain string
var flagAllDomains bool
var flagAppsDeactivated bool

var webappsCmdGroup = &cobra.Command{
	Use:   "apps [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack apps allows to interact with the cozy applications.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installWebappCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an application with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack apps install --domain cozy.tools:8080 drive 'git://github.com/cozy/cozy-drive.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Apps)
	},
}

var updateWebappCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the application with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Apps)
	},
}

var uninstallWebappCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the application with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Apps)
	},
}

var lsWebappsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed applications.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Apps)
	},
}

var konnectorsCmdGroup = &cobra.Command{
	Use:   "konnectors [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack konnectors allows to interact with the cozy konnectors.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installKonnectorCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an konnector with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack konnectors install --domain cozy.tools:8080 trainline 'git://github.com/cozy/cozy-konnector-trainline.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Konnectors)
	},
}

var updateKonnectorCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the konnector with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Konnectors)
	},
}

var uninstallKonnectorCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the konnector with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Konnectors)
	},
}

var lsKonnectorsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed konnectors.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Konnectors)
	},
}

func installApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) < 1 {
		return cmd.Help()
	}
	slug := args[0]
	var source string
	if len(args) == 1 {
		s, ok := consts.AppsRegistry[slug]
		if !ok {
			return cmd.Help()
		}
		source = s
	} else {
		source = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.InstallApp(&client.AppOptions{
				AppType:     appType,
				Slug:        slug,
				SourceURL:   source,
				Deactivated: flagAppsDeactivated,
			})
			if err != nil {
				if err.Error() == "Application with same slug already exists" {
					return nil
				}
				return err
			}
			fmt.Printf("Application installed successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.InstallApp(&client.AppOptions{
		AppType:     appType,
		Slug:        slug,
		SourceURL:   source,
		Deactivated: flagAppsDeactivated,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func updateApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) == 0 || len(args) > 2 {
		return cmd.Help()
	}
	var src string
	if len(args) > 1 {
		src = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.UpdateApp(&client.AppOptions{
				AppType:   appType,
				Slug:      args[0],
				SourceURL: src,
			})
			if err != nil {
				if err.Error() == "Application is not installed" {
					return nil
				}
				return err
			}
			fmt.Printf("Application updated successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UpdateApp(&client.AppOptions{
		AppType:   appType,
		Slug:      args[0],
		SourceURL: src,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func uninstallApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UninstallApp(&client.AppOptions{
		AppType: appType,
		Slug:    args[0],
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func lsApps(cmd *cobra.Command, args []string, appType string) error {
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	// TODO(pagination)
	apps, err := c.ListApps(appType)
	if err != nil {
		return err
	}
	for _, app := range apps {
		fmt.Printf("%s\t%s\t%s\t%s\n",
			app.Attrs.Slug, app.Attrs.Source, app.Attrs.Version, app.Attrs.State)
	}
	return nil
}

func foreachDomains(predicate func(*client.Instance) error) error {
	c := newAdminClient()
	// TODO(pagination): Make this iteration more robust
	list, err := c.ListInstances()
	if err != nil {
		return nil
	}
	var hasErr bool
	for _, i := range list {
		if err = predicate(i); err != nil {
			errPrintfln("%s: %s", i.Attrs.Domain, err)
			hasErr = true
		}
	}
	if hasErr {
		return errors.New("At least one error occured while executing this command")
	}
	return nil
}

func init() {
	webappsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	webappsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")
	installWebappCmd.PersistentFlags().BoolVar(&flagAppsDeactivated, "ask-permissions", false, "specify that the application should not be activated after installation")

	webappsCmdGroup.AddCommand(lsWebappsCmd)
	webappsCmdGroup.AddCommand(installWebappCmd)
	webappsCmdGroup.AddCommand(updateWebappCmd)
	webappsCmdGroup.AddCommand(uninstallWebappCmd)

	konnectorsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	konnectorsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")

	konnectorsCmdGroup.AddCommand(lsKonnectorsCmd)
	konnectorsCmdGroup.AddCommand(installKonnectorCmd)
	konnectorsCmdGroup.AddCommand(updateKonnectorCmd)
	konnectorsCmdGroup.AddCommand(uninstallKonnectorCmd)

	RootCmd.AddCommand(webappsCmdGroup)
	RootCmd.AddCommand(konnectorsCmdGroup)
}
<|endoftext|>


<commit_before>package beatboxer

import (
	"errors"
	"sync"

	"github.com/siggy/bbox/bbox"
	"github.com/siggy/bbox/beatboxer/keyboard"
	"github.com/siggy/bbox/beatboxer/render"
	"github.com/siggy/bbox/beatboxer/wavs"
	log "github.com/sirupsen/logrus"
)

const (
	SWITCH_COUNT = 5
)

var (
	switcher = bbox.Coord{1, 15}
)

// type registered struct {
// 	harness *Harness
// 	id      int
// }

// satisfy Output interface
// func (r *registered) Play(name string) time.Duration {
// 	return r.harness.play(r.id, name)
// }
// func (r *registered) Render(rs render.RenderState) {
// 	log.Debugf("(r *registered) Render start: %+d", r.id)
// 	r.harness.render(r.id, rs)
// 	log.Debugf("(r *registered) Render start: %+d", r.id)
// }
// func (r *registered) Yield() {
// 	r.harness.yield(r.id)
// }

type harness struct {
	renderer   render.Renderer
	terminal   *render.Terminal
	termRender chan render.RenderState
	// pressed   chan bbox.Coord
	kb *keyboard.Keyboard
	// flush     chan struct{}
	wavs      *wavs.Wavs
	keyMap    map[bbox.Key]*bbox.Coord
	amplitude *Amplitude
	programs  []Program
	// active    int
}

func InitHarness(
	renderer render.Renderer,
	keyMap map[bbox.Key]*bbox.Coord,
) *harness {
	// err := termbox.Init()
	// if err != nil {
	// 	panic(err)
	// }

	kb := keyboard.Init(keyMap)

	return &harness{
		renderer: renderer,
		// pressed:   make(chan bbox.Coord),
		termRender: make(chan render.RenderState),
		wavs:       wavs.InitWavs(),
		keyMap:     keyMap,
		amplitude:  InitAmplitude(),
		kb:         kb,
		terminal:   render.InitTerminal(kb),
		// flush:     make(chan struct{}),
	}
}

func (h *harness) Register(program Program) {
	h.programs = append(h.programs, program)
}

// func (h *harness) NextProgram() {
// 	prev := h.programs[h.active]
// 	h.active = (h.active + 1) % len(h.programs)
// 	prev.Close()

// 	// clear the display
// 	// h.renderFn(render.RenderState{})
// 	h.terminal.Render(render.RenderState{})

// 	reg := registered{
// 		harness: h,
// 		id:      h.active,
// 	}
// 	h.programs[h.active] = h.programs[h.active].New(&reg)
// }

// func (h *harness) Run() {
// 	// err := termbox.Init()
// 	// if err != nil {
// 	// 	panic(err)
// 	// }

// 	// h.kb = keyboard.Init(h.keyMap)

// 	go h.amplitude.Run()
// 	go h.kb.Run()

// 	// h.terminal = render.InitTerminal(h.kb)

// 	defer func() {
// 		log.Debugf("h.Run() defer func() 0")

// 		// termbox.Interrupt()
// 		// termbox.Close()
// 		prev := h.programs[h.active]
// 		// don't actually start the next program
// 		h.active = (h.active + 1) % len(h.programs)
// 		prev.Close()

// 		log.Debugf("h.Run() defer func() 1")

// 		// ensure nested shutdown for portaudio even though it shouldn't be necessary?
// 		go func() {
// 			h.amplitude.Close()
// 			h.wavs.Close()
// 		}()

// 		go h.kb.Close()

// 		log.Debugf("h.Run() defer func() 2")
// 	}()

// 	// make the first program active
// 	reg := registered{
// 		harness: h,
// 		id:      0,
// 	}
// 	h.programs[0] = h.programs[0].New(&reg)

// 	switcherCount := 0
// 	for {
// 		select {
// 		// case _, more := <-h.flush:
// 		// 	if !more {
// 		// 		log.Debugf("flush channel closed")
// 		// 		return
// 		// 	}
// 		// 	termbox.Flush()
// 		case level, more := <-h.level:
// 			log.Debugf("h.level")
// 			if !more {
// 				log.Debugf("harness: amplitude.level channel closed")
// 				return
// 			}

// 			// log.Debugf("h.programs[h.active].Amp(level) start")

// 			h.programs[h.active].Amp(level)

// 			// log.Debugf("h.programs[h.active].Amp(level) end")
// 		case coord, more := <-h.kb.Pressed():
// 			log.Debugf("h.kb.Pressed(): %+v", coord)
// 			if !more {
// 				log.Debugf("harness: pressed channel closed")
// 				return
// 			}
// 			if coord == switcher {
// 				switcherCount++

// 				if switcherCount >= SWITCH_COUNT {
// 					h.NextProgram()
// 					switcherCount = 0
// 					continue
// 				}
// 			} else {
// 				switcherCount = 0
// 			}

// 			h.programs[h.active].Pressed(coord[0], coord[1])
// 		case _, more := <-h.kb.Closing():
// 			log.Debugf("<-h.kb.Closing()")
// 			if !more {
// 				log.Debugf("harness: keyboard closing")
// 				return
// 			}
// 		case rs, more := <-h.termRender:
// 			log.Debugf("<-h.termRender")
// 			if !more {
// 				log.Debugf("harness: termRender channel closed")
// 				return
// 			}
// 			log.Debugf("h.terminal.Render(rs) start")
// 			h.terminal.Render(rs)
// 			log.Debugf("h.terminal.Render(rs) end")
// 		}
// 	}
// }

// func (h *harness) play(id int, name string) time.Duration {
// 	if id != h.active {
// 		log.Debugf("play called by invalid program %d: %s", id, name)
// 		return time.Duration(0)
// 	}

// 	return h.wavs.Play(name)
// }

// func (h *harness) render(id int, rs render.RenderState) {
// 	if id != h.active {
// 		log.Debugf("render called by invalid program %d: %+v", id, rs)
// 		return
// 	}

// 	// TODO: led renderer, which eventually call renderer.SetLed
// 	// h.led.Render(rs)

// 	log.Debugf("(h *Harness) render start: %+d", id)
// 	h.termRender <- rs
// 	log.Debugf("(h *Harness) render end: %+d", id)
// 	// h.renderFn(rs)

// 	// TODO: should renderFn() do this?
// 	// also TODO: should this be synch?
// 	// h.kb.Flush()

// 	// TODO: decide if a web renderer is performant enough
// 	// h.toRenderer(rs)
// }

// temporary until all the "68, 64, 60, 56" foo is moved over
func (h *harness) toRenderer(rs render.RenderState) {
	for col := 0; col < render.COLUMNS; col++ {
		for row := 0; row < render.ROWS-2; row++ {
			h.renderer.SetLed(0, col, rs.LEDs[row][col])
		}
		for row := render.ROWS - 2; row < render.ROWS; row++ {
			h.renderer.SetLed(1, col, rs.LEDs[row][col])
		}
	}
}

// func (h *harness) yield(id int) {
// 	if id != h.active {
// 		log.Debugf("yield called by invalid program %d", id)
// 		return
// 	}

// 	h.NextProgram()
// }

func (h *harness) Run() {
	go h.amplitude.Run()
	go h.kb.Run()

	defer func() {
		h.amplitude.Close()
		h.wavs.Close()
	}()
	defer h.kb.Close()

	active := 0
	cur := h.programs[active].New()

	for {
		err := h.RunProgram(cur)
		go func(cur Program) {
			cur.Close()
		}(cur)
		if err != nil {
			break
		}

		active = (active + 1) % len(h.programs)
		cur = h.programs[active].New()
	}
}

func (h *harness) RunProgram(p Program) error {
	var err error
	yielding := make(chan struct{})
	exiting := make(chan struct{})

	wg := sync.WaitGroup{}
	wg.Add(5)

	// input: amplitude
	go func() {
		log.Debugf("h.RunProgram AMP")
		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram AMP 1")
			select {
			case p.Amplitude() <- <-h.amplitude.Level():
				// log.Debugf("h.RunProgram AMP 2")
				// p.Amplitude() <- a
				// log.Debugf("h.RunProgram AMP 3")
			case _, more := <-yielding:
				if !more {
					return
				}
			case _, more := <-exiting:
				if !more {
					log.Debugf("h.RunProgram AMP 4")
					return
				}
			}
		}
	}()

	// input: keyboard
	go func() {
		log.Debugf("h.RunProgram KB")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram KB 1")
			select {
			case coord, _ := <-h.kb.Pressed():
				log.Debugf("h.RunProgram KB 2")
				p.Keyboard() <- coord
				log.Debugf("h.RunProgram KB 3")
			case _, more := <-h.kb.Closing():
				log.Debugf("h.RunProgram KB 4")
				if !more {
					close(exiting)
					err = errors.New("Exiting")
					log.Debugf("h.RunProgram KB 5")
					return
				}
			case _, more := <-yielding:
				if !more {
					return
				}
			}
		}
	}()

	// output: render
	go func() {
		log.Debugf("h.RunProgram RENDER")

		// wg.Add(1)
		defer wg.Done()

		for {
			// log.Debugf("h.RunProgram RENDER 1")
			select {
			case rs, _ := <-p.Render():
				// log.Debugf("h.RunProgram RENDER 2")
				h.terminal.Render(rs)
			case _, more := <-yielding:
				if !more {
					return
				}
			case _, more := <-exiting:
				if !more {
					log.Debugf("h.RunProgram RENDER 3")
					return
				}
			}
		}
	}()

	// output: play
	go func() {
		log.Debugf("h.RunProgram PLAY")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram PLAY 1")
			select {
			case name, _ := <-p.Play():
				log.Debugf("h.RunProgram PLAY 2")
				h.wavs.Play(name)
			case _, more := <-yielding:
				if !more {
					return
				}
			case _, more := <-exiting:
				if !more {
					log.Debugf("h.RunProgram PLAY 3")
					return
				}
			}
		}
	}()

	// output: yield
	go func() {
		log.Debugf("h.RunProgram YIELD")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram YIELD 1")
			select {
			case <-p.Yield():
				log.Debugf("h.RunProgram YIELD 2")
				close(yielding)
				return
				// go func() {
				// 	p.Close() <- struct{}{}
				// }()

				// h.terminal.Render(render.RenderState{})

				// active = (active + 1) % len(h.programs)
				// cur = h.programs[active].New()
			case _, more := <-exiting:
				if !more {
					log.Debugf("h.RunProgram YIELD 3")
					return
				}
			}
		}
	}()

	log.Debugf("h.RunProgram wg.Wait1")

	wg.Wait()

	log.Debugf("h.RunProgram wg.Wait2")

	return err
}
<commit_msg>add switching back<commit_after>package beatboxer

import (
	"errors"
	"sync"

	"github.com/siggy/bbox/bbox"
	"github.com/siggy/bbox/beatboxer/keyboard"
	"github.com/siggy/bbox/beatboxer/render"
	"github.com/siggy/bbox/beatboxer/wavs"
	log "github.com/sirupsen/logrus"
)

const (
	SWITCH_COUNT = 5
)

var (
	switcher = bbox.Coord{1, 15}
)

// type registered struct {
// 	harness *Harness
// 	id      int
// }

// satisfy Output interface
// func (r *registered) Play(name string) time.Duration {
// 	return r.harness.play(r.id, name)
// }
// func (r *registered) Render(rs render.RenderState) {
// 	log.Debugf("(r *registered) Render start: %+d", r.id)
// 	r.harness.render(r.id, rs)
// 	log.Debugf("(r *registered) Render start: %+d", r.id)
// }
// func (r *registered) Yield() {
// 	r.harness.yield(r.id)
// }

type harness struct {
	renderer   render.Renderer
	terminal   *render.Terminal
	termRender chan render.RenderState
	// pressed   chan bbox.Coord
	kb *keyboard.Keyboard
	// flush     chan struct{}
	wavs      *wavs.Wavs
	keyMap    map[bbox.Key]*bbox.Coord
	amplitude *Amplitude
	programs  []Program
	// active    int
}

func InitHarness(
	renderer render.Renderer,
	keyMap map[bbox.Key]*bbox.Coord,
) *harness {
	// err := termbox.Init()
	// if err != nil {
	// 	panic(err)
	// }

	kb := keyboard.Init(keyMap)

	return &harness{
		renderer: renderer,
		// pressed:   make(chan bbox.Coord),
		termRender: make(chan render.RenderState),
		wavs:       wavs.InitWavs(),
		keyMap:     keyMap,
		amplitude:  InitAmplitude(),
		kb:         kb,
		terminal:   render.InitTerminal(kb),
		// flush:     make(chan struct{}),
	}
}

func (h *harness) Register(program Program) {
	h.programs = append(h.programs, program)
}

// func (h *harness) NextProgram() {
// 	prev := h.programs[h.active]
// 	h.active = (h.active + 1) % len(h.programs)
// 	prev.Close()

// 	// clear the display
// 	// h.renderFn(render.RenderState{})
// 	h.terminal.Render(render.RenderState{})

// 	reg := registered{
// 		harness: h,
// 		id:      h.active,
// 	}
// 	h.programs[h.active] = h.programs[h.active].New(&reg)
// }

// func (h *harness) Run() {
// 	// err := termbox.Init()
// 	// if err != nil {
// 	// 	panic(err)
// 	// }

// 	// h.kb = keyboard.Init(h.keyMap)

// 	go h.amplitude.Run()
// 	go h.kb.Run()

// 	// h.terminal = render.InitTerminal(h.kb)

// 	defer func() {
// 		log.Debugf("h.Run() defer func() 0")

// 		// termbox.Interrupt()
// 		// termbox.Close()
// 		prev := h.programs[h.active]
// 		// don't actually start the next program
// 		h.active = (h.active + 1) % len(h.programs)
// 		prev.Close()

// 		log.Debugf("h.Run() defer func() 1")

// 		// ensure nested shutdown for portaudio even though it shouldn't be necessary?
// 		go func() {
// 			h.amplitude.Close()
// 			h.wavs.Close()
// 		}()

// 		go h.kb.Close()

// 		log.Debugf("h.Run() defer func() 2")
// 	}()

// 	// make the first program active
// 	reg := registered{
// 		harness: h,
// 		id:      0,
// 	}
// 	h.programs[0] = h.programs[0].New(&reg)

// 	switcherCount := 0
// 	for {
// 		select {
// 		// case _, more := <-h.flush:
// 		// 	if !more {
// 		// 		log.Debugf("flush channel closed")
// 		// 		return
// 		// 	}
// 		// 	termbox.Flush()
// 		case level, more := <-h.level:
// 			log.Debugf("h.level")
// 			if !more {
// 				log.Debugf("harness: amplitude.level channel closed")
// 				return
// 			}

// 			// log.Debugf("h.programs[h.active].Amp(level) start")

// 			h.programs[h.active].Amp(level)

// 			// log.Debugf("h.programs[h.active].Amp(level) end")
// 		case coord, more := <-h.kb.Pressed():
// 			log.Debugf("h.kb.Pressed(): %+v", coord)
// 			if !more {
// 				log.Debugf("harness: pressed channel closed")
// 				return
// 			}
// 			if coord == switcher {
// 				switcherCount++

// 				if switcherCount >= SWITCH_COUNT {
// 					h.NextProgram()
// 					switcherCount = 0
// 					continue
// 				}
// 			} else {
// 				switcherCount = 0
// 			}

// 			h.programs[h.active].Pressed(coord[0], coord[1])
// 		case _, more := <-h.kb.Closing():
// 			log.Debugf("<-h.kb.Closing()")
// 			if !more {
// 				log.Debugf("harness: keyboard closing")
// 				return
// 			}
// 		case rs, more := <-h.termRender:
// 			log.Debugf("<-h.termRender")
// 			if !more {
// 				log.Debugf("harness: termRender channel closed")
// 				return
// 			}
// 			log.Debugf("h.terminal.Render(rs) start")
// 			h.terminal.Render(rs)
// 			log.Debugf("h.terminal.Render(rs) end")
// 		}
// 	}
// }

// func (h *harness) play(id int, name string) time.Duration {
// 	if id != h.active {
// 		log.Debugf("play called by invalid program %d: %s", id, name)
// 		return time.Duration(0)
// 	}

// 	return h.wavs.Play(name)
// }

// func (h *harness) render(id int, rs render.RenderState) {
// 	if id != h.active {
// 		log.Debugf("render called by invalid program %d: %+v", id, rs)
// 		return
// 	}

// 	// TODO: led renderer, which eventually call renderer.SetLed
// 	// h.led.Render(rs)

// 	log.Debugf("(h *Harness) render start: %+d", id)
// 	h.termRender <- rs
// 	log.Debugf("(h *Harness) render end: %+d", id)
// 	// h.renderFn(rs)

// 	// TODO: should renderFn() do this?
// 	// also TODO: should this be synch?
// 	// h.kb.Flush()

// 	// TODO: decide if a web renderer is performant enough
// 	// h.toRenderer(rs)
// }

// temporary until all the "68, 64, 60, 56" foo is moved over
func (h *harness) toRenderer(rs render.RenderState) {
	for col := 0; col < render.COLUMNS; col++ {
		for row := 0; row < render.ROWS-2; row++ {
			h.renderer.SetLed(0, col, rs.LEDs[row][col])
		}
		for row := render.ROWS - 2; row < render.ROWS; row++ {
			h.renderer.SetLed(1, col, rs.LEDs[row][col])
		}
	}
}

// func (h *harness) yield(id int) {
// 	if id != h.active {
// 		log.Debugf("yield called by invalid program %d", id)
// 		return
// 	}

// 	h.NextProgram()
// }

func (h *harness) Run() {
	go h.amplitude.Run()
	go h.kb.Run()

	defer func() {
		h.amplitude.Close()
		h.wavs.Close()
	}()
	defer h.kb.Close()

	active := 0
	cur := h.programs[active].New()

	for {
		err := h.RunProgram(cur)
		go func(cur Program) {
			cur.Close()
		}(cur)
		if err != nil {
			break
		}

		active = (active + 1) % len(h.programs)
		cur = h.programs[active].New()
	}
}

func (h *harness) RunProgram(p Program) error {
	var err error
	closing := make(chan struct{})

	wg := sync.WaitGroup{}
	wg.Add(5)

	// input: amplitude
	go func() {
		log.Debugf("h.RunProgram AMP")
		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram AMP 1")
			select {
			case p.Amplitude() <- <-h.amplitude.Level():
				// log.Debugf("h.RunProgram AMP 2")
				// p.Amplitude() <- a
				// log.Debugf("h.RunProgram AMP 3")
			case _, more := <-closing:
				if !more {
					return
				}
			}
		}
	}()

	// input: keyboard
	go func() {
		switcherCount := 0

		log.Debugf("h.RunProgram KB")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram KB 1")
			select {
			case coord, _ := <-h.kb.Pressed():
				if coord == switcher {
					switcherCount++
					if switcherCount >= SWITCH_COUNT {
						close(closing)
						return
					}
				} else {
					switcherCount = 0
				}

				log.Debugf("h.RunProgram KB 2")
				p.Keyboard() <- coord
				log.Debugf("h.RunProgram KB 3")
			case _, more := <-h.kb.Closing():
				log.Debugf("h.RunProgram KB 4")
				if !more {
					close(closing)
					err = errors.New("Exiting")
					log.Debugf("h.RunProgram KB 5")
					return
				}
			case _, more := <-closing:
				if !more {
					return
				}
			}
		}
	}()

	// output: render
	go func() {
		log.Debugf("h.RunProgram RENDER")

		// wg.Add(1)
		defer wg.Done()

		for {
			// log.Debugf("h.RunProgram RENDER 1")
			select {
			case rs, _ := <-p.Render():
				// log.Debugf("h.RunProgram RENDER 2")
				h.terminal.Render(rs)
			case _, more := <-closing:
				if !more {
					return
				}
			}
		}
	}()

	// output: play
	go func() {
		log.Debugf("h.RunProgram PLAY")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram PLAY 1")
			select {
			case name, _ := <-p.Play():
				log.Debugf("h.RunProgram PLAY 2")
				h.wavs.Play(name)
			case _, more := <-closing:
				if !more {
					return
				}
			}
		}
	}()

	// output: yield
	go func() {
		log.Debugf("h.RunProgram YIELD")

		// wg.Add(1)
		defer wg.Done()

		for {
			log.Debugf("h.RunProgram YIELD 1")
			select {
			case <-p.Yield():
				log.Debugf("h.RunProgram YIELD 2")
				close(closing)
				return
				// go func() {
				// 	p.Close() <- struct{}{}
				// }()

				// h.terminal.Render(render.RenderState{})

				// active = (active + 1) % len(h.programs)
				// cur = h.programs[active].New()
			case _, more := <-closing:
				if !more {
					return
				}
			}
		}
	}()

	log.Debugf("h.RunProgram wg.Wait1")

	wg.Wait()

	log.Debugf("h.RunProgram wg.Wait2")

	return err
}
<|endoftext|>


<commit_before>// Package https is the supplement of the standard library `http`,
// not the protocal `https`.
package https

import (
	"fmt"
)

// HTTPError stands for a HTTP error.
type HTTPError struct {
	// The error information
	Err error

	// You can assign it any what you want.
	Flag int

	// You can place data into it to carry in an error.
	Data map[string]interface{}
}

// NewHTTPError returns a new HTTPError.
func NewHTTPError(flag int, err interface{}) error {
	switch err.(type) {
	case error:
	case []byte:
		err = fmt.Errorf("%s", string(err.([]byte)))
	default:
		err = fmt.Errorf("%v", err)
	}
	return HTTPError{Flag: flag, Err: err.(error)}
}

func (e HTTPError) Error() string {
	return e.Err.Error()
}
<commit_msg>Add HTTP error handler.<commit_after>// Package https is the supplement of the standard library `http`,
// not the protocal `https`.
package https

import (
	"fmt"
	"net/http"
	"os"
)

// HTTPError stands for a HTTP error.
type HTTPError struct {
	// The error information
	Err error

	// You can assign it any what you want.
	Flag int

	// You can place data into it to carry in an error.
	Data map[string]interface{}
}

// NewHTTPError returns a new HTTPError.
func NewHTTPError(flag int, err interface{}) error {
	switch err.(type) {
	case error:
	case []byte:
		err = fmt.Errorf("%s", string(err.([]byte)))
	default:
		err = fmt.Errorf("%v", err)
	}
	return HTTPError{Flag: flag, Err: err.(error)}
}

func (e HTTPError) Error() string {
	return e.Err.Error()
}

// ErrorLogFunc handles the http error log in ErrorHandler and
// ErrorHandlerWithStatusCode.
//
// Notice: The caller doesn't append the new line, so the function should
// append the new line.
var ErrorLogFunc func(format string, args ...interface{})

func init() {
	ErrorLogFunc = func(format string, args ...interface{}) {
		fmt.Fprintf(os.Stderr, format+"\n", args...)
	}
}

// ErrorHandler handles the error and responds it the client.
func ErrorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc {
	return ErrorHandlerWithStatusCode(func(w http.ResponseWriter,
		r *http.Request) (int, error) {
		return http.StatusInternalServerError, f(w, r)
	})
}

// ErrorHandlerWithStatusCode handles the error and responds it the client
// with the status code.
func ErrorHandlerWithStatusCode(f func(http.ResponseWriter, *http.Request) (
	int, error)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if code, err := f(w, r); err != nil {
			if code == 0 {
				code = http.StatusInternalServerError
			}
			http.Error(w, err.Error(), code)
			ErrorLogFunc("Handling %q: status=%d, err=%v", r.RequestURI, code, err)
		}
	}
}
<|endoftext|>


<commit_before>package rtime

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"io"
	"math"
	"math/rand"
	"time"

	"github.com/boltdb/bolt"
	"github.com/juju/errors"
)

var (
	boltdb      *bolt.DB
	ErrNotFound = errors.New("not found")
)

const (
	TFormat = "2006-01-02T15:04:05.999999"
)

func MustInitWriter(pth string) {
	var err error
	boltdb, err = bolt.Open(pth, 0600, nil)

	if err != nil {
		LOGGER.Error("db_open_failed", "err", errors.ErrorStack(err))
		panic(err)
	}
}

func Write(data []byte, p *packet) {
	err := boltdb.Update(func(tx *bolt.Tx) error {
		app, err := tx.CreateBucketIfNotExists([]byte(p.App))
		if err != nil {
			LOGGER.Error("cant_create_app_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		view, err := app.CreateBucketIfNotExists([]byte(p.Name))
		if err != nil {
			LOGGER.Error("cant_create_view_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		host, err := view.CreateBucketIfNotExists([]byte(p.Host))
		if err != nil {
			LOGGER.Error("cant_create_host_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		timings, err := host.CreateBucketIfNotExists([]byte("timings"))
		if err != nil {
			LOGGER.Error("cant_create_timings_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		jsons, err := host.CreateBucketIfNotExists([]byte("jsons"))
		if err != nil {
			LOGGER.Error("cant_create_jsons_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		ts := []byte(time.Now().Format(TFormat))
		LOGGER.Debug("key", "ts", string(ts))

		b := make([]byte, 8)
		binary.LittleEndian.PutUint64(b, p.OTime)

		err = timings.Put(ts, b)
		if err != nil {
			LOGGER.Error("cant_insert_timing", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		err = jsons.Put(ts, data)
		if err != nil {
			LOGGER.Error("cant_insert_json", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		LOGGER.Debug("inserted", "id", string(ts))
		return nil
	})

	if err != nil {
		LOGGER.Error("boltd_update_failed", "err", errors.ErrorStack(err))
	}
}

func ListViews(appname string) (views []string, err error) {
	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			app := tx.Bucket([]byte(appname))
			if app == nil {
				LOGGER.Error("unknown_app", "app", appname)
				return errors.New("unknown app")
			}

			return errors.Trace(
				app.ForEach(func(name, value []byte) error {
					if value == nil {
						views = append(views, string(name))
					}
					return nil
				}),
			)
		}),
	)
	return
}

type View struct {
	Name  string   `json:"name"`
	Hosts []string `json:"hosts"`
}

type App struct {
	Name  string  `json:"name"`
	Views []*View `json:"views"`
}

func ListApps() (apps []*App, err error) {
	apps = make([]*App, 0)
	err = boltdb.View(func(tx *bolt.Tx) error {
		err := tx.ForEach(func(name []byte, appb *bolt.Bucket) error {
			app := &App{Name: string(name), Views: []*View{}}
			apps = append(apps, app)

			err := appb.ForEach(func(name, value []byte) error {
				if value != nil {
					// should never happen
					return nil
				}
				view := &View{Name: string(name), Hosts: []string{}}
				LOGGER.Debug("found_view", "app", app.Name, "view", view.Name)
				app.Views = append(app.Views, view)

				err := appb.Bucket(name).ForEach(func(name, hostb []byte) error {
					if hostb != nil {
						// should never happen
						return nil
					}
					view.Hosts = append(view.Hosts, string(name))
					return nil
				})

				return errors.Trace(err)
			})

			return errors.Trace(err)
		})

		return errors.Trace(err)
	})

	return apps, errors.Trace(err)
}

func GetJson(app, view, host, ts string) (json []byte, err error) {
	LOGGER.Info("GetJson", "app", app, "view", view, "host", host, "ts", ts)
	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			appb := tx.Bucket([]byte(app))
			if appb == nil {
				LOGGER.Error("unknown_app", "app", app)
				return errors.New("unknown app")
			}

			viewb := appb.Bucket([]byte(view))
			if viewb == nil {
				LOGGER.Error("unknown_view", "app", app, "view", view)
				return errors.New("unknown view")
			}

			if host == "" {
				err := viewb.ForEach(func(name, value []byte) error {
					if value != nil {
						// should never happen
						return nil
					}

					fjson, err := get_json_from_host(viewb.Bucket(name), ts)
					if err != nil {
						if err != ErrNotFound {
							return errors.Trace(err)
						}
						return nil
					}

					json = fjson

					return nil
				})

				if err != nil {
					return errors.Trace(err)
				}
			} else {
				hostb := viewb.Bucket([]byte(host))
				if hostb == nil {
					LOGGER.Error(
						"unknown_host", "app", app, "view", view, "host", host,
					)
					return errors.New("unknown host")
				}

				fjson, err := get_json_from_host(hostb, ts)
				if err == nil && err != ErrNotFound {
					return errors.Trace(err)
				}

				json = fjson

				return nil
			}

			return nil
		}),
	)

	if json == nil && err == nil {
		err = ErrNotFound
	}

	return
}

func get_json_from_host(hostb *bolt.Bucket, ts string) ([]byte, error) {
	// .Get([]byte(ts))
	jsonb := hostb.Bucket([]byte("jsons"))
	if jsonb == nil {
		return nil, errors.New("host bucket has no jsons bucket")
	}

	data := jsonb.Get([]byte(ts))
	if data == nil {
		return nil, ErrNotFound
	}

	return data, nil
}

func UniqueID() string {
	u := make([]byte, 16)
	_, err := rand.Read(u)
	if err != nil {
		LOGGER.Error("rand_failed", "err", errors.ErrorStack(err))
	}
	return hex.EncodeToString(u)
}

type ViewData struct {
	timings []uint16
	app     string
	view    string
	host    string
	id      string
	ceiling uint64
	ids     []string  // not exported to clients
	created time.Time // not exported
}

func (vd *ViewData) writeTo(w io.Writer) error {
	LOGGER.Info("vd.id", "id", vd.id, "len", len(vd.id))
	for _, c := range vd.id {
		w.Write([]byte{byte(c), 0})
	}

	b := make([]byte, 8)
	binary.LittleEndian.PutUint64(b, vd.ceiling)
	w.Write(b)

	for _, v := range vd.timings {
		w.Write([]byte{byte(v % 256), byte(v / 256)})
	}

	return nil
}

func GetViewData(
	app, view, host, starts, ends string, floor, ceiling uint64,
) (*ViewData, error) {
	ids := []string{}
	timings := []uint64{}

	start, err := time.Parse(TFormat, starts)
	if err != nil {
		return nil, errors.Trace(err)
	}

	end, err := time.Parse(TFormat, ends)
	if err != nil {
		return nil, errors.Trace(err)
	}

	if end.Before(start) || end.UnixNano()-start.UnixNano() < 1024 {
		return nil, errors.New(
			fmt.Sprintf(
				"invalid start = %s, end = %s, close=%t",
				start.UnixNano(), end.UnixNano(), !end.Before(start),
			),
		)
	}

	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			appb := tx.Bucket([]byte(app))
			if appb == nil {
				LOGGER.Error("unknown_app", "app", app)
				return errors.New("unknown app")
			}

			viewb := appb.Bucket([]byte(view))
			if viewb == nil {
				LOGGER.Error("unknown_view", "app", app, "view", view)
				return errors.New("unknown view")
			}

			if host == "" {
				err := viewb.ForEach(func(name, value []byte) error {
					if value != nil {
						// should never happen
						return nil
					}

					var err error

					ids, timings, err = process_host(
						viewb.Bucket(name), ids, timings, starts, ends,
					)

					return errors.Trace(err)
				})

				if err != nil {
					return errors.Trace(err)
				}
			} else {
				hostb := viewb.Bucket([]byte(host))
				if hostb == nil {
					LOGGER.Error(
						"unknown_host", "app", app, "view", view, "host", host,
					)
					return errors.New("unknown host")
				}

				var err error
				ids, timings, err = process_host(hostb, ids, timings, starts, ends)

				return errors.Trace(err)
			}

			return nil
		}),
	)

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

	ceiling, tdigest, idigest := diget(start, end, ids, timings, floor, ceiling)
	LOGGER.Debug("digested", "tdigest", tdigest, "idigest", idigest)
	return &ViewData{
		timings: tdigest,
		app:     app,
		view:    view,
		host:    host,
		ids:     idigest,
		ceiling: ceiling,
		id:      UniqueID(),
		created: time.Now(),
	}, nil
}

func d2slot(snano, step uint64, dt string) uint16 {
	ts, err := time.Parse(TFormat, dt)
	if err != nil {
		LOGGER.Error("invalid_ts", "ts", dt)
		return 0
	}

	return uint16((uint64(ts.UnixNano()) - snano) / step)
}

func normalise(v, floor, ceiling uint64) uint8 {
	if v > ceiling {
		return 63
	}
	if v < floor {
		return 0
	}
	return uint8(math.Ceil(63 * (float64(v-floor) / float64(ceiling-floor))))
}

func pack(slot uint16, v uint8) uint16 {
	return uint16(v)*1024 + (slot % 1024)
}

func diget(
	start, end time.Time, ids []string, timings []uint64, floor, ceiling uint64,
) (uint64, []uint16, []string) {
	LOGGER.Info(
		"digest", "ids", ids, "timings", timings,
		"floor", floor, "ceiling", ceiling,
	)

	snano := uint64(start.UnixNano())
	step := (uint64(end.UnixNano()) - snano) / 1024

	tdigest := make([]uint16, 1024)
	idigest := make([]string, 1024)

	if ceiling == 0 {
		for _, v := range timings {
			if ceiling < v {
				ceiling = v
			}
		}
	}

	for i := range ids {
		slot := d2slot(snano, step, ids[i])
		idigest[slot] = ids[i]
		tdigest[slot] = pack(slot, normalise(timings[i], floor, ceiling))
	}

	return ceiling, tdigest, idigest
}

func process_host(
	hostb *bolt.Bucket, ids []string, timings []uint64, start, end string,
) ([]string, []uint64, error) {

	timingsb := hostb.Bucket([]byte("timings"))
	if timingsb == nil {
		LOGGER.Warn("no timings bucket")
		return ids, timings, nil
	}

	c := timingsb.Cursor()

	LOGGER.Debug("process_host", "start", start, "end", end)
	for k, v := c.Seek([]byte(start)); true; k, v = c.Next() {
		sk := string(k)
		if sk > end || sk == "" {
			break
		}

		if v == nil {
			// should never happen
			continue
		}

		ids = append(ids, sk)
		timings = append(timings, binary.LittleEndian.Uint64(v))
	}

	return ids, timings, nil
}

func JSON(id string) ([]byte, error) { return nil, nil }
<commit_msg>minor<commit_after>package rtime

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"io"
	"math"
	"math/rand"
	"time"

	"github.com/boltdb/bolt"
	"github.com/juju/errors"
)

var (
	boltdb      *bolt.DB
	ErrNotFound = errors.New("not found")
)

const (
	TFormat = "2006-01-02T15:04:05.999999"
)

func MustInitWriter(pth string) {
	var err error
	boltdb, err = bolt.Open(pth, 0600, nil)

	if err != nil {
		LOGGER.Error("db_open_failed", "err", errors.ErrorStack(err))
		panic(err)
	}
}

func Write(data []byte, p *packet) {
	err := boltdb.Update(func(tx *bolt.Tx) error {
		app, err := tx.CreateBucketIfNotExists([]byte(p.App))
		if err != nil {
			LOGGER.Error("cant_create_app_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		view, err := app.CreateBucketIfNotExists([]byte(p.Name))
		if err != nil {
			LOGGER.Error("cant_create_view_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		host, err := view.CreateBucketIfNotExists([]byte(p.Host))
		if err != nil {
			LOGGER.Error("cant_create_host_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		timings, err := host.CreateBucketIfNotExists([]byte("timings"))
		if err != nil {
			LOGGER.Error("cant_create_timings_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		jsons, err := host.CreateBucketIfNotExists([]byte("jsons"))
		if err != nil {
			LOGGER.Error("cant_create_jsons_bucket", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		ts := []byte(time.Now().Format(TFormat))
		LOGGER.Debug("key", "ts", string(ts))

		b := make([]byte, 8)
		binary.LittleEndian.PutUint64(b, p.OTime)

		err = timings.Put(ts, b)
		if err != nil {
			LOGGER.Error("cant_insert_timing", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		err = jsons.Put(ts, data)
		if err != nil {
			LOGGER.Error("cant_insert_json", "err", errors.ErrorStack(err))
			return errors.Trace(err)
		}

		LOGGER.Debug("inserted", "id", string(ts))
		return nil
	})

	if err != nil {
		LOGGER.Error("boltd_update_failed", "err", errors.ErrorStack(err))
	}
}

func ListViews(appname string) (views []string, err error) {
	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			app := tx.Bucket([]byte(appname))
			if app == nil {
				LOGGER.Error("unknown_app", "app", appname)
				return errors.New("unknown app")
			}

			return errors.Trace(
				app.ForEach(func(name, value []byte) error {
					if value == nil {
						views = append(views, string(name))
					}
					return nil
				}),
			)
		}),
	)
	return
}

type View struct {
	Name  string   `json:"name"`
	Hosts []string `json:"hosts"`
}

type App struct {
	Name  string  `json:"name"`
	Views []*View `json:"views"`
}

func ListApps() (apps []*App, err error) {
	apps = make([]*App, 0)
	err = boltdb.View(func(tx *bolt.Tx) error {
		err := tx.ForEach(func(name []byte, appb *bolt.Bucket) error {
			app := &App{Name: string(name), Views: []*View{}}
			apps = append(apps, app)

			err := appb.ForEach(func(name, value []byte) error {
				if value != nil {
					// should never happen
					return nil
				}
				view := &View{Name: string(name), Hosts: []string{}}
				LOGGER.Debug("found_view", "app", app.Name, "view", view.Name)
				app.Views = append(app.Views, view)

				err := appb.Bucket(name).ForEach(func(name, hostb []byte) error {
					if hostb != nil {
						// should never happen
						return nil
					}
					view.Hosts = append(view.Hosts, string(name))
					return nil
				})

				return errors.Trace(err)
			})

			return errors.Trace(err)
		})

		return errors.Trace(err)
	})

	return apps, errors.Trace(err)
}

func GetJson(app, view, host, ts string) (json []byte, err error) {
	LOGGER.Info("GetJson", "app", app, "view", view, "host", host, "ts", ts)
	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			appb := tx.Bucket([]byte(app))
			if appb == nil {
				LOGGER.Error("unknown_app", "app", app)
				return errors.New("unknown app")
			}

			viewb := appb.Bucket([]byte(view))
			if viewb == nil {
				LOGGER.Error("unknown_view", "app", app, "view", view)
				return errors.New("unknown view")
			}

			if host == "" {
				err := viewb.ForEach(func(name, value []byte) error {
					if value != nil {
						// should never happen
						return nil
					}

					fjson, err := get_json_from_host(viewb.Bucket(name), ts)
					if err != nil {
						if err != ErrNotFound {
							return errors.Trace(err)
						}
						return nil
					}

					json = fjson

					return nil
				})

				if err != nil {
					return errors.Trace(err)
				}
			} else {
				hostb := viewb.Bucket([]byte(host))
				if hostb == nil {
					LOGGER.Error(
						"unknown_host", "app", app, "view", view, "host", host,
					)
					return errors.New("unknown host")
				}

				fjson, err := get_json_from_host(hostb, ts)
				if err == nil && err != ErrNotFound {
					return errors.Trace(err)
				}

				json = fjson

				return nil
			}

			return nil
		}),
	)

	if json == nil && err == nil {
		err = ErrNotFound
	}

	return
}

func get_json_from_host(hostb *bolt.Bucket, ts string) ([]byte, error) {
	// .Get([]byte(ts))
	jsonb := hostb.Bucket([]byte("jsons"))
	if jsonb == nil {
		return nil, errors.New("host bucket has no jsons bucket")
	}

	data := jsonb.Get([]byte(ts))
	if data == nil {
		return nil, ErrNotFound
	}

	return data, nil
}

func UniqueID() string {
	u := make([]byte, 16)
	_, err := rand.Read(u)
	if err != nil {
		LOGGER.Error("rand_failed", "err", errors.ErrorStack(err))
	}
	return hex.EncodeToString(u)
}

type ViewData struct {
	timings []uint16
	app     string
	view    string
	host    string
	id      string
	ceiling uint64
	ids     []string  // not exported to clients
	created time.Time // not exported
}

func (vd *ViewData) writeTo(w io.Writer) error {
	LOGGER.Info("vd.id", "id", vd.id, "len", len(vd.id))
	for _, c := range vd.id {
		w.Write([]byte{byte(c), 0})
	}

	b := make([]byte, 8)
	binary.LittleEndian.PutUint64(b, vd.ceiling)
	w.Write(b)

	for _, v := range vd.timings {
		w.Write([]byte{byte(v % 256), byte(v / 256)})
	}

	return nil
}

func GetViewData(
	app, view, host, starts, ends string, floor, ceiling uint64,
) (*ViewData, error) {
	ids := []string{}
	timings := []uint64{}

	start, err := time.Parse(TFormat, starts)
	if err != nil {
		return nil, errors.Trace(err)
	}

	end, err := time.Parse(TFormat, ends)
	if err != nil {
		return nil, errors.Trace(err)
	}

	if end.Before(start) || end.UnixNano()-start.UnixNano() < 1024 {
		return nil, errors.New(
			fmt.Sprintf(
				"invalid start = %s, end = %s, close=%t",
				start.UnixNano(), end.UnixNano(), !end.Before(start),
			),
		)
	}

	err = errors.Trace(
		boltdb.View(func(tx *bolt.Tx) error {
			appb := tx.Bucket([]byte(app))
			if appb == nil {
				LOGGER.Error("unknown_app", "app", app)
				return errors.New("unknown app")
			}

			viewb := appb.Bucket([]byte(view))
			if viewb == nil {
				LOGGER.Error("unknown_view", "app", app, "view", view)
				return errors.New("unknown view")
			}

			if host == "" {
				err := viewb.ForEach(func(name, value []byte) error {
					if value != nil {
						// should never happen
						return nil
					}

					var err error

					ids, timings, err = process_host(
						viewb.Bucket(name), ids, timings, starts, ends,
					)

					return errors.Trace(err)
				})

				if err != nil {
					return errors.Trace(err)
				}
			} else {
				hostb := viewb.Bucket([]byte(host))
				if hostb == nil {
					LOGGER.Error(
						"unknown_host", "app", app, "view", view, "host", host,
					)
					return errors.New("unknown host")
				}

				var err error
				ids, timings, err = process_host(hostb, ids, timings, starts, ends)

				return errors.Trace(err)
			}

			return nil
		}),
	)

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

	ceiling, tdigest, idigest := diget(start, end, ids, timings, floor, ceiling)
	return &ViewData{
		timings: tdigest,
		app:     app,
		view:    view,
		host:    host,
		ids:     idigest,
		ceiling: ceiling,
		id:      UniqueID(),
		created: time.Now(),
	}, nil
}

func d2slot(snano, step uint64, dt string) uint16 {
	ts, err := time.Parse(TFormat, dt)
	if err != nil {
		LOGGER.Error("invalid_ts", "ts", dt)
		return 0
	}

	return uint16((uint64(ts.UnixNano()) - snano) / step)
}

func normalise(v, floor, ceiling uint64) uint8 {
	if v > ceiling {
		return 63
	}
	if v < floor {
		return 0
	}
	return uint8(math.Ceil(63 * (float64(v-floor) / float64(ceiling-floor))))
}

func pack(slot uint16, v uint8) uint16 {
	return uint16(v)*1024 + (slot % 1024)
}

func diget(
	start, end time.Time, ids []string, timings []uint64, floor, ceiling uint64,
) (uint64, []uint16, []string) {
	snano := uint64(start.UnixNano())
	step := (uint64(end.UnixNano()) - snano) / 1024

	tdigest := make([]uint16, 1024)
	idigest := make([]string, 1024)

	if ceiling == 0 {
		for _, v := range timings {
			if ceiling < v {
				ceiling = v
			}
		}
	}

	for i := range ids {
		slot := d2slot(snano, step, ids[i])
		idigest[slot] = ids[i]
		tdigest[slot] = pack(slot, normalise(timings[i], floor, ceiling))
	}

	return ceiling, tdigest, idigest
}

func process_host(
	hostb *bolt.Bucket, ids []string, timings []uint64, start, end string,
) ([]string, []uint64, error) {

	timingsb := hostb.Bucket([]byte("timings"))
	if timingsb == nil {
		LOGGER.Warn("no timings bucket")
		return ids, timings, nil
	}

	c := timingsb.Cursor()

	LOGGER.Debug("process_host", "start", start, "end", end)
	for k, v := c.Seek([]byte(start)); true; k, v = c.Next() {
		sk := string(k)
		if sk > end || sk == "" {
			break
		}

		if v == nil {
			// should never happen
			continue
		}

		ids = append(ids, sk)
		timings = append(timings, binary.LittleEndian.Uint64(v))
	}

	return ids, timings, nil
}

func JSON(id string) ([]byte, error) { return nil, nil }
<|endoftext|>


<commit_before>package broker

import (
	"fmt"
	"net/url"
	"sync"

	"github.com/apoydence/talaria/logging"
)

type connInfo struct {
	URL  string
	conn *syncedConnection
}

type Client struct {
	log          logging.Logger
	syncFetchIdx sync.Mutex
	nextFetchIdx uint64

	syncFileIds sync.RWMutex
	fileIds     map[uint64]*connInfo

	conns []*connInfo
}

func NewClient(URLs ...string) (*Client, error) {
	log := logging.Log("Client")
	var conns []*connInfo
	for _, URL := range URLs {
		verifyUrl(URL, log)
		conn, err := NewConnection(URL)
		if err != nil {
			return nil, err
		}
		conns = append(conns, &connInfo{
			URL:  URL,
			conn: newSyncedConnection(conn),
		})
	}

	return &Client{
		log:     log,
		conns:   conns,
		fileIds: make(map[uint64]*connInfo),
	}, nil
}

func (c *Client) FetchFile(name string) (uint64, error) {
	fileId := c.getNextFetchIdx()
	conn := c.conns[int(fileId)%len(c.conns)]
	err := conn.conn.FetchFile(fileId, name)
	if err == nil {
		c.saveFileId(fileId, conn)
		return fileId, nil
	}

	if err.Uri == "" {
		return 0, fmt.Errorf(err.errMessage)
	}

	conn = c.fetchConnection(err.Uri)
	if conn == nil {
		return 0, fmt.Errorf("Unknown broker: %s", err.Uri)
	}

	err = conn.conn.FetchFile(fileId, name)
	if err == nil {
		c.saveFileId(fileId, conn)
		return fileId, nil
	}

	return 0, fmt.Errorf(err.Error())
}

func (c *Client) Close() {
	for _, info := range c.conns {
		info.conn.Close()
	}
}

func (c *Client) WriteToFile(fileId uint64, data []byte) (int64, error) {
	conn := c.fetchConnectionById(fileId)
	if conn == nil {
		return 0, fmt.Errorf("Unknown fileId: %d", fileId)
	}

	return conn.conn.WriteToFile(fileId, data)
}

func (c *Client) ReadFromFile(fileId uint64) ([]byte, error) {
	conn := c.fetchConnectionById(fileId)
	if conn == nil {
		return nil, fmt.Errorf("Unknown fileId: %d", fileId)
	}

	return conn.conn.ReadFromFile(fileId)
}

func (c *Client) saveFileId(fileId uint64, conn *connInfo) {
	c.syncFileIds.Lock()
	defer c.syncFileIds.Unlock()
	c.fileIds[fileId] = conn
}

func (c *Client) fetchConnectionById(fileId uint64) *connInfo {
	c.syncFileIds.RLock()
	defer c.syncFileIds.RUnlock()
	conn, ok := c.fileIds[fileId]
	if !ok {
		return nil
	}

	return conn
}

func (c *Client) getNextFetchIdx() uint64 {
	c.syncFetchIdx.Lock()
	defer func() {
		c.nextFetchIdx++
		c.syncFetchIdx.Unlock()
	}()
	return c.nextFetchIdx
}

func (c *Client) fetchConnection(URL string) *connInfo {
	for _, info := range c.conns {
		if info.URL[2:] == URL[4:] {
			return info
		}
	}
	return nil
}

func verifyUrl(URL string, log logging.Logger) {
	u, _ := url.Parse(URL)
	if u == nil || u.Host == "" {
		log.Panicf("Invalid URL: %s", URL)
	}
}
<commit_msg>Updates error message to match other messages<commit_after>package broker

import (
	"fmt"
	"net/url"
	"sync"

	"github.com/apoydence/talaria/logging"
)

type connInfo struct {
	URL  string
	conn *syncedConnection
}

type Client struct {
	log          logging.Logger
	syncFetchIdx sync.Mutex
	nextFetchIdx uint64

	syncFileIds sync.RWMutex
	fileIds     map[uint64]*connInfo

	conns []*connInfo
}

func NewClient(URLs ...string) (*Client, error) {
	log := logging.Log("Client")
	var conns []*connInfo
	for _, URL := range URLs {
		verifyUrl(URL, log)
		conn, err := NewConnection(URL)
		if err != nil {
			return nil, err
		}
		conns = append(conns, &connInfo{
			URL:  URL,
			conn: newSyncedConnection(conn),
		})
	}

	return &Client{
		log:     log,
		conns:   conns,
		fileIds: make(map[uint64]*connInfo),
	}, nil
}

func (c *Client) FetchFile(name string) (uint64, error) {
	fileId := c.getNextFetchIdx()
	conn := c.conns[int(fileId)%len(c.conns)]
	err := conn.conn.FetchFile(fileId, name)
	if err == nil {
		c.saveFileId(fileId, conn)
		return fileId, nil
	}

	if err.Uri == "" {
		return 0, fmt.Errorf(err.errMessage)
	}

	conn = c.fetchConnection(err.Uri)
	if conn == nil {
		return 0, fmt.Errorf("Unknown broker: %s", err.Uri)
	}

	err = conn.conn.FetchFile(fileId, name)
	if err == nil {
		c.saveFileId(fileId, conn)
		return fileId, nil
	}

	return 0, fmt.Errorf(err.Error())
}

func (c *Client) Close() {
	for _, info := range c.conns {
		info.conn.Close()
	}
}

func (c *Client) WriteToFile(fileId uint64, data []byte) (int64, error) {
	conn := c.fetchConnectionById(fileId)
	if conn == nil {
		return 0, fmt.Errorf("Unknown file ID: %d", fileId)
	}

	return conn.conn.WriteToFile(fileId, data)
}

func (c *Client) ReadFromFile(fileId uint64) ([]byte, error) {
	conn := c.fetchConnectionById(fileId)
	if conn == nil {
		return nil, fmt.Errorf("Unknown file ID: %d", fileId)
	}

	return conn.conn.ReadFromFile(fileId)
}

func (c *Client) saveFileId(fileId uint64, conn *connInfo) {
	c.syncFileIds.Lock()
	defer c.syncFileIds.Unlock()
	c.fileIds[fileId] = conn
}

func (c *Client) fetchConnectionById(fileId uint64) *connInfo {
	c.syncFileIds.RLock()
	defer c.syncFileIds.RUnlock()
	conn, ok := c.fileIds[fileId]
	if !ok {
		return nil
	}

	return conn
}

func (c *Client) getNextFetchIdx() uint64 {
	c.syncFetchIdx.Lock()
	defer func() {
		c.nextFetchIdx++
		c.syncFetchIdx.Unlock()
	}()
	return c.nextFetchIdx
}

func (c *Client) fetchConnection(URL string) *connInfo {
	for _, info := range c.conns {
		if info.URL[2:] == URL[4:] {
			return info
		}
	}
	return nil
}

func verifyUrl(URL string, log logging.Logger) {
	u, _ := url.Parse(URL)
	if u == nil || u.Host == "" {
		log.Panicf("Invalid URL: %s", URL)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/sociam/xray-archiver/pipeline/util"
)

// AndroidManifest is a struct representing the interesting parts of the
// AndroidManifest.xml in APKs
type AndroidManifest struct {
	Package     string            `xml:"package,attr"`
	Perms       []util.Permission `xml:"uses-permission"`
	Sdk23Perms  []util.Permission `xml:"uses-permission-sdk-23"`
	Application manifestApp       `xml:"application"`
}

type manifestApp struct {
	Icon string `xml:"icon,attr"`
}

func parseManifest(app *util.App) (manifest *AndroidManifest, gotIcon bool, err error) {
	manifest = &AndroidManifest{}
	manifestFile, err := os.Open(path.Join(app.OutDir(), "AndroidManifest.xml"))
	if err != nil {
		return nil, false, err
	}
	bytes, err := ioutil.ReadAll(manifestFile)
	if err != nil {
		return nil, false, err
	}
	err = xml.Unmarshal(bytes, manifest)
	if err != nil {
		return nil, false, err
	}

	if manifest.Package != "" {
		app.ID = manifest.Package
	}

	split := strings.SplitN(manifest.Application.Icon, "/", 2)
	locn, name := split[0], split[1]
	locn = path.Join(app.OutDir(), "res", locn[1:]) // /tmp/<outdir>/res/{mipmap,drawable}
	name = name + ".png"                            // icon_katana.png

	var matches []string
	if matches, err = filepath.Glob(path.Join(locn+"-*xxxdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*xxdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*xdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*hdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*tvdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*mdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"*", name)); err == nil && len(matches) > 0 {
	} else {
		return manifest, false, nil
	}

	err = os.Rename(matches[0], path.Join(app.AppDir(), "icon.png"))
	if err != nil {
		fmt.Printf("Failed to rename icon %s to %s", matches[0], path.Join(app.AppDir(), "icon.png"))
		return manifest, false, nil
	}

	return manifest, true, nil
}

func (manifest *AndroidManifest) getPerms() []util.Permission {
	return append(manifest.Perms, manifest.Sdk23Perms...)
}

type company struct {
	ID           string   `json:"id"`
	Name         string   `json:"company"`
	Domains      []string `json:"domains"`
	Founded      string   `json:"founded"`
	Acquired     string   `json:"acquired in"`
	CType        string   `json:"type"`
	TypeTag      string   `json:"typetag"`
	Jurisdiction string   `json:"jurisdiction_code"`
	Parent       string   `json:"parent"`
	Capital      string   `json:"capital"`
	Equity       string   `json:"equity"`
	Size         string   `json:"size"`
	DataSource   string   `json:"data source"`
	Description  string   `json:"description"`
}

func simpleAnalyze(app *util.App) ([]string, error) {
	//TODO: fix error handling

	//TODO: replace with DB calls
	var companies map[string]company
	companyFile, err := os.Open(path.Join(util.Cfg.DataDir, "company_details.json"))
	if err != nil {
		return []string{}, err
	}
	bytes, err := ioutil.ReadAll(companyFile)
	if err != nil {
		return []string{}, err
	}
	err = json.Unmarshal(bytes, &companies)
	if err != nil {
		return []string{}, err
	}

	for name := range companies {
		if _, ok := trackers[name]; !ok {
			delete(companies, name)
		}
	}

	// getDomainCo := func(host string) *string {
	// 	for _, company := range companies {
	// 		for _, domain := range company.domains {
	// 			if strings.Contains(host, domain) {
	// 				return &company.id
	// 			}
	// 		}
	// 	}
	// 	return nil
	// }

	cmd := exec.Command("grep", "-Erho", "\"https?://[^ >]+\"",
		path.Join(app.OutDir(), "smali", "**", "*.smali"))
	out, err := cmd.Output()
	if err != nil {
		return []string{}, err
	}
	urls := strings.Split(string(out), "\n")

	// var appTrackers []string

	// irrelevant := []string{"app", "identity", "n/a", "other", "", "library"}
	// for name, company := range companies {
	// 	for _, domain := range company.Domains {
	// 		if strings.Contains(string(urls), string(domain)) {
	// 			toAppend := true
	// 			for _, cat := range irrelevant {
	// 				if companies[name].TypeTag == cat {
	// 					toAppend = false
	// 					break
	// 				}
	// 			}
	// 			if toAppend {
	// 				appTrackers = append(appTrackers, name)
	// 			}
	// 		}
	// 	}
	// }

	return urls, nil
}

func findPackages(app *util.App) ([]string, error) {
	// TODO: fix error handling
	paths := make(map[string]util.Unit)
	err := os.Chdir(path.Join(app.OutDir(), "smali"))
	if err != nil {
		return []string{}, err
	}

	err = filepath.Walk(".",
		func(fname string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if path.Ext(fname) == ".smali" {
				paths[path.Dir(fname)] = unit
			}

			return nil
		})

	pkgs := make([]string, 0, 20)
	for path := range paths {
		//pkg := strings.Replace(path, string(os.PathSeparator), ".", -1)
		pkg := strings.Map(func(ch rune) rune {
			if ch == os.PathSeparator {
				return '.'
			}
			return ch
		}, path)
		pkgs = append(pkgs, pkg)
	}

	return pkgs, err
}
<commit_msg>Hosts db Correct merge! (#28)<commit_after>package main

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/sociam/xray-archiver/pipeline/util"
)

// AndroidManifest is a struct representing the interesting parts of the
// AndroidManifest.xml in APKs
type AndroidManifest struct {
	Package     string            `xml:"package,attr"`
	Perms       []util.Permission `xml:"uses-permission"`
	Sdk23Perms  []util.Permission `xml:"uses-permission-sdk-23"`
	Application manifestApp       `xml:"application"`
}

type manifestApp struct {
	Icon string `xml:"icon,attr"`
}

func parseManifest(app *util.App) (manifest *AndroidManifest, gotIcon bool, err error) {
	manifest = &AndroidManifest{}
	manifestFile, err := os.Open(path.Join(app.OutDir(), "AndroidManifest.xml"))
	if err != nil {
		return nil, false, err
	}
	bytes, err := ioutil.ReadAll(manifestFile)
	if err != nil {
		return nil, false, err
	}
	err = xml.Unmarshal(bytes, manifest)
	if err != nil {
		return nil, false, err
	}

	if manifest.Package != "" {
		app.ID = manifest.Package
	}

	split := strings.SplitN(manifest.Application.Icon, "/", 2)
	locn, name := split[0], split[1]
	locn = path.Join(app.OutDir(), "res", locn[1:]) // /tmp/<outdir>/res/{mipmap,drawable}
	name = name + ".png"                            // icon_katana.png

	var matches []string
	if matches, err = filepath.Glob(path.Join(locn+"-*xxxdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*xxdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*xdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*hdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*tvdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"-*mdpi*", name)); err == nil && len(matches) > 0 {
	} else if matches, err = filepath.Glob(path.Join(locn+"*", name)); err == nil && len(matches) > 0 {
	} else {
		return manifest, false, nil
	}

	err = os.Rename(matches[0], path.Join(app.AppDir(), "icon.png"))
	if err != nil {
		fmt.Printf("Failed to rename icon %s to %s", matches[0], path.Join(app.AppDir(), "icon.png"))
		return manifest, false, nil
	}

	return manifest, true, nil
}

func (manifest *AndroidManifest) getPerms() []util.Permission {
	return append(manifest.Perms, manifest.Sdk23Perms...)
}

type company struct {
	ID           string   `json:"id"`
	Name         string   `json:"company"`
	Domains      []string `json:"domains"`
	Founded      string   `json:"founded"`
	Acquired     string   `json:"acquired in"`
	CType        string   `json:"type"`
	TypeTag      string   `json:"typetag"`
	Jurisdiction string   `json:"jurisdiction_code"`
	Parent       string   `json:"parent"`
	Capital      string   `json:"capital"`
	Equity       string   `json:"equity"`
	Size         string   `json:"size"`
	DataSource   string   `json:"data source"`
	Description  string   `json:"description"`
}

func simpleAnalyze(app *util.App) ([]string, error) {
	//TODO: fix error handling

	//TODO: replace with DB calls
	var companies map[string]company
	companyFile, err := os.Open(path.Join(util.Cfg.DataDir, "company_details.json"))
	if err != nil {
		return []string{}, err
	}
	bytes, err := ioutil.ReadAll(companyFile)
	if err != nil {
		return []string{}, err
	}
	err = json.Unmarshal(bytes, &companies)
	if err != nil {
		return []string{}, err
	}

	for name := range companies {
		if _, ok := trackers[name]; !ok {
			delete(companies, name)
		}
	}

	// getDomainCo := func(host string) *string {
	// 	for _, company := range companies {
	// 		for _, domain := range company.domains {
	// 			if strings.Contains(host, domain) {
	// 				return &company.id
	// 			}
	// 		}
	// 	}
	// 	return nil
	// }

	cmd := exec.Command("sh", "-c", "grep", "-Erho", "\"https?://[^ >]+\"", "--", path.Join(app.OutDir(), "smali/**/*.smali"))

	out, err := cmd.Output()
	if err != nil {
		return []string{}, err
	}
	urls := strings.Split(string(out), "\n")

	// var appTrackers []string

	// irrelevant := []string{"app", "identity", "n/a", "other", "", "library"}
	// for name, company := range companies {
	// 	for _, domain := range company.Domains {
	// 		if strings.Contains(string(urls), string(domain)) {
	// 			toAppend := true
	// 			for _, cat := range irrelevant {
	// 				if companies[name].TypeTag == cat {
	// 					toAppend = false
	// 					break
	// 				}
	// 			}
	// 			if toAppend {
	// 				appTrackers = append(appTrackers, name)
	// 			}
	// 		}
	// 	}
	// }

	return urls, nil
}

func findPackages(app *util.App) ([]string, error) {
	// TODO: fix error handling
	paths := make(map[string]util.Unit)
	err := os.Chdir(path.Join(app.OutDir(), "smali"))
	if err != nil {
		return []string{}, err
	}

	err = filepath.Walk(".",
		func(fname string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if path.Ext(fname) == ".smali" {
				paths[path.Dir(fname)] = unit
			}

			return nil
		})

	pkgs := make([]string, 0, 20)
	for path := range paths {
		//pkg := strings.Replace(path, string(os.PathSeparator), ".", -1)
		pkg := strings.Map(func(ch rune) rune {
			if ch == os.PathSeparator {
				return '.'
			}
			return ch
		}, path)
		pkgs = append(pkgs, pkg)
	}

	return pkgs, err
}
<|endoftext|>


<commit_before>// 2014 Iain Shigeoka - BSD license (see LICENSE)
package cli_test

import (
	. "github.com/gopackage/cli"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Argument Parsing", func() {

	BeforeEach(func() {
		// Nothing to do yet
	})

	Describe("Option parsing", func() {
		Context("with a single short option flag", func() {
			option := NewOption(nil, "-v", "display version information")
			It("should have a short but no long option", func() {
				Ω(option.Short).Should(Equal("-v"))
				Ω(option.Long).Should(Equal(""))
				Ω(option.Required).Should(BeFalse())
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("display version information"))
			})
		})
		Context("with short and long option flag", func() {
			option := NewOption(nil, "-v, --version", "display version information")
			It("should have both short and long options", func() {
				Ω(option.Short).Should(Equal("-v"))
				Ω(option.Long).Should(Equal("--version"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("display version information"))

			})
		})
		Context("with a required option parameter", func() {
			option := NewOption(nil, "-c, --config <path>", "set configuration file")
			It("should require an option parameter", func() {
				Ω(option.Short).Should(Equal("-c"))
				Ω(option.Long).Should(Equal("--config"))
				Ω(option.Required).Should(Equal(true))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("set configuration file"))
			})
		})
		Context("with an optional option parameter", func() {
			option := NewOption(nil, "-c, --config [path]", "set configuration file")
			It("should support the optional parameter", func() {
				Ω(option.Short).Should(Equal("-c"))
				Ω(option.Long).Should(Equal("--config"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(true))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("set configuration file"))
			})
		})
		Context("with an option flag (bool)", func() {
			option := NewOption(nil, "-T, --no-tests", "ignore tests")
			It("should contain a flag option", func() {
				Ω(option.Short).Should(Equal("-T"))
				Ω(option.Long).Should(Equal("--no-tests"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(true))
				Ω(option.Description).Should(Equal("ignore tests"))
			})
		})
	})

	Describe("Command parsing", func() {
		Context("with a simple command", func() {
			command := NewCommand(nil, "foo", "bar")
			It("should not expect parameters", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(command.Description).Should(Equal("bar"))
			})
		})
		Context("with a required parameter", func() {
			command := NewCommand(nil, "foo <bar>", "a foo bar command")
			It("should require a single parameter", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("bar"))
				Ω(command.Args[0].Required).Should(Equal(true))
				Ω(command.Description).Should(Equal("a foo bar command"))
			})
		})
		Context("with an optional parameter", func() {
			command := NewCommand(nil, "foo [bar]", "a foo bar command")
			It("should support an optional parameter", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("bar"))
				Ω(command.Args[0].Required).Should(Equal(false))
				Ω(command.Description).Should(Equal("a foo bar command"))
			})
		})
	})
	Describe("Normalizing arguments", func() {
		Context("with a simple string", func() {
			normalized := Normalize([]string{"help"})
			It("should return the string", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("help"))
			})
		})
		Context("with a simple number", func() {
			normalized := Normalize([]string{"8"})
			It("should return the number", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("8"))
			})
		})
		Context("with a single short option", func() {
			normalized := Normalize([]string{"-v"})
			It("should return the option", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("-v"))
			})
		})
		Context("with a single long option", func() {
			normalized := Normalize([]string{"--version"})
			It("should return the option", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("--version"))
			})
		})
		Context("with three short options together", func() {
			normalized := Normalize([]string{"-abc"})
			It("should return the three short options separately", func() {
				Ω(len(normalized)).Should(Equal(3))
				Ω(normalized[0]).Should(Equal("-a"))
				Ω(normalized[1]).Should(Equal("-b"))
				Ω(normalized[2]).Should(Equal("-c"))
			})
		})
		Context("with an option and parameter", func() {
			normalized := Normalize([]string{"--port", "8080"})
			It("should return the long option and parameter separately", func() {
				Ω(len(normalized)).Should(Equal(2))
				Ω(normalized[0]).Should(Equal("--port"))
				Ω(normalized[1]).Should(Equal("8080"))
			})
		})
		Context("with an option and parameter connected with an '='", func() {
			normalized := Normalize([]string{"--port=8080"})
			It("should return the long option and parameter separately", func() {
				Ω(len(normalized)).Should(Equal(2))
				Ω(normalized[0]).Should(Equal("--port"))
				Ω(normalized[1]).Should(Equal("8080"))
			})
		})
	})
	Describe("OptionFor", func() {
		Context("with a single option added", func() {
			program := New()
			It("should retrieve options by short and long flags", func() {
				option := program.OptionFor("-v")
				Ω(option).Should(BeNil())

				program.Option("-v, --version", "display option")

				option = program.OptionFor("-v")
				Ω(option.Name).Should(Equal("version"))

				option = program.OptionFor("--version")
				Ω(option.Name).Should(Equal("version"))

				option = program.OptionFor("-f")
				Ω(option).Should(BeNil())
				option = program.OptionFor("--foo")
				Ω(option).Should(BeNil())
			})
		})
	})
	Describe("ParseOptions", func() {
		Context("with an argument and no configured options", func() {
			program := New()
			args, unknown := program.ParseOptions([]string{"help"})
			It("should leave the argument as-is (left in args[])", func() {
				Ω(len(args)).Should(Equal(1))
				Ω(args[0]).Should(Equal("help"))
				Ω(len(unknown)).Should(Equal(0))
			})
		})
		Context("with a long option and no configured options", func() {
			program := New()
			args, unknown := program.ParseOptions([]string{"--foo"})
			It("should not match the option (add to unknown[] list)", func() {
				Ω(len(args)).Should(Equal(0))
				Ω(len(unknown)).Should(Equal(1))
				Ω(unknown[0]).Should(Equal("--foo"))
			})
		})
		Context("with a long option that matches a configured option", func() {
			program := New()
			program.Option("-f, --foo", "add a foo", "")
			args, unknown := program.ParseOptions([]string{"--foo"})
			option := program.OptionFor("--foo")
			It("should match the option", func() {
				Ω(len(args)).Should(Equal(0))
				Ω(len(unknown)).Should(Equal(0))
				Ω(option.Value).Should(Equal("true"))
			})
		})
	})
	Describe("ParseNormalizedArgs", func() {
		Context("with a configured program", func() {

			program := New()
			program.SetDescription("Device troubleshooting tool")
			program.Option("-v, --verbose", "display verbose information")
			program.Command("tcp <port>", "capture TCP packets on <port>").Option("-h, --host", "host address to bind to")
			program.Topic("path", "setting the path for reading")

			It("should not match unrecognized 'help' command", func() {
				command := program.ParseNormalizedArgs([]string{"help"}, []string{})

				Ω(command).Should(BeNil())
			})
			It("should parse command with required argument", func() {

				command := program.ParseNormalizedArgs([]string{"tcp", "8080"}, []string{})

				Ω(command.Command).Should(Equal("tcp"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("port"))
				Ω(command.Args[0].Value).Should(Equal("8080"))
			})
		})
	})

})
<commit_msg>Add a second test<commit_after>// 2014 Iain Shigeoka - BSD license (see LICENSE)
package cli_test

import (
	. "github.com/gopackage/cli"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Argument Parsing", func() {

	BeforeEach(func() {
		// Nothing to do yet
	})

	Describe("Option parsing", func() {
		Context("with a single short option flag", func() {
			option := NewOption(nil, "-v", "display version information")
			It("should have a short but no long option", func() {
				Ω(option.Short).Should(Equal("-v"))
				Ω(option.Long).Should(Equal(""))
				Ω(option.Required).Should(BeFalse())
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("display version information"))
			})
		})
		Context("with short and long option flag", func() {
			option := NewOption(nil, "-v, --version", "display version information")
			It("should have both short and long options", func() {
				Ω(option.Short).Should(Equal("-v"))
				Ω(option.Long).Should(Equal("--version"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("display version information"))

			})
		})
		Context("with a required option parameter", func() {
			option := NewOption(nil, "-c, --config <path>", "set configuration file")
			It("should require an option parameter", func() {
				Ω(option.Short).Should(Equal("-c"))
				Ω(option.Long).Should(Equal("--config"))
				Ω(option.Required).Should(Equal(true))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("set configuration file"))
			})
		})
		Context("with an optional option parameter", func() {
			option := NewOption(nil, "-c, --config [path]", "set configuration file")
			It("should support the optional parameter", func() {
				Ω(option.Short).Should(Equal("-c"))
				Ω(option.Long).Should(Equal("--config"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(true))
				Ω(option.Bool).Should(Equal(false))
				Ω(option.Description).Should(Equal("set configuration file"))
			})
		})
		Context("with an option flag (bool)", func() {
			option := NewOption(nil, "-T, --no-tests", "ignore tests")
			It("should contain a flag option", func() {
				Ω(option.Short).Should(Equal("-T"))
				Ω(option.Long).Should(Equal("--no-tests"))
				Ω(option.Required).Should(Equal(false))
				Ω(option.Optional).Should(Equal(false))
				Ω(option.Bool).Should(Equal(true))
				Ω(option.Description).Should(Equal("ignore tests"))
			})
		})
	})

	Describe("Command parsing", func() {
		Context("with a simple command", func() {
			command := NewCommand(nil, "foo", "bar")
			It("should not expect parameters", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(command.Description).Should(Equal("bar"))
			})
		})
		Context("with a required parameter", func() {
			command := NewCommand(nil, "foo <bar>", "a foo bar command")
			It("should require a single parameter", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("bar"))
				Ω(command.Args[0].Required).Should(Equal(true))
				Ω(command.Description).Should(Equal("a foo bar command"))
			})
		})
		Context("with an optional parameter", func() {
			command := NewCommand(nil, "foo [bar]", "a foo bar command")
			It("should support an optional parameter", func() {
				Ω(command.Command).Should(Equal("foo"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("bar"))
				Ω(command.Args[0].Required).Should(Equal(false))
				Ω(command.Description).Should(Equal("a foo bar command"))
			})
		})
	})
	Describe("Normalizing arguments", func() {
		Context("with a simple string", func() {
			normalized := Normalize([]string{"help"})
			It("should return the string", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("help"))
			})
		})
		Context("with a simple number", func() {
			normalized := Normalize([]string{"8"})
			It("should return the number", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("8"))
			})
		})
		Context("with a single short option", func() {
			normalized := Normalize([]string{"-v"})
			It("should return the option", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("-v"))
			})
		})
		Context("with a single long option", func() {
			normalized := Normalize([]string{"--version"})
			It("should return the option", func() {
				Ω(len(normalized)).Should(Equal(1))
				Ω(normalized[0]).Should(Equal("--version"))
			})
		})
		Context("with three short options together", func() {
			normalized := Normalize([]string{"-abc"})
			It("should return the three short options separately", func() {
				Ω(len(normalized)).Should(Equal(3))
				Ω(normalized[0]).Should(Equal("-a"))
				Ω(normalized[1]).Should(Equal("-b"))
				Ω(normalized[2]).Should(Equal("-c"))
			})
		})
		Context("with an option and parameter", func() {
			normalized := Normalize([]string{"--port", "8080"})
			It("should return the long option and parameter separately", func() {
				Ω(len(normalized)).Should(Equal(2))
				Ω(normalized[0]).Should(Equal("--port"))
				Ω(normalized[1]).Should(Equal("8080"))
			})
		})
		Context("with an option and parameter connected with an '='", func() {
			normalized := Normalize([]string{"--port=8080"})
			It("should return the long option and parameter separately", func() {
				Ω(len(normalized)).Should(Equal(2))
				Ω(normalized[0]).Should(Equal("--port"))
				Ω(normalized[1]).Should(Equal("8080"))
			})
		})
	})
	Describe("OptionFor", func() {
		Context("with a single option added", func() {
			program := New()
			It("should retrieve options by short and long flags", func() {
				option := program.OptionFor("-v")
				Ω(option).Should(BeNil())

				program.Option("-v, --version", "display option")

				option = program.OptionFor("-v")
				Ω(option.Name).Should(Equal("version"))

				option = program.OptionFor("--version")
				Ω(option.Name).Should(Equal("version"))

				option = program.OptionFor("-f")
				Ω(option).Should(BeNil())
				option = program.OptionFor("--foo")
				Ω(option).Should(BeNil())
			})
		})
	})
	Describe("ParseOptions", func() {
		Context("with an argument and no configured options", func() {
			program := New()
			args, unknown := program.ParseOptions([]string{"help"})
			It("should leave the argument as-is (left in args[])", func() {
				Ω(len(args)).Should(Equal(1))
				Ω(args[0]).Should(Equal("help"))
				Ω(len(unknown)).Should(Equal(0))
			})
		})
		Context("with a long option and no configured options", func() {
			program := New()
			args, unknown := program.ParseOptions([]string{"--foo"})
			It("should not match the option (add to unknown[] list)", func() {
				Ω(len(args)).Should(Equal(0))
				Ω(len(unknown)).Should(Equal(1))
				Ω(unknown[0]).Should(Equal("--foo"))
			})
		})
		Context("with a long option that matches a configured option", func() {
			program := New()
			program.Option("-f, --foo", "add a foo", "")
			args, unknown := program.ParseOptions([]string{"--foo"})
			option := program.OptionFor("--foo")
			It("should match the option", func() {
				Ω(len(args)).Should(Equal(0))
				Ω(len(unknown)).Should(Equal(0))
				Ω(option.Value).Should(Equal("true"))
			})
		})
	})
	Describe("ParseNormalizedArgs", func() {
		Context("with a configured single argument program", func() {

			program := New()
			program.SetDescription("Device troubleshooting tool")
			program.Option("-v, --verbose", "display verbose information")
			program.Command("tcp <port>", "capture TCP packets on <port>").Option("-h, --host", "host address to bind to")
			program.Topic("path", "setting the path for reading")

			It("should not match unrecognized 'help' command", func() {
				command := program.ParseNormalizedArgs([]string{"help"}, []string{})

				Ω(command).Should(BeNil())
			})
			It("should parse command with required argument", func() {

				command := program.ParseNormalizedArgs([]string{"tcp", "8080"}, []string{})

				Ω(command).ShouldNot(BeNil())
				Ω(command.Command).Should(Equal("tcp"))
				Ω(len(command.Args)).Should(Equal(1))
				Ω(command.Args[0].Name).Should(Equal("port"))
				Ω(command.Args[0].Value).Should(Equal("8080"))
				Ω(command.ArgFor("port").IntValue(0)).Should(Equal(8080))
			})
		})
		Context("with a configured two argument program", func() {

			program := New()
			program.SetDescription("Status display tool")
			program.Option("-v, --verbose", "display verbose information")
			program.Command("status <color1> <color2>", "display two colors on the status board").Option("-t, --timeout", "maximum time the test will wait for response")
			program.Topic("path", "setting the path for reading")

			It("should parse command with required argument", func() {

				command := program.ParseArgs([]string{"exe", "status", "#RGB", "#RRGGBB"})

				Ω(command).ShouldNot(BeNil())
				Ω(command.Command).Should(Equal("status"))
				Ω(len(command.Args)).Should(Equal(2))
				Ω(command.Args[0].Name).Should(Equal("color1"))
				Ω(command.Args[0].Value).Should(Equal("#RGB"))
				Ω(command.Args[1].Name).Should(Equal("color2"))
				Ω(command.Args[1].Value).Should(Equal("#RRGGBB"))
				Ω(command.ArgFor("color1").Value).Should(Equal("#RGB"))
				Ω(command.ArgFor("color2").Value).Should(Equal("#RRGGBB"))
			})
		})
	})
})
<|endoftext|>